From: Fredrik Tolf Date: Sun, 22 Jul 2007 01:57:27 +0000 (+0200) Subject: Seemingly working version of the HTTP library X-Git-Tag: 1.0~97 X-Git-Url: http://git.dolda2000.com/gitweb/?p=doldaconnect.git;a=commitdiff_plain;h=2b6703ac37a2c0f17923535d68fdf0ac2debedea Seemingly working version of the HTTP library --- diff --git a/common/http.c b/common/http.c index 9c34a3b..0338905 100644 --- a/common/http.c +++ b/common/http.c @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include #include #include @@ -30,8 +33,19 @@ #include #include +#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); } diff --git a/include/http.h b/include/http.h index 8223be3..e3cc1d4 100644 --- a/include/http.h +++ b/include/http.h @@ -22,6 +22,8 @@ #include +#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