Built in FTP/HTTP client/server

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@19 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2004-08-23 01:40:25 +00:00
parent 7c00f2b190
commit 3a7c75319a
7 changed files with 4018 additions and 0 deletions

868
engine/http/ftpclient.c Normal file
View File

@ -0,0 +1,868 @@
#include "bothdefs.h"
#ifdef WEBCLIENT
#include "iweb.h"
#ifdef _WIN32
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EMSGSIZE WSAEMSGSIZE
#define ECONNRESET WSAECONNRESET
#define ECONNABORTED WSAECONNABORTED
#define ECONNREFUSED WSAECONNREFUSED
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
#define qerrno WSAGetLastError()
#else
#define qerrno errno
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#ifdef sun
#include <sys/filio.h>
#endif
#ifdef NeXT
#include <libc.h>
#endif
#define closesocket close
#define ioctlsocket ioctl
#endif
typedef struct FTPclientconn_s{
char server[256];
char name[64];
char pwd[64];
char path[256];
char pathprefix[256]; //Urhum.. Without this we can browse various entire hard drives too easily.
char file[64];
char localfile[MAX_QPATH];
int transfersize;
int transfered;
int controlsock;
int datasock; //FTP only allows one transfer per connection.
enum {ftp_control, ftp_listing, ftp_getting, ftp_putting} type;
int stage;
IWEBFILE *f;
struct FTPclientconn_s *next;
} FTPclientconn_t;
FTPclientconn_t *FTPclientconn;
FTPclientconn_t *FTP_CreateConnection(char *addy)
{
unsigned long _true = true;
struct sockaddr_qstorage from;
FTPclientconn_t *con;
con = IWebMalloc(sizeof(FTPclientconn_t));
if ((con->controlsock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_UDP_OpenSocket: socket: %s\n", strerror(qerrno));
}
{//quake routines using dns and stuff (Really, I wanna keep quake and ftp fairly seperate)
netadr_t qaddy;
NET_StringToAdr (addy, &qaddy);
if (!qaddy.port)
qaddy.port = htons(21);
NetadrToSockadr(&qaddy, &from);
}
//not yet blocking.
if (connect(con->controlsock, (struct sockaddr *)&from, sizeof(from)) == -1)
{
IWebWarnPrintf ("FTP_TCP_OpenSocket: connect: %i %s\n", qerrno, strerror(qerrno));
closesocket(con->controlsock);
IWebFree(con);
return NULL;
}
if (ioctlsocket (con->controlsock, FIONBIO, &_true) == -1) //now make it non blocking.
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO: %s\n", strerror(qerrno));
}
Q_strncpyz(con->server, addy, sizeof(con->server));
strcpy(con->name, "anonymous");
con->next = FTPclientconn;
FTPclientconn = con;
con->stage = 1;
con->type = ftp_control;
strcpy(con->path, "/");
con->datasock = INVALID_SOCKET;
con->transfersize = -1;
con->transfered = 0;
return FTPclientconn;
}
//duplicate a connection to get multiple data channels with a server.
FTPclientconn_t *FTP_DuplicateConnection(FTPclientconn_t *old)
{
FTPclientconn_t *new;
new = FTP_CreateConnection(old->server);
*new->server = '\0'; //mark it as non control
strcpy(new->name, old->name);
strcpy(new->pwd, old->pwd);
strcpy(new->path, old->path);
strcpy(new->pathprefix, old->pathprefix);
return new;
}
int FTP_CL_makelistensocket(void)
{
char name[256];
unsigned long _true = true;
int sock;
struct hostent *hent;
struct sockaddr_in address;
// int fromlen;
address.sin_family = AF_INET;
if (gethostname(name, sizeof(name)) == -1)
return INVALID_SOCKET;
hent = gethostbyname(name);
if (!hent)
return INVALID_SOCKET;
address.sin_addr.s_addr = *(int *)(hent->h_addr_list[0]);
address.sin_port = 0;
if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno));
}
if (ioctlsocket (sock, FIONBIO, &_true) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
}
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
closesocket(sock);
return INVALID_SOCKET;
}
listen(sock, 1);
return sock;
}
int FTP_CL_makeconnectsocket(char *ftpdest)
{
unsigned long _true = true;
int sock;
struct sockaddr_in address;
if (!ftpdest)
return 0;
if (*ftpdest == '(')
ftpdest++;
if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
IWebWarnPrintf ("FTP_UDP_OpenSocket: socket:", strerror(qerrno));
return INVALID_SOCKET;
}
if (ioctlsocket (sock, FIONBIO, &_true) == -1)
{
closesocket(sock);
IWebWarnPrintf ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
return INVALID_SOCKET;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = 0;
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
closesocket(sock);
IWebWarnPrintf ("FTTP_UDP_OpenSocket: bind:", strerror(qerrno));
return INVALID_SOCKET;
}
FTP_StringToAdr(ftpdest, (qbyte *)&address.sin_addr, (qbyte *)&address.sin_port);
//this is commented out because connect always reports would_block, no matter what happens. So why check?
//if (
connect(sock, (struct sockaddr *)&address, sizeof(address));// == -1)
/* {
closesocket(sock);
Con_Printf ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
return INVALID_SOCKET;
}
*/
return sock;
}
iwboolean FTP_SocketToString (int socket, char *s)
{
struct sockaddr_in addr;
int adrlen = sizeof(addr);
if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1)
return false;
sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte*)&addr.sin_addr)[0], ((qbyte*)&addr.sin_addr)[1], ((qbyte*)&addr.sin_addr)[2], ((qbyte*)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]);
return true;
}
iwboolean FTP_ClientConnThink (FTPclientconn_t *con) //true to kill con
{
char *line, *msg;
int ret;
char readdata[8192];
char tempbuff[8192];
if (con->stage == 6)
{
int len;
if (con->type == ftp_getting)
{
if (!cls.downloadmethod || (cls.downloadmethod == DL_FTP && !strcmp(cls.downloadname, con->localfile)))
{
strcpy(cls.downloadname, con->localfile);
cls.downloadmethod = DL_FTP;
if (con->transfersize == -1)
cls.downloadpercent=50;
else
cls.downloadpercent = con->transfered*100.0f/con->transfersize;
}
while((len = recv(con->datasock, readdata, sizeof(readdata), 0)) >0 )
{
IWebFWrite(readdata, len, 1, con->f);
con->transfered += len;
}
if (len == 0)
{
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
}
}
else if (con->type == ftp_putting)
{
int pos, sent;
int ammount, wanted = sizeof(readdata);
pos = IWebFTell(con->f);
ammount = IWebFRead(readdata, 1, wanted, con->f);
sent = send(con->datasock, readdata, ammount, 0);
if (sent == -1)
IWebFSeek(con->f, pos, SEEK_SET); //go back. Too much data
else
{
IWebFSeek(con->f, pos + sent, SEEK_SET); //written this much
if (!ammount) //file is over
{
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
// msg = "226 Transfer complete.\r\n";
// send (con->controlsock, msg, strlen(msg), 0);
}
}
}
}
ret = recv(con->controlsock, (char *)readdata, sizeof(readdata)-1, 0);
if (ret == -1)
{
if (qerrno == EWOULDBLOCK)
return false;
if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
{
Con_TPrintf (TL_CONNECTIONLOSTORABORTED);
return true;
}
// Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
return true;
}
readdata[ret] = '\0'; //null terminate. (it's a string)
//we now have a message.
//We've got to work out what has happened already.
//a server can send many lines of text for one reply
//220-hello
// this
//220-is
// 220-all
//220 one reply
//so we only read lines that contain number space words, without any leading space
line = readdata;
while (1)
{
msg = line;
while (*line)
{
if (*line == '\n')
break;
line++;
}
if (!*line) //broken message
break;
*line = '\0';
line++;
if (*con->server)
IWebDPrintf("^2FTP: %s\n", COM_TrimString(msg));
if (*msg < '0' || *msg > '9') //make sure it starts with number
continue;
ret = atoi(msg);
while(*msg >= '0' && *msg <= '9') //find next non number
msg++;
if (*msg != ' ') //must be a space (definatly not a '-')
continue;
msg++;
if (ret == 220)
{
sprintf(tempbuff, "USER %s\r\n", con->name);
send(con->controlsock, tempbuff, strlen(tempbuff), 0);
con->stage = 1;
}
else if (ret == 331)
{
if (con->type == ftp_control)
sprintf(tempbuff, "PASS %s\r\nPWD %s\r\n", con->pwd, con->path);
else
sprintf(tempbuff, "PASS %s\r\n", con->pwd);
send(con->controlsock, tempbuff, strlen(tempbuff), 0);
con->stage = 2;
}
else if (ret == 230) //we must now do something useful
{
char adr[64];
if (con->type == ftp_control) //control is for browsing and duplicating
continue;
con->datasock = FTP_CL_makelistensocket();
if (!con->datasock || !FTP_SocketToString(con->datasock, adr))
{
return true;
}
sprintf(tempbuff, "CWD %s%s\r\nPORT %s\r\n", con->pathprefix, con->path, adr);
send(con->controlsock, tempbuff, strlen(tempbuff), 0);
con->stage = 3;
}
else if (ret == 200)
{
struct sockaddr addr;
int addrlen = sizeof(addr);
int temp;
if (con->type == ftp_control)
continue;
if (con->stage == 3)
{
temp = accept(con->datasock, &addr, &addrlen);
closesocket(con->datasock);
con->datasock = temp;
if (temp != INVALID_SOCKET)
{
con->stage = 6;
if (con->type == ftp_getting)
{
con->f = IWebFOpenWrite(con->localfile, false);
if (con->f)
{
sprintf(tempbuff, "RETR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
con->transfersize = -1;
}
else
{
sprintf(tempbuff, "QUIT\r\n");
con->stage = 7;
}
}
else if (con->type == ftp_putting)
{
con->f = IWebFOpenRead(con->localfile);
if (con->f)
{
sprintf(tempbuff, "STOR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
con->transfersize = con->f->length;
}
else
{
sprintf(tempbuff, "QUIT\r\n");
con->stage = 7;
}
}
else
sprintf(tempbuff, "LIST %s\r\n", con->pwd);
send(con->controlsock, tempbuff, strlen(tempbuff), 0);
}
else
{
Con_Printf("FTP: Trying passive server mode\n");
msg = va("PASV\r\n");
send(con->controlsock, msg, strlen(msg), 0);
con->stage = 4;
}
}
}
else if (ret == 213)
{
con->transfersize = atoi(msg);
msg = va("RETR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
send(con->controlsock, msg, strlen(msg), 0);
}
else if (ret == 125) //begining transfer
{
if (con->type == ftp_getting)
{
COM_StripExtension(con->localfile, msg);
strcat(msg, ".tmp");
con->f = IWebFOpenWrite(msg, false);
if (!con->f)
{
msg = va("ABOR\r\nQUIT\r\n"); //bummer. we couldn't open this file to output to.
send(con->controlsock, msg, strlen(msg), 0);
con->stage = 7;
return true;
}
}
// msg = va("LIST\r\n");
// send(con->controlsock, msg, strlen(msg), 0);
con->stage = 6;
}
else if (ret == 226) //transfer complete
{
int len;
char data[1024];
if (con->f)
{
if (con->type == ftp_getting)
{
while(1) //this is potentially dodgy.
{
len = recv(con->datasock, data, sizeof(data), 0);
if (len == 0)
break;
if (len == -1)
{
if (qerrno != EWOULDBLOCK)
break;
continue;
}
con->transfered+=len;
data[len] = 0;
IWebFWrite(data, len, 1, con->f);
}
}
IWebFClose(con->f);
con->f = NULL;
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
if (con->transfersize != -1 && con->transfered != con->transfersize)
{
IWebPrintf("Transfer corrupt\nTransfered %i of %i bytes\n", con->transfered, con->transfersize);
}
else
IWebPrintf("Transfer compleate\n");
}
else
{
while((len = recv(con->datasock, data, sizeof(data), 0)) >0 )
{
data[len] = 0;
if (strchr(data, '\r'))
{
line = data;
for(;;)
{
msg = strchr(line, '\r');
if (!msg)
break;
*msg = '\0';
Con_Printf("%s", line);
line = msg+1;
}
Con_Printf("%s", line);
}
else
Con_Printf("%s", data);
}
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
}
msg = va("QUIT\r\n");
send(con->controlsock, msg, strlen(msg), 0);
con->stage = 7;
}
else if (ret == 227)
{
// Con_Printf("FTP: Got passive server mode\n");
if (con->datasock != INVALID_SOCKET)
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
con->datasock = FTP_CL_makeconnectsocket(strchr(msg, '('));
if (con->datasock != INVALID_SOCKET)
{
if (con->type == ftp_getting)
{
con->f = IWebFOpenWrite(con->localfile, false);
if (con->f)
{
con->stage = 8;
msg = va("TYPE I\r\nSIZE %s\r\n", con->file);
con->transfersize = -1;
/*
msg = va("RETR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
*/
}
else
{
msg = va("QUIT\r\n");
con->stage = 7;
Con_Printf("FTP: Failed to open local file %s\n", con->localfile);
}
}
else if (con->type == ftp_putting)
{
con->f = IWebFOpenRead(con->localfile);
if (con->f)
{
msg = va("STOR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
con->transfersize = con->f->length;
}
else
{
msg = va("QUIT\r\n");
con->stage = 7;
Con_Printf("FTP: Failed to open local file %s\n", con->localfile);
}
}
else
{
msg = "LIST\r\n";
con->stage = 6;
}
}
else
{
msg = "QUIT\r\n";
con->stage = 7;
Con_Printf("FTP: Didn't connect\n");
}
send (con->controlsock, msg, strlen(msg), 0);
}
else if (ret == 250)
{
Con_Printf("FTP: %i %s\n", ret, msg);
}
else if (ret == 257)
{ //stick it on the beginning.
Con_Printf("FTP: %i %s\n", ret, msg);
msg = strchr(msg, '"');
if (msg)
{
Q_strncpyz(con->pathprefix, msg+1, sizeof(con->pathprefix));
msg = strchr(con->pathprefix, '"');
if (msg)
*msg = '\0';
}
else
Q_strcpyline(con->pathprefix, msg+4, sizeof(con->pathprefix)-1);
}
else
{
if (ret < 200)
continue;
if (con->stage == 5)
{
Con_DPrintf("FTP: Trying passive server mode\n");
msg = va("PASV\r\n");
send(con->controlsock, msg, strlen(msg), 0);
con->stage = 4;
continue;
}
if (ret != 221)
Con_Printf("^1FTP: %i %s\n", ret, msg);
return true;
}
continue;
}
return false;
}
void FTP_ClientThink (void)
{
FTPclientconn_t *con, *old=NULL;
for (con = FTPclientconn; con; con = con->next)
{
if (FTP_ClientConnThink(con))
{
if (cls.downloadmethod == DL_FTP && !strcmp(cls.downloadname, con->localfile))
{ //this was us
cls.downloadmethod = DL_NONE;
}
if (con->f)
IWebFClose(con->f);
if (con->controlsock != INVALID_SOCKET)
closesocket(con->controlsock);
if (con->datasock != INVALID_SOCKET)
closesocket(con->datasock);
if (!old)
{
FTPclientconn = con->next;
IWebFree(con);
break;
}
else
{
old->next = con->next;
IWebFree(con);
break;
}
}
old = con;
}
}
FTPclientconn_t *FTP_FindControl(void)
{
FTPclientconn_t *con;
for (con = FTPclientconn; con; con = con->next)
{
if (*con->server)
return con;
}
return NULL;
}
void FTP_Client_Command (char *cmd)
{
char command[64];
char server[MAX_OSPATH];
FTPclientconn_t *con;
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (!stricmp(command, "open"))
{
if (FTP_FindControl())
Con_Printf("You are already connected\n");
else
{
cmd = COM_ParseOut(cmd, server, sizeof(server));
if ((con = FTP_CreateConnection(server)))
{
Con_Printf("FTP connect succeded\n");
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (cmd)
{
Q_strncpyz(con->name, command, sizeof(con->name));
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (cmd)
Q_strncpyz(con->pwd, command, sizeof(con->pwd));
}
}
else
Con_Printf("FTP connect failed\n");
}
}
else if (!stricmp(command, "download"))
{
cmd = COM_ParseOut(cmd, server, sizeof(server));
con = FTP_CreateConnection(server);
if (!con)
{
Con_Printf("FTP: Couldn't connect\n");
return;
}
*con->server = '\0';
con->type = ftp_getting;
cmd = COM_ParseOut(cmd, server, sizeof(server));
Q_strncpyz(con->file, server, sizeof(con->file));
Q_strncpyz(con->localfile, server, sizeof(con->localfile));
if (cmd = COM_ParseOut(cmd, server, sizeof(server)))
Q_strncpyz(con->localfile, server, sizeof(con->localfile));
}
else if (!stricmp(command, "quit"))
{
con = FTP_FindControl();
if (con)
{
char *msg;
msg = va("QUIT\r\n");
send(con->controlsock, msg, strlen(msg), 0);
// if (con->datasock)
// closesocket(con->datasock);
// closesocket(con->controlsock);
}
else
Con_Printf("No main FTP connection\n");
}
else if (!stricmp(command, "list"))
{
FTPclientconn_t *new, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return;
}
new = FTP_DuplicateConnection(con);
if (!new)
{
Con_Printf("Failed duplicate connection\n");
return;
}
new->type = ftp_listing;
}
else if (!stricmp(command, "get"))
{
FTPclientconn_t *new, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return;
}
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (!cmd)
{
Con_Printf("No file specified\n");
return;
}
new = FTP_DuplicateConnection(con);
if (!new)
{
Con_Printf("Failed duplicate connection\n");
return;
}
new->type = ftp_getting;
sprintf(new->file, command);
sprintf(new->localfile, "%s%s", new->path, command);
}
else if (!stricmp(command, "put"))
{
FTPclientconn_t *new, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return;
}
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (!cmd)
{
Con_Printf("No file specified\n");
return;
}
new = FTP_DuplicateConnection(con);
if (!new)
{
Con_Printf("Failed duplicate connection\n");
return;
}
new->type = ftp_putting;
sprintf(new->file, command);
sprintf(new->localfile, "%s%s", new->path, command);
}
else if (!stricmp(command, "cwd"))
{
FTPclientconn_t *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return;
}
Con_Printf("%s\n", con->path);
}
else if (!stricmp(command, "cd"))
{
char *msg;
FTPclientconn_t *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return;
}
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (*command == '/') //absolute
Q_strncpyz(con->path, command, sizeof(con->path));
else //bung it on the end
{
strncat(con->path, "/", sizeof(con->path)-1);
strncat(con->path, command, sizeof(con->path)-1);
}
msg = va("CWD %s%s\r\n", con->pathprefix, con->path);
send(con->controlsock, msg, strlen(msg), 0);
}
else
Con_Printf("Unrecognised FTP command\n");
/*
com = COM_ParseOut(com, command, sizeof(command));
com = COM_ParseOut(com, command, sizeof(command));
com = COM_ParseOut(com, command, sizeof(command));
*/
}
#endif

