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