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:
parent
7c00f2b190
commit
3a7c75319a
7 changed files with 4018 additions and 0 deletions
868
engine/http/ftpclient.c
Normal file
868
engine/http/ftpclient.c
Normal 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
875
engine/http/ftpserver.c
Normal 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
473
engine/http/httpclient.c
Normal 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
565
engine/http/httpserver.c
Normal 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
162
engine/http/iweb.h
Normal 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
668
engine/http/iwebiface.c
Normal 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
407
engine/http/webgen.c
Normal 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
|
Loading…
Reference in a new issue