#include "quakedef.h" #ifdef WEBSERVER #include "iweb.h" #include "netinc.h" //FIXME: Before any admins use this for any serious usage, make the server send bits of file slowly. static qboolean httpserverinitied = false; qboolean httpserverfailed = false; static int httpserversocket; typedef enum {HTTP_WAITINGFORREQUEST,HTTP_SENDING} http_mode_t; qboolean HTTP_ServerInit(int port) { struct sockaddr_in address; unsigned long _true = true; int i; if ((httpserversocket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { IWebPrintf ("HTTP_ServerInit: socket: %s\n", strerror(qerrno)); httpserverfailed = true; return false; } if (ioctlsocket (httpserversocket, FIONBIO, &_true) == -1) { IWebPrintf ("HTTP_ServerInit: ioctl FIONBIO: %s\n", strerror(qerrno)); httpserverfailed = true; return false; } address.sin_family = AF_INET; //check for interface binding option if ((i = COM_CheckParm("-ip")) != 0 && i < com_argc) { address.sin_addr.s_addr = inet_addr(com_argv[i+1]); Con_TPrintf(TL_NETBINDINTERFACE, inet_ntoa(address.sin_addr)); } else address.sin_addr.s_addr = INADDR_ANY; if (port == PORT_ANY) address.sin_port = 0; else address.sin_port = htons((short)port); if( bind (httpserversocket, (void *)&address, sizeof(address)) == -1) { closesocket(httpserversocket); IWebPrintf("HTTP_ServerInit: failed to bind to socket\n"); httpserverfailed = true; return false; } listen(httpserversocket, 3); httpserverinitied = true; httpserverfailed = false; IWebPrintf("HTTP server is running\n"); return true; } void HTTP_ServerShutdown(void) { closesocket(httpserversocket); IWebPrintf("HTTP server closed\n"); httpserverinitied = false; } typedef struct HTTP_active_connections_s { SOCKET datasock; vfsfile_t *file; struct HTTP_active_connections_s *next; http_mode_t mode; qboolean modeswitched; qboolean closeaftertransaction; qboolean close; qboolean acceptgzip; char *inbuffer; unsigned int inbuffersize; unsigned int inbufferused; char *outbuffer; unsigned int outbuffersize; unsigned int outbufferused; } HTTP_active_connections_t; static HTTP_active_connections_t *HTTP_ServerConnections; static int httpconnectioncount; static void ExpandInBuffer(HTTP_active_connections_t *cl, unsigned int quant, qboolean fixedsize) { unsigned int newsize; if (fixedsize) newsize = quant; else newsize = cl->inbuffersize+quant; if (newsize <= cl->inbuffersize) return; cl->inbuffer = IWebRealloc(cl->inbuffer, newsize); cl->inbuffersize = newsize; } static void ExpandOutBuffer(HTTP_active_connections_t *cl, unsigned int quant, qboolean fixedsize) { unsigned int newsize; if (fixedsize) newsize = quant; else newsize = cl->outbuffersize+quant; if (newsize <= cl->outbuffersize) return; cl->outbuffer = IWebRealloc(cl->outbuffer, newsize); cl->outbuffersize = newsize; } void HTTP_RunExisting (void) { char *content; char *msg, *nl; char buf2[2560]; //short lived temp buffer. char resource[2560]; char mode[80]; qboolean hostspecified; unsigned int contentlen; int HTTPmarkup; //version int localerrno; HTTP_active_connections_t **link, *cl; link = &HTTP_ServerConnections; for (link = &HTTP_ServerConnections; *link;) { int ammount, wanted; cl = *link; if (cl->close) { *link = cl->next; closesocket(cl->datasock); cl->datasock = INVALID_SOCKET; if (cl->inbuffer) IWebFree(cl->inbuffer); if (cl->outbuffer) IWebFree(cl->outbuffer); if (cl->file) VFS_CLOSE(cl->file); IWebFree(cl); httpconnectioncount--; continue; } link = &(*link)->next; switch(cl->mode) { case HTTP_WAITINGFORREQUEST: if (cl->outbufferused) Sys_Error("Persistant connection was waiting for input with unsent output"); ammount = cl->inbuffersize-1 - cl->inbufferused; if (ammount < 128) { if (cl->inbuffersize>128*1024) { cl->close = true; //that's just taking the piss. continue; } ExpandInBuffer(cl, 1500, false); ammount = cl->inbuffersize-1 - cl->inbufferused; } if (cl->modeswitched) { ammount = 0; } else { //we can't try and recv 0 bytes as we use an expanding buffer ammount = recv(cl->datasock, cl->inbuffer+cl->inbufferused, ammount, 0); if (ammount < 0) { if (qerrno != EWOULDBLOCK) //they closed on us. Assume end. { cl->close = true; } continue; } if (ammount == 0) { cl->close = true; continue; } } cl->modeswitched = false; cl->inbufferused += ammount; cl->inbuffer[cl->inbufferused] = '\0'; content = NULL; msg = cl->inbuffer; nl = strchr(msg, '\n'); if (!nl) { cont: continue; //we need more... MORE!!! MORE I TELL YOU!!!! } //FIXME: COM_ParseOut recognises // comments, which is not correct. msg = COM_ParseOut(msg, mode, sizeof(mode)); msg = COM_ParseOut(msg, resource, sizeof(resource)); if (!*resource) { cl->close = true; //even if they forgot to specify a resource, we didn't find an HTTP so we have no option but to close. continue; } hostspecified = false; if (!strnicmp(resource, "http://", 7)) { //groan... 1.1 compliance requires parsing this correctly, without the client ever specifiying it. char *slash; //we don't do multiple hosts. hostspecified=true; slash = strchr(resource+7, '/'); if (!slash) strcpy(resource, "/"); else memmove(resource, slash, strlen(slash+1)); //just get rid of the http:// stuff. } if (!strcmp(resource, "/")) strcpy(resource, "/index.html"); cl->acceptgzip = false; msg = COM_ParseOut(msg, buf2, sizeof(buf2)); contentlen = 0; if (!strnicmp(buf2, "HTTP/", 5)) { if (!strncmp(buf2, "HTTP/1.1", 8)) HTTPmarkup = 3; else if (!strncmp(buf2, "HTTP/1", 6)) HTTPmarkup = 2; else { HTTPmarkup = 1; //0.9... lamer. cl->closeaftertransaction = true; } //expect X lines containing options. //then a blank line. Don't continue till we have that. msg = nl+1; while (1) { if (*msg == '\r') msg++; if (*msg == '\n') { msg++; break; //that was our blank line. } while(*msg == ' ') msg++; if (!strnicmp(msg, "Host: ", 6)) //parse needed header fields hostspecified = true; else if (!strnicmp(msg, "Content-Length: ", 16)) //parse needed header fields contentlen = strtoul(msg+16, NULL, 0); else if (!strnicmp(msg, "Accept-Encoding:", 16)) //parse needed header fields { char *e; msg += 16; while(*msg) { if (*msg == '\n') break; while (*msg == ' ' || *msg == '\t') msg++; if (*msg == ',') { msg++; continue; } e = msg; while(*e) { if (*e == ',' || *e == '\r' || *e == '\n') break; e++; } while(e > msg && (e[-1] == ' ' || e[-1] == '\t')) e--; if (e-msg == 4 && !strncmp(msg, "gzip", 4)) cl->acceptgzip = true; while(*msg && *msg != '\n' && *msg != ',') msg++; } } else if (!strnicmp(msg, "Transfer-Encoding: ", 18)) //parse needed header fields { cl->closeaftertransaction = true; goto notimplemented; } else if (!strnicmp(msg, "Connection: close", 17)) cl->closeaftertransaction = true; while(*msg != '\n') { if (!*msg) { goto cont; } msg++; } msg++; } } else { HTTPmarkup = 0; //strimmed... totally... cl->closeaftertransaction = true; //don't bother running to nl. } if (cl->inbufferused-(msg-cl->inbuffer) < contentlen) continue; cl->modeswitched = true; if (contentlen) { content = IWebMalloc(contentlen+1); memcpy(content, msg, contentlen+1); } memmove(cl->inbuffer, cl->inbuffer+(msg-cl->inbuffer+contentlen), cl->inbufferused-(msg-cl->inbuffer+contentlen)); cl->inbufferused -= msg-cl->inbuffer+contentlen; if (HTTPmarkup == 3 && !hostspecified) //1.1 requires the host to be specified... we ca,just ignore it as we're not routing or imitating two servers. (for complience we need to encourage the client to send - does nothing for compatability or anything, just compliance to spec. not always the same thing) { msg = "HTTP/1.1 400 Bad Request\r\n" /*"Content-Type: application/octet-stream\r\n"*/ "Content-Length: 69\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n" "400 Bad Request\r\nYour client failed to provide the host header line"; ammount = strlen(msg); ExpandOutBuffer(cl, ammount, true); memcpy(cl->outbuffer, msg, ammount); cl->outbufferused = ammount; cl->mode = HTTP_SENDING; } else if (!stricmp(mode, "GET") || !stricmp(mode, "HEAD") || !stricmp(mode, "POST")) { qboolean gzipped = false; if (*resource != '/') { resource[0] = '/'; resource[1] = 0; //I'm lazy, they need to comply } IWebPrintf("Download request for \"%s\"\n", resource+1); if (!strnicmp(mode, "P", 1)) //when stuff is posted, data is provided. Give an error message if we couldn't do anything with that data. cl->file = IWebGenerateFile(resource+1, content, contentlen); else { cl->file = NULL; if (SV_AllowDownload(resource+1)) { char nbuf[MAX_OSPATH]; if (cl->acceptgzip && strlen(resource+1) < sizeof(nbuf)-4) { sprintf(nbuf, "%s.gz", resource+1); cl->file = FS_OpenVFS(nbuf, "rb", FS_GAME); } if (cl->file) gzipped = true; else cl->file = FS_OpenVFS(resource+1, "rb", FS_GAME); } if (!cl->file) { cl->file = IWebGenerateFile(resource+1, content, contentlen); } } if (!cl->file) { IWebPrintf("Download rejected\n"); if (HTTPmarkup >= 3) msg = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n" "404 Bad address"; else if (HTTPmarkup == 2) msg = "HTTP/1.0 404 Not Found\r\n" "Content-Type: text/plain\r\n" "Content-Length: 15\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n" "404 Bad address"; else if (HTTPmarkup) msg = "HTTP/0.9 404 Not Found\r\n" "\r\n" "404 Bad address"; else msg = "