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