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)