fteqw/engine/http/ftpclient.c
Spoike c0680334c7 What could possibly go wrong?...
Multiple consoles can now be printed/cleared via extra con commands.
Fixed the tab-completion alignment, by adding support for \t characters.
Changing the download mechanisms. Don't try downloading an ftp:// file. It'll probably crash you for now.
Trying to fix load time issues on q3bsps with a lot of curves.
Fixed sprites.
Added warning prints/spam where the new backend is bypassed, thus marking things that still need to be fixed.
QTV proxy fixed to not sit on qw servers unless someone is actually watching. Will ping for status requests still.
QTV proxy now supports ipv6.
QTV proxy now attempts to use the fte browser plugin.
Reworked the browser plugin code, now uses threads instead of ugly hacks. This should make cooperation with other such plugins work. Fixes unresponsiveness of opera, and gives an API that can be used from any other bit of software you want, tbh (read: internet explorer/activex plugins).

git-svn-id: https://svn.code.sf.net/p/fteqw/code/branches/wip@3516 fc73d0e0-1445-4013-8a0c-d673dee63da5
2010-03-14 14:35:56 +00:00

898 lines
20 KiB
C

#include "quakedef.h"
#if 0//def WEBCLIENT
#include "iweb.h"
#include "netinc.h"
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;
vfsfile_t *f;
struct FTPclientconn_s *next;
void (*NotifyFunction)(vfsfile_t *file, char *localfile, qboolean sucess); //called when failed or succeeded, and only if it got a connection in the first place.
//ftp doesn't guarentee it for anything other than getting though. :(
} 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 *newf;
newf = FTP_CreateConnection(old->server);
*newf->server = '\0'; //mark it as non control
strcpy(newf->name, old->name);
strcpy(newf->pwd, old->pwd);
strcpy(newf->path, old->path);
strcpy(newf->pathprefix, old->pathprefix);
return newf;
}
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: %s", strerror(qerrno));
}
if (ioctlsocket (sock, FIONBIO, &_true) == -1)
{
Sys_Error ("FTP_TCP_OpenSocket: ioctl FIONBIO: %s", 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: %s", strerror(qerrno));
return INVALID_SOCKET;
}
if (ioctlsocket (sock, FIONBIO, &_true) == -1)
{
closesocket(sock);
IWebWarnPrintf ("FTTP_UDP_OpenSocket: ioctl FIONBIO: %s", 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: %s", 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: %s", 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
{
unsigned long _true = true;
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.downloadlocalname, con->localfile)))
{
strcpy(cls.downloadlocalname, con->localfile);
strcpy(cls.downloadremotename, 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 )
{
VFS_WRITE(con->f, readdata, len);
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 = VFS_TELL(con->f);
ammount = VFS_READ(con->f, readdata, wanted);
sent = send(con->datasock, readdata, ammount, 0);
if (sent == -1)
VFS_SEEK(con->f, pos); //go back. Too much data
else
{
VFS_SEEK(con->f, pos + sent); //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;
if (*line == '\r')
break;
line++;
}
if (!*line) //broken message
break;
*line = '\0';
line++;
if (*con->server)
IWebDPrintf("FTP: %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\n", con->pathprefix, con->path);
send(con->controlsock, tempbuff, strlen(tempbuff), 0);
goto usepasv;
sprintf(tempbuff, "PORT %s\r\n", 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)
{
ioctlsocket(temp, FIONBIO, &_true);
con->stage = 6;
if (con->type == ftp_getting)
{
con->f = FS_OpenVFS(con->localfile, "wb", FS_GAME);
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 = FS_OpenVFS (con->localfile, "rb", FS_GAME);
if (con->f)
{
sprintf(tempbuff, "STOR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
con->transfersize = VFS_GETLEN(con->f);
}
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
{
usepasv:
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)
{
char tempname[MAX_OSPATH];
COM_StripExtension(con->localfile, tempname, MAX_OSPATH);
strcat(tempname, ".tmp");
con->f = FS_OpenVFS (tempname, "wb", FS_GAME);
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;
VFS_WRITE(con->f, data, len);
}
}
VFS_CLOSE(con->f);
con->f = NULL;
closesocket(con->datasock);
con->datasock = INVALID_SOCKET;
if (con->NotifyFunction)
{
con->NotifyFunction(con->localfile, true);
con->NotifyFunction = NULL;
}
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 = FS_OpenVFS(con->localfile, "wb", FS_GAME);
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 = FS_OpenVFS(con->localfile, "rb", FS_GAME);
if (con->f)
{
msg = va("STOR %s\r\n", con->file);
con->stage = 6;
con->transfered = 0;
con->transfersize = VFS_GETLEN(con->f);
}
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(CON_ERROR "FTP: %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 (con->NotifyFunction)
con->NotifyFunction(con->localfile, false);
if (cls.downloadmethod == DL_FTP && !strcmp(cls.downloadlocalname, con->localfile))
{ //this was us
cls.downloadmethod = DL_NONE;
}
if (con->f)
VFS_CLOSE(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_FixupPath(char *out)
{ //convert \[ to [
char *in;
in = out;
while (*in)
{
if (*in == '\\')
{
in++;
if (*in == '\0')
break;
}
*out++ = *in++;
}
*out++ = '\0';
}
qboolean FTP_Client_Command (char *cmd, void (*NotifyFunction)(char *localfile, qboolean sucess))
{
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));
}
return true;
}
else
Con_Printf("FTP connect failed\n");
}
return false;
}
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 false;
}
con->NotifyFunction = NotifyFunction;
*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));
FTP_FixupPath(con->file);
FTP_FixupPath(con->localfile);
return true;
}
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");
return true;
}
else if (!stricmp(command, "list"))
{
FTPclientconn_t *newf, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return false;
}
newf = FTP_DuplicateConnection(con);
if (!newf)
{
Con_Printf("Failed duplicate connection\n");
return false;
}
newf->type = ftp_listing;
newf->NotifyFunction = NotifyFunction;
return true;
}
else if (!stricmp(command, "get"))
{
FTPclientconn_t *newf, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return false;
}
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (!cmd)
{
Con_Printf("No file specified\n");
return false;
}
newf = FTP_DuplicateConnection(con);
if (!newf)
{
Con_Printf("Failed duplicate connection\n");
return false;
}
newf->NotifyFunction = NotifyFunction;
newf->type = ftp_getting;
sprintf(newf->file, command);
sprintf(newf->localfile, "%s%s", newf->path, command);
return true;
}
else if (!stricmp(command, "put"))
{
FTPclientconn_t *newf, *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return false;
}
cmd = COM_ParseOut(cmd, command, sizeof(command));
if (!cmd)
{
Con_Printf("No file specified\n");
return false;
}
newf = FTP_DuplicateConnection(con);
if (!newf)
{
Con_Printf("Failed duplicate connection\n");
return false;
}
newf->NotifyFunction = NotifyFunction;
newf->type = ftp_putting;
sprintf(newf->file, command);
sprintf(newf->localfile, "%s%s", newf->path, command);
return true;
}
else if (!stricmp(command, "cwd"))
{
FTPclientconn_t *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return false;
}
Con_Printf("%s\n", con->path);
return true;
}
else if (!stricmp(command, "cd"))
{
char *msg;
FTPclientconn_t *con = FTP_FindControl();
if (!con)
{
Con_Printf("Not connected\n");
return false;
}
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);
if (send(con->controlsock, msg, strlen(msg), 0)==strlen(msg))
return true;
}
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));
*/
return false;
}
#endif