about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--btpd/http_tr_if.c64
-rw-r--r--misc/http_client.c220
-rw-r--r--misc/http_client.h23
3 files changed, 152 insertions, 155 deletions
diff --git a/btpd/http_tr_if.c b/btpd/http_tr_if.c
index 9247b5e..8e9dde2 100644
--- a/btpd/http_tr_if.c
+++ b/btpd/http_tr_if.c
@@ -1,6 +1,7 @@
 #include "btpd.h"
 
 #include <http_client.h>
+#include <iobuf.h>
 
 #define MAX_DOWNLOAD (1 << 18)  // 256kB
 
@@ -9,14 +10,23 @@ static const char *m_tr_events[] = { "started", "stopped", "completed", "" };
 struct http_tr_req {
     struct torrent *tp;
     struct http_req *req;
-    struct evbuffer *buf;
+    struct iobuf buf;
+    struct event rdev;
+    struct event wrev;
+    nameconn_t nc;
+    int sd;
     enum tr_event event;
 };
 
 static void
 http_tr_free(struct http_tr_req *treq)
 {
-    evbuffer_free(treq->buf);
+    if (treq->sd != -1) {
+        btpd_ev_del(&treq->rdev);
+        btpd_ev_del(&treq->wrev);
+        close(treq->sd);
+    }
+    iobuf_free(&treq->buf);
     free(treq);
 }
 
@@ -111,16 +121,16 @@ http_cb(struct http_req *req, struct http_response *res, void *arg)
         http_tr_free(treq);
         break;
     case HTTP_T_DATA:
-        if (treq->buf->off + res->v.data.l > MAX_DOWNLOAD) {
+        if (treq->buf.off + res->v.data.l > MAX_DOWNLOAD) {
             tr_result(treq->tp, TR_RES_FAIL, -1);
             http_tr_cancel(treq);
             break;
         }
-        if (evbuffer_add(treq->buf, res->v.data.p, res->v.data.l) != 0)
+        if (!iobuf_write(&treq->buf, res->v.data.p, res->v.data.l))
             btpd_err("Out of memory.\n");
         break;
     case HTTP_T_DONE:
-        if (parse_reply(treq->tp, treq->buf->buffer, treq->buf->off,
+        if (parse_reply(treq->tp, treq->buf.buf, treq->buf.off,
                 treq->event != TR_EV_STOPPED, &interval) == 0)
             tr_result(treq->tp, TR_RES_OK, interval);
         else
@@ -132,11 +142,47 @@ http_cb(struct http_req *req, struct http_response *res, void *arg)
     }
 }
 
+static void
+sd_wr_cb(int sd, short type, void *arg)
+{
+    struct http_tr_req *treq = arg;
+    if (http_write(treq->req, sd) && http_want_write(treq->req))
+        btpd_ev_add(&treq->wrev, NULL);
+}
+
+static void
+sd_rd_cb(int sd, short type, void *arg)
+{
+    struct http_tr_req *treq = arg;
+    if (http_read(treq->req, sd) && http_want_read(treq->req))
+        btpd_ev_add(&treq->rdev, NULL);
+}
+
+static void
+nc_cb(void *arg, int error, int sd)
+{
+    struct http_tr_req *treq = arg;
+    if (error) {
+        tr_result(treq->tp, TR_RES_FAIL, -1);
+        http_cancel(treq->req);
+        http_tr_free(treq);
+    } else {
+        treq->sd = sd;
+        event_set(&treq->wrev, sd, EV_WRITE, sd_wr_cb, treq);
+        event_set(&treq->rdev, sd, EV_READ, sd_rd_cb, treq);
+        if (http_want_read(treq->req))
+            btpd_ev_add(&treq->rdev, NULL);
+        if (http_want_write(treq->req))
+            btpd_ev_add(&treq->wrev, NULL);
+    }
+}
+
 struct http_tr_req *
 http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl)
 {
     char e_hash[61], e_id[61], ip_arg[INET_ADDRSTRLEN + 4], url[512], qc;
     const uint8_t *peer_id = btpd_get_peer_id();
+    struct http_url *http_url;
 
     qc = (strchr(aurl, '?') == NULL) ? '?' : '&';
 
@@ -166,16 +212,22 @@ http_tr_req(struct torrent *tp, enum tr_event event, const char *aurl)
         free(treq);
         return NULL;
     }
