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("
"); - } - 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;