From f137242b51a1f0b0a848d03a709d4c2c04d65331 Mon Sep 17 00:00:00 2001 From: Spoike Date: Fri, 16 Mar 2007 04:05:01 +0000 Subject: [PATCH] Shifted some stuff out to a new file to clean stuff up. Tweeked the admin page so it doesn't look horribly out of place. Code for permitting downloads from the http server is in place, but not activated. I first need to address the security issues with being able to do this. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@2480 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- fteqtv/Makefile | 2 +- fteqtv/forward.c | 668 ++-------------------------------------- fteqtv/httpsv.c | 741 +++++++++++++++++++++++++++++++++++++++++++++ fteqtv/qtv.h | 10 +- fteqtv/qtvprox.dsp | 4 + fteqtv/source.c | 2 +- 6 files changed, 785 insertions(+), 642 deletions(-) create mode 100644 fteqtv/httpsv.c diff --git a/fteqtv/Makefile b/fteqtv/Makefile index 5be0a047f..279589728 100644 --- a/fteqtv/Makefile +++ b/fteqtv/Makefile @@ -4,7 +4,7 @@ STRIP=strip STRIPFLAGS=--strip-unneeded --remove-section=.comment -OBJS = netchan.o parse.o qw.o source.o bsp.o rcon.o mdfour.o crc.o control.o forward.o pmove.o +OBJS = netchan.o parse.o qw.o source.o bsp.o rcon.o mdfour.o crc.o control.o forward.o pmove.o httpsv.o qtv: $(OBJS) qtv.h $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@.db -lm diff --git a/fteqtv/forward.c b/fteqtv/forward.c index 09a8ffe54..95189521e 100644 --- a/fteqtv/forward.c +++ b/fteqtv/forward.c @@ -406,6 +406,8 @@ void SV_ForwardStream(sv_t *qtv, char *buffer, int length) fclose(fre->file); else closesocket(fre->sock); + if (fre->srcfile) + fclose(fre->srcfile); free(fre); qtv->cluster->numproxies--; prox->next = next; @@ -437,540 +439,6 @@ void SV_ForwardStream(sv_t *qtv, char *buffer, int length) } } -static const char qfont_table[256] = { - '\0', '#', '#', '#', '#', '.', '#', '#', - '#', 9, 10, '#', ' ', 13, '.', '.', - '[', ']', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', '.', '<', '=', '>', - ' ', '!', '"', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', ':', ';', '<', '=', '>', '?', - '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', - 'x', 'y', 'z', '{', '|', '}', '~', '<', - - '<', '=', '>', '#', '#', '.', '#', '#', - '#', '#', ' ', '#', ' ', '>', '.', '.', - '[', ']', '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', '.', '<', '=', '>', - ' ', '!', '"', '#', '$', '%', '&', '\'', - '(', ')', '*', '+', ',', '-', '.', '/', - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', ':', ';', '<', '=', '>', '?', - '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', - 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', - 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', - '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', - 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', - 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', - 'x', 'y', 'z', '{', '|', '}', '~', '<' -}; - -void HTMLprintf(char *outb, int outl, char *fmt, ...) -{ - va_list val; - char qfmt[8192*4]; - char *inb = qfmt; - - va_start(val, fmt); - vsnprintf(qfmt, sizeof(qfmt), fmt, val); - va_end(val); - qfmt[sizeof(qfmt)-1] = 0; - - outl--; - outl -= 5; - while (outl > 0 && *inb) - { - if (*inb == '<') - { - *outb++ = '&'; - *outb++ = 'l'; - *outb++ = 't'; - *outb++ = ';'; - outl -= 4; - } - else if (*inb == '>') - { - *outb++ = '&'; - *outb++ = 'g'; - *outb++ = 't'; - *outb++ = ';'; - outl -= 4; - } - else if (*inb == '\n') - { - *outb++ = '<'; - *outb++ = 'b'; - *outb++ = 'r'; - *outb++ = '/'; - *outb++ = '>'; - outl -= 5; - } - else if (*inb == '&') - { - *outb++ = '&'; - *outb++ = 'a'; - *outb++ = 'm'; - *outb++ = 'p'; - *outb++ = ';'; - outl -= 5; - } - else - { - *outb++ = qfont_table[*(unsigned char*)inb]; - } - inb++; - } - *outb++ = 0; -} - -static const char* SV_GetTime() -{ - time_t rawtime; - time (&rawtime); - return ctime(&rawtime); -} - -static void SV_SendHTTPHeader(cluster_t *cluster, oproxy_t *dest, char *error_code, char *content_type, qboolean nocache) -{ - char *s; - char buffer[2048]; - - if (nocache) { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Cache-Control: no-cache, must-revalidate\n" - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\n" - "Last-Modified: %s" - "Connection: close\n" - "\n"; - snprintf(buffer, sizeof(buffer), s, error_code, content_type, SV_GetTime()); - } else { - s = "HTTP/1.1 %s OK\n" - "Content-Type: %s\n" - "Connection: close\n" - "\n"; - snprintf(buffer, sizeof(buffer), s, error_code, content_type); - } - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -static void SV_SendHTMLHeader(cluster_t *cluster, oproxy_t *dest, char *title) -{ - char *s; - char buffer[2048]; - - s = "\n" - "\n" - "\n" - " \n" - " %s\n" - " \n" - "\n" - "
"; - - snprintf(buffer, sizeof(buffer), s, title); - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -#define HTMLPRINT(str) { sprintf(buffer, str "\n"); Net_ProxySend(cluster, dest, buffer, strlen(buffer)); } - -void SV_GenerateNowPlayingHTTP(cluster_t *cluster, oproxy_t *dest) -{ - int player; - char *s; - char buffer[1024]; - char plname[64]; - sv_t *streams; - - SV_SendHTTPHeader(cluster, dest, "200", "text/html", true); - SV_SendHTMLHeader(cluster, dest, "QuakeTV: Now Playing"); - - snprintf(buffer, sizeof(buffer), "

