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