]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/chttpget.cpp
fix issue with looping audio streams
[taylor/freespace2.git] / src / inetfile / chttpget.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on
6  * the source.
7  */
8
9 /*
10 * $Logfile: /Freespace2/code/Inetfile/Chttpget.cpp $
11 * $Revision$
12 * $Date$
13 * $Author$
14 *
15 * HTTP Client class (get only)
16 *
17 * $Log$
18 * Revision 1.8  2002/06/21 03:04:12  relnev
19 * nothing important
20 *
21 * Revision 1.7  2002/06/17 06:33:09  relnev
22 * ryan's struct patch for gcc 2.95
23 *
24 * Revision 1.6  2002/06/09 04:41:21  relnev
25 * added copyright header
26 *
27 * Revision 1.5  2002/06/02 04:26:34  relnev
28 * warning cleanup
29 *
30 * Revision 1.4  2002/05/26 20:32:24  theoddone33
31 * Fix some minor stuff
32 *
33 * Revision 1.3  2002/05/26 20:20:54  relnev
34 * unix.h: updated
35 *
36 * inetfile/: complete
37 *
38 * Revision 1.2  2002/05/07 03:16:45  theoddone33
39 * The Great Newline Fix
40 *
41 * Revision 1.1.1.1  2002/05/03 03:28:09  root
42 * Initial import.
43 *
44  * 
45  * 5     8/24/99 1:49a Dave
46  * Fixed client-side afterburner stuttering. Added checkbox for no version
47  * checking on PXO join. Made button info passing more friendly between
48  * client and server.
49  * 
50  * 4     8/22/99 1:19p Dave
51  * Fixed up http proxy code. Cleaned up scoring code. Reverse the order in
52  * which d3d cards are detected.
53  * 
54  * 21    8/21/99 6:33p Kevin
55  * Fixed Proxy Stuff
56  * 
57  * 20    8/21/99 6:48a Jeff
58  * Linux port
59  * 
60  * 19    8/20/99 3:01p Kevin
61  * Added support for Proxies (I hope!)
62  * 
63  * 18    8/15/99 6:38p Jeff
64  * fixed compile error
65  * 
66  * 17    8/15/99 6:26p Kevin
67  * 
68  * 16    4/14/99 1:20a Jeff
69  * fixed case mismatched #includes
70  * 
71  * 15    3/03/99 12:28a Nate
72  * sped up something or other when the connection is done
73  * 
74  * 14    2/03/99 4:20p Kevin
75  * Got multiplayer working with .mn3 files, and setup autodownloading
76  * 
77  * 13    1/27/99 5:49p Kevin
78  * 
79  * 12    1/27/99 5:38p Kevin
80  * 
81  * 11    12/30/98 12:15p Kevin
82  * Auto Mission Download system
83  * 
84  * 10    10/12/98 4:59p Kevin
85  * Added delay to thread when cancelled...
86  * 
87  * 9     10/12/98 4:49p Nate
88  * More fixes
89  * 
90  * 8     10/12/98 1:54p Nate
91  * Fixed bug
92  * 
93  * 7     10/12/98 11:30a Kevin
94  * More memory stuff
95  * 
96  * 6     10/08/98 12:59p Nate
97  * fixed cancel
98  * 
99  * 5     10/08/98 9:57a Kevin
100  * made transfer cancellable
101  * 
102  * 4     7/31/98 12:19p Nate
103  * Fixed http abort problem.
104  * 
105  * 3     7/31/98 11:57a Kevin
106  * Added new functions for getting state
107  * 
108  * 2     6/01/98 10:10a Kevin
109  * Added DLL connection interface and auto update DLL
110  * 
111  * 1     5/27/98 9:52a Kevin
112  * 
113  * 1     5/25/98 5:31p Kevin
114  * Initial version
115 *
116 * $NoKeywords: $
117 */
118
119
120 #ifndef PLAT_UNIX
121 #include <winsock2.h>
122 #else
123 #include <sys/types.h>
124 #include <sys/socket.h>
125 #include <netinet/in.h>
126 #include <arpa/inet.h>
127 #include <netdb.h>
128 #include <sys/ioctl.h>
129 #include <errno.h>
130 #include <sys/time.h>
131 #include <unistd.h>
132 #endif
133
134 #include <string.h>
135 #include <stdio.h>
136 #include <stdlib.h>
137 #include <ctype.h>
138
139 #include "pstypes.h"
140 #include "inetgetfile.h"
141 #include "chttpget.h"
142
143
144
145 #define NW_AGHBN_CANCEL         1
146 #define NW_AGHBN_LOOKUP         2
147 #define NW_AGHBN_READ           3
148
149 int http_gethostbynameworker(void *parm);
150
151
152 int http_Asyncgethostbyname(in_addr_t *ip, int command, char *hostname);
153
154 int HTTPObjThread( void * obj )
155 {
156         ((ChttpGet *)obj)->WorkerThread();
157         ((ChttpGet *)obj)->m_Aborted = true;
158
159         return ((ChttpGet *)obj)->GetStatus();
160 }
161
162 void ChttpGet::AbortGet()
163 {
164         m_Aborting = true;
165         while(!m_Aborted) SDL_Delay(10); //Wait for the thread to end
166 }
167
168 ChttpGet::ChttpGet(char *URL,char *localfile,char *proxyip,unsigned short proxyport)
169 {
170         m_ProxyEnabled = true;
171         m_ProxyIP = proxyip;
172         m_ProxyPort = proxyport;
173         GetFile(URL,localfile);
174 }
175
176 ChttpGet::ChttpGet(char *URL,char *localfile)
177 {
178         m_ProxyEnabled = false;
179         GetFile(URL,localfile);
180 }
181
182
183 void ChttpGet::GetFile(char *URL,char *localfile)
184 {
185         m_DataSock = INVALID_SOCKET;
186         m_iBytesIn = 0;
187         m_iBytesTotal = 0;
188         m_State = HTTP_STATE_STARTUP;;
189         m_Aborting = false;
190         m_Aborted = false;
191
192         SDL_strlcpy(m_URL, URL, SDL_arraysize(m_URL));
193
194         LOCALFILE = fopen(localfile,"wb");
195         if(NULL == LOCALFILE)
196         {
197                 m_State = HTTP_STATE_CANT_WRITE_FILE;
198                 m_Aborted = true;
199                 return;
200         }
201         m_DataSock = socket(AF_INET, SOCK_STREAM, 0);
202         if(INVALID_SOCKET == m_DataSock)
203         {
204                 m_State = HTTP_STATE_SOCKET_ERROR;
205                 m_Aborted = true;
206                 return;
207         }
208         unsigned long arg;
209
210         arg = 1;
211
212         ioctlsocket( m_DataSock, FIONBIO, &arg );
213
214         char *pURL = URL;
215         if(SDL_strncasecmp(URL,"http:",5)==0)
216         {
217                 pURL +=5;
218                 while(*pURL == '/')
219                 {
220                         pURL++;
221                 }
222         }
223         //There shouldn't be any : in this string
224         if(SDL_strchr(pURL,':'))
225         {
226                 m_State = HTTP_STATE_URL_PARSING_ERROR;
227                 m_Aborted = true;
228                 return;
229         }
230         //read the filename by searching backwards for a /
231         //then keep reading until you find the first /
232         //when you found it, you have the host and dir
233         char *filestart = NULL;
234         char *dirstart = NULL;
235         for(int i = strlen(pURL);i>=0;i--)
236         {
237                 if(pURL[i]== '/')
238                 {
239                         if(!filestart)
240                         {
241                                 filestart = pURL+i+1;
242                                 dirstart = pURL+i+1;
243                                 SDL_strlcpy(m_szFilename, filestart, SDL_arraysize(m_szFilename));
244                         }
245                         else
246                         {
247                                 dirstart = pURL+i+1;
248                         }
249                 }
250         }
251         if((dirstart==NULL) || (filestart==NULL))
252         {
253                 m_State = HTTP_STATE_URL_PARSING_ERROR;
254                 m_Aborted = true;
255                 return;
256         }
257         else
258         {
259                 SDL_strlcpy(m_szDir, dirstart, SDL_arraysize(m_szDir));//,(filestart-dirstart));
260                 int len = SDL_min((dirstart-pURL), (int)SDL_arraysize(m_szHost));
261                 SDL_strlcpy(m_szHost, pURL, len);
262         }
263
264         SDL_Thread *thread = SDL_CreateThread(HTTPObjThread, "HTTPObjThread", this);
265
266         if(thread == NULL)
267         {
268                 m_State = HTTP_STATE_INTERNAL_ERROR;
269                 m_Aborted = true;
270         }
271         else
272         {
273                 SDL_DetachThread(thread);
274         }
275 }
276
277
278 ChttpGet::~ChttpGet()
279 {
280         if(m_DataSock != INVALID_SOCKET)
281         {
282                 shutdown(m_DataSock,2);
283                 closesocket(m_DataSock);
284         }
285
286         if(LOCALFILE != NULL)
287         {
288                 fclose(LOCALFILE);
289         }
290 }
291
292 int ChttpGet::GetStatus()
293 {
294         return m_State;
295 }
296
297 unsigned int ChttpGet::GetBytesIn()
298 {
299         return m_iBytesIn;
300 }
301
302 unsigned int ChttpGet::GetTotalBytes()
303 {
304         return m_iBytesTotal;
305 }
306
307
308 void ChttpGet::WorkerThread()
309 {
310         char szCommand[1000];
311         char *p;
312         int irsp = 0;
313         ConnectSocket();
314         if(m_Aborting)
315         {
316                 fclose(LOCALFILE);
317                 LOCALFILE = NULL;
318                 return;
319         }
320         if(m_State != HTTP_STATE_CONNECTED)
321         {
322                 fclose(LOCALFILE);
323                 LOCALFILE = NULL;
324                 return;
325         }
326         SDL_snprintf(szCommand,SDL_arraysize(szCommand),"GET %s%s HTTP/1.1\nAccept: */*\nAccept-Encoding: deflate\nHost: %s\n\n\n",m_ProxyEnabled?"":"/",m_ProxyEnabled?m_URL:m_szDir,m_szHost);
327         send(m_DataSock,szCommand,strlen(szCommand),0);
328         p = GetHTTPLine();
329         if(SDL_strncasecmp("HTTP/",p,5)==0)
330         {
331                 char *pcode;
332                 pcode = SDL_strchr(p,' ')+1;
333                 if(!pcode)
334                 {
335                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
336                         fclose(LOCALFILE);
337                         LOCALFILE = NULL;
338                         return;
339
340                 }
341                 pcode[3] = '\0';
342                 irsp = atoi(pcode);
343
344                 if(irsp == 0)
345                 {
346                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
347                         fclose(LOCALFILE);
348                         LOCALFILE = NULL;
349                         return;
350                 }
351                 if(irsp==200)
352                 {
353                         do
354                         {
355                                 p = GetHTTPLine();
356                                 if(p==NULL)
357                                 {
358                                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
359                                         fclose(LOCALFILE);
360                                         LOCALFILE = NULL;
361                                         return;
362                                 }
363                                 if(*p=='\0')
364                                 {
365                                         break;
366                                 }
367                                 if(SDL_strncasecmp(p,"Content-Length:",strlen("Content-Length:"))==0)
368                                 {
369                                         char *s = SDL_strchr(p,' ')+1;
370                                         p = s;
371                                         if(s)
372                                         {
373                                                 while(*s)
374                                                 {
375                                                         if(!isdigit(*s))
376                                                         {
377                                                                 *s='\0';
378                                                         }
379                                                         s++;
380                                                 };
381                                                 m_iBytesTotal = atoi(p);
382                                         }
383
384                                 }
385
386                                 SDL_Delay(1);
387                         }while(true);
388                 ReadDataChannel();
389                 return;
390                 }
391                 m_State = HTTP_STATE_FILE_NOT_FOUND;
392                 fclose(LOCALFILE);
393                 LOCALFILE = NULL;
394                 return;
395         }
396         else
397         {
398                 m_State = HTTP_STATE_UNKNOWN_ERROR;
399                 fclose(LOCALFILE);
400                 LOCALFILE = NULL;
401                 return;
402         }
403 }
404
405 int ChttpGet::ConnectSocket()
406 {
407         in_addr_t ip;
408         struct servent *se;
409         struct sockaddr_in hostaddr;
410         if(m_Aborting){
411                 return 0;
412         }
413         
414         ip = inet_addr((const char *)m_szHost);
415
416         int rcode = 0;
417         if(ip==INADDR_NONE)
418         {
419                 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_szHost);          
420                 do
421                 {       
422                         if(m_Aborting)
423                         {
424                                 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_szHost);
425                                 return 0;
426                         }
427                         rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_szHost);
428
429                         SDL_Delay(1);
430                 }while(rcode==0);
431         }
432         
433         if(rcode == -1)
434         {
435                 m_State = HTTP_STATE_HOST_NOT_FOUND;
436                 return 0;
437         }
438         //m_ControlSock
439         if(m_Aborting)
440                 return 0;
441         se = getservbyname("http", NULL);
442         if(m_Aborting)
443                 return 0;
444         if(se == NULL)
445         {
446                 hostaddr.sin_port = htons(80);
447         }
448         else
449         {
450                 hostaddr.sin_port = se->s_port;
451         }
452         hostaddr.sin_family = AF_INET;          
453         //ip = htonl(ip);
454         hostaddr.sin_addr.s_addr = ip;
455
456         if(m_ProxyEnabled)
457         {
458                 //This is on a proxy, so we need to make sure to connect to the proxy machine
459                 ip = inet_addr((const char *)m_ProxyIP);
460                                 
461                 if(ip==INADDR_NONE)
462                 {
463                         http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_ProxyIP);
464
465                         do
466                         {       
467                                 if(m_Aborting)
468                                 {
469                                         http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_ProxyIP);
470                                         return 0;
471                                 }
472                                 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_ProxyIP);
473
474                                 SDL_Delay(1);
475                         }while(rcode==0);
476                         
477                         
478                         if(rcode == -1)
479                         {
480                                 m_State = HTTP_STATE_HOST_NOT_FOUND;
481                                 return 0;
482                         }
483
484                 }
485                 //Use either the proxy port or 80 if none specified
486                 hostaddr.sin_port = htons((ushort)(m_ProxyPort ? m_ProxyPort : 80));
487                 //Copy the proxy address...
488                 hostaddr.sin_addr.s_addr = ip;
489
490         }
491         //Now we will connect to the host                                       
492         fd_set  wfds;
493
494         timeval timeout;
495         timeout.tv_sec = 0;
496         timeout.tv_usec = 0;
497         int serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
498         int cerr = WSAGetLastError();
499         if(serr)
500         {
501                 // fail after 20 seconds
502                 Uint32 failtime = SDL_GetTicks() + (20 * 1000);
503                 while((cerr==WSAEALREADY)||(cerr==WSAEINVAL)||NETCALL_WOULDBLOCK(cerr))
504                 {
505                         FD_ZERO(&wfds);
506                         FD_SET( m_DataSock, &wfds );
507                         if(select(m_DataSock+1,NULL,&wfds,NULL,&timeout))
508                         {
509                                 int error_code = 0;
510                                 SOCKLEN_T error_code_size = sizeof(error_code);
511
512                                 // check to make sure socket is *really* connected
513                                 int rc = getsockopt(m_DataSock, SOL_SOCKET, SO_ERROR, (char *)&error_code, &error_code_size);
514
515                                 if(!rc && !error_code)
516                                 {
517                                         serr = 0;
518                                 }
519
520                                 break;
521                         }
522                         if(m_Aborting)
523                                 return 0;
524                         serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
525                         if(serr == 0)
526                                 break;
527                         cerr = WSAGetLastError();
528                         if(cerr==WSAEISCONN)
529                         {
530                                 serr = 0;
531                                 break;
532                         }
533                         if(SDL_GetTicks()>failtime)
534                                 break;
535
536                         SDL_Delay(1);
537                 };
538         }
539         if(serr)
540         {
541                 m_State = HTTP_STATE_CANT_CONNECT;
542                 return 0;
543         }
544         m_State = HTTP_STATE_CONNECTED;
545         return 1;
546 }
547
548 char *ChttpGet::GetHTTPLine()
549 {
550         int iBytesRead;
551         char chunk[2];
552         unsigned int igotcrlf = 0;
553         memset(recv_buffer,0,1000);
554         do
555         {
556                 chunk[0]='\0';
557                 bool gotdata = false;
558                 do
559                 {
560                         iBytesRead = recv(m_DataSock,chunk,1,0);
561
562                         if(SOCKET_ERROR == iBytesRead)
563                         {       
564                                 int error = WSAGetLastError();
565                                 if(NETCALL_WOULDBLOCK(error))
566                                 {
567                                         gotdata = false;
568                                         continue;
569                                 }
570                                 else
571                                         return NULL;
572                         }
573                         else
574                         {
575                                 gotdata = true;
576                         }
577
578                         SDL_Delay(1);
579                 }while(!gotdata);
580                 
581                 if(chunk[0]==0x0d)
582                 {
583                         //This should always read a 0x0a
584                         do
585                         {
586                                 iBytesRead = recv(m_DataSock,chunk,1,0);
587
588                                 if(SOCKET_ERROR == iBytesRead)
589                                 {       
590                                         int error = WSAGetLastError();
591                                         if(NETCALL_WOULDBLOCK(error))
592                                         {
593                                                 gotdata = false;
594                                                 continue;
595                                         }
596                                         else
597                                                 return NULL;
598                                 }
599                                 else
600                                 {
601                                         gotdata = true;
602                                 }
603
604                                 SDL_Delay(1);
605                         }while(!gotdata);
606                         igotcrlf = 1;   
607                 }
608                 else
609                 {       chunk[1] = '\0';
610                         SDL_strlcat(recv_buffer, chunk, SDL_arraysize(recv_buffer));
611                 }
612                 
613                 SDL_Delay(1);
614         }while(igotcrlf==0);
615         return recv_buffer;     
616 }
617
618 unsigned int ChttpGet::ReadDataChannel()
619 {
620         char sDataBuffer[4096];         // Data-storage buffer for the data channel
621         int nBytesRecv = 0;                                             // Bytes received from the data channel
622
623         fd_set  wfds;
624
625         timeval timeout;
626         timeout.tv_sec = 0;
627         timeout.tv_usec = 500;
628
629         m_State = HTTP_STATE_RECEIVING;                 
630    do   
631    {
632                 FD_ZERO(&wfds);
633                 FD_SET( m_DataSock, &wfds );
634
635                 if((m_iBytesTotal)&&(m_iBytesIn==m_iBytesTotal))
636                 {
637                         break;
638                 }
639                 select(m_DataSock+1,&wfds,NULL,NULL,&timeout);
640         if(m_Aborting)
641                 {
642                         fclose(LOCALFILE);
643                         LOCALFILE = NULL;
644                         return 0;               
645                 }
646                 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
647         if(m_Aborting)
648                 {
649                         fclose(LOCALFILE);
650                         LOCALFILE = NULL;
651                         return 0;
652                 }
653                 if(SOCKET_ERROR == nBytesRecv)
654                 {       
655                         int error = WSAGetLastError();
656                         if(NETCALL_WOULDBLOCK(error))
657                         {
658                                 nBytesRecv = 1;
659                                 continue;
660                         }
661                 }
662                 m_iBytesIn += nBytesRecv;
663                 if (nBytesRecv > 0 )
664                 {
665                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
666                         //Write sDataBuffer, nBytesRecv
667         }
668                 
669                 SDL_Delay(1);
670         }while (nBytesRecv > 0);
671         fclose(LOCALFILE);
672         LOCALFILE = NULL;
673         // Close the file and check for error returns.
674         if (nBytesRecv == SOCKET_ERROR)
675         { 
676                 //Ok, we got a socket error -- xfer aborted?
677                 m_State = HTTP_STATE_RECV_FAILED;
678                 return 0;
679         }
680         else
681         {
682                 //OutputDebugString("HTTP File complete!\n");
683                 //done!
684                 m_State = HTTP_STATE_FILE_RECEIVED;
685                 return 1;
686         }
687 }       
688
689
690 typedef struct _async_dns_lookup
691 {
692         in_addr_t ip;   //resolved host. Write only to worker thread.
693         char * host;//host name to resolve. read only to worker thread
694         bool done;      //write only to the worker thread. Signals that the operation is complete
695         bool error; //write only to worker thread. Thread sets this if the name doesn't resolve
696         bool abort;     //read only to worker thread. If this is set, don't fill in the struct.
697 }async_dns_lookup;
698
699 async_dns_lookup httpaslu;
700 async_dns_lookup *http_lastaslu = NULL;
701
702 int http_gethostbynameworker(void *parm);
703
704 int http_Asyncgethostbyname(in_addr_t *ip,int command, char *hostname)
705 {
706         
707         if(command==NW_AGHBN_LOOKUP)
708         {
709                 if(http_lastaslu)
710                         http_lastaslu->abort = true;
711
712                 async_dns_lookup *newaslu;
713                 newaslu = (async_dns_lookup *)malloc(sizeof(async_dns_lookup));
714                 newaslu->ip = 0;
715                 newaslu->host = hostname;
716                 newaslu->done = false;
717                 newaslu->error = false;
718                 newaslu->abort = false;
719                 http_lastaslu = newaslu;
720                 httpaslu.done = false;
721
722                 SDL_CreateThread(http_gethostbynameworker, "GetHostByNameWorker", newaslu);
723
724                 return 1;
725         }
726         else if(command==NW_AGHBN_CANCEL)
727         {
728                 if(http_lastaslu)
729                         http_lastaslu->abort = true;
730                 http_lastaslu = NULL;
731         }
732         else if(command==NW_AGHBN_READ)
733         {
734                 if(!http_lastaslu)
735                         return -1;
736                 if(httpaslu.done)
737                 {
738                         //free(http_lastaslu);
739                         http_lastaslu = NULL;
740                         *ip = httpaslu.ip;
741                         return 1;
742                 }
743                 else if(httpaslu.error)
744                 {
745                         free(http_lastaslu);
746                         http_lastaslu = NULL;
747                         return -1;
748                 }
749                 else return 0;
750         }
751         return -2;
752
753 }
754
755 // This is the worker thread which does the lookup.
756 int http_gethostbynameworker(void *parm)
757 {
758         async_dns_lookup *lookup = (async_dns_lookup *)parm;
759         struct hostent *he = gethostbyname(lookup->host);
760         if(he==NULL)
761         {
762                 lookup->error = true;
763                 return 1;
764         }
765         else if(!lookup->abort)
766         {
767                 lookup->ip = ((in_addr *)(he->h_addr))->s_addr;
768                 lookup->done = true;
769                 memcpy(&httpaslu,lookup,sizeof(async_dns_lookup));
770         }
771         free(lookup);
772
773         return 0;
774 }