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