- #include <assert.h>
- #include <ctype.h>
- #include <errno.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <strings.h>
- #include <unistd.h>
-
- #include "iobuf.h"
- #include "subr.h"
- #include "http_client.h"
-
- struct http_url *
- http_url_parse(const char *url)
- {
- size_t ulen = strlen(url);
- if (strncmp(url, "http://", 7) != 0)
- return NULL;
- const char *cur, *at, *uri = NULL, *uri_e = NULL;
- const char *host = NULL, *host_e = NULL;
- const char *port = NULL, *port_e = NULL;
- at = strchr(url + 7, '@');
- uri = strchr(url + 7, '/');
- cur = strchr(url + 7, '?');
- if (cur != NULL && (uri == NULL || cur < uri))
- uri = cur;
- if (uri == NULL)
- uri = url + ulen;
- if (at != NULL && at < uri)
- host = at + 1;
- else
- host = url + 7;
- cur = host;
- while (cur < uri && *cur != ':')
- cur++;
- host_e = cur;
- if (host_e == host)
- return NULL;
- if (*cur == ':') {
- cur++;
- port = cur;
- while (cur < uri && *cur >= '0' && *cur <= '9')
- cur++;
- if (cur == port || cur != uri)
- return NULL;
- port_e = cur;
- }
- while (*cur != '\0')
- cur++;
- uri_e = cur;
- struct http_url *res =
- malloc(sizeof(*res) + host_e - host + 1 + uri_e - uri + 2);
- if (res == NULL)
- return NULL;
- if (port != NULL)
- sscanf(port, "%hu", &res->port);
- else
- res->port = 80;
- res->host = (char *)(res + 1);
- res->uri = res->host + (host_e - host + 1);
- bcopy(host, res->host, host_e - host);
- res->host[host_e - host] = '\0';
- if (*uri != '/') {
- res->uri[0] = '/';
- bcopy(uri, res->uri + 1, uri_e - uri);
- res->uri[uri_e - uri + 1] = '\0';
- } else {
- bcopy(uri, res->uri, uri_e - uri);
- res->uri[uri_e - uri] = '\0';
- }
- return res;
- }
-
- void
- http_url_free(struct http_url *url)
- {
- free(url);
- }
-
- struct http_req {
- enum {
- PS_HEAD, PS_CHUNK_SIZE, PS_CHUNK_DATA, PS_CHUNK_CRLF, PS_ID_DATA
- } pstate;
-
- int parsing;
- int cancel;
- int chunked;
- long length;
-
- http_cb_t cb;
- void *arg;
-
- struct http_url *url;
- struct iobuf rbuf;
- struct iobuf wbuf;
- };
-
- static void
- http_free(struct http_req *req)
- {
- if (req->url != NULL)
- http_url_free(req->url);
- iobuf_free(&req->rbuf);
- iobuf_free(&req->wbuf);
- free(req);
- }
-
- static void
- http_error(struct http_req *req)
- {
- struct http_response res;
- res.type = HTTP_T_ERR;
- res.v.error = 1;
- req->cb(req, &res, req->arg);
- http_free(req);
- }
-
- static char *
- strnl(char *str, int *nlsize)
- {
- char *nl = strchr(str, '\n');
- if (nl != NULL && nl > str && *(nl - 1) == '\r') {
- *nlsize = 2;
- return nl - 1;
- } else {
- *nlsize = 1;
- return nl;
- }
- }
-
- static int
- headers_parse(struct http_req *req, char *buf, char *end)
- {
- int code, majv, minv, nlsize;
- char *cur, *nl;
- char name[128], value[872];
- struct http_response res;
-
- req->chunked = 0;
- req->length = -1;
-
- if (sscanf(buf, "HTTP/%d.%d %d", &majv, &minv, &code) != 3)
- return 0;
- res.type = HTTP_T_CODE;
- res.v.code = code;
- req->cb(req, &res, req->arg);
- if (req->cancel)
- return 1;
-
- cur = strchr(buf, '\n') + 1;
- nl = strnl(cur, &nlsize);
- while (cur < end) {
- int i;
- char *colon = strchr(cur, ':');
- if (colon == NULL || colon > nl)
- return 0;
- snprintf(name, sizeof(name), "%.*s", (int)(colon - cur), cur);
-
- cur = colon + 1;
- i = 0;
- val_loop:
- while (isblank(*cur))
- cur++;
- while (cur < nl) {
- if (i < sizeof(value) - 1) {
- value[i] = *cur;
- i++;
- }
- cur++;
- }
- cur += nlsize;
- nl = strnl(cur, &nlsize);
- if (isblank(*cur)) {
- if (i < sizeof(value) - 1) {
- value[i] = ' ';
- i++;
- }
- cur++;
- goto val_loop;
- }
- value[i] = '\0';
- for (i--; i >= 0 && isblank(value[i]); i--)
- value[i] = '\0';
-
- res.type = HTTP_T_HEADER;
- res.v.header.n = name;
- res.v.header.v = value;
- req->cb(req, &res, req->arg);
- if (req->cancel)
- return 1;
- if ((!req->chunked
- && strcasecmp("Transfer-Encoding", name) == 0
- && strcasecmp("chunked", value) == 0))
- req->chunked = 1;
- if ((!req->chunked && req->length == -1
- && strcasecmp("Content-Length", name) == 0)) {
- errno = 0;
- req->length = strtol(value, NULL, 10);
- if (errno)
- req->length = -1;
- }
- }
- if (req->chunked)
- req->pstate = PS_CHUNK_SIZE;
- else
- req->pstate = PS_ID_DATA;
- return 1;
- }
-
- static int
- http_parse(struct http_req *req, int len)
- {
- char *end, *numend;
- size_t dlen;
- struct http_response res;
- again:
- switch (req->pstate) {
- case PS_HEAD:
- if (len == 0)
- goto error;
- if ((end = iobuf_find(&req->rbuf, "\r\n\r\n", 4)) != NULL)
- dlen = 4;
- else if ((end = iobuf_find(&req->rbuf, "\n\n", 2)) != NULL)
- dlen = 2;
- else {
- if (req->rbuf.off < (1 << 15))
- return 1;
- else
- goto error;
- }
- if (!iobuf_write(&req->rbuf, "", 1))
- goto error;
- req->rbuf.off--;
- if (!headers_parse(req, req->rbuf.buf, end))
- goto error;
- if (req->cancel)
- goto cancel;
- iobuf_consumed(&req->rbuf, end - (char *)req->rbuf.buf + dlen);
- goto again;
- case PS_CHUNK_SIZE:
- assert(req->chunked);
- if (len == 0)
- goto error;
- if ((end = iobuf_find(&req->rbuf, "\n", 1)) == NULL) {
- if (req->rbuf.off < 20)
- return 1;
- else
- goto error;
- }
- errno = 0;
- req->length = strtol(req->rbuf.buf, &numend, 16);
- if (req->length < 0 || numend == (char *)req->rbuf.buf || errno)
- goto error;
- if (req->length == 0)
- goto done;
- iobuf_consumed(&req->rbuf, end - (char *)req->rbuf.buf + 1);
- req->pstate = PS_CHUNK_DATA;
- goto again;
- case PS_CHUNK_DATA:
- if (len == 0)
- goto error;
- assert(req->length > 0);
- dlen = min(req->rbuf.off, req->length);
- if (dlen > 0) {
- res.type = HTTP_T_DATA;
- res.v.data.l = dlen;
- res.v.data.p = req->rbuf.buf;
- req->cb(req, &res, req->arg);
- if (req->cancel)
- goto cancel;
- iobuf_consumed(&req->rbuf, dlen);
- req->length -= dlen;
- if (req->length == 0) {
- req->pstate = PS_CHUNK_CRLF;
- goto again;
- }
- }
- return 1;
- case PS_CHUNK_CRLF:
- if (len == 0)
- goto error;
- assert(req->length == 0);
- if (req->rbuf.off < 2)
- return 1;
- if (req->rbuf.buf[0] == '\r' && req->rbuf.buf[1] == '\n')
- dlen = 2;
- else if (req->rbuf.buf[0] == '\n')
- dlen = 1;
- else
- goto error;
- iobuf_consumed(&req->rbuf, dlen);
- req->pstate = PS_CHUNK_SIZE;
- goto again;
- case PS_ID_DATA:
- if (len == 0 && req->length < 0)
- goto done;
- else if (len == 0)
- goto error;
- if (req->length < 0)
- dlen = req->rbuf.off;
- else
- dlen = min(req->rbuf.off, req->length);
- if (dlen > 0) {
- res.type = HTTP_T_DATA;
- res.v.data.p = req->rbuf.buf;
- res.v.data.l = dlen;
- req->cb(req, &res, req->arg);
- if (req->cancel)
- goto cancel;
- iobuf_consumed(&req->rbuf, dlen);
- if (req->length > 0) {
- req->length -= dlen;
- if (req->length == 0)
- goto done;
- }
- }
- return 1;
- default:
- abort();
- }
- error:
- http_error(req);
- return 0;
- done:
- res.type = HTTP_T_DONE;
- req->cb(req, &res, req->arg);
- cancel:
- http_free(req);
- return 0;
- }
-
- struct http_url *
- http_url_get(struct http_req *req)
- {
- return req->url;
- }
-
- int
- http_want_read(struct http_req *req)
- {
- return 1;
- }
-
- int
- http_want_write(struct http_req *req)
- {
- return req->wbuf.off > 0;
- }
-
- int
- http_read(struct http_req *req, int sd)
- {
- if (!iobuf_accommodate(&req->rbuf, 4096)) {
- http_error(req);
- return 0;
- }
- ssize_t nr = read(sd, req->rbuf.buf + req->rbuf.off, 4096);
- if (nr < 0 && errno == EAGAIN)
- return 1;
- else if (nr < 0) {
- http_error(req);
- return 0;
- } else {
- req->rbuf.off += nr;
- req->parsing = 1;
- if (http_parse(req, nr)) {
- req->parsing = 0;
- return 1;
- } else
- return 0;
- }
- }
-
- int
- http_write(struct http_req *req, int sd)
- {
- assert(req->wbuf.off > 0);
- ssize_t nw =
- write(sd, req->wbuf.buf, req->wbuf.off);
- if (nw < 0 && errno == EAGAIN)
- return 1;
- else if (nw < 0) {
- http_error(req);
- return 0;
- } else {
- iobuf_consumed(&req->wbuf, nw);
- return 1;
- }
- }
-
- int
- http_get(struct http_req **out, const char *url, const char *hdrs,
- http_cb_t cb, void *arg)
- {
- struct http_req *req = calloc(1, sizeof(*req));
- if (req == NULL)
- return 0;
- req->cb = cb;
- req->arg = arg;
- req->url = http_url_parse(url);
- if (req->url == NULL)
- goto error;
- req->rbuf = iobuf_init(4096);
- req->wbuf = iobuf_init(1024);
- if (!iobuf_print(&req->wbuf, "GET %s HTTP/1.1\r\n"
- "Host: %s:%hu\r\n"
- "Accept-Encoding:\r\n"
- "Connection: close\r\n"
- "%s"
- "\r\n", req->url->uri, req->url->host, req->url->port, hdrs))
- goto error;
- if (out != NULL)
- *out = req;
- return 1;
- error:
- http_free(req);
- return 0;
- }
-
- void
- http_cancel(struct http_req *req)
- {
- if (req->parsing)
- req->cancel = 1;
- else
- http_free(req);
- }
|