]> icculus.org git repositories - taylor/freespace2.git/blob - src/inetfile/chttpget.cpp
merge in some code cleanup
[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(in_addr_t *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         SDL_strlcpy(m_URL, URL, SDL_arraysize(m_URL));
194
195         LOCALFILE = fopen(localfile,"wb");
196         if(NULL == LOCALFILE)
197         {
198                 m_State = HTTP_STATE_CANT_WRITE_FILE;
199                 m_Aborted = true;
200                 return;
201         }
202         m_DataSock = socket(AF_INET, SOCK_STREAM, 0);
203         if(INVALID_SOCKET == m_DataSock)
204         {
205                 m_State = HTTP_STATE_SOCKET_ERROR;
206                 m_Aborted = true;
207                 return;
208         }
209         unsigned long arg;
210
211         arg = 1;
212
213         ioctlsocket( m_DataSock, FIONBIO, &arg );
214
215         char *pURL = URL;
216         if(SDL_strncasecmp(URL,"http:",5)==0)
217         {
218                 pURL +=5;
219                 while(*pURL == '/')
220                 {
221                         pURL++;
222                 }
223         }
224         //There shouldn't be any : in this string
225         if(SDL_strchr(pURL,':'))
226         {
227                 m_State = HTTP_STATE_URL_PARSING_ERROR;
228                 m_Aborted = true;
229                 return;
230         }
231         //read the filename by searching backwards for a /
232         //then keep reading until you find the first /
233         //when you found it, you have the host and dir
234         char *filestart = NULL;
235         char *dirstart = NULL;
236         for(int i = strlen(pURL);i>=0;i--)
237         {
238                 if(pURL[i]== '/')
239                 {
240                         if(!filestart)
241                         {
242                                 filestart = pURL+i+1;
243                                 dirstart = pURL+i+1;
244                                 SDL_strlcpy(m_szFilename, filestart, SDL_arraysize(m_szFilename));
245                         }
246                         else
247                         {
248                                 dirstart = pURL+i+1;
249                         }
250                 }
251         }
252         if((dirstart==NULL) || (filestart==NULL))
253         {
254                 m_State = HTTP_STATE_URL_PARSING_ERROR;
255                 m_Aborted = true;
256                 return;
257         }
258         else
259         {
260                 SDL_strlcpy(m_szDir, dirstart, SDL_arraysize(m_szDir));//,(filestart-dirstart));
261                 int len = min((dirstart-pURL), (int)SDL_arraysize(m_szHost));
262                 SDL_strlcpy(m_szHost, pURL, len);
263         }
264
265         SDL_Thread *thread = SDL_CreateThread(HTTPObjThread, "HTTPObjThread", this);
266
267         if(thread == NULL)
268         {
269                 m_State = HTTP_STATE_INTERNAL_ERROR;
270                 m_Aborted = true;
271         }
272         else
273         {
274                 int ret_val = 0;
275                 SDL_WaitThread(thread, &ret_val);
276         }
277 }
278
279
280 ChttpGet::~ChttpGet()
281 {
282         if(m_DataSock != INVALID_SOCKET)
283         {
284                 shutdown(m_DataSock,2);
285                 closesocket(m_DataSock);
286         }
287
288         if(LOCALFILE != NULL)
289         {
290                 fclose(LOCALFILE);
291         }
292 }
293
294 int ChttpGet::GetStatus()
295 {
296         return m_State;
297 }
298
299 unsigned int ChttpGet::GetBytesIn()
300 {
301         return m_iBytesIn;
302 }
303
304 unsigned int ChttpGet::GetTotalBytes()
305 {
306         return m_iBytesTotal;
307 }
308
309
310 void ChttpGet::WorkerThread()
311 {
312         char szCommand[1000];
313         char *p;
314         int irsp = 0;
315         ConnectSocket();
316         if(m_Aborting)
317         {
318                 fclose(LOCALFILE);
319                 LOCALFILE = NULL;
320                 return;
321         }
322         if(m_State != HTTP_STATE_CONNECTED)
323         {
324                 fclose(LOCALFILE);
325                 LOCALFILE = NULL;
326                 return;
327         }
328         SDL_snprintf(szCommand,SDL_arraysize(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);
329         send(m_DataSock,szCommand,strlen(szCommand),0);
330         p = GetHTTPLine();
331         if(SDL_strncasecmp("HTTP/",p,5)==0)
332         {
333                 char *pcode;
334                 pcode = SDL_strchr(p,' ')+1;
335                 if(!pcode)
336                 {
337                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
338                         fclose(LOCALFILE);
339                         LOCALFILE = NULL;
340                         return;
341
342                 }
343                 pcode[3] = '\0';
344                 irsp = atoi(pcode);
345
346                 if(irsp == 0)
347                 {
348                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
349                         fclose(LOCALFILE);
350                         LOCALFILE = NULL;
351                         return;
352                 }
353                 if(irsp==200)
354                 {
355                         do
356                         {
357                                 p = GetHTTPLine();
358                                 if(p==NULL)
359                                 {
360                                         m_State = HTTP_STATE_UNKNOWN_ERROR;     
361                                         fclose(LOCALFILE);
362                                         LOCALFILE = NULL;
363                                         return;
364                                 }
365                                 if(*p=='\0')
366                                 {
367                                         break;
368                                 }
369                                 if(SDL_strncasecmp(p,"Content-Length:",strlen("Content-Length:"))==0)
370                                 {
371                                         char *s = SDL_strchr(p,' ')+1;
372                                         p = s;
373                                         if(s)
374                                         {
375                                                 while(*s)
376                                                 {
377                                                         if(!isdigit(*s))
378                                                         {
379                                                                 *s='\0';
380                                                         }
381                                                         s++;
382                                                 };
383                                                 m_iBytesTotal = atoi(p);
384                                         }
385
386                                 }
387
388                                 SDL_Delay(1);
389                         }while(true);
390                 ReadDataChannel();
391                 return;
392                 }
393                 m_State = HTTP_STATE_FILE_NOT_FOUND;
394                 fclose(LOCALFILE);
395                 LOCALFILE = NULL;
396                 return;
397         }
398         else
399         {
400                 m_State = HTTP_STATE_UNKNOWN_ERROR;
401                 fclose(LOCALFILE);
402                 LOCALFILE = NULL;
403                 return;
404         }
405 }
406
407 int ChttpGet::ConnectSocket()
408 {
409         in_addr_t ip;
410         struct servent *se;
411         struct sockaddr_in hostaddr;
412         if(m_Aborting){
413                 return 0;
414         }
415         
416         ip = inet_addr((const char *)m_szHost);
417
418         int rcode = 0;
419         if(ip==INADDR_NONE)
420         {
421                 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_szHost);          
422                 do
423                 {       
424                         if(m_Aborting)
425                         {
426                                 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_szHost);
427                                 return 0;
428                         }
429                         rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_szHost);
430
431                         SDL_Delay(1);
432                 }while(rcode==0);
433         }
434         
435         if(rcode == -1)
436         {
437                 m_State = HTTP_STATE_HOST_NOT_FOUND;
438                 return 0;
439         }
440         //m_ControlSock
441         if(m_Aborting)
442                 return 0;
443         se = getservbyname("http", NULL);
444         if(m_Aborting)
445                 return 0;
446         if(se == NULL)
447         {
448                 hostaddr.sin_port = htons(80);
449         }
450         else
451         {
452                 hostaddr.sin_port = se->s_port;
453         }
454         hostaddr.sin_family = AF_INET;          
455         //ip = htonl(ip);
456         hostaddr.sin_addr.s_addr = ip;
457
458         if(m_ProxyEnabled)
459         {
460                 //This is on a proxy, so we need to make sure to connect to the proxy machine
461                 ip = inet_addr((const char *)m_ProxyIP);
462                                 
463                 if(ip==INADDR_NONE)
464                 {
465                         http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_ProxyIP);
466
467                         do
468                         {       
469                                 if(m_Aborting)
470                                 {
471                                         http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_ProxyIP);
472                                         return 0;
473                                 }
474                                 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_ProxyIP);
475
476                                 SDL_Delay(1);
477                         }while(rcode==0);
478                         
479                         
480                         if(rcode == -1)
481                         {
482                                 m_State = HTTP_STATE_HOST_NOT_FOUND;
483                                 return 0;
484                         }
485
486                 }
487                 //Use either the proxy port or 80 if none specified
488                 hostaddr.sin_port = htons((ushort)(m_ProxyPort ? m_ProxyPort : 80));
489                 //Copy the proxy address...
490                 hostaddr.sin_addr.s_addr = ip;
491
492         }
493         //Now we will connect to the host                                       
494         fd_set  wfds;
495
496         timeval timeout;
497         timeout.tv_sec = 0;
498         timeout.tv_usec = 0;
499         int serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
500         int cerr = WSAGetLastError();
501         if(serr)
502         {
503                 while((cerr==WSAEALREADY)||(cerr==WSAEINVAL)||NETCALL_WOULDBLOCK(cerr))
504                 {
505                         FD_ZERO(&wfds);
506                         FD_SET( m_DataSock, &wfds );
507                         if(select(m_DataSock+1,NULL,&wfds,NULL,&timeout))
508                         {
509                                 serr = 0;
510                                 break;
511                         }
512                         if(m_Aborting)
513                                 return 0;
514                         serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
515                         if(serr == 0)
516                                 break;
517                         cerr = WSAGetLastError();
518                         if(cerr==WSAEISCONN)
519                         {
520                                 serr = 0;
521                                 break;
522                         }
523
524                         SDL_Delay(1);
525                 };
526         }
527         if(serr)
528         {
529                 m_State = HTTP_STATE_CANT_CONNECT;
530                 return 0;
531         }
532         m_State = HTTP_STATE_CONNECTED;
533         return 1;
534 }
535
536 char *ChttpGet::GetHTTPLine()
537 {
538         int iBytesRead;
539         char chunk[2];
540         unsigned int igotcrlf = 0;
541         memset(recv_buffer,0,1000);
542         do
543         {
544                 chunk[0]='\0';
545                 bool gotdata = false;
546                 do
547                 {
548                         iBytesRead = recv(m_DataSock,chunk,1,0);
549
550                         if(SOCKET_ERROR == iBytesRead)
551                         {       
552                                 int error = WSAGetLastError();
553                                 if(NETCALL_WOULDBLOCK(error))
554                                 {
555                                         gotdata = false;
556                                         continue;
557                                 }
558                                 else
559                                         return NULL;
560                         }
561                         else
562                         {
563                                 gotdata = true;
564                         }
565
566                         SDL_Delay(1);
567                 }while(!gotdata);
568                 
569                 if(chunk[0]==0x0d)
570                 {
571                         //This should always read a 0x0a
572                         do
573                         {
574                                 iBytesRead = recv(m_DataSock,chunk,1,0);
575
576                                 if(SOCKET_ERROR == iBytesRead)
577                                 {       
578                                         int error = WSAGetLastError();
579                                         if(NETCALL_WOULDBLOCK(error))
580                                         {
581                                                 gotdata = false;
582                                                 continue;
583                                         }
584                                         else
585                                                 return NULL;
586                                 }
587                                 else
588                                 {
589                                         gotdata = true;
590                                 }
591
592                                 SDL_Delay(1);
593                         }while(!gotdata);
594                         igotcrlf = 1;   
595                 }
596                 else
597                 {       chunk[1] = '\0';
598                         SDL_strlcat(recv_buffer, chunk, SDL_arraysize(recv_buffer));
599                 }
600                 
601                 SDL_Delay(1);
602         }while(igotcrlf==0);
603         return recv_buffer;     
604 }
605
606 unsigned int ChttpGet::ReadDataChannel()
607 {
608         char sDataBuffer[4096];         // Data-storage buffer for the data channel
609         int nBytesRecv = 0;                                             // Bytes received from the data channel
610
611         fd_set  wfds;
612
613         timeval timeout;
614         timeout.tv_sec = 0;
615         timeout.tv_usec = 500;
616
617         m_State = HTTP_STATE_RECEIVING;                 
618    do   
619    {
620                 FD_ZERO(&wfds);
621                 FD_SET( m_DataSock, &wfds );
622
623                 if((m_iBytesTotal)&&(m_iBytesIn==m_iBytesTotal))
624                 {
625                         break;
626                 }
627                 select(m_DataSock+1,&wfds,NULL,NULL,&timeout);
628         if(m_Aborting)
629                 {
630                         fclose(LOCALFILE);
631                         LOCALFILE = NULL;
632                         return 0;               
633                 }
634                 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
635         if(m_Aborting)
636                 {
637                         fclose(LOCALFILE);
638                         LOCALFILE = NULL;
639                         return 0;
640                 }
641                 if(SOCKET_ERROR == nBytesRecv)
642                 {       
643                         int error = WSAGetLastError();
644                         if(NETCALL_WOULDBLOCK(error))
645                         {
646                                 nBytesRecv = 1;
647                                 continue;
648                         }
649                 }
650                 m_iBytesIn += nBytesRecv;
651                 if (nBytesRecv > 0 )
652                 {
653                         fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
654                         //Write sDataBuffer, nBytesRecv
655         }
656                 
657                 SDL_Delay(1);
658         }while (nBytesRecv > 0);
659         fclose(LOCALFILE);
660         LOCALFILE = NULL;
661         // Close the file and check for error returns.
662         if (nBytesRecv == SOCKET_ERROR)
663         { 
664                 //Ok, we got a socket error -- xfer aborted?
665                 m_State = HTTP_STATE_RECV_FAILED;
666                 return 0;
667         }
668         else
669         {
670                 //OutputDebugString("HTTP File complete!\n");
671                 //done!
672                 m_State = HTTP_STATE_FILE_RECEIVED;
673                 return 1;
674         }
675 }       
676
677
678 typedef struct _async_dns_lookup
679 {
680         in_addr_t ip;   //resolved host. Write only to worker thread.
681         char * host;//host name to resolve. read only to worker thread
682         bool done;      //write only to the worker thread. Signals that the operation is complete
683         bool error; //write only to worker thread. Thread sets this if the name doesn't resolve
684         bool abort;     //read only to worker thread. If this is set, don't fill in the struct.
685 }async_dns_lookup;
686
687 async_dns_lookup httpaslu;
688 async_dns_lookup *http_lastaslu = NULL;
689
690 int http_gethostbynameworker(void *parm);
691
692 int http_Asyncgethostbyname(in_addr_t *ip,int command, char *hostname)
693 {
694         
695         if(command==NW_AGHBN_LOOKUP)
696         {
697                 if(http_lastaslu)
698                         http_lastaslu->abort = true;
699
700                 async_dns_lookup *newaslu;
701                 newaslu = (async_dns_lookup *)malloc(sizeof(async_dns_lookup));
702                 newaslu->ip = 0;
703                 newaslu->host = hostname;
704                 newaslu->done = false;
705                 newaslu->error = false;
706                 newaslu->abort = false;
707                 http_lastaslu = newaslu;
708                 httpaslu.done = false;
709
710                 SDL_CreateThread(http_gethostbynameworker, "GetHostByNameWorker", newaslu);
711
712                 return 1;
713         }
714         else if(command==NW_AGHBN_CANCEL)
715         {
716                 if(http_lastaslu)
717                         http_lastaslu->abort = true;
718                 http_lastaslu = NULL;
719         }
720         else if(command==NW_AGHBN_READ)
721         {
722                 if(!http_lastaslu)
723                         return -1;
724                 if(httpaslu.done)
725                 {
726                         //free(http_lastaslu);
727                         http_lastaslu = NULL;
728                         *ip = httpaslu.ip;
729                         return 1;
730                 }
731                 else if(httpaslu.error)
732                 {
733                         free(http_lastaslu);
734                         http_lastaslu = NULL;
735                         return -1;
736                 }
737                 else return 0;
738         }
739         return -2;
740
741 }
742
743 // This is the worker thread which does the lookup.
744 int http_gethostbynameworker(void *parm)
745 {
746         async_dns_lookup *lookup = (async_dns_lookup *)parm;
747         struct hostent *he = gethostbyname(lookup->host);
748         if(he==NULL)
749         {
750                 lookup->error = true;
751                 return 1;
752         }
753         else if(!lookup->abort)
754         {
755                 lookup->ip = ((in_addr *)(he->h_addr))->s_addr;
756                 lookup->done = true;
757                 memcpy(&httpaslu,lookup,sizeof(async_dns_lookup));
758         }
759         free(lookup);
760
761         return 0;
762 }