Added basic support for parsing requests.
[ashd.git] / src / htparser.c
index 0aa0032..a8705fe 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 char *slowreadhead(int fd)
+{
+    int ret;
+    struct charbuf buf;
+    int nl;
+    char last;
+    
+    bufinit(buf);
+    nl = 0;
+    while(1) {
+       sizebuf(buf, buf.d + 1);
+       ret = recv(fd, buf.b + buf.d, 1, MSG_DONTWAIT);
+       if(ret <= 0) {
+           if((ret < 0) && (errno == EAGAIN)) {
+               block(fd, EV_READ);
+               continue;
+           }
+           goto err;
+       }
+       last = buf.b[buf.d++];
+       if(last == '\n') {
+           if(nl)
+               break;
+           nl = 1;
+       } else if(last == '\r') {
+       } else {
+           nl = 0;
+       }
+    }
+    bufadd(buf, 0);
+    return(buf.b);
+    
+err:
+    buffree(buf);
+    return(NULL);
+}
+
+#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)
+               return(NULL);
+           break;
+       }
+       if((nl = strchr(p, '\n')) == NULL)
+           return(NULL);
+       if(((p2 = strchr(p, ':')) == NULL) || (p2 > nl))
+           return(NULL);
+       *(p2++) = 0;
+       for(; (*p2 == ' ') || (*p2 == '\t'); p2++);
+       for(nl = p2; (*nl != '\r') && (*nl != '\n'); nl++);
+       if(!SKIPNL(nl))
+           return(NULL);
+       reqappheader(req, p, p2);
+       p = nl;
+    }
+    return(req);
+}
+
+static void serve(struct muth *muth, va_list args)
+{
+    vavar(int, fd);
+    char *hb;
+    struct htreq *req;
+    
+    hb = NULL;
+    while(1) {
+       if((hb = slowreadhead(fd)) == NULL)
+           goto out;
+       if((req = parseraw(hb)) == NULL)
+           goto out;
+       free(hb);
+       hb = NULL;
+       printf("\"%s\", \"%s\", \"%s\"\n", req->method, req->url, req->ver);
+       freereq(req);
+    }
+    
+out:
+    if(hb != NULL)
+       free(hb);
+    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);
 }