summaryrefslogtreecommitdiff
path: root/source/sv_http.c
diff options
context:
space:
mode:
authorAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
committerAndrey Nazarov <skuller@skuller.net>2007-08-14 20:18:08 +0000
commitf294db4ccf45f6274e65260dd6f9a2c5faa94313 (patch)
treee8cf1ba2bfe9c8417eec17faf912442f52fc4ef2 /source/sv_http.c
Initial import of the new Q2PRO tree.
Diffstat (limited to 'source/sv_http.c')
-rw-r--r--source/sv_http.c679
1 files changed, 679 insertions, 0 deletions
diff --git a/source/sv_http.c b/source/sv_http.c
new file mode 100644
index 0000000..a7f566d
--- /dev/null
+++ b/source/sv_http.c
@@ -0,0 +1,679 @@
+/*
+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 2.0//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\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 uri_status( const char *uri ) {
+ char buffer[MAX_STRING_CHARS];
+ cvar_t *var;
+ client_t *cl;
+ int count;
+
+ 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\r\n"
+// "Content-Encoding: deflate\r\n"
+ "\r\n" );
+
+ count = 0;
+ FOR_EACH_CLIENT( cl ) {
+ if( cl->state != cs_zombie ) {
+ count++;
+ }
+ }
+
+ Q_EscapeMarkup( buffer, sv_hostname->string, sizeof( buffer ) );
+ SV_HttpHeader( va( "%s - %d/%d", buffer, count, sv_maxclients->integer ) );
+ SV_HttpPrintf( "<h1>Status page of %s</h1>", buffer );
+
+ 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>Name</th></tr>" );
+ FOR_EACH_CLIENT( cl ) {
+ if( cl->state >= cs_connected ) {
+ Q_EscapeMarkup( buffer, cl->name, sizeof( buffer ) );
+ SV_HttpPrintf( "<tr><td>%d</td><td>%d</td><td>%s</td></tr>",
+ cl->edict->client->ps.stats[STAT_FRAGS],
+ cl->ping, 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 );
+ Q_EscapeMarkup( buffer, var->string, sizeof( buffer ) );
+ SV_HttpPrintf( "<td>%s</td></tr>", buffer );
+ }
+
+ SV_HttpPrintf( "</table><br><a href=\"quake2://%s\">Join this server</a>",
+ http_host );
+ SV_HttpFooter();
+
+// deflateInit( &client->z, Z_DEFAULT_COMPRESSION );
+
+ //SV_HttpFinish( client );
+
+ // deflateEnd( &client->z );
+
+ SV_HttpDrop( http_client, "200 OK" );
+}
+
+static void uri_mvdstream( const char *uri ) {
+ if( sv.state != ss_game && sv.state != ss_broadcast ) {
+ SV_HttpReject( "403 Forbidden",
+ "Live MVD stream is unavailable on this server." );
+ return;
+ }
+ 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 );
+ }
+}
+
+#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 },
+ { NULL }
+};
+
+void SV_HttpHandle( const uriEntry_t *e, const char *uri ) {
+ const char *p;
+ int 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, int length ) {
+ fifo_t *fifo = &client->stream.send;
+#if USE_ZLIB
+ z_streamp z = &client->z;
+ int param;
+#endif
+
+ if( client->state <= cs_zombie ) {
+ return;
+ }
+
+#if USE_ZLIB
+ if( !z->state ) {
+ if( !FIFO_Write( fifo, data, length ) ) {
+ SV_HttpDrop( client, "overflowed" );
+ }
+ return;
+ }
+
+ z->next_in = data;
+ z->avail_in = length;
+
+ param = Z_NO_FLUSH;
+ if( client->noflush > 120 ) {
+ param = Z_SYNC_FLUSH;
+ }
+
+ while( z->avail_in ) {
+ data = FIFO_Reserve( fifo, &length );
+ if( !length ) {
+ SV_HttpDrop( client, "overflowed" );
+ return;
+ }
+
+ z->next_out = data;
+ z->avail_out = length;
+
+ if( deflate( z, param ) != Z_OK ) {
+ SV_HttpDrop( client, "deflate failed" );
+ return;
+ }
+
+ length -= z->avail_out;
+ if( length ) {
+ FIFO_Commit( fifo, length );
+ client->noflush = 0;
+ }
+ }
+#else
+ if( !FIFO_Write( fifo, data, length ) ) {
+ SV_HttpDrop( client, "overflowed" );
+ }
+#endif
+}
+
+void SV_HttpFinish( tcpClient_t *client ) {
+#if USE_ZLIB
+ fifo_t *fifo = &client->stream.send;
+ z_streamp z = &client->z;
+ byte *data;
+ int length, ret;
+
+ if( client->state <= cs_zombie ) {
+ return;
+ }
+
+ if( !z->state ) {
+ return;
+ }
+
+ z->next_in = NULL;
+ z->avail_in = 0;
+
+ do {
+ data = FIFO_Reserve( fifo, &length );
+ if( !length ) {
+ SV_HttpDrop( client, "overflowed" );
+ return;
+ }
+
+ z->next_out = data;
+ z->avail_out = length;
+
+ ret = deflate( z, Z_FINISH );
+
+ FIFO_Commit( fifo, length - 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;
+ int length;
+
+ if( http_client->state <= cs_zombie ) {
+ return;
+ }
+
+ va_start( argptr, fmt );
+ length = Q_vsnprintf( buffer, sizeof( buffer ), fmt, argptr );
+ va_end( argptr );
+
+ if( !FIFO_Write( &http_client->stream.send, buffer, length ) ) {
+ 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 ) {
+ Com_DPrintf( "HTTP client %s removed\n",
+ NET_AdrToString( &client->stream.address ) );
+
+ NET_Close( &client->stream );
+ List_Delete( &client->entry );
+ Z_Free( client );
+}
+
+static void SV_HttpAccept( netstream_t *stream ) {
+ tcpClient_t *client;
+ netstream_t *s;
+ int count;
+
+ count = List_Count( &svs.tcpClients );
+ 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.tcpClients, 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;
+ }
+ }
+
+ Z_TagReserve( sizeof( *client ) + 256 + MAX_MSGLEN * 2, TAG_SERVER );
+ client = Z_ReservedAllocz( sizeof( *client ) );
+ client->lastmessage = svs.realtime;
+ client->state = cs_assigned;
+ List_Init( &client->mvdEntry );
+
+ s = &client->stream;
+ s->recv.data = Z_ReservedAlloc( 256 );
+ s->recv.size = 256;
+ s->send.data = Z_ReservedAlloc( MAX_MSGLEN * 2 );
+ s->send.size = MAX_MSGLEN * 2;
+ s->socket = stream->socket;
+ s->address = stream->address;
+ s->state = stream->state;
+
+ List_Append( &svs.tcpClients, &client->entry );
+
+ Com_DPrintf( "HTTP client %s accepted\n",
+ NET_AdrToString( &stream->address ) );
+}
+
+static qboolean SV_HttpParseRequest( tcpClient_t *client ) {
+ char key[MAX_TOKEN_CHARS];
+ char *p, *token;
+ const char *line;
+ byte *b, *data;
+ int 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",
+ "Maximum request line length exceeded." );
+ 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 );
+ 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",
+ "Specified method is not implemented." );
+ break;
+ }
+
+ // parse URI
+ token = COM_SimpleParse( &line );
+ if( !Q_stricmpn( token, "http://", 7 ) ) {
+ p = strchr( token + 7, '/' );
+ if( !p ) {
+ SV_HttpReject( "400 Bad Request", "Invalid absolute URI." );
+ break;
+ }
+ client->resource = SV_CopyString( p );
+ *p = 0;
+ client->host = SV_CopyString( token + 7 );
+ } else {
+ if( *token != '/' ) {
+ SV_HttpReject( "400 Bad Request", "Invalid relative URI." );
+ break;
+ }
+ client->resource = SV_CopyString( token );
+ }
+ Com_DPrintf( "GET %s\n", client->resource );
+
+ // parse version
+ token = COM_SimpleParse( &line );
+ if( strncmp( token, "HTTP/", 5 ) ) {
+ SV_HttpReject( "400 Bad Request", "HTTP version is malfromed." );
+ break;
+ }
+ major = strtoul( token + 5, &token, 10 );
+ if( *token != '.' ) {
+ SV_HttpReject( "400 Bad Request", "HTTP version is malformed." );
+ break;
+ }
+ minor = strtoul( token + 1, &token, 10 );
+ if( major != 1 || ( minor != 0 && minor != 1 ) ) {
+ SV_HttpReject( "505 HTTP Version not supported",
+ "HTTP version is not supported." );
+ break;
+ }
+ } else {
+ token = COM_SimpleParse( &line );
+ if( !token[0] ) {
+ if( !client->host ) {
+ SV_HttpReject( "400 Bad Request", "Missing host field." );
+ }
+ return qtrue; // end of header
+ }
+ // parse header fields
+ strcpy( key, token );
+ p = strchr( key, ':' );
+ if( !p ) {
+ SV_HttpReject( "400 Bad Request", "Malformed request field." );
+ break;
+ }
+ *p = 0;
+ Q_strlwr( key );
+
+ token = COM_SimpleParse( &line );
+ 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 );
+ 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;
+ uint32 point;
+
+ // 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.tcpClients, entry ) {
+ http_client = client;
+ http_header[0] = 0;
+
+ // check timeouts
+ if( client->lastmessage > svs.realtime ) {
+ client->lastmessage = svs.realtime;
+ }
+ switch( client->state ) {
+ case cs_zombie:
+ if( client->lastmessage < svs.zombiepoint ||
+ FIFO_Usage( &client->stream.send ) == 0 )
+ {
+ SV_HttpRemove( client );
+ continue;
+ }
+ break;
+ case cs_assigned:
+ point = svs.droppoint;
+ if( point < svs.ghostpoint ) {
+ point = svs.ghostpoint;
+ }
+ if( client->lastmessage < point ) {
+ SV_HttpReject( "408 Request Timeout", NULL );
+ continue;
+ }
+ break;
+ default:
+ if( client->lastmessage < svs.droppoint ) {
+ SV_HttpDrop( client, "connection timed out" );
+ SV_HttpRemove( client );
+ continue;
+ }
+ break;
+ }
+
+ // run network stream
+ ret = NET_Run( &client->stream );
+ if( ret == NET_AGAIN ) {
+ 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 ) ) {
+ client->state = cs_connected;
+ Q_EscapeMarkup( http_host, client->host, sizeof( http_host ) );
+ SV_HttpHandle( rootURIs, client->resource );
+ }
+
+ http_client = NULL;
+ }
+
+ }
+}
+