/* Copyright (C) 1996-1997 Id Software, Inc. 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 included (GNU.txt) 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. */ /* This is the file responsible for handling incoming tcp connections. This includes mvd recording. Password checks and stuff are implemented here. This is server side stuff. */ #include "qtv.h" #include "time.h" #undef IN #define IN(x) buffer[(x)&(MAX_PROXY_BUFFER-1)] void CheckMVDConsistancy(unsigned char *buffer, int pos, int size) { /* int length; int msec, type; while(pos < size) { msec = IN(pos++); type = IN(pos++); if (type == dem_set) { pos+=8; continue; } if (type == dem_multiple) pos+=4; length = (IN(pos+0)<<0) + (IN(pos+1)<<8) + (IN(pos+2)<<16) + (IN(pos+3)<<24); pos+=4; if (length > 1450) printf("too big (%i)\n", length); pos += length; } if (pos != size) printf("pos != size\n"); */ } void SV_FindProxies(SOCKET sock, cluster_t *cluster, sv_t *defaultqtv) { oproxy_t *prox; sock = accept(sock, NULL, NULL); if (sock == INVALID_SOCKET) return; if (cluster->maxproxies >= 0 && cluster->numproxies >= cluster->maxproxies) { const char buffer[] = {dem_all, 1, 'P','r','o','x','y',' ','i','s',' ','f','u','l','l','.'}; send(sock, buffer, strlen(buffer), 0); closesocket(sock); return; } prox = malloc(sizeof(*prox)); if (!prox) {//out of mem? closesocket(sock); return; } memset(prox, 0, sizeof(*prox)); prox->sock = sock; prox->file = NULL; cluster->numproxies++; #if 1 prox->defaultstream = defaultqtv; prox->next = cluster->pendingproxies; cluster->pendingproxies = prox; #else prox->next = qtv->pendingproxies; qtv->pendingproxies = prox; Net_SendConnectionMVD(qtv, prox); #endif } void Net_TryFlushProxyBuffer(cluster_t *cluster, oproxy_t *prox) { char *buffer; int length; int bufpos; if (prox->drop) return; while (prox->bufferpos >= MAX_PROXY_BUFFER) { //so we never get any issues with wrapping.. prox->bufferpos -= MAX_PROXY_BUFFER; prox->buffersize -= MAX_PROXY_BUFFER; } bufpos = prox->bufferpos&(MAX_PROXY_BUFFER-1); length = prox->buffersize - prox->bufferpos; if (length > MAX_PROXY_BUFFER-bufpos) //cap the length correctly. length = MAX_PROXY_BUFFER-bufpos; if (!length) return; //already flushed. buffer = prox->buffer + bufpos; // CheckMVDConsistancy(prox->buffer, prox->bufferpos, prox->buffersize); if (bufpos+length > MAX_PROXY_BUFFER) Sys_Printf(cluster, "oversize flush\n"); if (prox->file) length = fwrite(buffer, 1, length, prox->file); else length = send(prox->sock, buffer, length, 0); switch (length) { case 0: //eof / they disconnected prox->drop = true; break; case -1: if (qerrno != EWOULDBLOCK && qerrno != EAGAIN) //not a problem, so long as we can flush it later. { Sys_Printf(cluster, "oversize flush\n"); prox->drop = true; //drop them if we get any errors } break; default: prox->bufferpos += length; } } void Net_ProxySend(cluster_t *cluster, oproxy_t *prox, char *buffer, int length) { int wrap; if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER) { Net_TryFlushProxyBuffer(cluster, prox); //try flushing if (prox->buffersize-prox->bufferpos + length > MAX_PROXY_BUFFER) //damn, still too big. { //they're too slow. hopefully it was just momentary lag printf("QTV client is too lagged\n"); prox->flushing = true; return; } } #if 1 //just simple prox->buffersize+=length; for (wrap = prox->buffersize-length; wrap < prox->buffersize; wrap++) prox->buffer[wrap&(MAX_PROXY_BUFFER-1)] = *buffer++; #else //we don't do multiple wrappings, the above check cannot succeed if it were required. //find the wrap point wrap = prox->buffersize-(prox->buffersize&(MAX_PROXY_BUFFER-1)) + MAX_PROXY_BUFFER; wrap = wrap - (prox->buffersize&(MAX_PROXY_BUFFER-1)); //the ammount of data we can fit before wrapping. if (wrap > length) { //we don't wrap afterall memcpy(prox->buffer+(prox->buffersize)&(MAX_PROXY_BUFFER-1), buffer, length); prox->buffersize+=length; return; } memcpy(prox->buffer+prox->buffersize&(MAX_PROXY_BUFFER-1), buffer, wrap); buffer += wrap; length -= wrap; memcpy(prox->buffer, buffer, length); prox->buffersize+=length; #endif } void Prox_SendMessage(cluster_t *cluster, oproxy_t *prox, char *buf, int length, int dem_type, unsigned int playermask) { netmsg_t msg; char tbuf[16]; InitNetMsg(&msg, tbuf, sizeof(tbuf)); WriteByte(&msg, 0); WriteByte(&msg, dem_type); WriteLong(&msg, length); if (dem_type == dem_multiple) WriteLong(&msg, playermask); if (prox->buffersize-prox->bufferpos + length + msg.cursize > MAX_PROXY_BUFFER) { Net_TryFlushProxyBuffer(cluster, prox); //try flushing if (prox->buffersize-prox->bufferpos + length + msg.cursize > MAX_PROXY_BUFFER) //damn, still too big. { //they're too slow. hopefully it was just momentary lag prox->flushing = true; return; } } Net_ProxySend(cluster, prox, msg.data, msg.cursize); Net_ProxySend(cluster, prox, buf, length); } void Prox_SendPlayerStats(sv_t *qtv, oproxy_t *prox) { char buffer[MAX_MSGLEN]; netmsg_t msg; int player, snum; InitNetMsg(&msg, buffer, sizeof(buffer)); for (player = 0; player < MAX_CLIENTS; player++) { for (snum = 0; snum < MAX_STATS; snum++) { if (qtv->players[player].stats[snum]) { if ((unsigned)qtv->players[player].stats[snum] > 255) { WriteByte(&msg, svc_updatestatlong); WriteByte(&msg, snum); WriteLong(&msg, qtv->players[player].stats[snum]); } else { WriteByte(&msg, svc_updatestat); WriteByte(&msg, snum); WriteByte(&msg, qtv->players[player].stats[snum]); } } } if (msg.cursize) { // Prox_SendMessage(prox, msg.data, msg.cursize, dem_stats|(player<<3), (1<mapname) return; InitNetMsg(&msg, buffer, sizeof(buffer)); prox->flushing = false; BuildServerData(qtv, &msg, 0, NULL); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; for (prespawn = 0;prespawn >= 0;) { prespawn = SendList(qtv, prespawn, qtv->soundlist, svc_soundlist, &msg); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; } for (prespawn = 0;prespawn >= 0;) { prespawn = SendList(qtv, prespawn, qtv->modellist, svc_modellist, &msg); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; } Net_TryFlushProxyBuffer(qtv->cluster, prox); //that should be enough data to fill a packet. for(prespawn = 0;prespawn>=0;) { prespawn = Prespawn(qtv, 0, &msg, prespawn, MAX_CLIENTS-1); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; } //playerstates arn't actually delta-compressed, so the first send (simply forwarded from server) entirly replaces the old. //we do need to send entity states. Prox_SendInitialEnts(qtv, prox, &msg); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; WriteByte(&msg, svc_stufftext); WriteString(&msg, "skins\n"); Prox_SendMessage(qtv->cluster, prox, msg.data, msg.cursize, dem_read, (unsigned)-1); msg.cursize = 0; Net_TryFlushProxyBuffer(qtv->cluster, prox); Prox_SendPlayerStats(qtv, prox); Net_TryFlushProxyBuffer(qtv->cluster, prox); Net_ProxySend(qtv->cluster, prox, qtv->buffer, qtv->forwardpoint); //send all the info we've not yet processed. if (prox->flushing) { Sys_Printf(qtv->cluster, "Connection data is too big, dropping proxy client\n"); prox->drop = true; //this is unfortunate... } else Net_TryFlushProxyBuffer(qtv->cluster, prox); } oproxy_t *Net_FileProxy(sv_t *qtv, char *filename) { oproxy_t *prox; FILE *f; f = fopen(filename, "wb"); if (!f) return NULL; //no full proxy check, this is going to be used by proxy admins, who won't want to have to raise the limit to start recording. prox = malloc(sizeof(*prox)); if (!prox) return NULL; memset(prox, 0, sizeof(*prox)); prox->sock = INVALID_SOCKET; prox->file = f; prox->next = qtv->proxies; qtv->proxies = prox; qtv->cluster->numproxies++; Net_SendConnectionMVD(qtv, prox); return prox; } qboolean Net_StopFileProxy(sv_t *qtv) { oproxy_t *prox; for (prox = qtv->proxies; prox; prox = prox->next) { if (prox->file) { prox->drop = true; return true; } } return false; } void SV_ForwardStream(sv_t *qtv, char *buffer, int length) { //forward the stream on to connected clients oproxy_t *prox, *next, *fre; CheckMVDConsistancy(buffer, 0, length); while (qtv->proxies && qtv->proxies->drop) { next = qtv->proxies->next; fre = qtv->proxies; if (fre->file) fclose(fre->file); else closesocket(fre->sock); free(fre); qtv->cluster->numproxies--; qtv->proxies = next; } for (prox = qtv->proxies; prox; prox = prox->next) { while (prox->next && prox->next->drop) { next = prox->next->next; fre = prox->next; if (fre->file) fclose(fre->file); else closesocket(fre->sock); free(fre); qtv->cluster->numproxies--; prox->next = next; } if (prox->flushing) //don't send it if we're trying to empty thier buffer. { if (prox->buffersize == prox->bufferpos) { if (!qtv->parsingconnectiondata) Net_SendConnectionMVD(qtv, prox); //they're up to date, resend the connection info. } else { Net_TryFlushProxyBuffer(qtv->cluster, prox); //try and flush it. continue; } } if (prox->drop) continue; //add the new data Net_ProxySend(qtv->cluster, prox, buffer, length); Net_TryFlushProxyBuffer(qtv->cluster, prox); // Net_TryFlushProxyBuffer(qtv->cluster, prox); // Net_TryFlushProxyBuffer(qtv->cluster, prox); } } 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) { char tempbuf[512]; char *s; char *e; char *colon; int usableversion = 0; int len; qboolean raw; sv_t *qtv; if (pend->drop) { closesocket(pend->sock); free(pend); cluster->numproxies--; return true; } Net_TryFlushProxyBuffer(cluster, pend); if (pend->flushing) { if (pend->bufferpos == pend->buffersize) pend->drop = true; return false; } len = sizeof(pend->inbuffer) - pend->inbuffersize - 1; len = recv(pend->sock, pend->inbuffer+pend->inbuffersize, len, 0); if (len == 0) { pend->drop = true; return false; } if (len < 0) return false; pend->inbuffersize += len; pend->inbuffer[pend->inbuffersize] = '\0'; if (pend->inbuffersize >= 4) { if (strncmp(pend->inbuffer, "QTV\r", 4) && strncmp(pend->inbuffer, "QTV\n", 4) && strncmp(pend->inbuffer, "GET ", 4) && strncmp(pend->inbuffer, "POST ", 5)) { //I have no idea what the smeg you are. pend->drop = true; return false; } } //make sure there's a double \n somewhere for (s = pend->inbuffer; *s; s++) { if (s[0] == '\n' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n'))) break; } if (!*s) return false; //don't have enough yet s+=3; 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 // 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; } } 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)); } pend->flushing = true; return false; } raw = false; qtv = pend->defaultstream; e = pend->inbuffer; s = e; while(*e) { if (*e == '\n' || *e == '\r') { *e = '\0'; colon = strchr(s, ':'); if (*s) { if (!colon) { if (!strcmp(s, "QTV")) { //just a qtv request } else if (!strcmp(s, "SOURCELIST")) { //lists sources that are currently playing s = "QTVSV 1\n"; Net_ProxySend(cluster, pend, s, strlen(s)); if (!cluster->servers) { s = "PERROR: No sources currently available\n"; Net_ProxySend(cluster, pend, s, strlen(s)); } else { for (qtv = cluster->servers; qtv; qtv = qtv->next) { sprintf(tempbuf, "ASOURCE: %i: %15s: %15s\n", qtv->streamid, qtv->server, qtv->hostname); s = tempbuf; Net_ProxySend(cluster, pend, s, strlen(s)); } qtv = NULL; } s = "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } else if (!strcmp(s, "REVERSE")) { //this is actually a server trying to connect to us //start up a new stream } else if (!strcmp(s, "RECEIVE")) { //a client connection request without a source if (cluster->numservers == 1) { //only one stream anyway qtv = cluster->servers; } else { //try and hunt down an explicit stream (rather than a user-recorded one) int numfound = 0; sv_t *suitable; for (qtv = cluster->servers; qtv; qtv = qtv->next) { if (!qtv->disconnectwhennooneiswatching) { suitable = qtv; numfound++; } } if (numfound == 1) qtv = suitable; } if (!qtv) { s = "QTVSV 1\n"; Net_ProxySend(cluster, pend, s, strlen(s)); s = "PERROR: Multiple streams are currently playing\n"; Net_ProxySend(cluster, pend, s, strlen(s)); s = "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } } else if (!strcmp(s, "DEMOLIST")) { //lists sources that are currently playing int i; Cluster_BuildAvailableDemoList(cluster); s = "QTVSV 1\n"; Net_ProxySend(cluster, pend, s, strlen(s)); if (!cluster->availdemoscount) { s = "PERROR: No demos currently available\n"; Net_ProxySend(cluster, pend, s, strlen(s)); } else { for (i = 0; i < cluster->availdemoscount; i++) { sprintf(tempbuf, "ADEMO: %i: %15s\n", cluster->availdemos[i].size, cluster->availdemos[i].name); s = tempbuf; Net_ProxySend(cluster, pend, s, strlen(s)); } qtv = NULL; } s = "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } else if (!strcmp(s, "AUTH")) { //lists the demos available on this proxy //part of the connection process, can be ignored if there's no password } else printf("Unrecognised token in QTV connection request (%s)\n", s); } else { *colon++ = '\0'; if (!strcmp(s, "VERSION")) { switch(atoi(colon)) { case 1: //got a usable version usableversion = 1; break; default: //not recognised. break; } } else if (!strcmp(s, "RAW")) raw = atoi(colon); /*else if (!strcmp(s, "ROUTE")) { //pure rewroute... //is this safe? probably not. s = "QTVSV 1\n" "PERROR: ROUTE command not yet implemented\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } */ else if (!strcmp(s, "SOURCE")) { //connects, creating a new source while (*colon == ' ') colon++; for (s = colon; *s; s++) if (*s < '0' || *s > '9') break; if (*s) qtv = QTV_NewServerConnection(cluster, colon, "", false, true, true, false); else { //numerical source, use a stream id. for (qtv = cluster->servers; qtv; qtv = qtv->next) if (qtv->streamid == atoi(colon)) break; } } else if (!strcmp(s, "DEMO")) { //starts a demo off the server... source does the same thing though... char buf[256]; snprintf(buf, sizeof(buf), "demo:%s", colon); qtv = QTV_NewServerConnection(cluster, buf, "", false, true, true, false); if (!qtv) { s = "QTVSV 1\n" "PERROR: couldn't open demo\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } } else if (!strcmp(s, "AUTH")) { //lists the demos available on this proxy //part of the connection process, can be ignored if there's no password } else printf("Unrecognised token in QTV connection request (%s)\n", s); } } s = e+1; } e++; } if (!pend->flushing) { if (!usableversion) { s = "QTVSV 1\n" "PERROR: Requested protocol version not supported\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } if (!qtv) { s = "QTVSV 1\n" "PERROR: No stream selected\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; } } if (pend->flushing) return false; if (qtv->usequkeworldprotocols) { s = "QTVSV 1\n" "PERROR: This version of QTV is unable to convert QuakeWorld to QTV protocols\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; return false; } if (cluster->maxproxies>=0 && cluster->numproxies >= cluster->maxproxies) { s = "QTVSV 1\n" "TERROR: This QTV has reached it's connection limit\n" "\n"; Net_ProxySend(cluster, pend, s, strlen(s)); pend->flushing = true; return false; } pend->next = qtv->proxies; qtv->proxies = pend; if (!raw) { s = "QTVSV 1\n"; Net_ProxySend(cluster, pend, s, strlen(s)); s = "BEGIN: "; Net_ProxySend(cluster, pend, s, strlen(s)); s = qtv->server; Net_ProxySend(cluster, pend, s, strlen(s)); s = "\n\n"; Net_ProxySend(cluster, pend, s, strlen(s)); } // else if (passwordprotected) //raw mode doesn't support passwords, so reject them // { // pend->flushing = true; // return; // } Net_SendConnectionMVD(qtv, pend); return true; }