Use SOCK_SEQPACKET between processes to ensure EOF receipt. Probably only works on...
[ashd.git] / src / htparser.c
index 0aa0032..1a45ea1 100644 (file)
 #include <stdlib.h>
 #include <unistd.h>
 #include <stdio.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 #include <utils.h>
+#include <mt.h>
+#include <log.h>
+#include <req.h>
+
+#define EV_READ 1
+#define EV_WRITE 2
+
+struct blocker {
+    struct blocker *n, *p;
+    int fd;
+    int ev;
+    struct muth *th;
+};
+
+static struct blocker *blockers;
+
+static int block(int fd, int ev)
+{
+    struct blocker *bl;
+    int rv;
+    
+    omalloc(bl);
+    bl->fd = fd;
+    bl->ev = ev;
+    bl->th = current;
+    bl->n = blockers;
+    if(blockers)
+       blockers->p = bl;
+    blockers = bl;
+    rv = yield();
+    if(bl->n)
+       bl->n->p = bl->p;
+    if(bl->p)
+       bl->p->n = bl->n;
+    if(bl == blockers)
+       blockers = bl->n;
+    return(rv);
+}
+
+static int listensock4(int port)
+{
+    struct sockaddr_in name;
+    int fd;
+    int valbuf;
+    
+    memset(&name, 0, sizeof(name));
+    name.sin_family = AF_INET;
+    name.sin_port = htons(port);
+    if((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
+       return(-1);
+    valbuf = 1;
+    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
+    if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
+       close(fd);
+       return(-1);
+    }
+    if(listen(fd, 16) < 0) {
+       close(fd);
+       return(-1);
+    }
+    return(fd);
+}
+
+static int listensock6(int port)
+{
+    struct sockaddr_in6 name;
+    int fd;
+    int valbuf;
+    
+    memset(&name, 0, sizeof(name));
+    name.sin6_family = AF_INET6;
+    name.sin6_port = htons(port);
+    if((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0)
+       return(-1);
+    valbuf = 1;
+    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &valbuf, sizeof(valbuf));
+    if(bind(fd, (struct sockaddr *)&name, sizeof(name))) {
+       close(fd);
+       return(-1);
+    }
+    if(listen(fd, 16) < 0) {
+       close(fd);
+       return(-1);
+    }
+    return(fd);
+}
+
+static size_t readhead(int fd, struct charbuf *buf)
+{
+    int nl;
+    size_t off;
+    
+    int get1(void)
+    {
+       int ret;
+       
+       while(!(off < buf->d)) {
+           sizebuf(*buf, buf->d + 1024);
+           ret = recv(fd, buf->b + buf->d, buf->s - buf->d, MSG_DONTWAIT);
+           if(ret <= 0) {
+               if((ret < 0) && (errno == EAGAIN)) {
+                   block(fd, EV_READ);
+                   continue;
+               }
+               return(-1);
+           }
+           buf->d += ret;
+       }
+       return(buf->b[off++]);
+    }
+
+    nl = 0;
+    off = 0;
+    while(1) {
+       switch(get1()) {
+       case '\n':
+           if(nl)
+               return(off);
+           nl = 1;
+           break;
+       case '\r':
+           break;
+       case -1:
+           return(-1);
+       default:
+           nl = 0;
+           break;
+       }
+    }
+}
+
+#define SKIPNL(ptr) ({                         \
+           int __buf__;                        \
+           if(*(ptr) == '\r')                  \
+               *((ptr)++) = 0;                 \
+           if(*(ptr) != '\n') {                \
+               __buf__ = 0;                    \
+           } else {                            \
+               *((ptr)++) = 0;                 \
+               __buf__ = 1;                    \
+           }                                   \
+           __buf__;})
+static struct htreq *parseraw(char *buf)
+{
+    char *p, *p2, *nl;
+    char *method, *url, *ver;
+    struct htreq *req;
+    
+    if((nl = strchr(buf, '\n')) == NULL)
+       return(NULL);
+    if(((p = strchr(buf, ' ')) == NULL) || (p > nl))
+       return(NULL);
+    method = buf;
+    *(p++) = 0;
+    if(((p2 = strchr(p, ' ')) == NULL) || (p2 > nl))
+       return(NULL);
+    url = p;
+    p = p2;
+    *(p++) = 0;
+    if(strncmp(p, "HTTP/", 5))
+       return(NULL);
+    ver = (p += 5);
+    for(; ((*p >= '0') && (*p <= '9')) || (*p == '.'); p++);
+    if(!SKIPNL(p))
+       return(NULL);
+
+    req = mkreq(method, url, ver);
+    while(1) {
+       if(SKIPNL(p)) {
+           if(*p)
+               goto fail;
+           break;
+       }
+       if((nl = strchr(p, '\n')) == NULL)
+           goto fail;
+       if(((p2 = strchr(p, ':')) == NULL) || (p2 > nl))
+           goto fail;
+       *(p2++) = 0;
+       for(; (*p2 == ' ') || (*p2 == '\t'); p2++);
+       for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
+       if(!SKIPNL(nl))
+           goto fail;
+       reqappheader(req, p, p2);
+       p = nl;
+    }
+    return(req);
+    
+fail:
+    freereq(req);
+    return(NULL);
+}
+
+static void serve(struct muth *muth, va_list args)
+{
+    vavar(int, fd);
+    struct charbuf buf;
+    struct htreq *req;
+    size_t headoff;
+    
+    bufinit(buf);
+    while(1) {
+       buf.d = 0;
+       if((headoff = readhead(fd, &buf)) < 0)
+           goto out;
+       if((req = parseraw(buf.b)) == NULL)
+           goto out;
+       printf("\"%s\", \"%s\", \"%s\", \"%s\"\n", req->method, req->url, req->ver, getheader(req, "host"));
+       freereq(req);
+    }
+    
+out:
+    buffree(buf);
+    close(fd);
+}
+
+static void listenloop(struct muth *muth, va_list args)
+{
+    vavar(int, ss);
+    int ns;
+    struct sockaddr_storage name;
+    socklen_t namelen;
+    
+    while(1) {
+       namelen = sizeof(name);
+       block(ss, EV_READ);
+       ns = accept(ss, (struct sockaddr *)&name, &namelen);
+       if(ns < 0) {
+           flog(LOG_ERR, "accept: %s", strerror(errno));
+           goto out;
+       }
+       mustart(serve, ns);
+    }
+    
+out:
+    close(ss);
+}
+
+static void ioloop(void)
+{
+    int ret;
+    fd_set rfds, wfds, efds;
+    struct blocker *bl, *nbl;
+    int maxfd;
+    int ev;
+    
+    while(blockers != NULL) {
+       FD_ZERO(&rfds);
+       FD_ZERO(&wfds);
+       FD_ZERO(&efds);
+       maxfd = 0;
+       for(bl = blockers; bl; bl = bl->n) {
+           if(bl->ev & EV_READ)
+               FD_SET(bl->fd, &rfds);
+           if(bl->ev & EV_WRITE)
+               FD_SET(bl->fd, &wfds);
+           FD_SET(bl->fd, &efds);
+           if(bl->fd > maxfd)
+               maxfd = bl->fd;
+       }
+       ret = select(maxfd + 1, &rfds, &wfds, &efds, NULL);
+       if(ret < 0) {
+           if(errno != EINTR) {
+               flog(LOG_CRIT, "ioloop: select errored out: %s", strerror(errno));
+               /* To avoid CPU hogging in case it's bad, which it
+                * probably is. */
+               sleep(1);
+           }
+       }
+       for(bl = blockers; bl; bl = nbl) {
+           nbl = bl->n;
+           ev = 0;
+           if(FD_ISSET(bl->fd, &rfds))
+               ev |= EV_READ;
+           if(FD_ISSET(bl->fd, &wfds))
+               ev |= EV_WRITE;
+           if(FD_ISSET(bl->fd, &efds))
+               ev = -1;
+           if(ev != 0)
+               resume(bl->th, ev);
+       }
+    }
+}
 
 int main(int argc, char **argv)
 {
+    int fd;
+    
+    if((fd = listensock6(8080)) < 0) {
+       flog(LOG_ERR, "could not listen on IPv6: %s", strerror(errno));
+       return(1);
+    }
+    mustart(listenloop, fd);
+    if((fd = listensock4(8080)) < 0) {
+       if(errno != EADDRINUSE) {
+           flog(LOG_ERR, "could not listen on IPv4: %s", strerror(errno));
+           return(1);
+       }
+    } else {
+       mustart(listenloop, fd);
+    }
+    ioloop();
+    return(0);
 }