875
engine/http/ftpserver.c Normal file
View File

@ -0,0 +1,875 @@
#include "bothdefs.h"
#ifdef WEBSERVER
#include "iweb.h"
//hows this as a bug.
//TCP data can travel at different speeds.
//If the later bits of a data channel arrive after the message saying that a transfer was compleate,
//the later bits of the file may not arrive before the client closes the conenction.
//this is a major bug and can prevent the server from giving files at a high pl/ping
#ifdef _WIN32
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EMSGSIZE WSAEMSGSIZE
#define ECONNRESET WSAECONNRESET
#define ECONNABORTED WSAECONNABORTED
#define ECONNREFUSED WSAECONNREFUSED
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
#define qerrno WSAGetLastError()
#else
#define qerrno errno
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#ifdef sun
#include <sys/filio.h>
#endif
#ifdef NeXT
#include <libc.h>
#endif
#define closesocket close
#define ioctlsocket ioctl
#endif
char *COM_ParseOut (char *data, char *out, int outlen);
static iwboolean ftpserverinitied = false;
static int ftpserversocket;
typedef struct FTPclient_s{
char name[64];
char pwd[64];
int auth; //has it got auth?
char path[256];
char commandbuffer[256];
char messagebuffer[256];
int cmdbuflen;
int msgbuflen;
int controlsock;
int datasock; //FTP only allows one transfer per connection.
int dataislisten;
int datadir; //0 no data, 1 reading, 2 writing
IWEBFILE *file;
unsigned long blocking;
struct FTPclient_s *next;
} FTPclient_t;
FTPclient_t *FTPclient;
void FTP_ServerInit(void)
{
struct sockaddr_in address;
unsigned long _true = true;
int i;
int port = 21;
if ((ftpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno));
}
if (ioctlsocket (ftpserversocket, FIONBIO, &_true) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
}
address.sin_family = AF_INET;
//ZOID -- check for interface binding option
if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) {
address.sin_addr.s_addr = inet_addr(com_argv[i+1]);
Con_TPrintf(TL_NETBINDINTERFACE,
inet_ntoa(address.sin_addr));
} else
address.sin_addr.s_addr = INADDR_ANY;
if (port == PORT_ANY)
address.sin_port = 0;
else
address.sin_port = htons((short)port);
if( bind (ftpserversocket, (void *)&address, sizeof(address)) == -1)
{
closesocket(ftpserversocket);
return;
}
listen(ftpserversocket, 3);
ftpserverinitied = true;
IWebPrintf("FTP server is running\n");
return;
}
void FTP_ServerShutdown(void)
{
closesocket(ftpserversocket);
ftpserverinitied = false;
IWebPrintf("FTP server is deactivated\n");
}
int SendFileNameTo(char *fname, int size, void *socket)
{
// int i;
char buffer[256+1];
char *slash;
int isdir = fname[strlen(fname)-1] == '/';
#ifndef WEBSVONLY //copy protection of the like that QWSV normally has.
if (!isdir)
if (!SV_AllowDownload(fname)) //don't advertise if we're going to disallow it
return true;
#endif
if (isdir)
fname[strlen(fname)-1] = '\0';
while((slash = strchr(fname, '/')))
fname = slash+1;
if (isdir)
sprintf(buffer, "drw-r--r--\t1\troot\troot\t%8i Jan 1 12:00 %s\r\n", size, fname);
else
sprintf(buffer, "-rw-r--r--\t1\troot\troot\t%8i Jan 1 12:00 %s\r\n", size, fname);
// strcpy(buffer, fname);
// for (i = strlen(buffer); i < 40; i+=8)
// strcat(buffer, "\t");
send((int)socket, buffer, strlen(buffer), 0);
return true;
}
int FTP_SV_makelistensocket(unsigned long blocking)
{
char name[256];
unsigned long _true = true;
int sock;
struct hostent *hent;
struct sockaddr_in address;
// int fromlen;
address.sin_family = AF_INET;
if (gethostname(name, sizeof(name)) == -1)
return INVALID_SOCKET;
hent = gethostbyname(name);
if (!hent)
return INVALID_SOCKET;
address.sin_addr.s_addr = *(int *)(hent->h_addr_list[0]);
address.sin_port = 0;
if ((sock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: socket:", strerror(qerrno));
}
if (ioctlsocket (sock, FIONBIO, &blocking) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
}
if( bind (sock, (void *)&address, sizeof(address)) == -1)
{
closesocket(sock);
return INVALID_SOCKET;
}
listen(sock, 2);
return sock;
}
iwboolean FTP_SVSocketToString (int socket, char *s)
{
struct sockaddr_in addr;
int adrlen = sizeof(addr);
if (getsockname(socket, (struct sockaddr*)&addr, &adrlen) == -1)
return false;
sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte *)&addr.sin_addr)[0], ((qbyte *)&addr.sin_addr)[1], ((qbyte *)&addr.sin_addr)[2], ((qbyte *)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]);
return true;
}
iwboolean FTP_SVRemoteSocketToString (int socket, char *s)
{
struct sockaddr_in addr;
int adrlen = sizeof(addr);
addr.sin_family = AF_INET;
if (getpeername(socket, (struct sockaddr*)&addr, &adrlen) == -1)
return false;
sprintf(s, "%i,%i,%i,%i,%i,%i", ((qbyte *)&addr.sin_addr)[0], ((qbyte *)&addr.sin_addr)[1], ((qbyte *)&addr.sin_addr)[2], ((qbyte *)&addr.sin_addr)[3], ((qbyte *)&addr.sin_port)[0], ((qbyte *)&addr.sin_port)[1]);
return true;
}
/*
* Responsable for sending all control server -> client messages.
* Queues the message if it cannot send now.
* Kicks if too big a queue.
*/
void QueueMessage(FTPclient_t *cl, char *msg)
{
if (send (cl->controlsock, msg, strlen(msg), 0) == -1)
{ //wasn't sent
if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1)
closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell).
strcat(cl->messagebuffer, msg);
}
}
void VARGS QueueMessageva(FTPclient_t *cl, char *fmt, ...)
{
va_list argptr;
char msg[1024];
va_start (argptr, fmt);
_vsnprintf (msg,sizeof(msg)-1, fmt,argptr);
va_end (argptr);
if (send (cl->controlsock, msg, strlen(msg), 0) == -1)
{ //wasn't sent
if (strlen(msg) + strlen(cl->messagebuffer) >= sizeof(cl->messagebuffer)-1)
closesocket(cl->controlsock); //but don't mark it as closed, so we get errors later (for this is how we shall tell).
strcat(cl->messagebuffer, msg);
}
}
iwboolean FTP_ServerThinkForConnection(FTPclient_t *cl)
{
int ret;
struct sockaddr_in from;
int fromlen;
char *msg, *line;
unsigned long _true = true;
char mode[64];
static char resource[8192];
if (cl->datadir == 1)
{
int pos, sent;
int ammount, wanted = sizeof(resource);
pos = IWebFTell(cl->file);
ammount = IWebFRead(resource, 1, wanted, cl->file);
sent = send(cl->datasock, resource, ammount, 0);
if (sent == -1)
{
IWebFSeek(cl->file, pos, SEEK_SET);
if (qerrno != EWOULDBLOCK)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
IWebFClose(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
else
{
if (sent != ammount)
IWebFSeek(cl->file, pos + sent, SEEK_SET);
if (ammount != wanted && sent == ammount) //file is over
{
send(cl->datasock, resource, 0, 0);
send(cl->datasock, resource, 0, 0);
send(cl->datasock, resource, 0, 0);
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
IWebFClose(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
}
else if (cl->datadir == 2)
{
int len;
while((len = recv(cl->datasock, resource, sizeof(resource), 0)) >0 )
{
IWebFWrite(resource, len, 1, cl->file);
}
if (len == -1)
{
if (qerrno != EWOULDBLOCK)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
if (cl->file)
IWebFClose(cl->file);
cl->file = NULL;
QueueMessage (cl, "226 Transfer complete .\r\n");
cl->datadir = 0;
}
}
if (len == 0)
{
QueueMessage (cl, "226 Transfer complete .\r\n");
IWebFClose(cl->file);
cl->file = NULL;
cl->datadir = 0;
}
}
ret = recv(cl->controlsock, cl->commandbuffer+cl->cmdbuflen, sizeof(cl->commandbuffer)-1 - cl->cmdbuflen, 0);
if (ret == -1)
{
if (qerrno == EWOULDBLOCK)
return false; //remove
if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
return true;
Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
return true;
}
if (*cl->messagebuffer)
{
if (send (cl->controlsock, cl->messagebuffer, strlen(cl->messagebuffer), 0) != -1)
*cl->messagebuffer = '\0'; //YAY! It went!
}
if (ret == 0)
return false;
cl->cmdbuflen += ret;
cl->commandbuffer[cl->cmdbuflen] = 0;
line = cl->commandbuffer;
while (1)
{
msg = line;
while (*line)
{
if (*line == '\r')
*line = ' ';
if (*line == '\n')
break;
line++;
}
if (!*line) //broken client
{
memmove(cl->commandbuffer, line, strlen(line)+1);
cl->cmdbuflen = strlen(line);
break;
}
*line = '\0';
line++;
IWebPrintf("FTP: %s\n", msg);
msg = COM_ParseOut(msg, mode, sizeof(mode));
if (!stricmp(mode, "SYST"))
{
QueueMessage (cl, "215 UNIX Type: L8.\r\n"); //some browsers can be wierd about things.
}
else if (!stricmp(mode, "user"))
{
msg = COM_ParseOut(msg, cl->name, sizeof(cl->name));
QueueMessage (cl, "331 User name received, will be checked with password.\r\n");
}
else if (!stricmp(mode, "pass"))
{
msg = COM_ParseOut(msg, cl->pwd, sizeof(cl->pwd));
cl->auth = IWebAuthorize(cl->name, cl->pwd);
if (cl->auth)
QueueMessage (cl, "230 User logged in.\r\n");
else
QueueMessage (cl, "530 Username or Password was incorrect or otherwise invalid.\r\n");
}
else if (!stricmp(mode, "TYPE"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!stricmp(resource, "A")) //ascii
{
QueueMessage (cl, "200 asci selected.\r\n");
}
else if (!stricmp(resource, "I")) //binary
{
QueueMessage (cl, "200 binary selected.\r\n");
}
else
{
QueueMessage (cl, "200 asci selected.\r\n");
}
}
else if (!stricmp(mode, "PWD"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
QueueMessageva (cl, "257 \"%s\"\r\n", cl->path);
}
else if (!stricmp(mode, "CWD"))
{
char *p;
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
Q_strcpyline(cl->path, msg+1, sizeof(cl->path));//path starts after cmd and single space
for (p = cl->path+strlen(cl->path)-1; *p == ' ' && p >= cl->path; p--)
*p = '\0';
QueueMessage (cl, "200 directory changed.\r\n");
}
else if (!stricmp(mode, "PASV"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->datasock != INVALID_SOCKET)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
cl->datasock = FTP_SV_makelistensocket(cl->blocking);
if (cl->datasock == INVALID_SOCKET)
QueueMessage (cl, "425 server was unable to make a listen socket\r\n");
else
{
FTP_SVSocketToString(cl->datasock, resource);
QueueMessageva (cl, "227 Entering Passive Mode (%s).\r\n", resource);
}
cl->dataislisten = true;
}
else if (!stricmp(mode, "PORT"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->datasock != INVALID_SOCKET)
{
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
cl->dataislisten = false;
if ((cl->datasock = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("FTP_UDP_OpenSocket: socket:", strerror(qerrno));
}
if (ioctlsocket (cl->datasock, FIONBIO, &cl->blocking) == -1)
{
Sys_Error ("FTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
}
from.sin_family = AF_INET;
from.sin_addr.s_addr = INADDR_ANY;
from.sin_port = 0;
if( bind (cl->datasock, (void *)&from, sizeof(from)) == -1)
{
closesocket(cl->datasock);
cl->datasock=INVALID_SOCKET;
QueueMessage (cl, "425 server bind error.\r\n");
continue;
}
fromlen = sizeof(from);
FTP_StringToAdr(resource, (qbyte *)&from.sin_addr, (qbyte *)&from.sin_port);
connect(cl->datasock, (struct sockaddr *)&from, fromlen);
QueueMessage (cl, "200 Opened data channel.\r\n");
}
else if (!stricmp(mode, "LIST"))
{
char buffer[256];
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->dataislisten) //accept a connect.
{
int temp;
struct sockaddr_in adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno);
continue;
}
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "503 Bad sequence of commands.\r\n");
continue;
}
if (*cl->path == '/')
strcpy(buffer, cl->path+1);
else
strcpy(buffer, cl->path);
strcat(buffer, "*");
QueueMessage (cl, "125 Opening FAKE ASCII mode data connection for file.\r\n");
COM_EnumerateFiles(buffer, SendFileNameTo, (void *)cl->datasock);
QueueMessage (cl, "226 Transfer complete.\r\n");
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
}
// else if (!stricmp(mode, "SIZE")) //why IE can't use the list command to find file length, I've no idea.
// {
// msg = COM_ParseOut(msg, resource, sizeof(resource));
// }
else if (!stricmp(mode, "RETR"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
if (cl->dataislisten) //accept a connect.
{
int temp;
struct sockaddr_in adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno);
continue;
}
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "503 Bad sequence of commands.\r\n");
continue;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!cl->auth & IWEBACC_READ)
{
QueueMessage (cl, "550 No read access.\r\n");
continue;
}
if (!*resource == '/')
{
memmove(resource+strlen(cl->path), resource, strlen(resource)+1);
memcpy(resource, cl->path, strlen(cl->path));
}
if (*resource == '/')
{
if (SV_AllowDownload(resource+1))
cl->file = IWebFOpenRead(resource+1);
else
cl->file = IWebGenerateFile(resource+1, NULL, 0);
}
else
{
if (SV_AllowDownload(resource))
cl->file = IWebFOpenRead(resource);
else
cl->file = IWebGenerateFile(resource, NULL, 0);
}
if (!cl->file)
{
QueueMessage (cl, "550 File not found.\r\n");
}
else
{ //send data
QueueMessage (cl, "125 Opening BINARY mode data connection for file.\r\n");
cl->datadir = 1;
}
}
else if (!stricmp(mode, "STOR"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
Q_strcpyline(mode, msg+1, sizeof(mode));
if (!(cl->auth & IWEBACC_FULL) && (((cl->auth & IWEBACC_WRITE && !IWebAllowUpLoad(cl->path+1, cl->name)) || !(cl->auth & IWEBACC_WRITE))))
{
QueueMessage (cl, "550 Permission denied.\r\n");
}
else
{
if (cl->dataislisten) //accept a connect.
{
int temp;
struct sockaddr_in adr;
int adrlen = sizeof(adr);
temp = accept(cl->datasock, (struct sockaddr *)&adr, &adrlen);
closesocket(cl->datasock);
cl->datasock = temp;
cl->dataislisten = false;
if (cl->datasock == INVALID_SOCKET)
{
QueueMessageva (cl, "425 Your client connected too slowly - %i.\r\n", qerrno);
continue;
}
}
if (cl->datasock == INVALID_SOCKET)
{
QueueMessage (cl, "502 Bad sequence of commands.\r\n");
continue;
}
// msg = COM_ParseOut(msg, mode, sizeof(mode));
if (*mode == '/')
sprintf(resource, "%s%s", cl->path, mode);
else
sprintf(resource, "%s%s", cl->path, mode);
cl->file = IWebFOpenRead(resource);
if (cl->file)
{
IWebFClose(cl->file);
QueueMessage (cl, "550 File already exists.\r\n");
continue;
}
cl->file = IWebFOpenWrite(resource, false);
if (!cl->file)
{
QueueMessage (cl, "550 Couldn't open output.\r\n");
}
else
{ //send data
QueueMessage (cl, "125 Opening BINARY mode data connection for input.\r\n");
cl->datadir = 2;
}
}
}
else if (!stricmp(mode, "STRU"))
{
if (!cl->auth)
{
QueueMessage (cl, "530 Not logged in.\r\n");
continue;
}
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!strcmp(resource, "F"))
{
QueueMessage (cl, "200 recordless structure selected.\r\n");
}
else
{
QueueMessage (cl, "504 not implemented (it's a simple server).\r\n");
}
}
else if (!stricmp(mode, "NOOP"))
{
QueueMessage (cl, "200 Do something then!\r\n");
}
else if (!stricmp(mode, "QUIT"))
{
QueueMessage (cl, "200 About to quit.\r\n");
return true;
}
else
{
QueueMessage (cl, "502 Command not implemented.\r\n");
}
}
return false;
}
#if defined(WEBSVONLY) && defined(_WIN32)
unsigned int WINAPI BlockingClient(FTPclient_t *cl)
{
unsigned long _false = false;
if (ioctlsocket (cl->controlsock, FIONBIO, &_false) == -1)
{
IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(qerrno));
return 0;
}
cl->blocking = false;
while (!FTP_ServerThinkForConnection(cl))
{
Sleep(10);
}
if (cl->file)
IWebFClose(cl->file);
closesocket(cl->controlsock);
if (cl->datasock)
closesocket(cl->datasock);
IWebFree(cl);
return 0;
}
#endif
iwboolean FTP_ServerRun(iwboolean ftpserverwanted)
{
FTPclient_t *cl, *prevcl;
struct sockaddr_in from;
int fromlen;
int clientsock;
unsigned long _true = true;
if (!ftpserverinitied)
{
if (ftpserverwanted)
FTP_ServerInit();
return false;
}
else if (!ftpserverwanted)
{
FTP_ServerShutdown();
return false;
}
prevcl = NULL;
for (cl = FTPclient; cl; cl = cl->next)
{
if (FTP_ServerThinkForConnection(cl))
{
if (cl->file)
IWebFClose(cl->file);
closesocket(cl->controlsock);
if (cl->datasock)
closesocket(cl->datasock);
if (prevcl)
{
prevcl->next = cl->next;
IWebFree(cl);
cl = prevcl;
if (!cl) //kills loop
break;
}
else
{
FTPclient = cl->next;
IWebFree(cl);
cl = FTPclient;
if (!cl) //kills loop
break;
}
}
prevcl = cl;
}
fromlen = sizeof(from);
clientsock = accept(ftpserversocket, (struct sockaddr *)&from, &fromlen);
if (clientsock == -1)
{
if (qerrno == EWOULDBLOCK)
return false;
if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
{
Con_TPrintf (TL_CONNECTIONLOSTORABORTED);
return false;
}
Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
return false;
}
//is this needed?
if (ioctlsocket (clientsock, FIONBIO, &_true) == -1)
{
IWebPrintf ("FTP_ServerRun: blocking error: %s\n", strerror(qerrno));
return false;
}
cl = IWebMalloc(sizeof(FTPclient_t));
if (!cl) //iwebmalloc is allowed to fail.
{
char *msg = "421 Not enough memory is allocated.\r\n"; //don't be totally anti social
send(clientsock, msg, strlen(msg), 0);
closesocket(clientsock); //try to forget this ever happend
return true;
}
{
char resource[256];
FTP_SVRemoteSocketToString(clientsock, resource);
IWebPrintf("FTP connect from %s\n", resource);
}
cl->controlsock = clientsock;
cl->datasock = INVALID_SOCKET;
cl->next = FTPclient;
cl->blocking = false;
strcpy(cl->path, "/");
QueueMessage(cl, "220-QuakeWorld FTP Server.\r\n220 Welcomes all new users.\r\n");
#if defined(WEBSVONLY) && defined(_WIN32)
if (!CreateThread(NULL, 128, BlockingClient, cl, 0, NULL))
#endif
FTPclient = cl;
return true;
}
#endif

473
engine/http/httpclient.c Normal file
View File

@ -0,0 +1,473 @@
#include "bothdefs.h"
#ifdef WEBCLIENT
#include "iweb.h"
#ifdef _WIN32
//msvc crap
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EMSGSIZE WSAEMSGSIZE
#define ECONNRESET WSAECONNRESET
#define ECONNABORTED WSAECONNABORTED
#define ECONNREFUSED WSAECONNREFUSED
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
#define snprintf _snprintf
#define qerrno WSAGetLastError()
#else
//gcc stuff
#define qerrno errno
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#ifdef sun
#include <sys/filio.h>
#endif
#ifdef NeXT
#include <libc.h>
#endif
#define closesocket close
#define ioctlsocket ioctl
#endif
/*
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.
*/
qboolean HTTP_CL_Get(char *url, char *localfile);
typedef struct {
int sock;
enum {HC_REQUESTING,HC_GETTINGHEADER, HC_GETTING} state;
char *buffer;
char filename[MAX_QPATH];
int bufferused;
int bufferlen;
qboolean chunking;
int chunksize;
int chunked;
int contentlength;
} 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
HTTP_CL_Get(Location, con->filename);
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;
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;
}
}
}
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_Printf("Recieved file isn't the correct length - must be corrupt - %s\n", con->filename);
Con_Printf("Retrieved %s\n", con->filename);
snprintf(Location, sizeof(Location)-1, "%s/%s", com_gamedir, con->filename);
COM_CreatePath(Location);
COM_WriteFile(con->filename, con->buffer, con->bufferused);
return false;
}
break;
}
return true;
}
void HTTP_CL_Think(void)
{
http_con_t *con = httpcl;
if (con)
{
if (!HTTP_CL_Run(con))
{
if (cls.downloadmethod == DL_HTTP)
cls.downloadmethod = DL_NONE;
closesocket(con->sock);
if (con->buffer)
IWebFree(con->buffer);
IWebFree(con);
if (con == httpcl)
{
httpcl = NULL;
return;
}
con = NULL;
}
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->bufferused*100.0f/con->contentlength;
}
}
}
}
qboolean HTTP_CL_Get(char *url, char *localfile)
{
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;
FTP_Client_Command(va("download %s \"%s\" \"%s\"", server, uri+1, localfile));
return true;
}
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: FTE\r\n" "\r\n", uri, server);
con->bufferused = strlen(con->buffer);
con->contentlength = -1;
strcpy(con->filename, localfile);
/* slash = strchr(con->filename, '?');
if (slash)
*slash = '\0';*/
httpcl = con;
return true;
}
#endif