QuakeTV on %s: Now Playing

", cluster->hostname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - - HTMLPRINT("
"); - for (streams = cluster->servers; streams; streams = streams->next) - { - HTMLPRINT("
"); - HTMLprintf(buffer, sizeof(buffer), "%s (%s: %s)", streams->server, streams->gamedir, streams->mapname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - sprintf(buffer, " [ Watch Now ]", streams->streamid); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - HTMLPRINT("
    "); - - for (player = 0; player < MAX_CLIENTS; player++) - { - if (*streams->players[player].userinfo) - { - Info_ValueForKey(streams->players[player].userinfo, "name", plname, sizeof(plname)); - - if (streams->players[player].frags < -90) { - HTMLPRINT("
  • "); - } else { - HTMLPRINT("
  • "); - } - - HTMLprintf(buffer, sizeof(buffer), "%s", plname); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - HTMLPRINT("
  • "); - } - } - HTMLPRINT("
"); - } - HTMLPRINT("
"); - if (!cluster->servers) - { - s = "No streams are currently being played
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - } - - sprintf(buffer, "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); - - HTMLPRINT(""); -} - -void SV_GenerateCSSFile(cluster_t *cluster, oproxy_t *dest) -{ - char buffer[1024]; - - SV_SendHTTPHeader(cluster, dest, "200", "text/css", false); - - HTMLPRINT("* { font-family: Verdana, Helvetica, sans-serif; }"); - HTMLPRINT("body { color: #000; background-color: #fff; padding: 0 40px; }"); - HTMLPRINT("a { color: #00f; }"); - HTMLPRINT("a.qtvfile { font-weight: bold; }"); - HTMLPRINT("a:visited { color: #00f; }"); - HTMLPRINT("a:hover { background-color: black; color: yellow; }"); - HTMLPRINT("li.spectator { color: #666; font-size: 0.9ex; }"); - HTMLPRINT("dl.nowplaying dd { margin: 0 0 2em 0; }"); - HTMLPRINT("dl.nowplaying dt { margin: 1em 0 0 0; font-size: 1.1em; font-weight: bold; }"); - HTMLPRINT("dl.nowplaying li { list-style: none; margin: 0 0 0 1em; padding: 0; }"); - HTMLPRINT("dl.nowplaying ul { margin: 0 0 0 1em; padding: 0; }"); - HTMLPRINT("#navigation { background-color: #eef; }"); - HTMLPRINT("#navigation li { display: inline; list-style: none; margin: 0 3em; }"); -} - -qboolean SV_GetHTTPHeaderField(char *s, char *field, char *buffer, int buffersize) -{ - char *e; - char *colon; - int fieldnamelen = strlen(field); - - buffer[0] = 0; - - e = s; - while(*e) - { - if (*e == '\n') - { - *e = '\0'; - colon = strchr(s, ':'); - if (!colon) - { - if (!strncmp(field, s, fieldnamelen)) - { - if (s[fieldnamelen] <= ' ') - { - return true; - } - } - } - else - { - if (fieldnamelen == colon - s) - { - if (!strncmp(field, s, colon-s)) - { - colon++; - while (*colon == ' ') - colon++; - while (buffersize > 1) - { - if (*colon == '\r' || *colon == '\n') - break; - *buffer++ = *colon++; - } - *buffer = 0; - return true; - } - } - - } - s = e+1; - } - - e++; - } - return false; -} - -void SV_GenerateQTVStub(cluster_t *cluster, oproxy_t *dest, char *streamtype, char *streamid) -{ - char *s; - char hostname[64]; - char buffer[1024]; - - if (!SV_GetHTTPHeaderField(dest->inbuffer, "Host", hostname, sizeof(hostname))) - { - SV_SendHTTPHeader(cluster, dest, "400", "text/html", true); - SV_SendHTMLHeader(cluster, dest, "QuakeTV: Error"); - - s = "Your client did not send a Host field, which is required in HTTP/1.1\n
" - "Please try a different browser.\n" - "" - ""; - - Net_ProxySend(cluster, dest, s, strlen(s)); - return; - } - - SV_SendHTTPHeader(cluster, dest, "200", "text/x-quaketvident", false); - - { - char *ws; - for (ws = streamid; *ws > ' '; ws++) - ; - *ws = '\0'; - } - - - sprintf(buffer, "[QTV]\r\n" - "Stream: %s%s@%s\r\n" - "", - streamtype, streamid, hostname); - - - Net_ProxySend(cluster, dest, buffer, strlen(buffer)); -} - -char *SV_ParsePOST(char *post, char *buffer, int buffersize) -{ - while(*post && *post != '&') - { - if (--buffersize>0) - { - if (*post == '+') - *buffer++ = ' '; - else if (*post == '%') - { - *buffer = 0; - post++; - if (*post == '\0' || *post == '&') - break; - else if (*post >= 'a' && *post <= 'f') - *buffer += 10 + *post-'a'; - else if (*post >= 'A' && *post <= 'F') - *buffer += 10 + *post-'A'; - else if (*post >= '0' && *post <= '9') - *buffer += *post-'0'; - - *buffer <<= 4; - - post++; - if (*post == '\0' || *post == '&') - break; - else if (*post >= 'a' && *post <= 'f') - *buffer += 10 + *post-'a'; - else if (*post >= 'A' && *post <= 'F') - *buffer += 10 + *post-'A'; - else if (*post >= '0' && *post <= '9') - *buffer += *post-'0'; - - buffer++; - } - else - *buffer++ = *post; - } - post++; - } - *buffer = 0; - - return post; -} -void SV_GenerateAdminHTTP(cluster_t *cluster, oproxy_t *dest, int streamid, char *postbody) -{ - char pwd[64]; - char cmd[256]; - char result[8192]; - char *s; - char *o; - - if (!*cluster->adminpassword) - { - SV_SendHTTPHeader(cluster, dest, "403", "text/html", true); - SV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin Error"); - - s = "The admin password is disabled. You may not log in remotely.\n"; - Net_ProxySend(cluster, dest, s, strlen(s)); - return; - } - - - pwd[0] = 0; - cmd[0] = 0; - if (postbody) - while (*postbody) - { - if (!strncmp(postbody, "pwd=", 4)) - { - postbody = SV_ParsePOST(postbody+4, pwd, sizeof(pwd)); - } - else if (!strncmp(postbody, "cmd=", 4)) - { - postbody = SV_ParsePOST(postbody+4, cmd, sizeof(cmd)); - } - else - { - while(*postbody && *postbody != '&') - { - postbody++; - } - if (*postbody == '&') - postbody++; - } - } - - if (!*pwd) - o = ""; - else if (!strcmp(pwd, cluster->adminpassword)) - { - //small hack (as http connections are considered non-connected proxies) - cluster->numproxies--; - o = Rcon_Command(cluster, NULL, cmd, result, sizeof(result), false); - cluster->numproxies++; - } - else - { - o = "Bad Password"; - } - if (o != result) - { - strcpy(result, o); - o = result; - } - - s = "HTTP/1.1 200 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - - "" - "" - "QuakeTV: Admin\n" - -//this section of code is to put focus into the command box, so you don't need to click it all the time. -"\n" - "\n" - ""; - - - Net_ProxySend(cluster, dest, s, strlen(s)); - s = "

FTEQTV Admin: "; - Net_ProxySend(cluster, dest, s, strlen(s)); - s = cluster->hostname; - Net_ProxySend(cluster, dest, s, strlen(s)); - s = "

"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - s = - "
" - "
" - "Password " - "
" - "Command " - "" - "
" - "
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - while(*o) - { - s = strchr(o, '\n'); - if (s) - *s = 0; - HTMLprintf(cmd, sizeof(cmd), "%s", o); - Net_ProxySend(cluster, dest, cmd, strlen(cmd)); - Net_ProxySend(cluster, dest, "
", 6); - if (!s) - break; - o = s+1; - } - - s = "
Now Playing
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - s = "Available Demos
"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - sprintf(result, "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); - Net_ProxySend(cluster, dest, result, strlen(result)); - - s = "" - ""; - Net_ProxySend(cluster, dest, s, strlen(s)); -} - -#ifndef _WIN32 -#include -#endif - -void SV_GenerateQTVDemoListing(cluster_t *cluster, oproxy_t *dest) -{ - int i; - char link[256]; - char *s; - - SV_SendHTTPHeader(cluster, dest, "200", "text/html", true); - SV_SendHTMLHeader(cluster, dest, "QuakeTV: Demos"); - - s = "

QuakeTV: Demo Listing

"; - Net_ProxySend(cluster, dest, s, strlen(s)); - - Cluster_BuildAvailableDemoList(cluster); - for (i = 0; i < cluster->availdemoscount; i++) - { - snprintf(link, sizeof(link), "%s (%ikb)
", cluster->availdemos[i].name, cluster->availdemos[i].name, cluster->availdemos[i].size/1024); - Net_ProxySend(cluster, dest, link, strlen(link)); - } - - sprintf(link, "

Total: %i demos

", cluster->availdemoscount); - Net_ProxySend(cluster, dest, link, strlen(link)); - - - sprintf(link, "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); - Net_ProxySend(cluster, dest, link, strlen(link)); - - - s = "" - ""; - Net_ProxySend(cluster, dest, s, strlen(s)); -} - - - //returns true if the pending proxy should be unlinked //truth does not imply that it should be freed/released, just unlinked. qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) @@ -986,6 +454,8 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) if (pend->drop) { + if (pend->srcfile) + fclose(pend->srcfile); closesocket(pend->sock); free(pend); cluster->numproxies--; @@ -997,8 +467,27 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) if (pend->flushing) { if (pend->bufferpos == pend->buffersize) - pend->drop = true; - return false; + { + if (pend->srcfile) + { + char buffer[4096]; + len = fread(buffer, 1, sizeof(buffer), pend->srcfile); + if (!len) + { + fclose(pend->srcfile); + pend->srcfile = NULL; + } + Net_ProxySend(cluster, pend, buffer, len); + return false; //don't try reading anything yet + } + else + { + pend->drop = true; + return false; + } + } + else + return false; } len = sizeof(pend->inbuffer) - pend->inbuffersize - 1; @@ -1036,113 +525,14 @@ qboolean SV_ReadPendingProxy(cluster_t *cluster, oproxy_t *pend) if (!strncmp(pend->inbuffer, "POST ", 5)) { - if (!SV_GetHTTPHeaderField(pend->inbuffer, "Content-Length", tempbuf, sizeof(tempbuf))) - { - s = "HTTP/1.1 411 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "QuakeTVNo Content-Length was provided.\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - pend->flushing = true; - return false; - } - len = atoi(tempbuf); - if (pend->inbuffersize + len >= sizeof(pend->inbuffer)-20) - { //too much data - pend->flushing = true; - return false; - } - len = s - (char*)pend->inbuffer + len; - if (len > pend->inbuffersize) - return false; //still need the body + HTTPSV_PostMethod(cluster, pend, s); -// if (len <= pend->inbuffersize) - { - if (!strncmp(pend->inbuffer+5, "/admin", 6)) - { - SV_GenerateAdminHTTP(cluster, pend, 0, s); - } - else - { - s = "HTTP/1.1 404 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "QuakeTVThat HTTP method is not supported for that URL.\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - - } - pend->flushing = true; - return false; - } + return false; //not keen on this.. } else if (!strncmp(pend->inbuffer, "GET ", 4)) { - if (!strncmp(pend->inbuffer+4, "/nowplaying", 11)) - { - SV_GenerateNowPlayingHTTP(cluster, pend); - } - else if (!strncmp(pend->inbuffer+4, "/watch.qtv?sid=", 15)) - { - SV_GenerateQTVStub(cluster, pend, "", pend->inbuffer+19); - } - else if (!strncmp(pend->inbuffer+4, "/watch.qtv?demo=", 16)) - { - SV_GenerateQTVStub(cluster, pend, "file:", pend->inbuffer+20); - } - else if (!strncmp(pend->inbuffer+4, "/about", 6)) - { //redirect them to our funky website - s = "HTTP/1.0 302 Found\n" - "Location: http://www.fteqw.com/\n" - "\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - } - else if (!strncmp(pend->inbuffer+4, "/admin", 6)) - { - SV_GenerateAdminHTTP(cluster, pend, 0, NULL); - } - else if (!strncmp(pend->inbuffer+4, "/ ", 2)) - { - s = "HTTP/1.0 302 Found\n" - "Location: /nowplaying/\n" - "\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - } - else if (!strncmp(pend->inbuffer+4, "/demos", 6)) - { - SV_GenerateQTVDemoListing(cluster, pend); - /* - s = "HTTP/1.1 200 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "FTEQTVNot Yet Supported"; - Net_ProxySend(cluster, pend, s, strlen(s)); - */ - } -/* else - { - s = "HTTP/0.9 200 OK\n" - "Content-Type: text/plain\n" - "Content-Length: 12\n" - "\n" - "Hello World\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - }*/ - else if (!strncmp(pend->inbuffer+4, "/style.css", 10)) - { - SV_GenerateCSSFile(cluster, pend); - } - else - { - s = "HTTP/1.1 404 OK\n" - "Content-Type: text/html\n" - "Connection: close\n" - "\n" - "QuakeTVThe url you have specified was not recognised.\n"; - Net_ProxySend(cluster, pend, s, strlen(s)); - } + HTTPSV_GetMethod(cluster, pend); + pend->flushing = true; return false; } diff --git a/fteqtv/httpsv.c b/fteqtv/httpsv.c new file mode 100644 index 000000000..5a56fcd62 --- /dev/null +++ b/fteqtv/httpsv.c @@ -0,0 +1,741 @@ +#include "qtv.h" + +//main reason to use connection close is because we're lazy and don't want to give sizes in advance (yes, we could use chunks..) + +//#define ALLOWDOWNLOADS + + + + +static const char qfont_table[256] = { + '\0', '#', '#', '#', '#', '.', '#', '#', + '#', 9, 10, '#', ' ', 13, '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<', + + '<', '=', '>', '#', '#', '.', '#', '#', + '#', '#', ' ', '#', ' ', '>', '.', '.', + '[', ']', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '.', '<', '=', '>', + ' ', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', '{', '|', '}', '~', '<' +}; + +static void HTMLprintf(char *outb, int outl, char *fmt, ...) +{ + va_list val; + char qfmt[8192*4]; + char *inb = qfmt; + + va_start(val, fmt); + vsnprintf(qfmt, sizeof(qfmt), fmt, val); + va_end(val); + qfmt[sizeof(qfmt)-1] = 0; + + outl--; + outl -= 5; + while (outl > 0 && *inb) + { + if (*inb == '<') + { + *outb++ = '&'; + *outb++ = 'l'; + *outb++ = 't'; + *outb++ = ';'; + outl -= 4; + } + else if (*inb == '>') + { + *outb++ = '&'; + *outb++ = 'g'; + *outb++ = 't'; + *outb++ = ';'; + outl -= 4; + } + else if (*inb == '\n') + { + *outb++ = '<'; + *outb++ = 'b'; + *outb++ = 'r'; + *outb++ = '/'; + *outb++ = '>'; + outl -= 5; + } + else if (*inb == '&') + { + *outb++ = '&'; + *outb++ = 'a'; + *outb++ = 'm'; + *outb++ = 'p'; + *outb++ = ';'; + outl -= 5; + } + else + { + *outb++ = qfont_table[*(unsigned char*)inb]; + } + inb++; + } + *outb++ = 0; +} + +static void HTTPSV_SendHTTPHeader(cluster_t *cluster, oproxy_t *dest, char *error_code, char *content_type, qboolean nocache) +{ + char *s; + char buffer[2048]; + + if (nocache) + { + s = "HTTP/1.1 %s OK\n" + "Content-Type: %s\n" + "Cache-Control: no-cache, must-revalidate\n" + "Expires: Mon, 26 Jul 1997 05:00:00 GMT\n" + "Connection: close\n" + "\n"; + } + else + { + s = "HTTP/1.1 %s OK\n" + "Content-Type: %s\n" + "Connection: close\n" + "\n"; + } + + snprintf(buffer, sizeof(buffer), s, error_code, content_type); + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static void HTTPSV_SendHTMLHeader(cluster_t *cluster, oproxy_t *dest, char *title) +{ + char *s; + char buffer[2048]; + + s = "\n" + "\n" + "\n" + " \n" + " %s\n" + " \n" + "\n" + "
"; + + snprintf(buffer, sizeof(buffer), s, title); + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static void HTTPSV_SendHTMLFooter(cluster_t *cluster, oproxy_t *dest) +{ + char *s; + char buffer[2048]; + + sprintf(buffer, "
QTV Version: %i www.fteqw.com
", cluster->buildnumber); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + s = "\n" + "\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); +} + +#define HTMLPRINT(str) Net_ProxySend(cluster, dest, str "\n", strlen(str "\n")) + +static void HTTPSV_GenerateNowPlaying(cluster_t *cluster, oproxy_t *dest) +{ + int player; + char *s; + char buffer[1024]; + char plname[64]; + sv_t *streams; + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Now Playing"); + + if (!strcmp(cluster->hostname, DEFAULT_HOSTNAME)) + snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing

