2005-02-28 07:16:19 +00:00
# include "quakedef.h"
2004-08-23 01:40:25 +00:00
# ifdef WEBSERVER
# include "iweb.h"
2005-11-29 13:14:15 +00:00
# include "netinc.h"
2004-08-23 01:40:25 +00:00
2005-11-29 13:14:15 +00:00
//FIXME: Before any admins use this for any serious usage, make the server send bits of file slowly.
2004-08-23 01:40:25 +00:00
static qboolean httpserverinitied = false ;
2006-03-14 02:50:56 +00:00
qboolean httpserverfailed = false ;
2004-08-23 01:40:25 +00:00
static int httpserversocket ;
2004-09-20 23:25:38 +00:00
typedef enum { HTTP_WAITINGFORREQUEST , HTTP_SENDING } http_mode_t ;
2004-08-23 01:40:25 +00:00
2007-09-17 20:35:39 +00:00
qboolean HTTP_ServerInit ( int port )
2011-05-19 13:34:07 +00:00
{
2004-08-23 01:40:25 +00:00
struct sockaddr_in address ;
unsigned long _true = true ;
int i ;
if ( ( httpserversocket = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ) ) = = - 1 )
{
2009-11-04 21:16:50 +00:00
IWebPrintf ( " HTTP_ServerInit: socket: %s \n " , strerror ( qerrno ) ) ;
2006-03-14 02:50:56 +00:00
httpserverfailed = true ;
return false ;
2004-08-23 01:40:25 +00:00
}
if ( ioctlsocket ( httpserversocket , FIONBIO , & _true ) = = - 1 )
{
2009-11-04 21:16:50 +00:00
IWebPrintf ( " HTTP_ServerInit: ioctl FIONBIO: %s \n " , strerror ( qerrno ) ) ;
2006-03-14 02:50:56 +00:00
httpserverfailed = true ;
return false ;
2004-08-23 01:40:25 +00:00
}
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 ) ;
2011-05-19 13:34:07 +00:00
2004-08-23 01:40:25 +00:00
if ( bind ( httpserversocket , ( void * ) & address , sizeof ( address ) ) = = - 1 )
{
closesocket ( httpserversocket ) ;
2009-11-04 21:16:50 +00:00
IWebPrintf ( " HTTP_ServerInit: failed to bind to socket \n " ) ;
2006-03-14 02:50:56 +00:00
httpserverfailed = true ;
return false ;
2004-08-23 01:40:25 +00:00
}
2011-05-19 13:34:07 +00:00
2004-08-23 01:40:25 +00:00
listen ( httpserversocket , 3 ) ;
httpserverinitied = true ;
2006-03-14 09:20:07 +00:00
httpserverfailed = false ;
2004-08-23 01:40:25 +00:00
IWebPrintf ( " HTTP server is running \n " ) ;
2006-03-14 02:50:56 +00:00
return true ;
2004-08-23 01:40:25 +00:00
}
void HTTP_ServerShutdown ( void )
{
closesocket ( httpserversocket ) ;
IWebPrintf ( " HTTP server closed \n " ) ;
httpserverinitied = false ;
}
typedef struct HTTP_active_connections_s {
int datasock ;
2005-12-21 03:07:33 +00:00
vfsfile_t * file ;
2004-08-23 01:40:25 +00:00
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
2006-04-05 04:31:05 +00:00
int localerrno ;
2004-08-23 01:40:25 +00:00
2009-05-24 10:11:17 +00:00
HTTP_active_connections_t * * link , * cl ;
2004-08-23 01:40:25 +00:00
2009-05-24 10:11:17 +00:00
link = & HTTP_ServerConnections ;
for ( link = & HTTP_ServerConnections ; * link ; )
2004-08-23 01:40:25 +00:00
{
int ammount , wanted ;
2009-05-24 10:11:17 +00:00
cl = * link ;
2004-08-23 01:40:25 +00:00
if ( cl - > close )
{
2009-05-24 10:11:17 +00:00
* link = cl - > next ;
2004-08-23 01:40:25 +00:00
closesocket ( cl - > datasock ) ;
cl - > datasock = INVALID_SOCKET ;
if ( cl - > inbuffer )
IWebFree ( cl - > inbuffer ) ;
if ( cl - > outbuffer )
IWebFree ( cl - > outbuffer ) ;
if ( cl - > file )
2005-12-22 02:29:11 +00:00
VFS_CLOSE ( cl - > file ) ;
2004-08-23 01:40:25 +00:00
IWebFree ( cl ) ;
httpconnectioncount - - ;
2009-05-24 10:11:17 +00:00
continue ;
2004-08-23 01:40:25 +00:00
}
2009-05-24 10:11:17 +00:00
link = & ( * link ) - > next ;
2004-08-23 01:40:25 +00:00
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.
}
2011-05-19 13:34:07 +00:00
2004-08-23 01:40:25 +00:00
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 )
{
2009-11-04 21:16:50 +00:00
content = IWebMalloc ( contentlen + 1 ) ;
2004-08-23 01:40:25 +00:00
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)
{
2012-04-09 19:12:12 +00:00
msg = " HTTP/1.1 400 Bad Request \r \n " /*"Content-Type: application/octet-stream\r\n"*/ " Content-Length: 69 \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " " 400 Bad Request \r \n Your client failed to provide the host header line " ;
2004-08-23 01:40:25 +00:00
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 " ) )
2007-09-17 20:35:39 +00:00
{
2012-04-24 07:59:11 +00:00
qboolean gzipped = false ;
2007-09-17 20:35:39 +00:00
if ( * resource ! = ' / ' )
2009-05-24 10:11:17 +00:00
{
resource [ 0 ] = ' / ' ;
2007-09-17 20:35:39 +00:00
resource [ 1 ] = 0 ; //I'm lazy, they need to comply
2009-05-24 10:11:17 +00:00
}
2009-11-04 21:16:50 +00:00
IWebPrintf ( " Download request for \" %s \" \n " , resource + 1 ) ;
2004-08-23 01:40:25 +00:00
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
2009-05-24 10:11:17 +00:00
{
2012-04-24 07:59:11 +00:00
cl - > file = NULL ;
if ( SV_AllowDownload ( resource + 1 ) )
{
char nbuf [ MAX_OSPATH ] ;
if ( HTTPmarkup > = 3 & & strlen ( resource + 1 ) < sizeof ( nbuf ) - 4 )
{
sprintf ( nbuf , " %s.gz " , resource + 1 ) ;
cl - > file = FS_OpenVFS ( nbuf , " rb " , FS_GAME ) ;
}
if ( cl - > file )
gzipped = true ;
else
cl - > file = FS_OpenVFS ( resource + 1 , " rb " , FS_GAME ) ;
}
2011-05-19 13:34:07 +00:00
2009-05-24 10:11:17 +00:00
if ( ! cl - > file )
{
cl - > file = IWebGenerateFile ( resource + 1 , content , contentlen ) ;
}
}
2004-08-23 01:40:25 +00:00
if ( ! cl - > file )
{
2012-04-24 07:59:11 +00:00
IWebPrintf ( " Download rejected \n " ) ;
2004-08-23 01:40:25 +00:00
if ( HTTPmarkup > = 3 )
2005-11-14 01:32:21 +00:00
msg = " HTTP/1.1 404 Not Found \r \n " " Content-Type: text/plain \r \n " " Content-Length: 15 \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " " 404 Bad address " ;
2004-08-23 01:40:25 +00:00
else if ( HTTPmarkup = = 2 )
2005-11-14 01:32:21 +00:00
msg = " HTTP/1.0 404 Not Found \r \n " " Content-Type: text/plain \r \n " " Content-Length: 15 \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " " 404 Bad address " ;
2004-08-23 01:40:25 +00:00
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 )
2012-04-24 07:59:11 +00:00
sprintf ( resource , " HTTP/1.1 200 OK \r \n " " %s%s " " Content-Length: %i \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " , strstr ( resource , " .htm " ) ? " Content-Type: text/html \r \n " : " " , gzipped ? " Content-Encoding: gzip \r \n Cache-Control: public, max-age=86400 \r \n " : " " , ( int ) VFS_GETLEN ( cl - > file ) ) ;
2004-08-23 01:40:25 +00:00
else if ( HTTPmarkup = = 2 )
2012-04-24 07:59:11 +00:00
sprintf ( resource , " HTTP/1.0 200 OK \r \n " " %s%s " " Content-Length: %i \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " , strstr ( resource , " .htm " ) ? " Content-Type: text/html \r \n " : " " , gzipped ? " Content-Encoding: gzip \r \n Cache-Control: public, max-age=86400 \r \n " : " " , ( int ) VFS_GETLEN ( cl - > file ) ) ;
2004-08-23 01:40:25 +00:00
else if ( HTTPmarkup )
sprintf ( resource , " HTTP/0.9 200 OK \r \n \r \n " ) ;
else
2004-09-20 23:25:38 +00:00
strcpy ( resource , " " ) ;
2004-08-23 01:40:25 +00:00
msg = resource ;
if ( * mode = = ' H ' | | * mode = = ' h ' )
{
2011-05-19 13:34:07 +00:00
2005-12-22 02:29:11 +00:00
VFS_CLOSE ( cl - > file ) ;
2004-08-23 01:40:25 +00:00
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 )
2009-11-04 21:16:50 +00:00
IWebFree ( content ) ;
2004-08-23 01:40:25 +00:00
break ;
case HTTP_SENDING :
2009-05-24 10:11:17 +00:00
if ( cl - > outbufferused < 8192 )
2004-08-23 01:40:25 +00:00
{
if ( cl - > file )
{
2009-05-24 10:11:17 +00:00
ExpandOutBuffer ( cl , 32768 , true ) ;
2004-08-23 01:40:25 +00:00
wanted = cl - > outbuffersize - cl - > outbufferused ;
2005-12-22 02:29:11 +00:00
ammount = VFS_READ ( cl - > file , cl - > outbuffer + cl - > outbufferused , wanted ) ;
2004-08-23 01:40:25 +00:00
if ( ! ammount )
{
2005-12-22 02:29:11 +00:00
VFS_CLOSE ( cl - > file ) ;
2004-08-23 01:40:25 +00:00
cl - > file = NULL ;
2012-01-28 10:30:44 +00:00
IWebPrintf ( " Download complete \n " ) ;
2004-08-23 01:40:25 +00:00
}
else
cl - > outbufferused + = ammount ;
}
}
ammount = send ( cl - > datasock , cl - > outbuffer , cl - > outbufferused , 0 ) ;
if ( ammount = = - 1 )
{
2006-04-05 04:31:05 +00:00
localerrno = qerrno ;
if ( localerrno ! = EWOULDBLOCK )
2004-08-23 01:40:25 +00:00
{
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.
{
2005-12-22 02:29:11 +00:00
VFS_CLOSE ( cl - > file ) ;
2004-08-23 01:40:25 +00:00
cl - > file = NULL ;
cl - > close = true ;
continue ;
}
}
if ( sent ! = 0 )
IWebFWrite ( resource , 1 , sent , cl - > file ) ;
break ; */
}
}
}
2007-09-17 20:35:39 +00:00
qboolean HTTP_ServerPoll ( qboolean httpserverwanted , int portnum ) //loop while true
2004-08-23 01:40:25 +00:00
{
2009-05-24 10:11:17 +00:00
struct sockaddr_qstorage from ;
2004-08-23 01:40:25 +00:00
int fromlen ;
int clientsock ;
2007-09-04 20:54:24 +00:00
int _true = true ;
2009-05-24 10:11:17 +00:00
char buf [ 128 ] ;
netadr_t na ;
2004-08-23 01:40:25 +00:00
HTTP_active_connections_t * cl ;
if ( ! httpserverinitied )
{
if ( httpserverwanted )
2007-09-17 20:35:39 +00:00
return HTTP_ServerInit ( portnum ) ;
2004-08-23 01:40:25 +00:00
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 ;
}
2011-05-19 13:34:07 +00:00
if ( ioctlsocket ( clientsock , FIONBIO , ( u_long * ) & _true ) = = - 1 )
2007-09-17 20:35:39 +00:00
{
2009-11-04 21:16:50 +00:00
IWebPrintf ( " HTTP_ServerInit: ioctl FIONBIO: %s \n " , strerror ( qerrno ) ) ;
2007-09-17 20:35:39 +00:00
closesocket ( clientsock ) ;
return false ;
}
2009-11-04 21:16:50 +00:00
# ifndef WEBSVONLY
2009-05-24 10:11:17 +00:00
SockadrToNetadr ( & from , & na ) ;
2009-11-04 21:16:50 +00:00
IWebPrintf ( " New http connection from %s \n " , NET_AdrToString ( buf , sizeof ( buf ) , na ) ) ;
# endif
2007-09-04 20:54:24 +00:00
2004-08-23 01:40:25 +00:00
cl = IWebMalloc ( sizeof ( HTTP_active_connections_t ) ) ;
cl - > datasock = clientsock ;
cl - > next = HTTP_ServerConnections ;
2011-05-19 13:34:07 +00:00
HTTP_ServerConnections = cl ;
2004-08-23 01:40:25 +00:00
httpconnectioncount + + ;
return true ;
}
# endif