565
engine/http/httpserver.c Normal file
View File

@ -0,0 +1,565 @@
#include "bothdefs.h"
#ifdef WEBSERVER
#include "iweb.h"
//FIXME: Before any admins use this for any serious usage, make the server send bits of file slowly.
#ifdef _WIN32
#define EWOULDBLOCK WSAEWOULDBLOCK
#define EMSGSIZE WSAEMSGSIZE
#define ECONNRESET WSAECONNRESET
#define ECONNABORTED WSAECONNABORTED
#define ECONNREFUSED WSAECONNREFUSED
#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
#define qerrno WSAGetLastError()
#else
#define qerrno errno
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#ifdef sun
#include <sys/filio.h>
#endif
#ifdef NeXT
#include <libc.h>
#endif
#define closesocket close
#define ioctlsocket ioctl
#endif
static qboolean httpserverinitied = false;
static int httpserversocket;
typedef enum {HTTP_WAITINGFORREQUEST,HTTP_SENDING,HTTP_RECEIVING} http_mode_t;
void HTTP_ServerInit(void)
{
struct sockaddr_in address;
unsigned long _true = true;
int i;
int port = 80;
if ((httpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
Sys_Error ("HTTP_UDP_OpenSocket: socket:", strerror(qerrno));
}
if (ioctlsocket (httpserversocket, FIONBIO, &_true) == -1)
{
Sys_Error ("HTTP_UDP_OpenSocket: ioctl FIONBIO:", strerror(qerrno));
}
address.sin_family = AF_INET;
//check for interface binding option
if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc)
{
address.sin_addr.s_addr = inet_addr(com_argv[i+1]);
Con_TPrintf(TL_NETBINDINTERFACE,
inet_ntoa(address.sin_addr));
}
else
address.sin_addr.s_addr = INADDR_ANY;
if (port == PORT_ANY)
address.sin_port = 0;
else
address.sin_port = htons((short)port);
if( bind (httpserversocket, (void *)&address, sizeof(address)) == -1)
{
closesocket(httpserversocket);
return;
}
listen(httpserversocket, 3);
httpserverinitied = true;
IWebPrintf("HTTP server is running\n");
return;
}
void HTTP_ServerShutdown(void)
{
closesocket(httpserversocket);
IWebPrintf("HTTP server closed\n");
httpserverinitied = false;
}
typedef struct HTTP_active_connections_s {
int datasock;
IWEBFILE *file;
struct HTTP_active_connections_s *next;
http_mode_t mode;
qboolean modeswitched;
qboolean closeaftertransaction;
qboolean close;
char *inbuffer;
int inbuffersize;
int inbufferused;
char *outbuffer;
int outbuffersize;
int outbufferused;
} HTTP_active_connections_t;
static HTTP_active_connections_t *HTTP_ServerConnections;
static int httpconnectioncount;
static void ExpandInBuffer(HTTP_active_connections_t *cl, int quant, qboolean fixedsize)
{
int newsize;
if (fixedsize)
newsize = quant;
else
newsize = cl->inbuffersize+quant;
if (newsize <= cl->inbuffersize)
return;
cl->inbuffer = IWebRealloc(cl->inbuffer, newsize);
cl->inbuffersize = newsize;
}
static void ExpandOutBuffer(HTTP_active_connections_t *cl, int quant, qboolean fixedsize)
{
int newsize;
if (fixedsize)
newsize = quant;
else
newsize = cl->outbuffersize+quant;
if (newsize <= cl->outbuffersize)
return;
cl->outbuffer = IWebRealloc(cl->outbuffer, newsize);
cl->outbuffersize = newsize;
}
void HTTP_RunExisting (void)
{
char *content;
char *msg, *nl;
char buf2[256]; //short lived temp buffer.
char resource[256];
char mode[8];
qboolean hostspecified;
int contentlen;
int HTTPmarkup; //version
int errno;
HTTP_active_connections_t *prev, *cl = HTTP_ServerConnections;
prev = NULL;
for (prev = NULL; cl; cl=(prev=cl)->next)
{
int ammount, wanted;
if (cl->close)
{
if (prev)
prev->next = cl->next;
else
HTTP_ServerConnections = cl->next;
closesocket(cl->datasock);
cl->datasock = INVALID_SOCKET;
if (cl->inbuffer)
IWebFree(cl->inbuffer);
if (cl->outbuffer)
IWebFree(cl->outbuffer);
if (cl->file)
IWebFClose(cl->file);
IWebFree(cl);
httpconnectioncount--;
cl = prev;
break;
}
switch(cl->mode)
{
case HTTP_WAITINGFORREQUEST:
if (cl->outbufferused)
Sys_Error("Persistant connection was waiting for input with unsent output");
ammount = cl->inbuffersize - cl->inbufferused - 1;
if (ammount < 128)
{
if (cl->inbuffersize>128*1024)
{
cl->close = true; //that's just taking the piss.
continue;
}
ExpandInBuffer(cl, 1500, false);
ammount = cl->inbuffersize - cl->inbufferused - 1;
}
if (cl->modeswitched)
{
ammount = 0;
}
else
{
//we can't try and recv 0 bytes as we use an expanding buffer
ammount = recv(cl->datasock, cl->inbuffer+cl->inbufferused, ammount, 0);
if (ammount < 0)
{
if (qerrno != EWOULDBLOCK) //they closed on us. Assume end.
{
cl->close = true;
}
continue;
}
if (ammount == 0)
{
cl->close = true;
continue;
}
}
cl->modeswitched = false;
cl->inbufferused += ammount;
cl->inbuffer[cl->inbufferused] = '\0';
content = NULL;
msg = cl->inbuffer;
nl = strchr(msg, '\n');
if (!nl)
{
cont:
continue; //we need more... MORE!!! MORE I TELL YOU!!!!
}
msg = COM_ParseOut(msg, mode, sizeof(mode));
msg = COM_ParseOut(msg, resource, sizeof(resource));
if (!*resource)
{
cl->close = true; //even if they forgot to specify a resource, we didn't find an HTTP so we have no option but to close.
continue;
}
hostspecified = false;
if (!strnicmp(resource, "http://", 7))
{ //groan... 1.1 compliance requires parsing this correctly, without the client ever specifiying it.
char *slash; //we don't do multiple hosts.
hostspecified=true;
slash = strchr(resource+7, '/');
if (!slash)
strcpy(resource, "/");
else
memmove(resource, slash, strlen(slash+1)); //just get rid of the http:// stuff.
}
if (!strcmp(resource, "/"))
strcpy(resource, "/index.html");
msg = COM_ParseOut(msg, buf2, sizeof(buf2));
contentlen = 0;
if (!strnicmp(buf2, "HTTP/", 5))
{
if (!strncmp(buf2, "HTTP/1.1", 8))
HTTPmarkup = 3;
else if (!strncmp(buf2, "HTTP/1", 6))
HTTPmarkup = 2;
else
{
HTTPmarkup = 1; //0.9... lamer.
cl->closeaftertransaction = true;
}
//expect X lines containing options.
//then a blank line. Don't continue till we have that.
msg = nl+1;
while (1)
{
if (*msg == '\r')
msg++;
if (*msg == '\n')
{
msg++;
break; //that was our blank line.
}
while(*msg == ' ')
msg++;
if (!strnicmp(msg, "Host: ", 6)) //parse needed header fields
hostspecified = true;
else if (!strnicmp(msg, "Content-Length: ", 16)) //parse needed header fields
contentlen = atoi(msg+16);
else if (!strnicmp(msg, "Transfer-Encoding: ", 18)) //parse needed header fields
{
cl->closeaftertransaction = true;
goto notimplemented;
}
else if (!strnicmp(msg, "Connection: close", 17))
cl->closeaftertransaction = true;
while(*msg != '\n')
{
if (!*msg)
{
goto cont;
}
msg++;
}
msg++;
}
}
else
{
HTTPmarkup = 0; //strimmed... totally...
cl->closeaftertransaction = true;
//don't bother running to nl.
}
if (cl->inbufferused-(msg-cl->inbuffer) < contentlen)
continue;
cl->modeswitched = true;
if (contentlen)
{
content = BZ_Malloc(contentlen+1);
memcpy(content, msg, contentlen+1);
}
memmove(cl->inbuffer, cl->inbuffer+(msg-cl->inbuffer+contentlen), cl->inbufferused-(msg-cl->inbuffer+contentlen));
cl->inbufferused -= msg-cl->inbuffer+contentlen;
if (HTTPmarkup == 3 && !hostspecified) //1.1 requires the host to be specified... we ca,just ignore it as we're not routing or imitating two servers. (for complience we need to encourage the client to send - does nothing for compatability or anything, just compliance to spec. not always the same thing)
{
msg = "HTTP/1.1 400 Bad Request\r\n" "Content-Type: text/plain\r\n" "Content-Length: 69\r\n" "Server: FTE/0\r\n" "\r\n" "400 Bad Request\r\nYour client failed to provide the host header line";
ammount = strlen(msg);
ExpandOutBuffer(cl, ammount, true);
memcpy(cl->outbuffer, msg, ammount);
cl->outbufferused = ammount;
cl->mode = HTTP_SENDING;
}
else if (!stricmp(mode, "GET") || !stricmp(mode, "HEAD") || !stricmp(mode, "POST"))
{
if (!strnicmp(mode, "P", 1)) //when stuff is posted, data is provided. Give an error message if we couldn't do anything with that data.
cl->file = IWebGenerateFile(resource+1, content, contentlen);
else
cl->file = IWebFOpenRead(resource);
if (!cl->file)
{
if (HTTPmarkup >= 3)
msg = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: FTE/0\r\n" "\r\n" "404 Bad address";
else if (HTTPmarkup == 2)
msg = "HTTP/1.0 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: FTE/0\r\n" "\r\n" "404 Bad address";
else if (HTTPmarkup)
msg = "HTTP/0.9 404 Not Found\r\n" "\r\n" "404 Bad address";
else
msg = "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY>404 Not Found<BR>The specified file could not be found on the server</HEAD></HTML>";
ammount = strlen(msg);
ExpandOutBuffer(cl, ammount, true);
memcpy(cl->outbuffer, msg, ammount);
cl->outbufferused = ammount;
cl->mode = HTTP_SENDING;
}
else
{
if (HTTPmarkup>=3)
sprintf(resource, "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %i\r\n" "Server: FTE/0\r\n" "\r\n", strstr(resource, ".htm")?"text/html":"text/plain", cl->file->length);
else if (HTTPmarkup==2)
sprintf(resource, "HTTP/1.0 200 OK\r\n" "Content-Type: %s\r\n" "Content-Length: %i\r\n" "Server: FTE/0\r\n" "\r\n", strstr(resource, ".htm")?"text/html":"text/plain", cl->file->length);
else if (HTTPmarkup)
sprintf(resource, "HTTP/0.9 200 OK\r\n\r\n");
else
sprintf(resource, "");
msg = resource;
if (*mode == 'H' || *mode == 'h')
{
IWebFClose(cl->file);
cl->file = NULL;
}
ammount = strlen(msg);
ExpandOutBuffer(cl, ammount, true);
memcpy(cl->outbuffer, msg, ammount);
cl->outbufferused = ammount;
cl->mode = HTTP_SENDING;
}
}
//PUT/POST must support chunked transfer encoding for 1.1 compliance.
/* else if (!stricmp(mode, "PUT")) //put is replacement of a resource. (file uploads)
{
}
*/
else
{
notimplemented:
if (HTTPmarkup >= 3)
msg = "HTTP/1.1 501 Not Implemented\r\n\r\n";
else if (HTTPmarkup == 2)
msg = "HTTP/1.0 501 Not Implemented\r\n\r\n";
else if (HTTPmarkup)
msg = "HTTP/0.9 501 Not Implemented\r\n\r\n";
else
{
msg = NULL;
cl->close = true;
}
if (msg)
{
ammount = strlen(msg);
ExpandOutBuffer(cl, ammount, true);
memcpy(cl->outbuffer, msg, ammount);
cl->outbufferused = ammount;
cl->mode = HTTP_SENDING;
}
}
if (content)
BZ_Free(content);
break;
case HTTP_SENDING:
if (cl->outbufferused < 128)
{
if (cl->file)
{
ExpandOutBuffer(cl, 1500, true);
wanted = cl->outbuffersize - cl->outbufferused;
ammount = IWebFRead(cl->outbuffer+cl->outbufferused, 1, wanted, cl->file);
if (!ammount)
{
IWebFClose(cl->file);
cl->file = NULL;
}
else
cl->outbufferused+=ammount;
}
}
ammount = send(cl->datasock, cl->outbuffer, cl->outbufferused, 0);
if (ammount == -1)
{
errno = qerrno;
if (errno != EWOULDBLOCK)
{
cl->close = true;
}
}
else if (ammount||!cl->outbufferused)
{
memcpy(cl->outbuffer, cl->outbuffer+ammount, cl->outbufferused-ammount);
cl->outbufferused -= ammount;
if (!cl->outbufferused && !cl->file)
{
cl->modeswitched = true;
cl->mode = HTTP_WAITINGFORREQUEST;
if (cl->closeaftertransaction)
cl->close = true;
}
}
else
cl->close = true;
break;
/* case HTTP_RECEIVING:
sent = recv(cl->datasock, resource, ammount, 0);
if (sent == -1)
{
if (qerrno != EWOULDBLOCK) //they closed on us. Assume end.
{
IWebFClose(cl->file);
cl->file = NULL;
cl->close = true;
continue;
}
}
if (sent != 0)
IWebFWrite(resource, 1, sent, cl->file);
break;*/
}
}
}
qboolean HTTP_ServerPoll(qboolean httpserverwanted) //loop while true
{
struct sockaddr_in from;
int fromlen;
int clientsock;
HTTP_active_connections_t *cl;
if (!httpserverinitied)
{
if (httpserverwanted)
HTTP_ServerInit();
return false;
}
else if (!httpserverwanted)
{
HTTP_ServerShutdown();
return false;
}
if (httpconnectioncount>32)
return false;
fromlen = sizeof(from);
clientsock = accept(httpserversocket, (struct sockaddr *)&from, &fromlen);
if (clientsock == -1)
{
if (qerrno == EWOULDBLOCK)
{
HTTP_RunExisting();
return false;
}
if (qerrno == ECONNABORTED || qerrno == ECONNRESET)
{
Con_TPrintf (TL_CONNECTIONLOSTORABORTED);
return false;
}
Con_TPrintf (TL_NETGETPACKETERROR, strerror(qerrno));
return false;
}
cl = IWebMalloc(sizeof(HTTP_active_connections_t));
cl->datasock = clientsock;
cl->next = HTTP_ServerConnections;
HTTP_ServerConnections = cl;
httpconnectioncount++;
return true;
}
#endif

