2005-02-28 07:16:19 +00:00
# include "quakedef.h"
2004-08-23 01:40:25 +00:00
# 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 ;
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
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)
{
2005-11-14 01:32:21 +00:00
msg = " HTTP/1.1 400 Bad Request \r \n " " Content-Type: text/plain \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 " ) )
{
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 )
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 )
2005-11-14 01:32:21 +00:00
sprintf ( resource , " HTTP/1.1 200 OK \r \n " " Content-Type: %s \r \n " " Content-Length: %i \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " , strstr ( resource , " .htm " ) ? " text/html " : " text/plain " , cl - > file - > length ) ;
2004-08-23 01:40:25 +00:00
else if ( HTTPmarkup = = 2 )
2005-11-14 01:32:21 +00:00
sprintf ( resource , " HTTP/1.0 200 OK \r \n " " Content-Type: %s \r \n " " Content-Length: %i \r \n " " Server: " FULLENGINENAME " /0 \r \n " " \r \n " , strstr ( resource , " .htm " ) ? " text/html " : " text/plain " , cl - > file - > length ) ;
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 ' )
{
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