2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Inetfile/Chttpget.cpp $
15 * HTTP Client class (get only)
18 * Revision 1.8 2002/06/21 03:04:12 relnev
21 * Revision 1.7 2002/06/17 06:33:09 relnev
22 * ryan's struct patch for gcc 2.95
24 * Revision 1.6 2002/06/09 04:41:21 relnev
25 * added copyright header
27 * Revision 1.5 2002/06/02 04:26:34 relnev
30 * Revision 1.4 2002/05/26 20:32:24 theoddone33
31 * Fix some minor stuff
33 * Revision 1.3 2002/05/26 20:20:54 relnev
38 * Revision 1.2 2002/05/07 03:16:45 theoddone33
39 * The Great Newline Fix
41 * Revision 1.1.1.1 2002/05/03 03:28:09 root
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
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.
54 * 21 8/21/99 6:33p Kevin
57 * 20 8/21/99 6:48a Jeff
60 * 19 8/20/99 3:01p Kevin
61 * Added support for Proxies (I hope!)
63 * 18 8/15/99 6:38p Jeff
66 * 17 8/15/99 6:26p Kevin
68 * 16 4/14/99 1:20a Jeff
69 * fixed case mismatched #includes
71 * 15 3/03/99 12:28a Nate
72 * sped up something or other when the connection is done
74 * 14 2/03/99 4:20p Kevin
75 * Got multiplayer working with .mn3 files, and setup autodownloading
77 * 13 1/27/99 5:49p Kevin
79 * 12 1/27/99 5:38p Kevin
81 * 11 12/30/98 12:15p Kevin
82 * Auto Mission Download system
84 * 10 10/12/98 4:59p Kevin
85 * Added delay to thread when cancelled...
87 * 9 10/12/98 4:49p Nate
90 * 8 10/12/98 1:54p Nate
93 * 7 10/12/98 11:30a Kevin
96 * 6 10/08/98 12:59p Nate
99 * 5 10/08/98 9:57a Kevin
100 * made transfer cancellable
102 * 4 7/31/98 12:19p Nate
103 * Fixed http abort problem.
105 * 3 7/31/98 11:57a Kevin
106 * Added new functions for getting state
108 * 2 6/01/98 10:10a Kevin
109 * Added DLL connection interface and auto update DLL
111 * 1 5/27/98 9:52a Kevin
113 * 1 5/25/98 5:31p Kevin
121 #include <winsock2.h>
123 #include <sys/types.h>
124 #include <sys/socket.h>
125 #include <netinet/in.h>
126 #include <arpa/inet.h>
128 #include <sys/ioctl.h>
130 #include <sys/time.h>
140 #include "inetgetfile.h"
141 #include "chttpget.h"
145 #define NW_AGHBN_CANCEL 1
146 #define NW_AGHBN_LOOKUP 2
147 #define NW_AGHBN_READ 3
149 int http_gethostbynameworker(void *parm);
152 int http_Asyncgethostbyname(in_addr_t *ip, int command, char *hostname);
154 int HTTPObjThread( void * obj )
156 ((ChttpGet *)obj)->WorkerThread();
157 ((ChttpGet *)obj)->m_Aborted = true;
159 return ((ChttpGet *)obj)->GetStatus();
162 void ChttpGet::AbortGet()
165 while(!m_Aborted) SDL_Delay(10); //Wait for the thread to end
168 ChttpGet::ChttpGet(char *URL,char *localfile,char *proxyip,unsigned short proxyport)
170 m_ProxyEnabled = true;
172 m_ProxyPort = proxyport;
173 GetFile(URL,localfile);
176 ChttpGet::ChttpGet(char *URL,char *localfile)
178 m_ProxyEnabled = false;
179 GetFile(URL,localfile);
183 void ChttpGet::GetFile(char *URL,char *localfile)
185 m_DataSock = INVALID_SOCKET;
188 m_State = HTTP_STATE_STARTUP;;
192 SDL_strlcpy(m_URL, URL, SDL_arraysize(m_URL));
194 LOCALFILE = fopen(localfile,"wb");
195 if(NULL == LOCALFILE)
197 m_State = HTTP_STATE_CANT_WRITE_FILE;
201 m_DataSock = socket(AF_INET, SOCK_STREAM, 0);
202 if(INVALID_SOCKET == m_DataSock)
204 m_State = HTTP_STATE_SOCKET_ERROR;
212 ioctlsocket( m_DataSock, FIONBIO, &arg );
215 if(SDL_strncasecmp(URL,"http:",5)==0)
223 //There shouldn't be any : in this string
224 if(SDL_strchr(pURL,':'))
226 m_State = HTTP_STATE_URL_PARSING_ERROR;
230 //read the filename by searching backwards for a /
231 //then keep reading until you find the first /
232 //when you found it, you have the host and dir
233 char *filestart = NULL;
234 char *dirstart = NULL;
235 for(int i = strlen(pURL);i>=0;i--)
241 filestart = pURL+i+1;
243 SDL_strlcpy(m_szFilename, filestart, SDL_arraysize(m_szFilename));
251 if((dirstart==NULL) || (filestart==NULL))
253 m_State = HTTP_STATE_URL_PARSING_ERROR;
259 SDL_strlcpy(m_szDir, dirstart, SDL_arraysize(m_szDir));//,(filestart-dirstart));
260 int len = min((dirstart-pURL), (int)SDL_arraysize(m_szHost));
261 SDL_strlcpy(m_szHost, pURL, len);
264 SDL_Thread *thread = SDL_CreateThread(HTTPObjThread, "HTTPObjThread", this);
268 m_State = HTTP_STATE_INTERNAL_ERROR;
273 SDL_DetachThread(thread);
278 ChttpGet::~ChttpGet()
280 if(m_DataSock != INVALID_SOCKET)
282 shutdown(m_DataSock,2);
283 closesocket(m_DataSock);
286 if(LOCALFILE != NULL)
292 int ChttpGet::GetStatus()
297 unsigned int ChttpGet::GetBytesIn()
302 unsigned int ChttpGet::GetTotalBytes()
304 return m_iBytesTotal;
308 void ChttpGet::WorkerThread()
310 char szCommand[1000];
320 if(m_State != HTTP_STATE_CONNECTED)
326 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);
327 send(m_DataSock,szCommand,strlen(szCommand),0);
329 if(SDL_strncasecmp("HTTP/",p,5)==0)
332 pcode = SDL_strchr(p,' ')+1;
335 m_State = HTTP_STATE_UNKNOWN_ERROR;
346 m_State = HTTP_STATE_UNKNOWN_ERROR;
358 m_State = HTTP_STATE_UNKNOWN_ERROR;
367 if(SDL_strncasecmp(p,"Content-Length:",strlen("Content-Length:"))==0)
369 char *s = SDL_strchr(p,' ')+1;
381 m_iBytesTotal = atoi(p);
391 m_State = HTTP_STATE_FILE_NOT_FOUND;
398 m_State = HTTP_STATE_UNKNOWN_ERROR;
405 int ChttpGet::ConnectSocket()
409 struct sockaddr_in hostaddr;
414 ip = inet_addr((const char *)m_szHost);
419 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_szHost);
424 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_szHost);
427 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_szHost);
435 m_State = HTTP_STATE_HOST_NOT_FOUND;
441 se = getservbyname("http", NULL);
446 hostaddr.sin_port = htons(80);
450 hostaddr.sin_port = se->s_port;
452 hostaddr.sin_family = AF_INET;
454 hostaddr.sin_addr.s_addr = ip;
458 //This is on a proxy, so we need to make sure to connect to the proxy machine
459 ip = inet_addr((const char *)m_ProxyIP);
463 http_Asyncgethostbyname(&ip,NW_AGHBN_LOOKUP,m_ProxyIP);
469 http_Asyncgethostbyname(&ip,NW_AGHBN_CANCEL,m_ProxyIP);
472 rcode = http_Asyncgethostbyname(&ip,NW_AGHBN_READ,m_ProxyIP);
480 m_State = HTTP_STATE_HOST_NOT_FOUND;
485 //Use either the proxy port or 80 if none specified
486 hostaddr.sin_port = htons((ushort)(m_ProxyPort ? m_ProxyPort : 80));
487 //Copy the proxy address...
488 hostaddr.sin_addr.s_addr = ip;
491 //Now we will connect to the host
497 int serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
498 int cerr = WSAGetLastError();
501 // fail after 20 seconds
502 Uint32 failtime = SDL_GetTicks() + (20 * 1000);
503 while((cerr==WSAEALREADY)||(cerr==WSAEINVAL)||NETCALL_WOULDBLOCK(cerr))
506 FD_SET( m_DataSock, &wfds );
507 if(select(m_DataSock+1,NULL,&wfds,NULL,&timeout))
510 SOCKLEN_T error_code_size = sizeof(error_code);
512 // check to make sure socket is *really* connected
513 int rc = getsockopt(m_DataSock, SOL_SOCKET, SO_ERROR, (char *)&error_code, &error_code_size);
515 if(!rc && !error_code)
524 serr = connect(m_DataSock, (struct sockaddr *)&hostaddr, sizeof(struct sockaddr));
527 cerr = WSAGetLastError();
533 if(SDL_GetTicks()>failtime)
541 m_State = HTTP_STATE_CANT_CONNECT;
544 m_State = HTTP_STATE_CONNECTED;
548 char *ChttpGet::GetHTTPLine()
552 unsigned int igotcrlf = 0;
553 memset(recv_buffer,0,1000);
557 bool gotdata = false;
560 iBytesRead = recv(m_DataSock,chunk,1,0);
562 if(SOCKET_ERROR == iBytesRead)
564 int error = WSAGetLastError();
565 if(NETCALL_WOULDBLOCK(error))
583 //This should always read a 0x0a
586 iBytesRead = recv(m_DataSock,chunk,1,0);
588 if(SOCKET_ERROR == iBytesRead)
590 int error = WSAGetLastError();
591 if(NETCALL_WOULDBLOCK(error))
610 SDL_strlcat(recv_buffer, chunk, SDL_arraysize(recv_buffer));
618 unsigned int ChttpGet::ReadDataChannel()
620 char sDataBuffer[4096]; // Data-storage buffer for the data channel
621 int nBytesRecv = 0; // Bytes received from the data channel
627 timeout.tv_usec = 500;
629 m_State = HTTP_STATE_RECEIVING;
633 FD_SET( m_DataSock, &wfds );
635 if((m_iBytesTotal)&&(m_iBytesIn==m_iBytesTotal))
639 select(m_DataSock+1,&wfds,NULL,NULL,&timeout);
646 nBytesRecv = recv(m_DataSock, (char *)&sDataBuffer,sizeof(sDataBuffer), 0);
653 if(SOCKET_ERROR == nBytesRecv)
655 int error = WSAGetLastError();
656 if(NETCALL_WOULDBLOCK(error))
662 m_iBytesIn += nBytesRecv;
665 fwrite(sDataBuffer,nBytesRecv,1,LOCALFILE);
666 //Write sDataBuffer, nBytesRecv
670 }while (nBytesRecv > 0);
673 // Close the file and check for error returns.
674 if (nBytesRecv == SOCKET_ERROR)
676 //Ok, we got a socket error -- xfer aborted?
677 m_State = HTTP_STATE_RECV_FAILED;
682 //OutputDebugString("HTTP File complete!\n");
684 m_State = HTTP_STATE_FILE_RECEIVED;
690 typedef struct _async_dns_lookup
692 in_addr_t ip; //resolved host. Write only to worker thread.
693 char * host;//host name to resolve. read only to worker thread
694 bool done; //write only to the worker thread. Signals that the operation is complete
695 bool error; //write only to worker thread. Thread sets this if the name doesn't resolve
696 bool abort; //read only to worker thread. If this is set, don't fill in the struct.
699 async_dns_lookup httpaslu;
700 async_dns_lookup *http_lastaslu = NULL;
702 int http_gethostbynameworker(void *parm);
704 int http_Asyncgethostbyname(in_addr_t *ip,int command, char *hostname)
707 if(command==NW_AGHBN_LOOKUP)
710 http_lastaslu->abort = true;
712 async_dns_lookup *newaslu;
713 newaslu = (async_dns_lookup *)malloc(sizeof(async_dns_lookup));
715 newaslu->host = hostname;
716 newaslu->done = false;
717 newaslu->error = false;
718 newaslu->abort = false;
719 http_lastaslu = newaslu;
720 httpaslu.done = false;
722 SDL_CreateThread(http_gethostbynameworker, "GetHostByNameWorker", newaslu);
726 else if(command==NW_AGHBN_CANCEL)
729 http_lastaslu->abort = true;
730 http_lastaslu = NULL;
732 else if(command==NW_AGHBN_READ)
738 //free(http_lastaslu);
739 http_lastaslu = NULL;
743 else if(httpaslu.error)
746 http_lastaslu = NULL;
755 // This is the worker thread which does the lookup.
756 int http_gethostbynameworker(void *parm)
758 async_dns_lookup *lookup = (async_dns_lookup *)parm;
759 struct hostent *he = gethostbyname(lookup->host);
762 lookup->error = true;
765 else if(!lookup->abort)
767 lookup->ip = ((in_addr *)(he->h_addr))->s_addr;
769 memcpy(&httpaslu,lookup,sizeof(async_dns_lookup));