162
engine/http/iweb.h Normal file
View File

@ -0,0 +1,162 @@
#ifdef WEBSERVER
#ifndef IWEB_H__
#define IWEB_H__
#ifdef WEBSVONLY
typedef unsigned char qbyte;
typedef enum {false, true} qboolean;
typedef enum {NA_INVALID, NA_LOOPBACK, NA_IP, NA_IPX, NA_BROADCAST_IP, NA_BROADCAST_IPX} netadrtype_t;
typedef struct
{
netadrtype_t type;
qbyte ip[4];
qbyte ipx[10];
unsigned short port;
} netadr_t;
#include <stdlib.h>
#include <stdio.h>
#include <malloc.h>
#ifdef _WIN32
#include <winsock.h>
#endif
#define PORT_ANY 0
void Sys_Error(char *fmt, ...);
int COM_CheckParm(char *parm);
int com_argc;
char **com_argv;
#define Con_TPrintf IWebPrintf
#define TL_NETBINDINTERFACE "binding to %s"
#define TL_CONNECTIONLOSTORABORTED "connection lost or aborted"
#define TL_NETGETPACKETERROR "get packet error"
#define IWebPrintf printf
#define com_gamedir "." //current dir.
#define IWebMalloc(x) calloc(x, 1)
#define IWebRealloc(x, y) realloc(x, y)
#define IWebFree free
#define MAX_OSPATH 1024
#else
#ifndef QUAKEDEF_H__
#include "quakedef.h"
#ifdef _WIN32
#include "winquake.h"
#endif
#else
//#include <netinet/in.h>
#endif
#endif
#define IWEBACC_READ 1
#define IWEBACC_WRITE 2
#define IWEBACC_FULL 4
struct sockaddr_in;
struct sockaddr;
struct sockaddr_qstorage;
void NetadrToSockadr (netadr_t *a, struct sockaddr_qstorage *s);
qboolean SV_AllowDownload (char *name);
typedef qboolean iwboolean;
#ifndef _WIN32
#define INVALID_SOCKET ~0
#endif
//it's not allowed to error.
#ifndef WEBSVONLY
void VARGS IWebDPrintf(char *fmt, ...);
void VARGS IWebPrintf(char *fmt, ...);
void VARGS IWebWarnPrintf(char *fmt, ...);
#endif
typedef struct {
float gentime; //useful for generating a new file (if too old, removes reference)
int references; //freed if 0
char *data;
int len;
} IWeb_FileGen_t;
typedef struct {
//one or the other
FILE *f;
int pos;
IWeb_FileGen_t *bufferdata;
int start;
int length;
int end;
} IWEBFILE;
IWEBFILE *IWebFOpenRead(char *name); //fread(name, "rb");
IWEBFILE *IWebFOpenWrite(char *name, int append); //fopen(name, append?"ab":"wb");
int IWebFWrite(void *data, int s1, int s2, IWEBFILE *); //fwrite
int IWebFRead(void *data, int s1, int s2, IWEBFILE *); //fwrite
void IWebFClose(IWEBFILE *);
void IWebFSeek(IWEBFILE *file, long pos, int type);
int IWebFTell(IWEBFILE *file);
#ifndef WEBSVONLY
void *IWebMalloc(int size);
void *IWebRealloc(void *old, int size);
void IWebFree(void *mem);
#define IWebFree Z_Free
#endif
int IWebAuthorize(char *name, char *password);
iwboolean IWebAllowUpLoad(char *fname, char *uname);
IWEBFILE *IWebGenerateFile(char *name, char *content, int contentlength);
char *COM_ParseOut (char *data, char *out, int outlen);
void COM_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm);
char *Q_strcpyline(char *out, char *in, int maxlen);
iwboolean FTP_StringToAdr (char *s, qbyte ip[4], qbyte port[2]);
//server tick/control functions
iwboolean FTP_ServerRun(iwboolean ftpserverwanted);
qboolean HTTP_ServerPoll(qboolean httpserverwanted);
void HTTP_CL_Think(void);
qboolean HTTP_CL_Get(char *url, char *localfile);
//server interface called from main server routines.
void IWebInit(void);
void IWebRun(void);
void IWebShutdown(void);
void FTP_Client_Command (char *cmd);
void IRC_Command(char *imsg);
void FTP_ClientThink (void);
void IRC_Frame(void);
qboolean SV_POP3(qboolean activewanted);
qboolean SV_SMTP(qboolean activewanted);
#endif
#endif

