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