-    if ((treq->buf = evbuffer_new()) == NULL)
+    treq->buf = iobuf_init(4096);
+    if (treq->buf.error)
         btpd_err("Out of memory.\n");
     treq->tp = tp;
     treq->event = event;
+    treq->sd = -1;
+    http_url = http_url_get(treq->req);
+    treq->nc = btpd_name_connect(http_url->host, http_url->port, nc_cb, treq);
     return treq;
 }
 
 void
 http_tr_cancel(struct http_tr_req *treq)
 {
+    if (treq->sd == -1)
+        btpd_name_connect_cancel(treq->nc);
     http_cancel(treq->req);
     http_tr_free(treq);
 }
diff --git a/misc/http_client.c b/misc/http_client.c
index c5d8b04..177dfb6 100644
--- a/misc/http_client.c
+++ b/misc/http_client.c
@@ -1,25 +1,16 @@
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <sys/time.h>
-
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
-#include <event.h>
-#include <evdns.h>
-
+#include "iobuf.h"
 #include "subr.h"
 #include "http_client.h"
 
-#define TIMEOUT (& (struct timeval) { 45, 0 })
-
 struct http_url *
 http_url_parse(const char *url)
 {
@@ -89,13 +80,10 @@ http_url_free(struct http_url *url)
 
 struct http_req {
     enum {
-        HTTP_RESOLVE, HTTP_CONNECT, HTTP_WRITE, HTTP_RECEIVE, HTTP_PARSE
-    } state;
-    enum {
         PS_HEAD, PS_CHUNK_SIZE, PS_CHUNK_DATA, PS_CHUNK_CRLF, PS_ID_DATA
     } pstate;
 
-    int sd;
+    int parsing;
     int cancel;
     int chunked;
     long length;
@@ -104,8 +92,8 @@ struct http_req {
     void *arg;
 
     struct http_url *url;
-    struct evbuffer *buf;
-    struct event ev;
+    struct iobuf rbuf;
+    struct iobuf wbuf;
 };
 
 static void
@@ -113,12 +101,8 @@ http_free(struct http_req *req)
 {
     if (req->url != NULL)
         http_url_free(req->url);
-    if (req->buf != NULL)
-        evbuffer_free(req->buf);
-    if (req->sd > 0) {
-        event_del(&req->ev);
-        close(req->sd);
-    }
+    iobuf_free(&req->rbuf);
+    iobuf_free(&req->wbuf);
     free(req);
 }
 
@@ -235,57 +219,57 @@ again:
     case PS_HEAD:
         if (len == 0)
             goto error;
-        if ((end = evbuffer_find(req->buf, "\r\n\r\n", 4)) != NULL)
+        if ((end = iobuf_find(&req->rbuf, "\r\n\r\n", 4)) != NULL)
             dlen = 4;
-        else if ((end = evbuffer_find(req->buf, "\n\n", 2)) != NULL)
+        else if ((end = iobuf_find(&req->rbuf, "\n\n", 2)) != NULL)
             dlen = 2;
         else {
-            if (req->buf->off < (1 << 15))
+            if (req->rbuf.off < (1 << 15))
                 return 1;
             else
                 goto error;
         }
-        if (evbuffer_add(req->buf, "", 1) != 0)
+        if (!iobuf_write(&req->rbuf, "", 1))
             goto error;
-        req->buf->off--;
-        if (!headers_parse(req, req->buf->buffer, end))
+        req->rbuf.off--;
+        if (!headers_parse(req, req->rbuf.buf, end))
             goto error;
         if (req->cancel)
             goto cancel;
-        evbuffer_drain(req->buf, end - (char *)req->buf->buffer + dlen);
+        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 = evbuffer_find(req->buf, "\n", 1)) == NULL) {
-            if (req->buf->off < 20)
+        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->buf->buffer, &numend, 16);
-        if (req->length < 0 || numend == (char *)req->buf->buffer || errno)
+        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;
-        evbuffer_drain(req->buf, end - (char *)req->buf->buffer + 1);
+        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->buf->off, req->length);
+        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->buf->buffer;
+            res.v.data.p = req->rbuf.buf;
             req->cb(req, &res, req->arg);
             if (req->cancel)
                 goto cancel;