668
engine/http/iwebiface.c Normal file
View File

@ -0,0 +1,668 @@
#ifdef WEBSVONLY
#define WEBSERVER
#else
#include "bothdefs.h"
#endif
#ifdef WEBSERVER
#include "iweb.h"
#ifdef WEBSVONLY //we need some functions from quake
/*char *va(char *format, ...)
{
#define VA_BUFFERS 2 //power of two
va_list argptr;
static char string[VA_BUFFERS][1024];
static int bufnum;
bufnum++;
bufnum &= (VA_BUFFERS-1);
va_start (argptr, format);
_vsnprintf (string[bufnum],sizeof(string[bufnum])-1, format,argptr);
va_end (argptr);
return string[bufnum];
}*/
void Sys_Error(char *format, ...)
{
va_list argptr;
char string[1024];
va_start (argptr, format);
_vsnprintf (string,sizeof(string)-1, format,argptr);
va_end (argptr);
printf("%s", string);
getchar();
exit(1000);
}
int COM_CheckParm(char *parm)
{
return 0;
}
char *authedusername;
char *autheduserpassword;
int main(int argc, char **argv)
{
WSADATA pointlesscrap;
WSAStartup(2, &pointlesscrap);
if (argc == 3)
{
authedusername = argv[1];
autheduserpassword = argv[2];
printf("Username = \"%s\"\nPassword = \"%s\"\n", authedusername, autheduserpassword);
}
else
printf("Server is read only\n");
while(1)
{
FTP_ServerRun(1);
HTTP_ServerPoll(1);
// SV_POP3(1);
// SV_SMTP(1);
Sleep(1);
}
}
void COM_EnumerateFiles (char *match, int (*func)(char *, int, void *), void *parm)
{
HANDLE r;
WIN32_FIND_DATA fd;
char apath[MAX_OSPATH];
char file[MAX_OSPATH];
char *s;
int go;
strcpy(apath, match);
// sprintf(apath, "%s%s", gpath, match);
for (s = apath+strlen(apath)-1; s>= apath; s--)
{
if (*s == '/')
break;
}
s++;
*s = '\0';
strcpy(file, match);
r = FindFirstFile(file, &fd);
if (r==(HANDLE)-1)
return;
go = true;
do
{
if (*fd.cFileName == '.');
else if (fd.dwFileAttributes != 16) //is a directory
{
sprintf(file, "%s%s", apath, fd.cFileName);
go = func(file, fd.nFileSizeLow, parm);
}
else
{
sprintf(file, "%s%s/", apath, fd.cFileName);
go = func(file, fd.nFileSizeLow, parm);
}
}
while(FindNextFile(r, &fd) && go);
FindClose(r);
}
enum {TTP_UNKNOWN, TTP_STRING} com_tokentype;
char *COM_ParseOut (char *data, char *out, int outlen)
{
int c;
int len;
len = 0;
out[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c=='/')
{
if (data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
}
// handle quoted strings specially
if (c == '\"')
{
com_tokentype = TTP_STRING;
data++;
while (1)
{
if (len >= outlen-1)
return data;
c = *data++;
if (c=='\"' || !c)
{
out[len] = 0;
return data;
}
out[len] = c;
len++;
}
}
com_tokentype = TTP_UNKNOWN;
// parse a regular word
do
{
if (len >= outlen-1)
return data;
out[len] = c;
data++;
len++;
c = *data;
} while (c>32);
out[len] = 0;
return data;
}
char com_token[2048];
char *COM_ParseToken (char *data)
{
int c;
int len;
len = 0;
com_token[0] = 0;
if (!data)
return NULL;
// skip whitespace
skipwhite:
while ( (c = *data) <= ' ')
{
if (c == 0)
return NULL; // end of file;
data++;
}
// skip // comments
if (c=='/')
{
if (data[1] == '/')
{
while (*data && *data != '\n')
data++;
goto skipwhite;
}
else if (data[1] == '*')
{
data+=2;
while (*data && (*data != '*' || data[1] != '/'))
data++;
data+=2;
goto skipwhite;
}
}
// handle quoted strings specially
if (c == '\"')
{
com_tokentype = TTP_STRING;
data++;
while (1)
{
c = *data++;
if (c=='\"' || !c)
{
com_token[len] = 0;
return data;
}
com_token[len] = c;
len++;
}
}
com_tokentype = TTP_UNKNOWN;
// parse single characters
if (c==',' || c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c==';' || c == '=' || c == '!' || c == '>' || c == '<' || c == '&' || c == '|' || c == '+')
{
com_token[len] = c;
len++;
com_token[len] = 0;
return data+1;
}
// parse a regular word
do
{
com_token[len] = c;
data++;
len++;
c = *data;
if (c==',' || c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':' || c==';' || c == '=' || c == '!' || c == '>' || c == '<' || c == '&' || c == '|' || c == '+')
break;
} while (c>32);
com_token[len] = 0;
return data;
}
IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb");
{
FILE *f;
char name2[512];
if (strstr(name, ".."))
return NULL;
sprintf(name2, "%s/%s", com_gamedir, name);
f = fopen(name2, "rb");
if (f)
{
IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE));
if (!ret)
{
fclose(f);
return NULL;
}
ret->f = f;
ret->start = 0;
fseek(f, 0, SEEK_END);
ret->end = ftell(f);//ret->start+ret->length;
fseek(f, 0, SEEK_SET);
ret->length = ret->end - ret->start;
return ret;
}
return NULL;
}
#else
#ifndef CLIENTONLY
cvar_t ftpserver = {"sv_ftp", "0"};
cvar_t httpserver = {"sv_http", "0"};
cvar_t pop3server = {"sv_pop3", "0"};
cvar_t smtpserver = {"sv_smtp", "0"};
cvar_t sv_readlevel = {"sv_readlevel", "0"}; //default to allow anyone
cvar_t sv_writelevel = {"sv_writelevel", "35"}; //allowed to write to uploads/uname
cvar_t sv_fulllevel = {"sv_fulllevel", "49"}; //allowed to write anywhere, replace any file...
#endif
//this file contains functions called from each side.
void VARGS IWebDPrintf(char *fmt, ...)
{
va_list argptr;
char msg[4096];
if (!developer.value)
return;
va_start (argptr,fmt);
_vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible)
va_end (argptr);
Con_Printf("%s", msg);
}
void VARGS IWebPrintf(char *fmt, ...)
{
va_list argptr;
char msg[4096];
va_start (argptr,fmt);
_vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible)
va_end (argptr);
Con_Printf("%s", msg);
}
void VARGS IWebWarnPrintf(char *fmt, ...)
{
va_list argptr;
char msg[4096];
va_start (argptr,fmt);
_vsnprintf (msg,sizeof(msg)-10, fmt,argptr); //catch any nasty bugs... (this is hopefully impossible)
va_end (argptr);
Con_Printf("^1%s", msg);
}
void IWebInit(void)
{
#ifdef WEBSERVER
Cvar_Register(&sv_fulllevel, "Internet Server Access");
Cvar_Register(&sv_writelevel, "Internet Server Access");
Cvar_Register(&sv_readlevel, "Internet Server Access");
Cvar_Register(&ftpserver, "Internet Server Access");
Cvar_Register(&httpserver, "Internet Server Access");
#endif
#ifdef EMAILSERVER
Cvar_Register(&pop3server, "Internet Server Access");
Cvar_Register(&smtpserver, "Internet Server Access");
#endif
}
void IWebRun(void)
{
#ifdef WEBSERVER
FTP_ServerRun(ftpserver.value!= 0);
HTTP_ServerPoll(httpserver.value!=0);
#endif
#ifdef EMAILSERVER
SV_POP3(pop3server.value!=0);
SV_SMTP(smtpserver.value!=0);
#endif
}
void IWebShutdown(void)
{
}
extern int file_from_pak;
IWEBFILE *IWebFOpenRead(char *name) //fread(name, "rb");
{
IWEBFILE *gf;
FILE *f;
if (*name == '/')
name++;
if (strstr(name, ".."))
return NULL;
if ((com_filesize=COM_FOpenFile(name, &f)) >= 0)
{
IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE));
if (!ret)
{
fclose(f);
return NULL;
}
ret->f = f;
ret->length = com_filesize;
ret->start = ftell(f);
ret->end = ret->start+ret->length;
return ret;
}
if (file_from_pak==2)
{
char *buffer;
IWEBFILE *ret;
return NULL; //reject - we don't want to have this hanging over us
//big files take a LOT of memory.
buffer = COM_LoadMallocFile(name);
if (buffer)
{
ret = IWebMalloc(sizeof(IWEBFILE) + sizeof(IWeb_FileGen_t));
ret->bufferdata = (IWeb_FileGen_t *)(ret+1);
ret->length = ret->bufferdata->len = com_filesize;
ret->bufferdata->data = buffer;
ret->bufferdata->references=-1000;
ret->start = 0;
ret->pos = 0;
ret->end = com_filesize;
return ret;
}
}
#ifndef CLIENTONLY
gf = IWebGenerateFile(name, NULL, 0);
if (gf)
return gf;
#endif
return NULL;
}
#endif
IWEBFILE *IWebFOpenWrite(char *name, int append) //fopen(name, append?"ab":"wb");
{
FILE *f;
char name2[512];
if (strstr(name, ".."))
return NULL;
if (*name == '/')
sprintf(name2, "%s%s", com_gamedir, name);
else
sprintf(name2, "%s/%s", com_gamedir, name);
// COM_CreatePath(name2);
f = fopen(name2, append?"ab":"wb");
if (f)
{
IWEBFILE *ret = IWebMalloc(sizeof(IWEBFILE));
if (!ret)
{
fclose(f);
return NULL;
}
ret->f = f;
ret->length = 0;
ret->start = 0;
ret->end = ret->start+ret->length;
return ret;
}
return NULL;
}
int IWebFWrite(void *data, int s1, int s2, IWEBFILE *file) //fwrite
{
return fwrite(data, s1, s2, file->f);
}
int IWebFRead(void *data, int s1, int s2, IWEBFILE *file) //fread
{
#ifdef PARANOID
if (s1 != 1)
Sys_Error("IWebFRead: s1 must be 1"); //should never happen. It's a debugging thing.
#endif
if (!file->f)
{
int readable;
readable = s2;
if (s1*readable + file->pos >= file->bufferdata->len)
readable = (file->length - file->pos)/s1;
memcpy(data, file->bufferdata->data+file->pos, readable*s1);
file->pos += readable*s1;
return readable;
}
if (s2 + ftell(file->f) > file->end) //cut down the ammount readable.
s2 = file->end - ftell(file->f);
return fread(data, s1, s2, file->f);
}
void IWebFClose(IWEBFILE *file)
{
if (file->f)
fclose(file->f);
else
{
if (file->bufferdata->references == -1000) //temp condition where buffer->data is malloc, and the buffer header is part of the file info
IWebFree(file->bufferdata->data);
else
{
file->bufferdata->references--;
}
}
IWebFree(file);
}
void IWebFSeek(IWEBFILE *file, long pos, int type)
{
if (!file->f)
{
file->pos = pos;
return;
}
if (type == SEEK_SET)
fseek(file->f, pos + file->start, SEEK_SET);
else
Sys_Error("IWebFSeek: Bad seek type\n");
}
int IWebFTell(IWEBFILE *file)
{
if (!file->f)
return file->pos;
return ftell(file->f) - file->start;
}
#ifndef WEBSVONLY
//replacement for Z_Malloc. It simply allocates up to a reserve ammount.
void *IWebMalloc(int size)
{
char *mem = Z_TagMalloc(size+32768, 15);
if (!mem)
return NULL; //bother
Z_Free(mem);
return Z_Malloc(size); //allocate the real ammount
}
void *IWebRealloc(void *old, int size)
{
char *mem = Z_TagMalloc(size+32768, 15);
if (!mem) //make sure there will be padding left
return NULL; //bother
Z_Free(mem);
return BZ_Realloc(old, size);
}
#endif
int IWebAuthorize(char *name, char *password)
{
#ifdef WEBSVONLY
if (authedusername)
if (!strcmp(name, authedusername))
if (!strcmp(password, autheduserpassword))
return IWEBACC_FULL;
return IWEBACC_READ;
#else
#ifndef CLIENTONLY
int id = Rank_GetPlayerID(name, atoi(password), false);
rankinfo_t info;
if (!id)
{
if (!sv_readlevel.value)
return IWEBACC_READ; //read only anywhere
return 0;
}
Rank_GetPlayerInfo(id, &info);
if (info.s.trustlevel >= sv_fulllevel.value)
return IWEBACC_READ | IWEBACC_WRITE | IWEBACC_FULL; //allowed to read and write anywhere to the quake filesystem
if (info.s.trustlevel >= sv_writelevel.value)
return IWEBACC_READ | IWEBACC_WRITE; //allowed to read anywhere write to specific places
if (info.s.trustlevel >= sv_readlevel.value)
return IWEBACC_READ; //read only anywhere
#endif
return 0;
#endif
}
iwboolean IWebAllowUpLoad(char *fname, char *uname) //called for partial write access
{
if (strstr(fname, ".."))
return false;
if (!strncmp(fname, "uploads/", 8))
{
if (!strncmp(fname+8, uname, strlen(uname)))
if (fname[8+strlen(uname)] == '/')
return true;
}
return false;
}
iwboolean FTP_StringToAdr (char *s, qbyte ip[4], qbyte port[2])
{
s = COM_ParseToken(s);
ip[0] = atoi(com_token);
s = COM_ParseToken(s);
if (*com_token != ',')
return false;
s = COM_ParseToken(s);
ip[1] = atoi(com_token);
s = COM_ParseToken(s);
if (*com_token != ',')
return false;
s = COM_ParseToken(s);
ip[2] = atoi(com_token);
s = COM_ParseToken(s);
if (*com_token != ',')
return false;
s = COM_ParseToken(s);
ip[3] = atoi(com_token);
s = COM_ParseToken(s);
if (*com_token != ',')
return false;
s = COM_ParseToken(s);
port[0] = atoi(com_token);
s = COM_ParseToken(s);
if (*com_token != ',')
return false;
s = COM_ParseToken(s);
port[1] = atoi(com_token);
return true;
}
char *Q_strcpyline(char *out, char *in, int maxlen)
{
char *w = out;
while (*in && maxlen > 0)
{
if (*in == '\r' || *in == '\n')
break;
*w = *in;
in++;
w++;
maxlen--;
}
*w = '\0';
return out;
}
#endif

