4068fdf967
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1958 fc73d0e0-1445-4013-8a0c-d673dee63da5
513 lines
12 KiB
C
513 lines
12 KiB
C
#include "quakedef.h"
|
|
|
|
#ifdef WEBCLIENT
|
|
|
|
#include "iweb.h"
|
|
|
|
#ifdef _WIN32
|
|
//msvc crap
|
|
#define snprintf _snprintf
|
|
#endif
|
|
|
|
#include "netinc.h"
|
|
|
|
/*
|
|
Test files/servers.:
|
|
|
|
http://mywebpages.comcast.net/jsgeneric/prog5.asm
|
|
http://mywebpages.comcast.net/jsgeneric/sshot001.jpg
|
|
http://spike.corecodec.org/ftemqwtest.zip
|
|
http://www.fuhquake.net/files/releases/v0.31/fuhquake-win32-v0.31.zip
|
|
http://download.microsoft.com/download/d/c/3/dc37439a-172b-4f20-beac-bab52cdd38bc/Windows-KB833330-ENU.exe
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
This file does one thing. Connects to servers and grabs the specified file. It doesn't do any uploading whatsoever. Live with it.
|
|
It doesn't use persistant connections.
|
|
|
|
*/
|
|
|
|
typedef struct http_con_s {
|
|
int sock;
|
|
|
|
enum {HC_REQUESTING,HC_GETTINGHEADER, HC_GETTING} state;
|
|
|
|
char *buffer;
|
|
char filename[MAX_QPATH];
|
|
int bufferused;
|
|
int bufferlen;
|
|
|
|
int totalreceived; //useful when we're just dumping to a file.
|
|
|
|
qboolean chunking;
|
|
int chunksize;
|
|
int chunked;
|
|
|
|
int contentlength;
|
|
|
|
vfsfile_t *file;
|
|
|
|
void (*NotifyFunction)(char *localfile, qboolean sucess); //called when failed or succeeded, and only if it got a connection in the first place.
|
|
struct http_con_s *next;
|
|
} http_con_t;
|
|
|
|
static http_con_t *httpcl;
|
|
|
|
static void ExpandBuffer(http_con_t *con, int quant)
|
|
{
|
|
int newlen;
|
|
newlen = con->bufferlen + quant;
|
|
con->buffer = IWebRealloc(con->buffer, newlen);
|
|
con->bufferlen = newlen;
|
|
}
|
|
|
|
static qboolean HTTP_CL_Run(http_con_t *con)
|
|
{
|
|
char buffer[256];
|
|
char Location[256];
|
|
char *nl;
|
|
char *msg;
|
|
int ammount;
|
|
switch(con->state)
|
|
{
|
|
case HC_REQUESTING:
|
|
ammount = send(con->sock, con->buffer, con->bufferused, 0);
|
|
if (!ammount)
|
|
return false;
|
|
|
|
if (ammount < 0)
|
|
{
|
|
if (qerrno != EWOULDBLOCK)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
con->bufferused -= ammount;
|
|
memmove(con->buffer, con->buffer+ammount, con->bufferused);
|
|
if (!con->bufferused) //that's it, all sent.
|
|
con->state = HC_GETTINGHEADER;
|
|
break;
|
|
|
|
case HC_GETTINGHEADER:
|
|
if (con->bufferlen - con->bufferused < 1530)
|
|
ExpandBuffer(con, 1530);
|
|
|
|
ammount = recv(con->sock, con->buffer+con->bufferused, con->bufferlen-con->bufferused-15, 0);
|
|
if (!ammount)
|
|
return false;
|
|
if (ammount < 0)
|
|
{
|
|
if (qerrno != EWOULDBLOCK)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
con->bufferused+=ammount;
|
|
con->buffer[con->bufferused] = '\0';
|
|
//have we got the entire thing yet?
|
|
|
|
msg = con->buffer;
|
|
con->chunking = false;
|
|
if (strnicmp(msg, "HTTP/", 5))
|
|
{ //pre version 1. (lame servers.
|
|
con->state = HC_GETTING;
|
|
con->contentlength = -1; //meaning end of stream.
|
|
}
|
|
else
|
|
{
|
|
while(*msg)
|
|
{
|
|
if (*msg == '\n')
|
|
{
|
|
if (msg[1] == '\n')
|
|
{ //tut tut, not '\r'? that's not really allowed...
|
|
msg+=1;
|
|
break;
|
|
}
|
|
if (msg[2] == '\n')
|
|
{
|
|
msg+=2;
|
|
break;
|
|
}
|
|
}
|
|
msg++;
|
|
if (!strnicmp(msg, "Content-Length: ", 16))
|
|
con->contentlength = atoi(msg+16);
|
|
else if (!strnicmp(msg, "Location: ", 10))
|
|
{
|
|
nl = strchr(msg, '\n');
|
|
if (nl)
|
|
{
|
|
*nl = '\0';
|
|
Q_strncpyz(Location, COM_TrimString(msg+10), sizeof(Location));
|
|
*nl = '\n';
|
|
}
|
|
}
|
|
else if (!strnicmp(msg, "Transfer-Encoding: ", 19))
|
|
{
|
|
char *chunk = strstr(msg, "chunked");
|
|
nl = strchr(msg, '\n');
|
|
if (nl)
|
|
if (chunk < nl)
|
|
con->chunking = true;
|
|
}
|
|
}
|
|
if (!*msg)
|
|
break;//switch
|
|
msg++;
|
|
|
|
ammount = msg - con->buffer;
|
|
|
|
msg = COM_ParseOut(con->buffer, buffer, sizeof(buffer));
|
|
msg = COM_ParseOut(msg, buffer, sizeof(buffer));
|
|
if (!stricmp(buffer, "100"))
|
|
{ //http/1.1 servers can give this. We ignore it.
|
|
|
|
con->bufferused -= ammount;
|
|
memmove(con->buffer, con->buffer+ammount, con->bufferused);
|
|
return true;
|
|
}
|
|
|
|
if (!stricmp(buffer, "301") || !stricmp(buffer, "302") || !stricmp(buffer, "303"))
|
|
{
|
|
nl = strchr(msg, '\n');
|
|
if (nl)
|
|
*nl = '\0';
|
|
Con_Printf("HTTP: %s %s\n", buffer, COM_TrimString(msg));
|
|
if (!*Location)
|
|
Con_Printf("Server redirected to null location\n");
|
|
else
|
|
{
|
|
if (HTTP_CL_Get(Location, con->filename, con->NotifyFunction))
|
|
con->NotifyFunction = NULL;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (stricmp(buffer, "200"))
|
|
{
|
|
nl = strchr(msg, '\n');
|
|
if (!nl)
|
|
return false; //eh?
|
|
if (nl>msg&&nl[-1] == '\r')
|
|
nl--;
|
|
*nl = '\0';
|
|
Con_Printf("HTTP: %s%s\n", buffer, msg);
|
|
return false; //something went wrong.
|
|
}
|
|
|
|
con->bufferused -= ammount;
|
|
|
|
con->file = FS_OpenVFS(con->filename, "wb", FS_GAME);
|
|
if (!con->file)
|
|
{
|
|
Con_Printf("HTTP: Couldn't open file %s\n", con->filename);
|
|
return false;
|
|
}
|
|
|
|
if (!con->file)
|
|
{
|
|
VFS_WRITE(con->file, con->buffer+ammount, con->bufferused);
|
|
con->bufferused = 0;
|
|
}
|
|
else
|
|
memmove(con->buffer, con->buffer+ammount, con->bufferused);
|
|
|
|
|
|
con->state = HC_GETTING;
|
|
|
|
}
|
|
//Fall through
|
|
|
|
case HC_GETTING:
|
|
if (con->bufferlen - con->bufferused < 1530)
|
|
ExpandBuffer(con, 1530);
|
|
|
|
ammount = recv(con->sock, con->buffer+con->bufferused, con->bufferlen-con->bufferused-1, 0);
|
|
if (ammount < 0)
|
|
{
|
|
if (qerrno != EWOULDBLOCK)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
con->bufferused+=ammount;
|
|
|
|
if (con->chunking) //FIXME: NEEDS TESTING!!!
|
|
{
|
|
int trim;
|
|
char *nl;
|
|
con->buffer[con->bufferused] = '\0';
|
|
for(;;)
|
|
{ //work out as we go.
|
|
if (con->chunksize)//we are trying to parse a chunk.
|
|
{
|
|
trim = con->bufferused - con->chunked;
|
|
if (trim > con->chunksize)
|
|
trim = con->chunksize; //don't go into the next size field.
|
|
con->chunksize -= trim;
|
|
con->chunked += trim;
|
|
|
|
if (!con->chunksize)
|
|
{ //we need to find the next \n and trim it.
|
|
nl = strchr(con->buffer+con->chunked, '\n');
|
|
if (!nl)
|
|
break;
|
|
nl++;
|
|
trim = nl - (con->buffer+con->chunked);
|
|
memmove(con->buffer + con->chunked, nl, con->buffer+con->bufferused-nl+1);
|
|
con->bufferused -= trim;
|
|
}
|
|
if (!(con->bufferused - con->chunked))
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
nl = strchr(con->buffer+con->chunked, '\n');
|
|
if (!nl)
|
|
break;
|
|
con->chunksize = strtol(con->buffer+con->chunked, NULL, 16); //it's hex.
|
|
nl++;
|
|
trim = nl - (con->buffer+con->chunked);
|
|
memmove(con->buffer + con->chunked, nl, con->buffer+con->bufferused-nl+1);
|
|
con->bufferused -= trim;
|
|
}
|
|
}
|
|
|
|
|
|
con->totalreceived+=con->chunked;
|
|
if (con->file && con->chunked) //we've got a chunk in the buffer
|
|
{ //write it
|
|
if (VFS_WRITE(con->file, con->buffer, con->chunked) != con->chunked)
|
|
{
|
|
Con_Printf("Write error whilst downloading %s\nDisk full?\n", con->filename);
|
|
return false;
|
|
}
|
|
|
|
//and move the unparsed chunk to the front.
|
|
con->bufferused -= con->chunked;
|
|
memmove(con->buffer, con->buffer+con->chunked, con->bufferused);
|
|
con->chunked = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
con->totalreceived+=ammount;
|
|
if (con->file) //we've got a chunk in the buffer
|
|
{ //write it
|
|
if (VFS_WRITE(con->file, con->buffer, con->bufferused) != con->bufferused)
|
|
{
|
|
Con_Printf("Write error whilst downloading %s\nDisk full?\n", con->filename);
|
|
return false;
|
|
}
|
|
con->bufferused = 0;
|
|
}
|
|
}
|
|
|
|
if (!ammount)
|
|
{ //server closed off the connection.
|
|
if (con->chunksize)
|
|
Con_Printf("Download was part way through chunking - must be corrupt - %s\n", con->filename);
|
|
else if (con->bufferused != con->contentlength && !con->file)
|
|
Con_Printf("Recieved file isn't the correct length - must be corrupt - %s\n", con->filename);
|
|
Con_Printf("Retrieved %s\n", con->filename);
|
|
if (con->file)
|
|
VFS_CLOSE(con->file);
|
|
else
|
|
{
|
|
FS_WriteFile(con->filename, con->buffer, con->bufferused, FS_GAME);
|
|
}
|
|
if (con->NotifyFunction)
|
|
{
|
|
con->NotifyFunction(con->filename, true);
|
|
con->NotifyFunction = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HTTP_CL_Think(void)
|
|
{
|
|
http_con_t *con = httpcl;
|
|
http_con_t *prev = NULL;
|
|
while (con)
|
|
{
|
|
if (!HTTP_CL_Run(con))
|
|
{
|
|
if (con->NotifyFunction)
|
|
con->NotifyFunction(con->filename, false);
|
|
|
|
if (cls.downloadmethod == DL_HTTP)
|
|
cls.downloadmethod = DL_NONE;
|
|
closesocket(con->sock);
|
|
|
|
if (prev)
|
|
prev->next = con->next;
|
|
else
|
|
httpcl = con->next;
|
|
|
|
if (con->buffer)
|
|
IWebFree(con->buffer);
|
|
IWebFree(con);
|
|
|
|
//I don't fancy fixing this up.
|
|
return;
|
|
}
|
|
else if (!cls.downloadmethod)
|
|
{
|
|
cls.downloadmethod = DL_HTTP;
|
|
if (con->state != HC_GETTING)
|
|
cls.downloadpercent = 0;
|
|
else if (con->contentlength <= 0)
|
|
cls.downloadpercent = 50;
|
|
else
|
|
cls.downloadpercent = con->bufferused*100.0f/con->contentlength;
|
|
strcpy(cls.downloadname, con->filename);
|
|
}
|
|
else if (cls.downloadmethod == DL_HTTP)
|
|
{
|
|
if (!strcmp(cls.downloadname, con->filename))
|
|
{
|
|
if (con->state != HC_GETTING)
|
|
cls.downloadpercent = 0;
|
|
else if (con->contentlength <= 0)
|
|
cls.downloadpercent = 50;
|
|
else
|
|
cls.downloadpercent = con->totalreceived*100.0f/con->contentlength;
|
|
}
|
|
}
|
|
|
|
prev = con;
|
|
con = con->next;
|
|
}
|
|
}
|
|
|
|
//returns true if we start downloading it
|
|
//returns false if we couldn't connect
|
|
//note that this return value is actually pretty useless
|
|
//the NotifyFunction will only ever be called after this has returned true, and won't always suceed.
|
|
qboolean HTTP_CL_Get(char *url, char *localfile, void (*NotifyFunction)(char *localfile, qboolean sucess))
|
|
{
|
|
unsigned long _true = true;
|
|
struct sockaddr_qstorage from;
|
|
http_con_t *con;
|
|
|
|
char server[128];
|
|
char uri[MAX_OSPATH];
|
|
char *slash;
|
|
|
|
if (localfile)
|
|
if (!*localfile)
|
|
localfile = NULL;
|
|
|
|
if (!strnicmp(url, "http://", 7))
|
|
url+=7;
|
|
else if (!strnicmp(url, "ftp://", 6))
|
|
{
|
|
url+=6;
|
|
slash = strchr(url, '/');
|
|
if (!slash)
|
|
{
|
|
Q_strncpyz(server, url, sizeof(server));
|
|
Q_strncpyz(uri, "/", sizeof(uri));
|
|
}
|
|
else
|
|
{
|
|
Q_strncpyz(uri, slash, sizeof(uri));
|
|
Q_strncpyz(server, url, sizeof(server));
|
|
server[slash-url] = '\0';
|
|
}
|
|
|
|
if (!localfile)
|
|
localfile = uri+1;
|
|
|
|
return FTP_Client_Command(va("download %s \"%s\" \"%s\"", server, uri+1, localfile), NotifyFunction);
|
|
}
|
|
else
|
|
{
|
|
Con_Printf("Bad URL: %s\n", url);
|
|
return false;
|
|
}
|
|
|
|
slash = strchr(url, '/');
|
|
if (!slash)
|
|
{
|
|
Q_strncpyz(server, url, sizeof(server));
|
|
Q_strncpyz(uri, "/", sizeof(uri));
|
|
}
|
|
else
|
|
{
|
|
Q_strncpyz(uri, slash, sizeof(uri));
|
|
Q_strncpyz(server, url, sizeof(server));
|
|
server[slash-url] = '\0';
|
|
}
|
|
|
|
if (!localfile)
|
|
localfile = uri+1;
|
|
|
|
con = IWebMalloc(sizeof(http_con_t));
|
|
|
|
if ((con->sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
|
|
{
|
|
Sys_Error ("HTTPCL_TCP_OpenSocket: socket: %s\n", strerror(qerrno));
|
|
}
|
|
|
|
|
|
{//quake routines using dns and stuff (Really, I wanna keep quake and ftp fairly seperate)
|
|
netadr_t qaddy;
|
|
if (!NET_StringToAdr (server, &qaddy))
|
|
{
|
|
IWebWarnPrintf ("HTTPCL_TCP_OpenSocket: Failed to resolve host: %s\n", server);
|
|
closesocket(con->sock);
|
|
IWebFree(con);
|
|
return false;
|
|
}
|
|
if (!qaddy.port)
|
|
qaddy.port = htons(80);
|
|
NetadrToSockadr(&qaddy, &from);
|
|
}//end of quake.
|
|
|
|
//not yet blocking.
|
|
if (connect(con->sock, (struct sockaddr *)&from, sizeof(from)) == -1)
|
|
{
|
|
IWebWarnPrintf ("HTTPCL_TCP_OpenSocket: connect: %i %s\n", qerrno, strerror(qerrno));
|
|
closesocket(con->sock);
|
|
IWebFree(con);
|
|
return false;
|
|
}
|
|
|
|
if (ioctlsocket (con->sock, FIONBIO, &_true) == -1) //now make it non blocking.
|
|
{
|
|
Sys_Error ("HTTPCL_TCP_OpenSocket: ioctl FIONBIO: %s\n", strerror(qerrno));
|
|
}
|
|
|
|
ExpandBuffer(con, 2048);
|
|
sprintf(con->buffer, "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "Connection: close\r\n" "User-Agent: "FULLENGINENAME"\r\n" "\r\n", uri, server);
|
|
con->bufferused = strlen(con->buffer);
|
|
con->contentlength = -1;
|
|
con->NotifyFunction = NotifyFunction;
|
|
if (!NotifyFunction)
|
|
Con_Printf("No NotifyFunction\n");
|
|
Q_strncpyz(con->filename, localfile, sizeof(con->filename));
|
|
|
|
slash = strchr(con->filename, '?');
|
|
if (slash)
|
|
*slash = '_';
|
|
|
|
con->next = httpcl;
|
|
httpcl = con;
|
|
|
|
HTTP_CL_Think();
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif
|