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