407
engine/http/webgen.c Normal file
View File

@ -0,0 +1,407 @@
#include "bothdefs.h"
#ifdef WEBSERVER
#include "iweb.h"
#ifdef CLIENTONLY
IWEBFILE *IWebGenerateFile(char *name)
{
return NULL;
}
#else
char lastrecordedmvd[MAX_QPATH];
IWeb_FileGen_t *IWeb_GenerationBuffer;
int IWeb_GenerationBufferTotal;
void IWeb_MoreGeneratedResize(int newsize)
{
IWeb_FileGen_t *ob;
if (IWeb_GenerationBuffer && IWeb_GenerationBufferTotal >= newsize)
return; //already big enough
ob = IWeb_GenerationBuffer;
IWeb_GenerationBuffer = BZ_Malloc(sizeof(IWeb_GenerationBuffer) + newsize);
IWeb_GenerationBuffer->data = (char *)(IWeb_GenerationBuffer+1);
if (ob)
{
memcpy(IWeb_GenerationBuffer->data, ob->data, ob->len);
BZ_Free(ob);
}
IWeb_GenerationBufferTotal = newsize;
}
void IWeb_Generate(const char *buf)
{
long count = strlen(buf);
if (!IWeb_GenerationBuffer || IWeb_GenerationBuffer->len + count >= IWeb_GenerationBufferTotal)
IWeb_MoreGeneratedResize(IWeb_GenerationBufferTotal + count+(16*1024));
memcpy(&IWeb_GenerationBuffer->data[IWeb_GenerationBuffer->len], buf, count);
IWeb_GenerationBuffer->len+=count;
}
int Rank_Enumerate (unsigned int first, unsigned int last, void (*callback) (const rankinfo_t *ri)); //leader first.
void IWeb_ParseForm(char *info, int infolen, char *text)
{
char *eq, *and;
char *token, *out;
*info = '\0';
if (!text)
return;
while(*text)
{
eq = strchr(text, '=');
if (!eq)
break;
*eq = '\0';
and = strchr(eq+1, '&');
if (and)
*and = '\0';
for (out = token = eq+1; *token;)
{
if (*token == '+')
{
*out++ = ' ';
token++;
}
else if (*token == '%' && token[1] && token[2])
{
int c = 0;
if (token[1] >= '0' && token[1] <= '9')
{
c += token[1] - '0';
}
else if (token[1] >= 'a' && token[1] <= 'f')
{
c += token[1] - 'a'+10;
}
else if (token[1] >= 'A' && token[1] <= 'F')
{
c += token[1] - 'A'+10;
}
c*=16;
if (token[2] >= '0' && token[2] <= '9')
{
c += token[2] - '0';
}
else if (token[2] >= 'a' && token[2] <= 'f')
{
c += token[2] - 'a'+10;
}
else if (token[2] >= 'A' && token[2] <= 'F')
{
c += token[2] - 'A'+10;
}
*out++ = c;
token+=3;
}
else
*out++ = *token++;
}
*out = '\0';
Info_SetValueForKey(info, text, eq+1, infolen);
if (!and)
return;
text = and+1;
}
}
void IWeb_GenerateAdminFile(char *parms, char *content, int contentlength)
{
extern char outputbuf[]; //redirected buffer - always null termed.
char info[16384];
char *pwd;
char *cmd;
char *mark, *start;
extern cvar_t rcon_password;
IWeb_Generate("<HTML><HEAD><TITLE>FTEQWSV - admin</TITLE></HEAD><BODY>");
if (*rcon_password.string)
{
IWeb_ParseForm(info, sizeof(info), content);
pwd = Info_ValueForKey(info, "pwd");
cmd = Info_ValueForKey(info, "cmd");
IWeb_Generate("<FORM action=\"admin.html\" method=\"post\">");
IWeb_Generate("<CENTER>");
IWeb_Generate("<input name=pwd value=\"");
if (!strcmp(rcon_password.string, pwd))
IWeb_Generate(rcon_password.string);
IWeb_Generate("\">");
IWeb_Generate("<BR>");
IWeb_Generate("<input name=cmd maxsize=255 size=40 value=\"\">");
IWeb_Generate("<BR>");
IWeb_Generate("<input type=submit value=\"Submit\" name=btn>");
IWeb_Generate("</CENTER>");
IWeb_Generate("</FORM>");
if (!strcmp(rcon_password.string, pwd))
{
Con_Printf("Web based rcon: %s\n", cmd);
SV_BeginRedirect(-1);
Cmd_ExecuteString(cmd, RESTRICT_RCON);
for (mark = start = outputbuf; *mark; mark++)
{
if (*mark == '\n')
{
*mark = '\0';
IWeb_Generate(start);
IWeb_Generate("<br>");
start = mark+1;
}
}
IWeb_Generate(start);
SV_EndRedirect();
}
else if (*pwd)
IWeb_Generate("Password is incorrect.");
}
else
IWeb_Generate("<H1>Remote administration is not enabled.<H2>");
IWeb_Generate("</BODY></HTML>");
}
void IWeb_GenerateRankingsFileCallback(const rankinfo_t *ri)
{
IWeb_Generate("<TR><TD ALIGN = \"center\">");
IWeb_Generate(ri->h.name);
IWeb_Generate("</TD><TD ALIGN = \"center\">");
IWeb_Generate(va("%i", ri->s.kills));
IWeb_Generate("</TD><TD ALIGN = \"center\">");
IWeb_Generate(va("%i", ri->s.deaths));
IWeb_Generate("</TD>");
IWeb_Generate("</TR>");
}
void IWeb_GenerateRankingsFile (char *parms, char *content, int contentlength)
{
IWeb_Generate("<HTML><HEAD></HEAD><BODY>");
if (Rank_OpenRankings())
{
IWeb_Generate("<TABLE CELLSPACING=\"1\" CELLPADDING=\"1\">");
IWeb_Generate("<CAPTION>Players</CAPTION>");
if (Rank_Enumerate(atoi(parms), atoi(parms)+20, IWeb_GenerateRankingsFileCallback) == 20)
{
IWeb_Generate("</TABLE>");
if (atoi(parms) >= 20)
IWeb_Generate(va("<A HREF=allplayers.html?%i>Less</A>", atoi(parms)-20));
else if (atoi(parms) > 0)
IWeb_Generate(va("<A HREF=allplayers.html?%i>Less</A>", 0));
IWeb_Generate(va("<A HREF=allplayers.html?%i>More</A>", atoi(parms)+20));
}
else
{
IWeb_Generate("</TABLE>");
if (atoi(parms) >= 20)
IWeb_Generate(va("<A HREF=allplayers.html?%i>Less</A>", atoi(parms)-20));
else if (atoi(parms) > 0)
IWeb_Generate(va("<A HREF=allplayers.html?%i>Less</A>", 0));
}
}
else
IWeb_Generate("<H1>Rankings are disabled. Sorry.<H2>");
IWeb_Generate("</BODY></HTML>");
}
void IWeb_GenerateIndexFile (char *parms, char *content, int contentlength)
{
extern cvar_t rcon_password;
char *s, *o;
char key[128], value[128];
int l;
qboolean added;
client_t *cl;
IWeb_Generate("<HTML><HEAD><TITLE>FTEQWSV</TITLE></HEAD><BODY>");
IWeb_Generate("<H1>");
IWeb_Generate(hostname.string);
IWeb_Generate("</H1>");
IWeb_Generate("<A HREF=\"http://spike.corecodec.org\">Server website</A><P>");
if (Rank_OpenRankings())
IWeb_Generate("<A HREF=\"allplayers.html\">Click here to see ranked players.</A><P>");
if (*rcon_password.string)
IWeb_Generate("<A HREF=\"admin.html\">Admin.</A><P>");
s = svs.info;
IWeb_Generate("<TABLE CELLSPACING=\"1\" CELLPADDING=\"1\">");
IWeb_Generate("<CAPTION>Server Info</CAPTION>");
if (*s == '\\')
s++;
while (*s)
{
o = key;
while (*s && *s != '\\')
*o++ = *s++;
l = o - key;
// if (l < 20)
// {
// memset (o, ' ', 20-l);
// key[20] = 0;
// }
// else
*o = 0;
IWeb_Generate("<TR><TD ALIGN = \"center\">");
IWeb_Generate(key);
if (!*s)
{
IWeb_Generate("MISSING VALUE\n");
return;
}
o = value;
s++;
while (*s && *s != '\\')
*o++ = *s++;
*o = 0;
if (*s)
s++;
IWeb_Generate("</TD><TD ALIGN = \"center\">");
IWeb_Generate(value);
IWeb_Generate("</TD></TR>");
}
IWeb_Generate("</TABLE>");
IWeb_Generate("<P><TABLE CELLSPACING=\"1\" CELLPADDING=\"1\">");
IWeb_Generate("<CAPTION>Players</CAPTION>");
added = false;
for (l = 0, cl = svs.clients; l < sv.allocated_client_slots; l++, cl++)
{
if (cl->state <= cs_zombie)
continue;
IWeb_Generate("<TR><TD ALIGN = \"center\">");
IWeb_Generate(cl->name);
IWeb_Generate("</TD><TD ALIGN = \"center\">");
IWeb_Generate(va("%i", cl->old_frags));
IWeb_Generate("</TD></TR>");
added = true;
}
if (!added)
{
IWeb_Generate("<TR><TD ALIGN = \"center\">");
IWeb_Generate("No players on server");
IWeb_Generate("</TD><TD ALIGN = \"center\">");
}
IWeb_Generate("</TABLE>");
IWeb_Generate("</BODY></HTML>");
}
typedef struct {
char *name;
void (*GenerationFunction) (char *parms, char *content, int contentlength);
float lastgenerationtime;
float oldbysecs;
IWeb_FileGen_t *buffer;
int genid;
} IWebFile_t;
IWebFile_t IWebFiles[] = {
{"allplayers.html", IWeb_GenerateRankingsFile},
{"index.html", IWeb_GenerateIndexFile},
{"admin.html", IWeb_GenerateAdminFile}
};
IWEBFILE *IWebGenerateFile(char *name, char *content, int contentlength)
{
int fnum;
char *parms;
int len;
if (!sv.state)
return NULL;
if (*lastrecordedmvd && !strcmp(name, "lastdemo.mvd"))
if (strcmp(name, "lastdemo.mvd")) //no infinate loops please...
return IWebFOpenRead(lastrecordedmvd);
parms = strchr(name, '?');
if (!parms)
parms = name + strlen(name);
len = parms-name;
if (*parms)
parms++;
if (!*name)
return NULL;
for (fnum = 0; fnum < sizeof(IWebFiles) / sizeof(IWebFile_t); fnum++)
{
if (!Q_strncasecmp(name, IWebFiles[fnum].name, len+1))
{
IWEBFILE *ret;
if (IWebFiles[fnum].buffer)
{
if (IWebFiles[fnum].lastgenerationtime+10 < Sys_DoubleTime() || contentlength||*parms) //10 sec lifetime
{
IWebFiles[fnum].buffer->references--; //remove our reference and check free
if (IWebFiles[fnum].buffer->references<=0)
{
BZ_Free(IWebFiles[fnum].buffer);
IWebFiles[fnum].buffer = NULL;
}
}
}
if (!IWebFiles[fnum].buffer)
{
if (IWeb_GenerationBuffer!=NULL)
Sys_Error("Recursive file generation\n");
IWebFiles[fnum].GenerationFunction(parms, content, contentlength);
IWebFiles[fnum].buffer = IWeb_GenerationBuffer;
if (!contentlength)
{
IWeb_GenerationBuffer->references++; //so it can't be sent once and freed instantly.
IWebFiles[fnum].lastgenerationtime = Sys_DoubleTime();
}
else
IWebFiles[fnum].lastgenerationtime = -10;
}
ret = IWebMalloc(sizeof(IWEBFILE));
if (!ret)
{
BZ_Free(IWeb_GenerationBuffer);
return NULL;
}
ret->f = NULL;
ret->bufferdata = IWebFiles[fnum].buffer;
ret->length = ret->bufferdata->len;
ret->bufferdata->references++;
ret->pos = 0;
ret->start = 0;
ret->end = ret->start+ret->length;
IWeb_GenerationBuffer = NULL;
return ret;
}
}
return NULL;
}
#endif
#endif