"); //don't show the hostname if its set to the default + else + snprintf(buffer, sizeof(buffer), "

QuakeTV: Now Playing on %s

", cluster->hostname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + + HTMLPRINT("
"); + for (streams = cluster->servers; streams; streams = streams->next) + { + HTMLPRINT("
"); + HTMLprintf(buffer, sizeof(buffer), "%s (%s: %s)", streams->server, streams->gamedir, streams->mapname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + sprintf(buffer, " [ Watch Now ]", streams->streamid); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + HTMLPRINT("
    "); + + for (player = 0; player < MAX_CLIENTS; player++) + { + if (*streams->players[player].userinfo) + { + Info_ValueForKey(streams->players[player].userinfo, "name", plname, sizeof(plname)); + + if (streams->players[player].frags < -90) + { + HTMLPRINT("
  • "); + } + else + { + HTMLPRINT("
  • "); + } + + HTMLprintf(buffer, sizeof(buffer), "%s", plname); + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); + HTMLPRINT("
  • "); + } + } + HTMLPRINT("
"); + } + HTMLPRINT("
"); + if (!cluster->servers) + { + s = "No streams are currently being played
"; + Net_ProxySend(cluster, dest, s, strlen(s)); + } + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateCSSFile(cluster_t *cluster, oproxy_t *dest) +{ + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/css", false); + + HTMLPRINT("* { font-family: Verdana, Helvetica, sans-serif; }"); + HTMLPRINT("body { color: #000; background-color: #fff; padding: 0 40px; }"); + HTMLPRINT("a { color: #00f; }"); + HTMLPRINT("a.qtvfile { font-weight: bold; }"); + HTMLPRINT("a:visited { color: #00f; }"); + HTMLPRINT("a:hover { background-color: black; color: yellow; }"); + HTMLPRINT("li.spectator { color: #666; font-size: 0.9ex; }"); + HTMLPRINT("dl.nowplaying dd { margin: 0 0 2em 0; }"); + HTMLPRINT("dl.nowplaying dt { margin: 1em 0 0 0; font-size: 1.1em; font-weight: bold; }"); + HTMLPRINT("dl.nowplaying li { list-style: none; margin: 0 0 0 1em; padding: 0; }"); + HTMLPRINT("dl.nowplaying ul { margin: 0 0 0 1em; padding: 0; }"); + HTMLPRINT("#navigation { background-color: #eef; }"); + HTMLPRINT("#navigation li { display: inline; list-style: none; margin: 0 3em; }"); +} + +static qboolean HTTPSV_GetHeaderField(char *s, char *field, char *buffer, int buffersize) +{ + char *e; + char *colon; + int fieldnamelen = strlen(field); + + buffer[0] = 0; + + e = s; + while(*e) + { + if (*e == '\n') + { + *e = '\0'; + colon = strchr(s, ':'); + if (!colon) + { + if (!strncmp(field, s, fieldnamelen)) + { + if (s[fieldnamelen] <= ' ') + { + return true; + } + } + } + else + { + if (fieldnamelen == colon - s) + { + if (!strncmp(field, s, colon-s)) + { + colon++; + while (*colon == ' ') + colon++; + while (buffersize > 1) + { + if (*colon == '\r' || *colon == '\n') + break; + *buffer++ = *colon++; + } + *buffer = 0; + return true; + } + } + + } + s = e+1; + } + + e++; + } + return false; +} + +static void HTTPSV_GenerateQTVStub(cluster_t *cluster, oproxy_t *dest, char *streamtype, char *streamid) +{ + char *s; + char hostname[64]; + char buffer[1024]; + + + char fname[256]; + s = fname; + while (*streamid > ' ') + { + if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be + break; + if (*streamid == '%') + { + *s = 0; + streamid++; + if (*streamid <= ' ') + break; + else if (*streamid >= 'a' && *streamid <= 'f') + *s += 10 + *streamid-'a'; + else if (*streamid >= 'A' && *streamid <= 'F') + *s += 10 + *streamid-'A'; + else if (*streamid >= '0' && *streamid <= '9') + *s += *streamid-'0'; + + *s <<= 4; + + streamid++; + if (*streamid <= ' ') + break; + else if (*streamid >= 'a' && *streamid <= 'f') + *s += 10 + *streamid-'a'; + else if (*streamid >= 'A' && *streamid <= 'F') + *s += 10 + *streamid-'A'; + else if (*streamid >= '0' && *streamid <= '9') + *s += *streamid-'0'; + + s++; + } + else + *s++ = *streamid++; + } + *s = 0; + streamid = fname; + + + if (!HTTPSV_GetHeaderField(dest->inbuffer, "Host", hostname, sizeof(hostname))) + { + HTTPSV_SendHTTPHeader(cluster, dest, "400", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Error"); + + s = "Your client did not send a Host field, which is required in HTTP/1.1\n
" + "Please try a different browser.\n" + "" + ""; + + Net_ProxySend(cluster, dest, s, strlen(s)); + return; + } + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/x-quaketvident", false); + + sprintf(buffer, "[QTV]\r\n" + "Stream: %s%s@%s\r\n" + "", + streamtype, streamid, hostname); + + + Net_ProxySend(cluster, dest, buffer, strlen(buffer)); +} + +static char *HTTPSV_ParsePOST(char *post, char *buffer, int buffersize) +{ + while(*post && *post != '&') + { + if (--buffersize>0) + { + if (*post == '+') + *buffer++ = ' '; + else if (*post == '%') + { + *buffer = 0; + post++; + if (*post == '\0' || *post == '&') + break; + else if (*post >= 'a' && *post <= 'f') + *buffer += 10 + *post-'a'; + else if (*post >= 'A' && *post <= 'F') + *buffer += 10 + *post-'A'; + else if (*post >= '0' && *post <= '9') + *buffer += *post-'0'; + + *buffer <<= 4; + + post++; + if (*post == '\0' || *post == '&') + break; + else if (*post >= 'a' && *post <= 'f') + *buffer += 10 + *post-'a'; + else if (*post >= 'A' && *post <= 'F') + *buffer += 10 + *post-'A'; + else if (*post >= '0' && *post <= '9') + *buffer += *post-'0'; + + buffer++; + } + else + *buffer++ = *post; + } + post++; + } + *buffer = 0; + + return post; +} +static void HTTPSV_GenerateAdmin(cluster_t *cluster, oproxy_t *dest, int streamid, char *postbody) +{ + char pwd[64]; + char cmd[256]; + char result[8192]; + char *s; + char *o; + int passwordokay = false; + + if (!*cluster->adminpassword) + { + HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin Error"); + + s = "The admin password is disabled. You may not log in remotely.\n"; + Net_ProxySend(cluster, dest, s, strlen(s)); + return; + } + + + pwd[0] = 0; + cmd[0] = 0; + if (postbody) + while (*postbody) + { + if (!strncmp(postbody, "pwd=", 4)) + { + postbody = HTTPSV_ParsePOST(postbody+4, pwd, sizeof(pwd)); + } + else if (!strncmp(postbody, "cmd=", 4)) + { + postbody = HTTPSV_ParsePOST(postbody+4, cmd, sizeof(cmd)); + } + else + { + while(*postbody && *postbody != '&') + { + postbody++; + } + if (*postbody == '&') + postbody++; + } + } + + if (!*pwd) + { + if (postbody) + o = "No Password"; + else + o = ""; + } + else if (!strcmp(pwd, cluster->adminpassword)) + { + passwordokay = true; + //small hack (as http connections are considered non-connected proxies) + cluster->numproxies--; + if (*cmd) + o = Rcon_Command(cluster, NULL, cmd, result, sizeof(result), false); + else + o = ""; + cluster->numproxies++; + } + else + { + o = "Bad Password"; + } + if (o != result) + { + strcpy(result, o); + o = result; + } + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Admin"); + + s = "

QuakeTV Admin: "; + Net_ProxySend(cluster, dest, s, strlen(s)); + s = cluster->hostname; + Net_ProxySend(cluster, dest, s, strlen(s)); + s = "

"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + + s = "
" + "
" + "Password " + "
" + "Command " + "" + "
" + "
"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + if (passwordokay) + HTMLPRINT(""); + else + HTMLPRINT(""); + + while(*o) + { + s = strchr(o, '\n'); + if (s) + *s = 0; + HTMLprintf(cmd, sizeof(cmd), "%s", o); + Net_ProxySend(cluster, dest, cmd, strlen(cmd)); + Net_ProxySend(cluster, dest, "
", 6); + if (!s) + break; + o = s+1; + } + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateDemoListing(cluster_t *cluster, oproxy_t *dest) +{ + int i; + char link[256]; + char *s; + + HTTPSV_SendHTTPHeader(cluster, dest, "200", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "QuakeTV: Demos"); + + s = "

QuakeTV: Demo Listing

"; + Net_ProxySend(cluster, dest, s, strlen(s)); + + Cluster_BuildAvailableDemoList(cluster); + for (i = 0; i < cluster->availdemoscount; i++) + { + snprintf(link, sizeof(link), "%s (%ikb)
", cluster->availdemos[i].name, cluster->availdemos[i].name, cluster->availdemos[i].size/1024); + Net_ProxySend(cluster, dest, link, strlen(link)); + } + + sprintf(link, "

Total: %i demos

", cluster->availdemoscount); + Net_ProxySend(cluster, dest, link, strlen(link)); + + HTTPSV_SendHTMLFooter(cluster, dest); +} + +static void HTTPSV_GenerateDownload(cluster_t *cluster, oproxy_t *dest, char *filename) +{ +#ifdef ALLOWDOWNLOADS + char fname[256]; + char *s; + + if (cluster->allowdownloads) +#endif + { + HTTPSV_SendHTTPHeader(cluster, dest, "403", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "Permission denied"); + HTMLPRINT("

403: Forbidden

"); + HTMLPRINT("File downloads from this proxy are currently not permitted."); + HTTPSV_SendHTMLFooter(cluster, dest); + return; + } +#ifdef ALLOWDOWNLOADS + s = fname; + while (*filename > ' ') + { + if (s > fname + sizeof(fname)-4) //4 cos I'm too lazy to work out what the actual number should be + break; + if (*filename == '%') + { + *s = 0; + filename++; + if (*filename <= ' ') + break; + else if (*filename >= 'a' && *filename <= 'f') + *s += 10 + *filename-'a'; + else if (*filename >= 'A' && *filename <= 'F') + *s += 10 + *filename-'A'; + else if (*filename >= '0' && *filename <= '9') + *s += *filename-'0'; + + *s <<= 4; + + filename++; + if (*filename <= ' ') + break; + else if (*filename >= 'a' && *filename <= 'f') + *s += 10 + *filename-'a'; + else if (*filename >= 'A' && *filename <= 'F') + *s += 10 + *filename-'A'; + else if (*filename >= '0' && *filename <= '9') + *s += *filename-'0'; + + s++; + } + else + *s++ = *filename++; + } + *s = 0; + dest->srcfile = fopen(fname, "rb"); + + if (dest->srcfile) + { + HTTPSV_SendHTTPHeader(cluster, dest, "200", "application/x-forcedownload", false); + } + else + { + HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "File not found"); + HTMLPRINT("

404: File not found

"); + HTTPSV_SendHTMLFooter(cluster, dest); + } +#endif +} + + + + + + + +void HTTPSV_PostMethod(cluster_t *cluster, oproxy_t *pend, char *postdata) +{ + char tempbuf[512]; + char *s; + int len; + + if (!HTTPSV_GetHeaderField(pend->inbuffer, "Content-Length", tempbuf, sizeof(tempbuf))) + { + s = "HTTP/1.1 411 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + "QuakeTVNo Content-Length was provided.\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + pend->flushing = true; + return; + } + len = atoi(tempbuf); + if (pend->inbuffersize + len >= sizeof(pend->inbuffer)-20) + { //too much data + pend->flushing = true; + return; + } + len = postdata - (char*)pend->inbuffer + len; + if (len > pend->inbuffersize) + return; //still need the body + +// if (len <= pend->inbuffersize) + { + if (!strncmp(pend->inbuffer+5, "/admin", 6)) + { + HTTPSV_GenerateAdmin(cluster, pend, 0, postdata); + } + else + { + s = "HTTP/1.1 404 OK\n" + "Content-Type: text/html\n" + "Connection: close\n" + "\n" + "QuakeTVThat HTTP method is not supported for that URL.\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + + } + pend->flushing = true; + return; + } +} + +void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend) +{ + char *s; + if (!strncmp(pend->inbuffer+4, "/nowplaying", 11)) + { + HTTPSV_GenerateNowPlaying(cluster, pend); + } + else if (!strncmp(pend->inbuffer+4, "/watch.qtv?sid=", 15)) + { + HTTPSV_GenerateQTVStub(cluster, pend, "", pend->inbuffer+19); + } + else if (!strncmp(pend->inbuffer+4, "/watch.qtv?demo=", 16)) + { + HTTPSV_GenerateQTVStub(cluster, pend, "file:", pend->inbuffer+20); + } +// else if (!strncmp(pend->inbuffer+4, "/demo/", 6)) +// { //fixme: make this send the demo as an http download +// HTTPSV_GenerateQTVStub(cluster, pend, "file:", pend->inbuffer+10); +// } + else if (!strncmp(pend->inbuffer+4, "/about", 6)) + { //redirect them to our funky website + s = "HTTP/1.0 302 Found\n" + "Location: http://www.fteqw.com/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp(pend->inbuffer+4, "/admin", 6)) + { + HTTPSV_GenerateAdmin(cluster, pend, 0, NULL); + } + else if (!strncmp(pend->inbuffer+4, "/ ", 2)) + { + s = "HTTP/1.0 302 Found\n" + "Location: /nowplaying/\n" + "\n"; + Net_ProxySend(cluster, pend, s, strlen(s)); + } + else if (!strncmp(pend->inbuffer+4, "/demos", 6)) + { + HTTPSV_GenerateDemoListing(cluster, pend); + } + else if (!strncmp(pend->inbuffer+4, "/file/", 6)) + { + HTTPSV_GenerateDownload(cluster, pend, pend->inbuffer+10); + } + else if (!strncmp(pend->inbuffer+4, "/style.css", 10)) + { + HTTPSV_GenerateCSSFile(cluster, pend); + } + else + { +#define dest pend + HTTPSV_SendHTTPHeader(cluster, dest, "404", "text/html", true); + HTTPSV_SendHTMLHeader(cluster, dest, "Address not recognised"); + HTMLPRINT("

Address not recognised

"); + HTTPSV_SendHTMLFooter(cluster, dest); + } +} diff --git a/fteqtv/qtv.h b/fteqtv/qtv.h index 3e8e29035..69787859c 100644 --- a/fteqtv/qtv.h +++ b/fteqtv/qtv.h @@ -455,7 +455,8 @@ typedef struct oproxy_s { sv_t *defaultstream; - FILE *file; //recording a demo + FILE *srcfile; //buffer is padded with data from this file when its empty + FILE *file; //recording a demo (written to) SOCKET sock; //playing to a proxy unsigned char inbuffer[MAX_PROXY_INBUFFER]; @@ -921,9 +922,11 @@ void Info_SetValueForStarKey (char *s, const char *key, const char *value, int m void ReadDeltaUsercmd (netmsg_t *m, const usercmd_t *from, usercmd_t *move); unsigned Com_BlockChecksum (void *buffer, int length); void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); +void Cluster_BuildAvailableDemoList(cluster_t *cluster); void Sys_Printf(cluster_t *cluster, char *fmt, ...); +void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, char *buffer, int length); oproxy_t *Net_FileProxy(sv_t *qtv, char *filename); sv_t *QTV_NewServerConnection(cluster_t *cluster, char *server, char *password, qboolean force, qboolean autoclose, qboolean noduplicates, qboolean query); SOCKET Net_MVDListen(int port); @@ -941,6 +944,11 @@ void ChooseFavoriteTrack(sv_t *tv); void DemoViewer_Update(sv_t *svtest); +//httpsv.c +void HTTPSV_GetMethod(cluster_t *cluster, oproxy_t *pend); +void HTTPSV_PostMethod(cluster_t *cluster, oproxy_t *pend, char *postdata); + + #ifdef __cplusplus } #endif diff --git a/fteqtv/qtvprox.dsp b/fteqtv/qtvprox.dsp index e008dca2d..88ea64425 100644 --- a/fteqtv/qtvprox.dsp +++ b/fteqtv/qtvprox.dsp @@ -437,6 +437,10 @@ SOURCE=.\forward.c # End Source File # Begin Source File +SOURCE=.\httpsv.c +# End Source File +# Begin Source File + SOURCE=.\mdfour.c # End Source File # Begin Source File diff --git a/fteqtv/source.c b/fteqtv/source.c index 0aae9cd3e..f5f395c3f 100644 --- a/fteqtv/source.c +++ b/fteqtv/source.c @@ -1684,7 +1684,7 @@ void QTV_Run(sv_t *qtv) if (qtv->serverquery) { - Sys_Printf(qtv->cluster, "End of sources\n", colon); + Sys_Printf(qtv->cluster, "End of list\n", colon); qtv->drop = true; qtv->buffersize = 0; return;