diff --git a/engine/http/httpserver.c b/engine/http/httpserver.c index 70ffb4896..ca2844cdb 100644 --- a/engine/http/httpserver.c +++ b/engine/http/httpserver.c @@ -126,8 +126,36 @@ void checknatpmp(int port) typedef enum {HTTP_WAITINGFORREQUEST,HTTP_SENDING} http_mode_t; +#ifndef _WIN32 +static qboolean HTTP_DoAccepts(int epfd); +struct http_epoll_ctx +{ + struct epollctx_s pub; + int epollfd; +}; +static void HTTP_ServerEPolled(struct epollctx_s *pubctx, unsigned int ev) +{ + struct http_epoll_ctx *ctx = (struct http_epoll_ctx*)pubctx; + if (!HTTP_DoAccepts(ctx->epollfd)) + { + int e = neterrno(); + switch(e) + { + /*case NET_EWOULDBLOCK: + break; + case NET_ECONNABORTED: + case NET_ECONNRESET: + Con_TPrintf ("Connection lost or aborted\n"); + break;*/ + default: + IWebPrintf ("NET_GetPacket: %s\n", strerror(e)); + break; + } + } +} +#endif -qboolean HTTP_ServerInit(int port) +qboolean HTTP_ServerInit(int epfd, int port) { struct sockaddr_qstorage address; unsigned long _true = true; @@ -173,7 +201,7 @@ qboolean HTTP_ServerInit(int port) return false; } - if( bind (httpserversocket, (void *)&address, sizeof(address)) == -1) + if (bind (httpserversocket, (void *)&address, sizeof(address)) == -1) { closesocket(httpserversocket); IWebPrintf("HTTP_ServerInit: failed to bind to socket\n"); @@ -187,6 +215,19 @@ qboolean HTTP_ServerInit(int port) httpserverfailed = false; httpserverport = port; +#ifndef _WIN32 + if (epfd >= 0) + { + static struct http_epoll_ctx ctx; + struct epoll_event ev; + ctx.epollfd = epfd; + ctx.pub.Polled = HTTP_ServerEPolled; + ev.events = EPOLLIN; + ev.data.ptr = &ctx; + epoll_ctl(epfd, EPOLL_CTL_ADD, httpserversocket, &ev); + } +#endif + IWebPrintf("HTTP server is running\n"); return true; } @@ -200,6 +241,10 @@ void HTTP_ServerShutdown(void) } typedef struct HTTP_active_connections_s { +#ifndef _WIN32 + struct epollctx_s pub; + int epfd; +#endif SOCKET datasock; char peername[256]; vfsfile_t *file; @@ -208,7 +253,6 @@ typedef struct HTTP_active_connections_s { http_mode_t mode; qboolean modeswitched; qboolean closeaftertransaction; - char *closereason; qboolean acceptgzip; char *inbuffer; @@ -249,7 +293,7 @@ static void ExpandOutBuffer(HTTP_active_connections_t *cl, unsigned int quant, q cl->outbuffersize = newsize; } -void HTTP_RunExisting (void) +const char *HTTP_RunClient (HTTP_active_connections_t *cl) { char *content; char *msg, *nl; @@ -263,18 +307,426 @@ void HTTP_RunExisting (void) int HTTPmarkup; //version int localerrno; + int ammount, wanted; + + 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) + return "headers larger than 128kb"; //that's just taking the piss. + + 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) + { + int e = neterrno(); + if (e == NET_EWOULDBLOCK) //they closed on us. Assume end. + return NULL; + return "recv error"; + } + if (ammount == 0) + return "peer closed connection"; + } + cl->modeswitched = false; + + cl->inbufferused += ammount; + cl->inbuffer[cl->inbufferused] = '\0'; + + content = NULL; + msg = cl->inbuffer; + nl = strchr(msg, '\n'); + if (!nl) + return NULL; //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) + return "not http"; //even if they forgot to specify a resource, we didn't find an HTTP so we have no option but to close. + + host[0] = '?'; + host[1] = 0; + + 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 + { + int hlen = slash-(resource+7); + if (hlen > sizeof(host)-1) + hlen = sizeof(host)-1; + memcpy(host, resource+7, hlen); + host[hlen] = 0; + 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; + cl->closeaftertransaction = false; + } + else if (!strncmp(buf2, "HTTP/1", 6)) + { + HTTPmarkup = 2; + cl->closeaftertransaction = true; + } + 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 + { + int l = 0; + msg += 6; + while (*msg == ' ' || *msg == '\t') + msg++; + while (*msg != '\r' && *msg != '\n' && l < sizeof(host)-1) + host[l++] = *msg++; + host[l] = 0; + 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; + else if (!strnicmp(msg, "Connection: Keep-Alive", 22)) + cl->closeaftertransaction = false; + + while(*msg != '\n') + { + if (!*msg) + { + if (msg == cl->inbuffer+cl->inbufferused) + return NULL; + else + return "Unexpected null byte"; + } + msg++; + } + msg++; + } + } + else + { + HTTPmarkup = 0; //strimmed... totally... + cl->closeaftertransaction = true; + //don't bother running to nl. + } + + if (cl->inbufferused-(msg-cl->inbuffer) < contentlen) + return NULL; + + 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"; + + IWebPrintf("%s: no host specified\n", cl->peername); + + 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("%s: Download request for \"http://%s/%s\"\n", cl->peername, host, 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 + { + char filename[MAX_OSPATH], *q; + cl->file = NULL; + Q_strncpyz(filename, resource+1, sizeof(filename)); + q = strchr(filename, '?'); + if (q) *q = 0; + + if (SV_AllowDownload(filename)) + { + char nbuf[MAX_OSPATH]; + + if (cl->acceptgzip && strlen(filename) < sizeof(nbuf)-4) + { + Q_strncpyz(nbuf, filename, sizeof(nbuf)); + Q_strncatz(nbuf, ".gz", sizeof(nbuf)); + cl->file = FS_OpenVFS(nbuf, "rb", FS_GAME); + } + if (cl->file) + gzipped = true; + else + cl->file = FS_OpenVFS(filename, "rb", FS_GAME); + } + + if (!cl->file) + { + cl->file = IWebGenerateFile(resource+1, content, contentlen); + } + } + if (!cl->file) + { + IWebPrintf("%s: 404 - not found\n", cl->peername); + + 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 = "404 Not Found404 Not Found
The specified file could not be found on the server"; + + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + else + { + //fixme: add connection: keep-alive or whatever so that ie3 is happy... + if (HTTPmarkup>=3) + sprintf(resource, "HTTP/1.1 200 OK\r\n" "%s%s" "Connection: %s\r\n" "Content-Length: %i\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n", strstr(resource, ".htm")?"Content-Type: text/html\r\n":"", gzipped?"Content-Encoding: gzip\r\nCache-Control: public, max-age=86400\r\n":"", cl->closeaftertransaction?"close":"keep-alive", (int)VFS_GETLEN(cl->file)); + else if (HTTPmarkup==2) + sprintf(resource, "HTTP/1.0 200 OK\r\n" "%s%s" "Connection: %s\r\n" "Content-Length: %i\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n", strstr(resource, ".htm")?"Content-Type: text/html\r\n":"", gzipped?"Content-Encoding: gzip\r\nCache-Control: public, max-age=86400\r\n":"", cl->closeaftertransaction?"close":"keep-alive", (int)VFS_GETLEN(cl->file)); + else if (HTTPmarkup) + sprintf(resource, "HTTP/0.9 200 OK\r\n\r\n"); + else + strcpy(resource, ""); + msg = resource; + + if (*mode == 'H' || *mode == 'h') + { + + VFS_CLOSE(cl->file); + cl->file = NULL; + } + + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + } + //PUT/POST must support chunked transfer encoding for 1.1 compliance. +/* else if (!stricmp(mode, "PUT")) //put is replacement of a resource. (file uploads) + { + } +*/ + else + { +notimplemented: + if (HTTPmarkup >= 3) + msg = "HTTP/1.1 501 Not Implemented\r\n\r\n"; + else if (HTTPmarkup == 2) + msg = "HTTP/1.0 501 Not Implemented\r\n\r\n"; + else if (HTTPmarkup) + msg = "HTTP/0.9 501 Not Implemented\r\n\r\n"; + else + { + msg = NULL; + return "unsupported http version"; + } + IWebPrintf("%s: 501 - not implemented\n", cl->peername); + + if (msg) + { + ammount = strlen(msg); + ExpandOutBuffer(cl, ammount, true); + memcpy(cl->outbuffer, msg, ammount); + cl->outbufferused = ammount; + cl->mode = HTTP_SENDING; + } + } + + if (content) + IWebFree(content); + break; + + case HTTP_SENDING: + if (cl->outbufferused < 8192) + { + if (cl->file) + { + ExpandOutBuffer(cl, 32768, true); + wanted = cl->outbuffersize - cl->outbufferused; + ammount = VFS_READ(cl->file, cl->outbuffer+cl->outbufferused, wanted); + + if (!ammount) + { + VFS_CLOSE(cl->file); + cl->file = NULL; + + IWebPrintf("%s: Download complete\n", cl->peername); + } + else + cl->outbufferused+=ammount; + } + } + + ammount = send(cl->datasock, cl->outbuffer, cl->outbufferused, 0); + + if (ammount == -1) + { + localerrno = neterrno(); + if (localerrno != NET_EWOULDBLOCK) + return "some error when sending"; + } + else if (ammount||!cl->outbufferused) + { + memcpy(cl->outbuffer, cl->outbuffer+ammount, cl->outbufferused-ammount); + cl->outbufferused -= ammount; + if (!cl->outbufferused && !cl->file) + { + cl->modeswitched = true; + cl->mode = HTTP_WAITINGFORREQUEST; + if (cl->closeaftertransaction) + return "file sent"; + } + } + else + return "peer prematurely closed connection"; + break; + +/* case HTTP_RECEIVING: + sent = recv(cl->datasock, resource, ammount, 0); + if (sent == -1) + { + if (qerrno != EWOULDBLOCK) //they closed on us. Assume end. + { + VFS_CLOSE(cl->file); + cl->file = NULL; + cl->close = true; + continue; + } + } + if (sent != 0) + IWebFWrite(resource, 1, sent, cl->file); + break;*/ + default: + return "Unknown state"; + } + return NULL; +} + +void HTTP_RunExisting (void) +{ + const char *err; HTTP_active_connections_t **link, *cl; link = &HTTP_ServerConnections; for (link = &HTTP_ServerConnections; *link;) { - int ammount, wanted; - cl = *link; - if (cl->closereason) + err = HTTP_RunClient(cl); + if (err) { - IWebPrintf("%s: Closing connection: %s\n", cl->peername, cl->closereason); + IWebPrintf("%s: Closing connection: %s\n", cl->peername, err); *link = cl->next; closesocket(cl->datasock); @@ -289,439 +741,107 @@ void HTTP_RunExisting (void) httpconnectioncount--; continue; } - link = &(*link)->next; + } +} +#ifndef _WIN32 +static void HTTP_ClientEPolled(struct epollctx_s *pubctx, unsigned int ev) +{ + HTTP_active_connections_t *cl = (HTTP_active_connections_t *)pubctx; + const char *err = HTTP_RunClient(cl); + + if (err) + { + IWebPrintf("%s: Closing connection: %s\n", cl->peername, err); + + //should be automatic thanks to the closesocket, but do it just in case. + epoll_ctl(cl->epfd, EPOLL_CTL_DEL, cl->datasock, NULL); + + 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--; + } + else + { + struct epoll_event ev; 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->closereason = "headers larger than 128kb"; //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) - { - int e = neterrno(); - if (e != NET_EWOULDBLOCK) //they closed on us. Assume end. - { - cl->closereason = "recv error"; - } - continue; - } - if (ammount == 0) - { - cl->closereason = "peer closed connection"; - 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->closereason = "not http"; //even if they forgot to specify a resource, we didn't find an HTTP so we have no option but to close. - continue; - } - - host[0] = '?'; - host[1] = 0; - - 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 - { - int hlen = slash-(resource+7); - if (hlen > sizeof(host)-1) - hlen = sizeof(host)-1; - memcpy(host, resource+7, hlen); - host[hlen] = 0; - 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; - cl->closeaftertransaction = false; - } - else if (!strncmp(buf2, "HTTP/1", 6)) - { - HTTPmarkup = 2; - cl->closeaftertransaction = true; - } - 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 - { - int l = 0; - msg += 6; - while (*msg == ' ' || *msg == '\t') - msg++; - while (*msg != '\r' && *msg != '\n' && l < sizeof(host)-1) - host[l++] = *msg++; - host[l] = 0; - 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; - else if (!strnicmp(msg, "Connection: Keep-Alive", 22)) - cl->closeaftertransaction = false; - - 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"; - - IWebPrintf("%s: no host specified\n", cl->peername); - - 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("%s: Download request for \"http://%s/%s\"\n", cl->peername, host, 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 - { - char filename[MAX_OSPATH], *q; - cl->file = NULL; - Q_strncpyz(filename, resource+1, sizeof(filename)); - q = strchr(filename, '?'); - if (q) *q = 0; - - if (SV_AllowDownload(filename)) - { - char nbuf[MAX_OSPATH]; - - if (cl->acceptgzip && strlen(filename) < sizeof(nbuf)-4) - { - Q_strncpyz(nbuf, filename, sizeof(nbuf)); - Q_strncatz(nbuf, ".gz", sizeof(nbuf)); - cl->file = FS_OpenVFS(nbuf, "rb", FS_GAME); - } - if (cl->file) - gzipped = true; - else - cl->file = FS_OpenVFS(filename, "rb", FS_GAME); - } - - if (!cl->file) - { - cl->file = IWebGenerateFile(resource+1, content, contentlen); - } - } - if (!cl->file) - { - IWebPrintf("%s: 404 - not found\n", cl->peername); - - 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 = "404 Not Found404 Not Found
The specified file could not be found on the server"; - - ammount = strlen(msg); - ExpandOutBuffer(cl, ammount, true); - memcpy(cl->outbuffer, msg, ammount); - cl->outbufferused = ammount; - cl->mode = HTTP_SENDING; - } - else - { - //fixme: add connection: keep-alive or whatever so that ie3 is happy... - if (HTTPmarkup>=3) - sprintf(resource, "HTTP/1.1 200 OK\r\n" "%s%s" "Connection: %s\r\n" "Content-Length: %i\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n", strstr(resource, ".htm")?"Content-Type: text/html\r\n":"", gzipped?"Content-Encoding: gzip\r\nCache-Control: public, max-age=86400\r\n":"", cl->closeaftertransaction?"close":"keep-alive", (int)VFS_GETLEN(cl->file)); - else if (HTTPmarkup==2) - sprintf(resource, "HTTP/1.0 200 OK\r\n" "%s%s" "Connection: %s\r\n" "Content-Length: %i\r\n" "Server: "FULLENGINENAME"/0\r\n" "\r\n", strstr(resource, ".htm")?"Content-Type: text/html\r\n":"", gzipped?"Content-Encoding: gzip\r\nCache-Control: public, max-age=86400\r\n":"", cl->closeaftertransaction?"close":"keep-alive", (int)VFS_GETLEN(cl->file)); - else if (HTTPmarkup) - sprintf(resource, "HTTP/0.9 200 OK\r\n\r\n"); - else - strcpy(resource, ""); - msg = resource; - - if (*mode == 'H' || *mode == 'h') - { - - VFS_CLOSE(cl->file); - cl->file = NULL; - } - - ammount = strlen(msg); - ExpandOutBuffer(cl, ammount, true); - memcpy(cl->outbuffer, msg, ammount); - cl->outbufferused = ammount; - cl->mode = HTTP_SENDING; - } - } - //PUT/POST must support chunked transfer encoding for 1.1 compliance. -/* else if (!stricmp(mode, "PUT")) //put is replacement of a resource. (file uploads) - { - } -*/ - else - { -notimplemented: - if (HTTPmarkup >= 3) - msg = "HTTP/1.1 501 Not Implemented\r\n\r\n"; - else if (HTTPmarkup == 2) - msg = "HTTP/1.0 501 Not Implemented\r\n\r\n"; - else if (HTTPmarkup) - msg = "HTTP/0.9 501 Not Implemented\r\n\r\n"; - else - { - msg = NULL; - cl->closereason = "unsupported http version"; - } - IWebPrintf("%s: 501 - not implemented\n", cl->peername); - - if (msg) - { - ammount = strlen(msg); - ExpandOutBuffer(cl, ammount, true); - memcpy(cl->outbuffer, msg, ammount); - cl->outbufferused = ammount; - cl->mode = HTTP_SENDING; - } - } - - if (content) - IWebFree(content); + ev.events = EPOLLIN; break; - case HTTP_SENDING: - if (cl->outbufferused < 8192) - { - if (cl->file) - { - ExpandOutBuffer(cl, 32768, true); - wanted = cl->outbuffersize - cl->outbufferused; - ammount = VFS_READ(cl->file, cl->outbuffer+cl->outbufferused, wanted); - - if (!ammount) - { - VFS_CLOSE(cl->file); - cl->file = NULL; - - IWebPrintf("%s: Download complete\n", cl->peername); - } - else - cl->outbufferused+=ammount; - } - } - - ammount = send(cl->datasock, cl->outbuffer, cl->outbufferused, 0); - - if (ammount == -1) - { - localerrno = neterrno(); - if (localerrno != NET_EWOULDBLOCK) - { - cl->closereason = "some error when sending"; - } - } - else if (ammount||!cl->outbufferused) - { - memcpy(cl->outbuffer, cl->outbuffer+ammount, cl->outbufferused-ammount); - cl->outbufferused -= ammount; - if (!cl->outbufferused && !cl->file) - { - cl->modeswitched = true; - cl->mode = HTTP_WAITINGFORREQUEST; - if (cl->closeaftertransaction) - cl->closereason = "file sent"; - } - } - else - cl->closereason = "peer prematurely closed connection"; + ev.events = EPOLLOUT; + break; + default: //don't know, spam it. + ev.events = EPOLLIN|EPOLLOUT; break; - - /* case HTTP_RECEIVING: - sent = recv(cl->datasock, resource, ammount, 0); - if (sent == -1) - { - if (qerrno != EWOULDBLOCK) //they closed on us. Assume end. - { - VFS_CLOSE(cl->file); - cl->file = NULL; - cl->close = true; - continue; - } - } - if (sent != 0) - IWebFWrite(resource, 1, sent, cl->file); - break;*/ } + ev.data.ptr = &cl->pub; + epoll_ctl(cl->epfd, EPOLL_CTL_ADD, cl->datasock, &ev); } } +#endif + +static qboolean HTTP_DoAccepts(int epfd) +{ + struct sockaddr_qstorage from; + int fromlen = sizeof(from); + HTTP_active_connections_t *cl; + int _true = true; + int clientsock = accept(httpserversocket, (struct sockaddr *)&from, &fromlen); + + if (clientsock < 0) + return false; + + if (ioctlsocket (clientsock, FIONBIO, (u_long *)&_true) == -1) + { + IWebPrintf ("HTTP_ServerInit: ioctl FIONBIO: %s\n", strerror(neterrno())); + closesocket(clientsock); + return false; + } + + cl = IWebMalloc(sizeof(HTTP_active_connections_t)); + NET_SockadrToString(cl->peername, sizeof(cl->peername), &from, sizeof(from)); + IWebPrintf("%s: New http connection\n", cl->peername); + + cl->datasock = clientsock; + +#ifndef _WIN32 + if (epfd >= 0) + { + struct epoll_event ev; + cl->pub.Polled = HTTP_ClientEPolled; + cl->epfd = epfd; + ev.events = EPOLLIN|EPOLLOUT; + ev.data.ptr = &cl->pub; + epoll_ctl(epfd, EPOLL_CTL_ADD, clientsock, &ev); + } + else +#endif + { + cl->next = HTTP_ServerConnections; + HTTP_ServerConnections = cl; + } + httpconnectioncount++; + return true; +} qboolean HTTP_ServerPoll(qboolean httpserverwanted, int portnum) //loop while true { - struct sockaddr_qstorage from; - int fromlen; - int clientsock; - int _true = true; - - HTTP_active_connections_t *cl; - if (httpserverport != portnum && httpserverinitied) HTTP_ServerShutdown(); if (!httpserverinitied) { if (httpserverwanted) - return HTTP_ServerInit(portnum); + return HTTP_ServerInit(-1, portnum); return false; } else if (!httpserverwanted) @@ -735,10 +855,7 @@ qboolean HTTP_ServerPoll(qboolean httpserverwanted, int portnum) //loop while tr if (httpconnectioncount>32) return false; - fromlen = sizeof(from); - clientsock = accept(httpserversocket, (struct sockaddr *)&from, &fromlen); - - if (clientsock == -1) + if (!HTTP_DoAccepts(-1)) { int e = neterrno(); if (e == NET_EWOULDBLOCK) @@ -757,24 +874,6 @@ qboolean HTTP_ServerPoll(qboolean httpserverwanted, int portnum) //loop while tr IWebPrintf ("NET_GetPacket: %s\n", strerror(e)); return false; } - - if (ioctlsocket (clientsock, FIONBIO, (u_long *)&_true) == -1) - { - IWebPrintf ("HTTP_ServerInit: ioctl FIONBIO: %s\n", strerror(neterrno())); - closesocket(clientsock); - return false; - } - - cl = IWebMalloc(sizeof(HTTP_active_connections_t)); - NET_SockadrToString(cl->peername, sizeof(cl->peername), &from, sizeof(from)); - IWebPrintf("%s: New http connection\n", cl->peername); - - cl->datasock = clientsock; - - cl->next = HTTP_ServerConnections; - HTTP_ServerConnections = cl; - httpconnectioncount++; - return true; } diff --git a/engine/http/iweb.h b/engine/http/iweb.h index 34efb4c02..b66937f0d 100644 --- a/engine/http/iweb.h +++ b/engine/http/iweb.h @@ -62,7 +62,8 @@ char *Q_strcpyline(char *out, const char *in, int maxlen); //server tick/control functions iwboolean FTP_ServerRun(iwboolean ftpserverwanted, int port); -qboolean HTTP_ServerPoll(qboolean httpserverwanted, int port); + +qboolean HTTP_ServerInit(int epfd, int port); //server interface called from main server routines. void IWebInit(void); diff --git a/engine/http/iwebiface.c b/engine/http/iwebiface.c index 170f2d843..776d7e86e 100644 --- a/engine/http/iwebiface.c +++ b/engine/http/iwebiface.c @@ -149,6 +149,132 @@ int COM_CheckParm(const char *parm) #include #endif +#ifndef _WIN32 +struct stun_ctx +{ + struct epollctx_s pub; + int udpsock; + int reportport; + int id[3]; +}; +struct stunheader_s +{ + unsigned short msgtype; + unsigned short msglen; + unsigned int magiccookie; + unsigned int transactid[3]; +}; +static void StunResponse(struct epollctx_s *pubctx, unsigned int ev) +{ + struct stun_ctx *ctx = (struct stun_ctx*)pubctx; + unsigned char buf[8192]; + int respsize = recvfrom(ctx->udpsock, buf, sizeof(buf), 0, NULL, NULL); + int offset; + struct stunheader_s *h = (struct stunheader_s*)buf; + if (h->transactid[0] != ctx->id[0] || + h->transactid[1] != ctx->id[1] || + h->transactid[2] != ctx->id[2]) + return; //someone trying to spoof? + + if (((buf[0]<<8)|buf[1]) == 0x0101) + { + unsigned short attr; + unsigned short sz; + offset = sizeof(struct stunheader_s); + while (offset+4 < respsize) + { + attr = (buf[offset+0]<<8)|buf[offset+1]; + sz = (buf[offset+2]<<8)|buf[offset+3]; + offset+= 4; + + if (offset + sz > respsize) + break; //corrupt. + if ((attr == 0x1 || attr == 0x20) && sz >= 4) + { + unsigned short type = (buf[offset+0]<<8)|buf[offset+1]; + unsigned short port = (buf[offset+2]<<8)|buf[offset+3]; + if (attr == 0x20) + port ^= (buf[4]<<8)|buf[5]; + if (sz == 4+4 && type == 1) + { + printf("Address: %s%i.%i.%i.%i:%u\n", + ctx->reportport?"http://":"", + buf[offset+4]^((attr == 0x20)?buf[4]:0), + buf[offset+5]^((attr == 0x20)?buf[5]:0), + buf[offset+6]^((attr == 0x20)?buf[6]:0), + buf[offset+7]^((attr == 0x20)?buf[7]:0),ctx->reportport?ctx->reportport:port + ); + } + } + offset += sz; + } + } +} +void PrepareStun(int epfd, int reportport) +{ +#if 0 + char *stunserver = "localhost"; + int stunport = 27500; +#elif 1 + char *stunserver = "stun.l.google.com"; + int stunport = 19302; +#else + char *stunserver = "master.frag-net.com"; + int stunport = 27950; +#endif + + SOCKET newsocket; + struct sockaddr_in address; + struct hostent *h; + + struct stunheader_s msg = {htons(1), 0, htonl(0x2112a442), {42,42,42}}; + if (epfd < 0) + return; + + h = gethostbyname(stunserver); + if (!h) + return; + + if (h->h_addrtype != AF_INET) + return; //too many assumptions + + if ((newsocket = socket (h->h_addrtype, SOCK_CLOEXEC|SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET) + return; + + address.sin_family = h->h_addrtype; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = 0; + + if (bind (newsocket, (void *)&address, sizeof(address)) == -1) + return; + + //FIXME: not random enough to avoid hacks. + srand(time(NULL)^*(int*)&msg); + msg.transactid[0] = rand(); + msg.transactid[1] = rand(); + msg.transactid[2] = rand(); + if (epfd >= 0) + { + static struct stun_ctx ctx; + struct epoll_event ev; + ctx.udpsock = newsocket; + ctx.pub.Polled = StunResponse; + ctx.id[0] = msg.transactid[0]; + ctx.id[1] = msg.transactid[1]; + ctx.id[2] = msg.transactid[2]; + ctx.reportport = reportport; + ev.events = EPOLLIN; + ev.data.ptr = &ctx; + epoll_ctl(epfd, EPOLL_CTL_ADD, newsocket, &ev); + } + + msg.msglen = htons(sizeof(msg)-20); + memcpy(&address.sin_addr, h->h_addr, h->h_length); + address.sin_port = htons(stunport); + sendto(newsocket, &msg, sizeof(msg), 0, (struct sockaddr*)&address, sizeof(address)); +} +#endif + char *authedusername; char *autheduserpassword; int lport_min, lport_max; @@ -163,6 +289,7 @@ int main(int argc, char **argv) WSADATA pointlesscrap; WSAStartup(2, &pointlesscrap); #else + int ep = epoll_create1(0); signal(SIGPIPE, SIG_IGN); //so we don't crash out if a peer closes the socket half way through. #endif @@ -240,6 +367,7 @@ int main(int argc, char **argv) else printf("Server is read only\n"); +#ifdef _WIN32 while(1) { if (ftpport) @@ -252,6 +380,22 @@ int main(int argc, char **argv) usleep(10000); #endif } +#else + + while (!HTTP_ServerInit(ep, httpport)) + sleep(5); + PrepareStun(ep, httpport); + for (;;) + { + struct epoll_event events[1]; + int e, me = epoll_wait(ep, events, countof(events), -1); + for (e = 0; e < me; e++) + { + struct epollctx_s *ctx = events[e].data.ptr; + ctx->Polled(ctx, events[e].events); + } + } +#endif } int IWebGetSafeListeningPort(void)