Seemingly working version of the HTTP library
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 22 Jul 2007 01:57:27 +0000 (03:57 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 22 Jul 2007 01:57:27 +0000 (03:57 +0200)
common/http.c
include/http.h

index 9c34a3b..0338905 100644 (file)
@@ -21,6 +21,9 @@
 #include <stdio.h>
 #include <unistd.h>
 #include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/poll.h>
 
 #include <utils.h>
 #include <http.h>
 
+#if 0
+#define HTDEBUG(fmt...) fprintf(stderr, "httplib: " fmt)
+#else
+#define HTDEBUG(fmt...)
+#endif
+
 #define STATE_SYN 0
 #define STATE_TXREQ 1
+#define STATE_RXRES 2
+#define STATE_RXBODY 3
+#define STATE_RXCHLEN 4
+#define STATE_RXCHUNK 5
+#define STATE_DONE 6
 
 void freeurl(struct hturlinfo *ui)
 {
@@ -66,7 +80,8 @@ struct hturlinfo *parseurl(char *url)
        p = p2;
        if((p2 = strchr(p, '?')) != NULL)
            *(p2++) = 0;
-       ui->path = sstrdup(p);
+       p[-1] = '/';
+       ui->path = sstrdup(p - 1);
     }
     if(p2 == NULL) {
        ui->query = sstrdup("");
@@ -117,6 +132,10 @@ void freehtconn(struct htconn *cn)
        free(cn->outbuf);
     if(cn->inbuf != NULL)
        free(cn->inbuf);
+    if(cn->databuf != NULL)
+       free(cn->databuf);
+    if(cn->resstr != NULL)
+       free(cn->resstr);
     freeurl(cn->url);
     freeaddrinfo(cn->ailist);
     if(cn->fd != -1)
@@ -130,48 +149,327 @@ struct htconn *htconnect(struct hturlinfo *ui)
     
     cn = memset(smalloc(sizeof(*cn)), 0, sizeof(*cn));
     cn->fd = -1;
+    cn->tlen = -1;
     cn->url = dupurl(ui);
     cn->ailist = resolvtcp(ui->host, ui->port);
+    if(htprocess(cn, 0) < 0) {
+       freehtconn(cn);
+       return(NULL);
+    }
     return(cn);
 }
 
-int htpollflags(struct htconn *hc)
+int htpollflags(struct htconn *cn)
 {
     int ret;
     
     ret = POLLIN;
-    if(hc->outbufdata > 0)
+    if((cn->state == STATE_SYN) || (cn->outbufdata > 0))
        ret |= POLLOUT;
     return(ret);
 }
 
-int htprocess(struct htconn *hc)
+static char safechars[128] = {
+ /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,
+};
+
+static void consreq(struct htconn *cn)
 {
-    int ret;
+    char *p;
+    
+    bufcat(cn->outbuf, "GET ", 4);
+    for(p = cn->url->path; *p; p++) {
+       if(!(*p & 0x80) && safechars[(int)*p])
+           addtobuf(cn->outbuf, *p);
+       else
+           bprintf(cn->outbuf, "%%%02X", *p);
+    }
+    if(*(cn->url->query)) {
+       addtobuf(cn->outbuf, '?');
+       for(p = cn->url->path; *p; p++) {
+           if(!(*p & 0x80) && (safechars[(int)*p] || (*p == '&')))
+               addtobuf(cn->outbuf, *p);
+           else
+               bprintf(cn->outbuf, "%%%02X", *p);
+       }
+    }
+    bufcat(cn->outbuf, " HTTP/1.1\r\n", 11);
+    if(cn->url->port != 80)
+       bprintf(cn->outbuf, "Host: %s:%i\r\n", cn->url->host, cn->url->port);
+    else
+       bprintf(cn->outbuf, "Host: %s\r\n", cn->url->host);
+    bprintf(cn->outbuf, "User-Agent: DoldaConnect/%s\r\n", VERSION);
+    bufcat(cn->outbuf, "\r\n", 2);
+}
+
+static void trimcr(char *buf)
+{
+    for(; *buf; buf++);
+    if(*(--buf) == '\r')
+       *buf = 0;
+}
+
+static int parseheaders(struct htconn *cn)
+{
+    char *p, *p2, *p3, *p4;
+    
+    while((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
+       *(p++) = 0;
+       trimcr(cn->inbuf);
+       if(!*(cn->inbuf)) {
+           memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
+           return(1);
+       }
+       if((p2 = strchr(cn->inbuf, ':')) == NULL)
+           goto skip;
+       *(p2++) = 0;
+       for(p3 = cn->inbuf; isspace(*p3); p3++);
+       if(!*p3)
+           goto skip;
+       for(p4 = p2 - 2; isspace(*p4); p4--) {
+       }
+       *(++p4) = 0;
+       for(; isspace(*p2); p2++);
+       for(p4 = p3; *p4; p4++)
+           *p4 = tolower(*p4);
+       newstrpair(p3, p2, &cn->headers);
+       if(!strcmp(p3, "content-length"))
+           cn->tlen = atoi(p2);
+    skip:
+       memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
+    }
+    return(0);
+}
+
+int htprocess(struct htconn *cn, int pollflags)
+{
+    int ret, done;
     socklen_t optlen;
+    char rxbuf[1024];
+    char *p, *p2, *p3;
     
-    if(hc->state == STATE_SYN) {
-       if(hc->fd != -1) {
+    if(cn->state == STATE_SYN) {
+       if(cn->fd != -1) {
            optlen = sizeof(ret);
-           getsockopt(hc->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
+           getsockopt(cn->fd, SOL_SOCKET, SO_ERROR, &ret, &optlen);
            if(ret) {
-               hc->fd = -1;
+               cn->fd = -1;
            } else {
-               hc->state = STATE_TXREQ;
+               consreq(cn);
+               cn->state = STATE_TXREQ;
            }
        }
-       if(hc->fd == -1) {
-           if(hc->curai == NULL)
-               hc->curai = hc->ailist;
+       if(cn->fd == -1) {
+           if(cn->curai == NULL)
+               cn->curai = cn->ailist;
            else
-               hc->curai = hc->curai->ai_next;
-           if(hc->curai == NULL) {
+               cn->curai = cn->curai->ai_next;
+           if(cn->curai == NULL) {
                /* Bleh! Linux and BSD don't share any good
                 * errno for this. */
                errno = ENOENT;
                return(-1);
            }
+           if((cn->fd = socket(cn->curai->ai_family, cn->curai->ai_socktype, cn->curai->ai_protocol)) < 0)
+               return(-1);
+           fcntl(cn->fd, F_SETFL, fcntl(cn->fd, F_GETFL) | O_NONBLOCK);
+           if(connect(cn->fd, cn->curai->ai_addr, cn->curai->ai_addrlen) < 0) {
+               if(errno != EINPROGRESS)
+                   return(-1);
+           } else {
+               consreq(cn);
+               cn->state = STATE_TXREQ;
+           }
        }
     }
-    return(0);
+    if(cn->state == STATE_TXREQ) {
+       HTDEBUG("connected, sending request\n");
+       if(pollflags & POLLIN) {
+           close(cn->fd);
+           cn->fd = -1;
+           return(-1);
+       }
+       if(pollflags & POLLOUT) {
+           if((ret = send(cn->fd, cn->outbuf, cn->outbufdata, MSG_DONTWAIT)) < 0) {
+               if(errno != EAGAIN) {
+                   close(cn->fd);
+                   cn->fd = -1;
+                   return(-1);
+               }
+           } else {
+               memmove(cn->outbuf, cn->outbuf + ret, cn->outbufdata -= ret);
+               if(cn->outbufdata == 0)
+                   cn->state = STATE_RXRES;
+           }
+       }
+    }
+    /*
+     * All further states will do receiving
+     */
+    if(pollflags & POLLIN) {
+       if(cn->fd == -1) {
+           ret = 0;
+       } else {
+           if((ret = recv(cn->fd, rxbuf, sizeof(rxbuf), MSG_DONTWAIT)) < 0) {
+               HTDEBUG("error in recv: %s\n", strerror(errno));
+               if(errno != EAGAIN) {
+                   close(cn->fd);
+                   cn->fd = -1;
+                   return(-1);
+               }
+               return(0);
+           } else if(ret == 0) {
+               HTDEBUG("EOF received\n");
+               close(cn->fd);
+               cn->fd = -1;
+           } else {
+               bufcat(cn->inbuf, rxbuf, ret);
+               HTDEBUG("received %i bytes of raw data, %i bytes in buffer\n", ret, cn->inbufdata);
+           }
+       }
+    }
+    /* We need to loop until all processable data has been processed,
+     * or we won't get called again */
+    do {
+       done = 1;
+       if(cn->state == STATE_RXRES) {
+           if(ret == 0) {
+               if(cn->rescode == 0) {
+                   HTDEBUG("received EOF before response, flaggin EPROTO\n");
+                   errno = EPROTO;
+                   return(-1);
+               }
+               HTDEBUG("EOF after headers, no body\n");
+               cn->state = STATE_DONE;
+           } else {
+               /* Headers shouldn't be this long! */
+               if(cn->inbufdata >= 65536) {
+                   HTDEBUG("got suspiciously long headers, flagging ENOMEM\n");
+                   close(cn->fd);
+                   cn->fd = -1;
+                   errno = ENOMEM;
+                   return(-1);
+               }
+               HTDEBUG("received some header data\n");
+           }
+           if(cn->rescode == 0) {
+               if((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) {
+                   HTDEBUG("received response line\n");
+                   *(p++) = 0;
+                   trimcr(cn->inbuf);
+                   p2 = cn->inbuf;
+                   if((p3 = strchr(p2, ' ')) == NULL) {
+                       close(cn->fd);
+                       cn->fd = -1;
+                       errno = EPROTO;
+                       return(-1);
+                   }
+                   *(p3++) = 0;
+                   if(strncmp(p2, "HTTP/", 5)) {
+                       close(cn->fd);
+                       cn->fd = -1;
+                       errno = EPROTO;
+                       return(-1);
+                   }
+                   p2 = p3;
+                   if((p3 = strchr(p2, ' ')) == NULL) {
+                       close(cn->fd);
+                       cn->fd = -1;
+                       errno = EPROTO;
+                       return(-1);
+                   }
+                   *(p3++) = 0;
+                   cn->rescode = atoi(p2);
+                   if((cn->rescode < 100) || (cn->rescode >= 1000)) {
+                       close(cn->fd);
+                       cn->fd = -1;
+                       errno = EPROTO;
+                       return(-1);
+                   }
+                   cn->resstr = sstrdup(p3);
+                   memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
+                   HTDEBUG("parsed response line (%i, %s)\n", cn->rescode, cn->resstr);
+               }
+           }
+           if(cn->rescode != 0) {
+               HTDEBUG("parsing some headers\n");
+               if(parseheaders(cn)) {
+                   HTDEBUG("all headers received\n");
+                   if(((p = spfind(cn->headers, "transfer-encoding")) != NULL) && !strcasecmp(p, "chunked")) {
+                       HTDEBUG("doing chunky decoding\n");
+                       cn->chl = -1;
+                       cn->state = STATE_RXCHLEN;
+                   } else {
+                       HTDEBUG("receiving normally\n");
+                       cn->state = STATE_RXBODY;
+                   }
+               }
+           }
+       }
+       if(cn->state == STATE_RXBODY) {
+           if(ret == 0) {
+               HTDEBUG("EOF in body, flagging as done\n");
+               cn->state = STATE_DONE;
+           } else {
+               bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
+               HTDEBUG("transferred %i bytes from inbuf to databuf, %i bytes now in databuf\n", cn->inbufdata, cn->databufdata);
+               cn->rxd += cn->inbufdata;
+               cn->inbufdata = 0;
+               if((cn->tlen != -1) && (cn->rxd >= cn->tlen)) {
+                   HTDEBUG("received Content-Length, flagging as done\n");
+                   cn->state = STATE_DONE;
+               }
+           }
+       }
+       if(cn->state == STATE_RXCHLEN) {
+           HTDEBUG("trying to parse chunk length\n");
+           while(((p = memchr(cn->inbuf, '\n', cn->inbufdata)) != NULL) && (cn->chl == -1)) {
+               *(p++) = 0;
+               trimcr(cn->inbuf);
+               HTDEBUG("trimmed chunk line: %s\n", cn->inbuf);
+               if(!*cn->inbuf)
+                   goto skip;
+               cn->chl = strtol(cn->inbuf, NULL, 16);
+               HTDEBUG("parsed chunk length: %i\n", cn->chl);
+           skip:
+               memmove(cn->inbuf, p, cn->inbufdata -= (p - cn->inbuf));
+           }
+           if(cn->chl == 0) {
+               HTDEBUG("zero chunk length, looking for CRLF\n");
+               if((cn->inbuf[0] == '\r') && (cn->inbuf[1] == '\n')) {
+                   HTDEBUG("ending CRLF gotten, flagging as done\n");
+                   cn->state = STATE_DONE;
+               }
+           } else {
+               HTDEBUG("will read chunk\n");
+               cn->state = STATE_RXCHUNK;
+           }
+       }
+       if(cn->state == STATE_RXCHUNK) {
+           if(cn->inbufdata >= cn->chl) {
+               bufcat(cn->databuf, cn->inbuf, cn->chl);
+               memmove(cn->inbuf, cn->inbuf + cn->chl, cn->inbufdata -= cn->chl);
+               HTDEBUG("received final %i bytes of chunk, inbuf %i bytes, databuf %i bytes\n", cn->chl, cn->inbufdata, cn->databufdata);
+               cn->rxd += cn->chl;
+               cn->chl = 0;
+               cn->state = STATE_RXCHLEN;
+               done = 0;
+           } else {
+               bufcat(cn->databuf, cn->inbuf, cn->inbufdata);
+               cn->chl -= cn->inbufdata;
+               cn->rxd += cn->inbufdata;
+               HTDEBUG("received %i bytes of chunk, %i bytes remaining, %i bytes in databuf\n", cn->inbufdata, cn->chl, cn->databufdata);
+               cn->inbufdata = 0;
+           }
+       }
+    } while(!done);
+    return((cn->state == STATE_DONE)?1:0);
 }
index 8223be3..e3cc1d4 100644 (file)
@@ -22,6 +22,8 @@
 
 #include <netdb.h>
 
+#include "utils.h"
+
 struct hturlinfo {
     char *host;
     int port;
@@ -34,10 +36,15 @@ struct htconn {
     int state;
     int fd;
     struct addrinfo *ailist, *curai;
-    char *outbuf, *inbuf;
+    char *outbuf, *inbuf, *databuf;
     size_t outbufsize, outbufdata;
     size_t inbufsize, inbufdata;
+    size_t databufsize, databufdata;
     struct hturlinfo *url;
+    int rescode;
+    char *resstr;
+    struct strpair *headers;
+    ssize_t tlen, rxd, chl;
 };
 
 struct htcookie {
@@ -47,7 +54,9 @@ struct htcookie {
 
 struct hturlinfo *parseurl(char *url);
 void freeurl(struct hturlinfo *ui);
+void freehtconn(struct htconn *cn);
 struct htconn *htconnect(struct hturlinfo *ui);
 int htpollflags(struct htconn *hc);
+int htprocess(struct htconn *hc, int pollflags);
 
 #endif