summaryrefslogtreecommitdiff
path: root/source/sv_http.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/sv_http.c')
-rw-r--r--source/sv_http.c820
1 files changed, 0 insertions, 820 deletions
diff --git a/source/sv_http.c b/source/sv_http.c
deleted file mode 100644
index 58fb210..0000000
--- a/source/sv_http.c
+++ /dev/null
@@ -1,820 +0,0 @@
-/*
-Copyright (C) 2003-2006 Andrey Nazarov
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-*/
-
-#include "sv_local.h"
-#include "mvd_local.h"
-
-char http_host[MAX_STRING_CHARS];
-char http_header[MAX_STRING_CHARS];
-tcpClient_t *http_client;
-
-void SV_HttpHeader( const char *title ) {
- SV_HttpPrintf(
- "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 3.2//EN\">"
- "<html><head><title>%s</title></head><body>", title );
-}
-
-void SV_HttpFooter( void ) {
- SV_HttpPrintf( "<hr><address>" APPLICATION " "
- VERSION " at <a href=\"http://%s/\">%s</a></address></body></html>",
- http_host, http_host );
-}
-
-void SV_HttpReject( const char *error, const char *reason ) {
- if( http_client->state <= cs_zombie ) {
- return;
- }
-
- // construct HTTP response header
- SV_HttpPrintf( "HTTP/1.0 %s\r\n", error );
- if( http_header[0] ) {
- SV_HttpPrintf( "%s", http_header );
- }
- if( reason && http_client->method != HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "Content-Type: text/html; charset=us-ascii\r\n" );
- }
- SV_HttpPrintf( "\r\n" );
-
- // add optional response body
- if( reason && http_client->method != HTTP_METHOD_HEAD ) {
- SV_HttpHeader( error );
- SV_HttpPrintf( "<h1>%s</h1><p>%s</p>", error, reason );
- SV_HttpFooter();
- }
-
- SV_HttpDrop( http_client, error );
-}
-
-static void SV_HttpPrintTime( int sec ) {
- int min, hour, day;
-
- min = sec / 60; sec %= 60;
- hour = min / 60; min %= 60;
- day = hour / 24; hour %= 24;
-
- if( day ) {
- SV_HttpPrintf(
- "%d day%s, %d hour%s, %d min%s",
- day, day == 1 ? "" : "s",
- hour, hour == 1 ? "" : "s",
- min, min == 1 ? "" : "s" );
- } else if( hour ) {
- SV_HttpPrintf(
- "%d hour%s, %d min%s",
- hour, hour == 1 ? "" : "s",
- min, min == 1 ? "" : "s" );
- } else if( min ) {
- SV_HttpPrintf(
- "%d min%s",
- min, min == 1 ? "" : "s" );
- } else {
- SV_HttpPrintf( "&lt; 1 min" );
- }
-}
-
-static void SV_GetStatus( void ) {
- char buffer[MAX_STRING_CHARS];
- cvar_t *var;
- client_t *cl;
- int count, len, sec;
- time_t clock;
-
- if( sv_status_show->integer < 1 ) {
- SV_HttpReject( "403 Forbidden",
- "You do not have permission to view "
- "the status page of this server." );
- return;
- }
-
- SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" );
-
- if( http_client->method == HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "\r\n" );
- SV_HttpDrop( http_client, "200 OK" );
- return;
- }
-
- SV_HttpPrintf(
- "Content-Type: text/html; charset=us-ascii\r\n"
- "\r\n" );
-
- count = SV_CountClients();
- len = Q_EscapeMarkup( buffer, sv_hostname->string, sizeof( buffer ) );
- Q_snprintf( buffer + len, sizeof( buffer ) - len, " - %d/%d",
- count, sv_maxclients->integer - sv_reserved_slots->integer );
-
- SV_HttpHeader( buffer );
-
- buffer[len] = 0;
- SV_HttpPrintf( "<h1>%s</h1>", buffer );
-
- time( &clock );
-
- if( sv_status_show->integer > 1 ) {
- SV_HttpPrintf( "<h2>Player Info</h2>" );
- if( count ) {
- SV_HttpPrintf( "<table border=\"1\">"
- "<tr><th>Score</th><th>Ping</th><th>Time</th><th>Name</th></tr>" );
- FOR_EACH_CLIENT( cl ) {
- if( cl->state < cs_connected ) {
- continue;
- }
-
- SV_HttpPrintf( "<tr><td>%d</td><td>%d</td><td>",
- cl->edict->client->ps.stats[STAT_FRAGS], cl->ping );
-
- if( cl->connect_time > clock ) {
- cl->connect_time = clock;
- }
- sec = clock - cl->connect_time;
-
- SV_HttpPrintTime( sec );
-
- Q_EscapeMarkup( buffer, cl->name, sizeof( buffer ) );
- SV_HttpPrintf( "</td><td>%s</td></tr>", buffer );
- }
- SV_HttpPrintf( "</table>" );
- } else {
- SV_HttpPrintf( "<p>No players.</p>" );
- }
- }
-
- SV_HttpPrintf(
- "<h2>Server Info</h2><table border=\"1\"><tr>"
- "<th>Key</th><th>Value</th></tr>" );
-
- for( var = cvar_vars; var; var = var->next ) {
- if( !( var->flags & CVAR_SERVERINFO ) ) {
- continue;
- }
- if( !var->string[0] ) {
- continue;
- }
-
- Q_EscapeMarkup( buffer, var->name, sizeof( buffer ) );
- SV_HttpPrintf( "<tr><td>%s</td>", buffer );
-
- // XXX: ugly hack to hide reserved slots
- if( var == sv_maxclients && sv_reserved_slots->integer ) {
- SV_HttpPrintf( "<td>%d</td></tr>",
- sv_maxclients->integer - sv_reserved_slots->integer );
- } else {
- Q_EscapeMarkup( buffer, var->string, sizeof( buffer ) );
- SV_HttpPrintf( "<td>%s</td></tr>", buffer );
- }
- }
-
- // add uptime
- if( sv_uptime->integer ) {
- if( com_startTime > clock ) {
- com_startTime = clock;
- }
- sec = clock - com_startTime;
-
- SV_HttpPrintf( "<tr><td>uptime</td><td>" );
- SV_HttpPrintTime( sec );
- SV_HttpPrintf( "</td></tr>" );
- }
-
- SV_HttpPrintf( "</table>"
- "<p><a href=\"quake2://%s\">Join this server</a></p>", http_host );
- if( sv_mvd_enable->integer ) {
- SV_HttpPrintf(
- "<p><a href=\"http://%s/mvdstream\">Download MVD stream</a></p>",
- http_host );
- }
- SV_HttpFooter();
-
- SV_HttpDrop( http_client, "200 OK" );
-}
-
-static void uri_status( const char *uri ) {
- if( sv.state == ss_game ) {
- SV_GetStatus();
- } else {
- MVD_GetStatus();
- }
-}
-
-static void uri_mvdstream( const char *uri ) {
- if( !sv_mvd_enable->integer ) {
- SV_HttpReject( "403 Forbidden",
- "You do not have permission to access "
- "live MVD stream on this server." );
- return;
- }
-
- if( sv_mvd_auth->string[0] && ( http_client->credentials == NULL ||
- strcmp( http_client->credentials, sv_mvd_auth->string ) ) )
- {
- strcpy( http_header,
- "WWW-Authenticate: Basic realm=\"mvdstream\"\r\n" );
- SV_HttpReject( "401 Not Authorized",
- "You are not authorized to access "
- "live MVD stream on this server." );
- return;
- }
-
- if( sv.state == ss_game ) {
- SV_MvdGetStream( uri );
- } else {
- MVD_GetStream( uri );
- }
-}
-
-void SV_ConsoleOutput( const char *msg ) {
- tcpClient_t *client;
- char text[MAXPRINTMSG];
- char *p, *maxp;
- size_t len;
- int c;
-
- if( !svs.initialized ) {
- return;
- }
- if( LIST_EMPTY( &svs.console_list ) ) {
- return;
- }
-
- p = text;
- maxp = text + sizeof( text ) - 1;
- while( *msg ) {
- if( Q_IsColorString( msg ) ) {
- msg += 2;
- continue;
- }
-
- if( p == maxp ) {
- break;
- }
-
- c = *msg++;
- c &= 127;
-
- *p++ = c;
- }
- *p = 0;
-
- len = p - text;
-
- LIST_FOR_EACH( tcpClient_t, client, &svs.console_list, mvdEntry ) {
- if( FIFO_Write( &client->stream.send, text, len ) != len ) {
- SV_HttpDrop( client, "overflowed" );
- }
- }
-}
-
-static void uri_console( const char *uri ) {
- char *auth = sv_console_auth->string;
- char *cred = http_client->credentials;
-
- if( !auth[0] || !cred || strcmp( cred, auth ) ) {
- strcpy( http_header,
- "WWW-Authenticate: Basic realm=\"console\"\r\n" );
- SV_HttpReject( "401 Not Authorized",
- "You are not authorized to access "
- "console stream on this server." );
- return;
- }
-
- if( http_client->method == HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "HTTP/1.0 200 OK\r\n\r\n" );
- SV_HttpDrop( http_client, "200 OK " );
- return;
- }
-
- SV_HttpPrintf(
- "HTTP/1.0 200 OK\r\n"
- "Content-Type: text/plain\r\n"
- "\r\n" );
- List_Append( &svs.console_list, &http_client->mvdEntry );
- http_client->state = cs_spawned;
-}
-
-#if 0
-static void uri_root( const char *uri ) {
- SV_HttpPrintf( "HTTP/1.0 200 OK\r\n" );
-
- if( http_client->method == HTTP_METHOD_HEAD ) {
- SV_HttpPrintf( "\r\n" );
- SV_HttpDrop( http_client, "200 OK " );
- return;
- }
-
- SV_HttpPrintf(
- "Content-Type: text/html\r\n"
- "\r\n" );
- SV_HttpPrintf(
- "<html><head><title>Hello</title></head><body>"
- "<h1>Hello</h1><p>Hello world!</p>" );
- SV_HttpFooter();
-
- SV_HttpDrop( http_client, "200 OK" );
-}
-#endif
-
-typedef struct {
- const char *uri;
- void (*handler)( const char * );
-} uriEntry_t;
-
-static const uriEntry_t rootURIs[] = {
- { "", uri_status },
- { "status", uri_status },
- { "mvdstream", uri_mvdstream },
- { "console", uri_console },
- { NULL }
-};
-
-void SV_HttpHandle( const uriEntry_t *e, const char *uri ) {
- const char *p;
- size_t length;
-
- if( *uri == '/' ) {
- uri++;
- }
-
- if( ( p = strchr( uri, '/' ) ) != NULL ) {
- length = p - uri;
- } else {
- length = strlen( uri );
- p = uri + length;
- }
-
- for( ; e->uri; e++ ) {
- if( !strncmp( e->uri, uri, length ) ) {
- e->handler( p );
- break;
- }
- }
-
- if( !e->uri ) {
- SV_HttpReject( "404 Not Found",
- "The requested URL was not found on this server." );
- }
-}
-
-void SV_HttpWrite( tcpClient_t *client, void *data, size_t len ) {
- fifo_t *fifo = &client->stream.send;
-
- if( client->state <= cs_zombie ) {
- return;
- }
-
-#if USE_ZLIB
- if( client->z.state ) {
- z_streamp z = &client->z;
- int param = Z_NO_FLUSH;
-
- if( client->noflush > 120 ) {
- param = Z_SYNC_FLUSH;
- }
-
- z->next_in = data;
- z->avail_in = ( uInt )len;
-
- while( z->avail_in ) {
- data = FIFO_Reserve( fifo, &len );
- if( !len ) {
- SV_HttpDrop( client, "overflowed" );
- return;
- }
-
- z->next_out = data;
- z->avail_out = ( uInt )len;
-
- if( deflate( z, param ) != Z_OK ) {
- SV_HttpDrop( client, "deflate failed" );
- return;
- }
-
- len -= z->avail_out;
- if( len > 0 ) {
- FIFO_Commit( fifo, len );
- client->noflush = 0;
- }
- }
- return;
- }
-#endif
-
- if( !FIFO_TryWrite( fifo, data, len ) ) {
- SV_HttpDrop( client, "overflowed" );
- }
-}
-
-void SV_HttpFinish( tcpClient_t *client ) {
-#if USE_ZLIB
- fifo_t *fifo = &client->stream.send;
- z_streamp z = &client->z;
- byte *data;
- size_t len;
- int ret;
-
- if( client->state <= cs_zombie ) {
- return;
- }
-
- if( !z->state ) {
- return;
- }
-
- z->next_in = NULL;
- z->avail_in = 0;
-
- do {
- data = FIFO_Reserve( fifo, &len );
- if( !len ) {
- SV_HttpDrop( client, "overflowed" );
- return;
- }
-
- z->next_out = data;
- z->avail_out = ( uInt )len;
-
- ret = deflate( z, Z_FINISH );
-
- FIFO_Commit( fifo, len - z->avail_out );
- } while( ret == Z_OK );
-
- if( ret != Z_STREAM_END ) {
- SV_HttpDrop( client, "deflate failed" );
- }
-#endif
-}
-
-void SV_HttpPrintf( const char *fmt, ... ) {
- char buffer[MAX_STRING_CHARS];
- va_list argptr;
- size_t len;
-
- if( http_client->state <= cs_zombie ) {
- return;
- }
-
- va_start( argptr, fmt );
- len = Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr );
- va_end( argptr );
-
- if( len >= sizeof( buffer ) || FIFO_Write( &http_client->stream.send, buffer, len ) != len ) {
- SV_HttpDrop( http_client, "overflowed" );
- }
-}
-
-void SV_HttpDrop( tcpClient_t *client, const char *error ) {
- if( client->state <= cs_zombie ) {
- return;
- }
- if( error ) {
- Com_DPrintf( "HTTP client %s dropped: %s\n",
- NET_AdrToString( &client->stream.address ), error );
- }
-
- if( client->resource ) {
- Z_Free( client->resource );
- client->resource = NULL;
- }
- if( client->host ) {
- Z_Free( client->host );
- client->host = NULL;
- }
- if( client->agent ) {
- Z_Free( client->agent );
- client->agent = NULL;
- }
- if( client->credentials ) {
- Z_Free( client->credentials );
- client->credentials = NULL;
- }
-#if USE_ZLIB
- if( client->z.state ) {
- deflateEnd( &client->z );
- }
-#endif
-
- List_Remove( &client->mvdEntry );
- client->mvd = NULL;
- client->state = cs_zombie;
- client->lastmessage = svs.realtime;
-}
-
-void SV_HttpRemove( tcpClient_t *client ) {
- char *addr, *dest;
- int count;
-
- addr = NET_AdrToString( &client->stream.address );
- NET_Close( &client->stream );
- List_Remove( &client->entry );
-
- count = List_Count( &svs.tcp_client_pool );
- if( count < sv_http_minclients->integer ) {
- List_Insert( &svs.tcp_client_pool, &client->entry );
- dest = "pool";
- } else {
- Z_Free( client );
- dest = "memory";
- }
-
- Com_DPrintf( "HTTP client %s removed into %s\n", addr, dest );
-}
-
-static void SV_HttpAccept( netstream_t *stream ) {
- tcpClient_t *client;
- netstream_t *s;
- int count;
- char *from;
-
- count = List_Count( &svs.tcp_client_list );
- if( count >= sv_http_maxclients->integer ) {
- Com_DPrintf( "HTTP client %s rejected: too many clients\n",
- NET_AdrToString( &stream->address ) );
- // NET_TrySend( stream, "HTTP/1.0 503 Service Unavailable\r\n\r\n" );
- NET_Close( stream );
- return;
- }
-
- if( sv_iplimit->integer > 0 ) {
- count = 0;
- LIST_FOR_EACH( tcpClient_t, client, &svs.tcp_client_list, entry ) {
- if( NET_IsEqualBaseAdr( &client->stream.address, &stream->address ) ) {
- count++;
- }
- }
- if( count >= sv_iplimit->integer ) {
- Com_DPrintf( "HTTP client %s rejected: too many connections "
- "from single IP address\n",
- NET_AdrToString( &stream->address ) );
- NET_Close( stream );
- return;
- }
- }
-
- if( LIST_EMPTY( &svs.tcp_client_pool ) ) {
- client = SV_Malloc( sizeof( *client ) + 256 + MAX_MSGLEN * 2 );
- from = "memory";
- } else {
- client = LIST_FIRST( tcpClient_t, &svs.tcp_client_pool, entry );
- List_Remove( &client->entry );
- from = "pool";
- }
-
- memset( client, 0, sizeof( *client ) );
- List_Init( &client->mvdEntry );
-
- s = &client->stream;
- s->recv.data = ( byte * )( client + 1 );
- s->recv.size = 256;
- s->send.data = s->recv.data + 256;
- s->send.size = MAX_MSGLEN * 2;
- s->socket = stream->socket;
- s->address = stream->address;
- s->state = stream->state;
-
- client->lastmessage = svs.realtime;
- client->state = cs_assigned;
- List_SeqAdd( &svs.tcp_client_list, &client->entry );
-
- Com_DPrintf( "HTTP client %s accepted from %s\n",
- NET_AdrToString( &stream->address ), from );
-}
-
-static qboolean SV_HttpParseRequest( tcpClient_t *client ) {
- char key[MAX_TOKEN_CHARS];
- char *p, *token;
- const char *line;
- byte *b, *data;
- size_t length;
- int major, minor;
-
- while( 1 ) {
- data = FIFO_Peek( &client->stream.recv, &length );
- if( !length ) {
- break;
- }
- if( ( b = memchr( data, '\n', length ) ) != NULL ) {
- // TODO: support folded lines
- length = b - data + 1;
- }
- if( client->requestLength + length > MAX_NET_STRING - 1 ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
-
- memcpy( client->request + client->requestLength, data, length );
- client->requestLength += length;
- client->request[client->requestLength] = 0;
-
- FIFO_Decommit( &client->stream.recv, length );
-
- if( !b ) {
- continue;
- }
-
- line = client->request;
- client->requestLength = 0;
-
- if( !client->method ) {
- // parse request line
- token = COM_SimpleParse( &line, NULL );
- if( !token[0] ) {
- continue; // ignore empty lines
- }
-
- // parse method
- if( !strcmp( token, "GET" ) ) {
- client->method = HTTP_METHOD_GET;
- } else if( !strcmp( token, "HEAD" ) ) {
- client->method = HTTP_METHOD_HEAD;
- /*} else if( !strcmp( token, "POST" ) ) {
- client->method = HTTP_METHOD_POST;*/
- } else {
- SV_HttpReject( "501 Not Implemented", NULL );
- break;
- }
-
- // parse URI
- token = COM_SimpleParse( &line, NULL );
- if( !Q_stricmpn( token, "http://", 7 ) ) {
- p = strchr( token + 7, '/' );
- if( !p ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
- client->resource = SV_CopyString( p );
- *p = 0;
- client->host = SV_CopyString( token + 7 );
- } else {
- if( *token != '/' ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
- client->resource = SV_CopyString( token );
- }
-
- // parse version
- token = COM_SimpleParse( &line, NULL );
- if( strncmp( token, "HTTP/", 5 ) ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
- major = strtoul( token + 5, &token, 10 );
- if( *token != '.' ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
- minor = strtoul( token + 1, &token, 10 );
- if( major != 1 || ( minor != 0 && minor != 1 ) ) {
- SV_HttpReject( "505 HTTP Version not supported", NULL );
- break;
- }
- } else {
- token = COM_SimpleParse( &line, NULL );
- if( !token[0] ) {
- if( !client->host || !client->resource ) {
- SV_HttpReject( "400 Bad Request", NULL );
- return qfalse;
- }
- return qtrue; // end of header
- }
- // parse header fields
- strcpy( key, token );
- p = strchr( key, ':' );
- if( !p ) {
- SV_HttpReject( "400 Bad Request", NULL );
- break;
- }
- *p = 0;
- Q_strlwr( key );
-
- token = COM_SimpleParse( &line, NULL );
- if( !strcmp( key, "host" ) ) {
- if( !client->host ) {
- client->host = SV_CopyString( token );
- }
- continue;
- }
-
- if( !strcmp( key, "authorization" ) ) {
- if( Q_stricmp( token, "Basic" ) ) {
- continue;
- }
- token = COM_SimpleParse( &line, NULL );
- if( Q_Decode64( key, token, sizeof( key ) ) == -1 ) {
- continue;
- }
- client->credentials = SV_CopyString( key );
- continue;
- }
-
- if( !strcmp( key, "user-agent" ) ) {
- if( !client->agent ) {
- client->agent = SV_CopyString( token );
- }
- continue;
- }
- }
- }
-
- return qfalse;
-}
-
-
-void SV_HttpRun( void ) {
- tcpClient_t *client, *next;
- neterr_t ret;
- netstream_t stream;
- unsigned zombie_time = 1000 * sv_zombietime->value;
- unsigned drop_time = 1000 * sv_timeout->value;
- unsigned ghost_time = 1000 * sv_ghostime->value;
- unsigned delta;
-
- // accept new connections
- ret = NET_Accept( &net_from, &stream );
- if( ret == NET_ERROR ) {
- Com_DPrintf( "%s from %s, ignored\n", NET_ErrorString(),
- NET_AdrToString( &net_from ) );
- } else if( ret == NET_OK ) {
- SV_HttpAccept( &stream );
- }
-
- // run existing connections
- LIST_FOR_EACH_SAFE( tcpClient_t, client, next, &svs.tcp_client_list, entry ) {
- http_client = client;
- http_header[0] = 0;
-
- // check timeouts
- delta = svs.realtime - client->lastmessage;
- switch( client->state ) {
- case cs_zombie:
- if( delta > zombie_time || !FIFO_Usage( &client->stream.send ) ) {
- SV_HttpRemove( client );
- continue;
- }
- break;
- case cs_assigned:
- if( delta > ghost_time || delta > drop_time ) {
- SV_HttpReject( "408 Request Timeout", NULL );
- continue;
- }
- break;
- default:
- if( delta > drop_time ) {
- SV_HttpDrop( client, "connection timed out" );
- SV_HttpRemove( client );
- continue;
- }
- break;
- }
-
- // run network stream
- ret = NET_Run( &client->stream );
- if( ret == NET_AGAIN ) {
- // don't timeout
- if( client->state >= cs_connected && !FIFO_Usage( &client->stream.send ) ) {
- client->lastmessage = svs.realtime;
- }
- continue;
- }
- if( ret != NET_OK ) {
- SV_HttpDrop( client, "connection reset by peer" );
- SV_HttpRemove( client );
- continue;
- }
-
- // don't timeout
- if( client->state >= cs_connected ) {
- client->lastmessage = svs.realtime;
- }
-
- // parse the request
- if( client->state == cs_assigned ) {
- if( SV_HttpParseRequest( client ) ) {
- Com_DPrintf( "GET %s\n", client->resource );
- client->state = cs_connected;
- Q_EscapeMarkup( http_host, client->host, sizeof( http_host ) );
- SV_HttpHandle( rootURIs, client->resource );
- }
-
- http_client = NULL;
- }
-
- }
-}
-