-            evbuffer_drain(req->buf, dlen);
+            iobuf_consumed(&req->rbuf, dlen);
             req->length -= dlen;
             if (req->length == 0) {
                 req->pstate = PS_CHUNK_CRLF;
@@ -297,15 +281,15 @@ again:
         if (len == 0)
             goto error;
         assert(req->length == 0);
-        if (req->buf->off < 2)
+        if (req->rbuf.off < 2)
             return 1;
-        if (req->buf->buffer[0] == '\r' && req->buf->buffer[1] == '\n')
+        if (req->rbuf.buf[0] == '\r' && req->rbuf.buf[1] == '\n')
             dlen = 2;
-        else if (req->buf->buffer[0] == '\n')
+        else if (req->rbuf.buf[0] == '\n')
             dlen = 1;
         else
             goto error;
-        evbuffer_drain(req->buf, dlen);
+        iobuf_consumed(&req->rbuf, dlen);
         req->pstate = PS_CHUNK_SIZE;
         goto again;
     case PS_ID_DATA:
@@ -314,17 +298,17 @@ again:
         else if (len == 0)
             goto error;
         if (req->length < 0)
-            dlen = req->buf->off;
+            dlen = req->rbuf.off;
         else
-            dlen = min(req->buf->off, req->length);
+            dlen = min(req->rbuf.off, req->length);
         if (dlen > 0) {
             res.type = HTTP_T_DATA;
-            res.v.data.p = req->buf->buffer;
+            res.v.data.p = req->rbuf.buf;
             res.v.data.l = dlen;
             req->cb(req, &res, req->arg);
             if (req->cancel)
                 goto cancel;
-            evbuffer_drain(req->buf, dlen);
+            iobuf_consumed(&req->rbuf, dlen);
             if (req->length > 0) {
                 req->length -= dlen;
                 if (req->length == 0)
@@ -346,129 +330,85 @@ cancel:
     return 0;
 }
 
-static void
-http_read_cb(int sd, short type, void *arg)
+struct http_url *
+http_url_get(struct http_req *req)
 {
-    struct http_req *req = arg;
-    if (type == EV_TIMEOUT) {
-        http_error(req);
-        return;
-    }
-    int nr = evbuffer_read(req->buf, sd, 1 << 14);
-    if (nr < 0) {
-        if (nr == EAGAIN)
-            goto more;
-        else {
-            http_error(req);
-            return;
-        }
-    }
-    req->state = HTTP_PARSE;
-    if (!http_parse(req, nr))
-        return;
-    req->state = HTTP_RECEIVE;
-more:
-    if (event_add(&req->ev, TIMEOUT) != 0)
-        http_error(req);
+    return req->url;
 }
 
-static void
-http_write_cb(int sd, short type, void *arg)
+int
+http_want_read(struct http_req *req)
 {
-    struct http_req *req = arg;
-    if (type == EV_TIMEOUT) {
+    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;
-    }
-    int nw = evbuffer_write(req->buf, sd);
-    if (nw == -1) {
-        if (errno == EAGAIN)
-            goto out;
-        else
-            goto error;
+        return 0;
     }
-out:
-    if (req->buf->off != 0) {
-        if (event_add(&req->ev, TIMEOUT) != 0)
-            goto error;
+    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->state = HTTP_RECEIVE;
-        event_set(&req->ev, req->sd, EV_READ, http_read_cb, req);
-        if (event_add(&req->ev, TIMEOUT) != 0)
-            goto error;
+        req->rbuf.off += nr;
+        req->parsing = 1;
+        if (http_parse(req, nr)) {
+            req->parsing = 0;
+            return 1;
+        } else
+            return 0;
     }
-    return;
-error:
-    http_error(req);
-}
-
-static int
-http_connect(struct http_req *req, struct in_addr inaddr)
-{
-    struct sockaddr_in addr;
-    addr.sin_family = AF_INET;
-    addr.sin_port = htons(req->url->port);
-    addr.sin_addr = inaddr;
-    req->state = HTTP_CONNECT;
-    if ((req->sd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
-        goto error;
-    if (set_nonblocking(req->sd) != 0)
-        goto error;
-    if ((connect(req->sd, (struct sockaddr *)&addr, sizeof(addr)) != 0
-            && errno != EINPROGRESS))
-        goto error;
-    event_set(&req->ev, req->sd, EV_WRITE, http_write_cb, req);
-    if (event_add(&req->ev, TIMEOUT) != 0)
-        goto error;
-    return 1;
-error:
-    return 0;
 }
 
-static void
-http_dnscb(int result, char type, int count, int ttl, void *addrs, void *arg)
+int
+http_write(struct http_req *req, int sd)
 {
-    struct http_req *req = arg;
-    if (req->cancel)
-        http_free(req);
-    else if (result == DNS_ERR_NONE && type == DNS_IPv4_A && count > 0) {
-        int addri = rand_between(0, count - 1);
-        struct in_addr inaddr;
-        bcopy(addrs + addri * sizeof(struct in_addr), &inaddr,
-            sizeof(struct in_addr));
-        if (!http_connect(req, inaddr))
-            http_error(req);
-    } else
+    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 in_addr addr;
     struct http_req *req = calloc(1, sizeof(*req));
     if (req == NULL)
         return 0;
-    req->sd = -1;
     req->cb = cb;
     req->arg = arg;
     req->url = http_url_parse(url);
     if (req->url == NULL)
         goto error;
-    if ((req->buf = evbuffer_new()) == NULL)
-        goto error;
-    if (evbuffer_add_printf(req->buf, "GET %s HTTP/1.1\r\n"
+    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) == -1)
-        goto error;
-    if (inet_aton(req->url->host, &addr) == 1) {
-        if (!http_connect(req, addr))
-            goto error;
-    } else if (evdns_resolve_ipv4(req->url->host, 0, http_dnscb, req) != 0)
+            "\r\n", req->url->uri, req->url->host, req->url->port, hdrs))
         goto error;
     if (out != NULL)
         *out = req;
@@ -481,7 +421,7 @@ error:
 void
 http_cancel(struct http_req *req)
 {
-    if (req->state == HTTP_RESOLVE || req->state == HTTP_PARSE)
+    if (req->parsing)
         req->cancel = 1;
     else
         http_free(req);
diff --git a/misc/http_client.h b/misc/http_client.h
index e6a11af..0adc7da 100644
--- a/misc/http_client.h
+++ b/misc/http_client.h
@@ -1,6 +1,15 @@
 #ifndef BTPD_HTTP_CLIENT_H
 #define BTPD_HTTP_CLIENT_H
 
+struct http_url {
+    char *host;
+    char *uri;
+    uint16_t port;
+};
+
+struct http_url *http_url_parse(const char *url);
+void http_url_free(struct http_url *url);
+
 struct http_response {
     enum {
         HTTP_T_ERR, HTTP_T_CODE, HTTP_T_HEADER, HTTP_T_DATA, HTTP_T_DONE
@@ -19,20 +28,16 @@ struct http_response {
     } v;
 };
 
-struct http_url {
-    char *host;
-    char *uri;
-    uint16_t port;
-};
-
 struct http_req;
-
 typedef void (*http_cb_t)(struct http_req *, struct http_response *, void *);
 
-struct http_url *http_url_parse(const char *url);
-void http_url_free(struct http_url *url);
 int http_get(struct http_req **out, const char *url, const char *hdrs,
     http_cb_t cb, void *arg);
 void http_cancel(struct http_req *req);
+struct http_url *http_url_get(struct http_req *req);
+int http_want_read(struct http_req *req);
+int http_want_write(struct http_req *req);
+int http_read(struct http_req *req, int sd);
+int http_write(struct http_req *req, int sd);
 
 #endif