pass the initial / from uclient core to the proto handler
[project/uclient.git] / uclient-http.c
index bf0b051..9e7505c 100644 (file)
 
 static struct ustream_ssl_ctx *ssl_ctx;
 
+enum auth_type {
+       AUTH_TYPE_UNKNOWN,
+       AUTH_TYPE_NONE,
+       AUTH_TYPE_BASIC,
+};
+
 enum request_type {
        REQ_GET,
        REQ_HEAD,
@@ -44,10 +50,16 @@ struct uclient_http {
        struct ustream_ssl ussl;
 
        bool ssl;
+       bool eof;
+       bool connection_close;
        enum request_type req_type;
        enum http_state state;
 
-       unsigned int send_len;
+       enum auth_type auth_type;
+       char *auth_str;
+
+       long read_chunked;
+       long content_length;
 
        struct blob_buf headers;
        struct blob_buf meta;
@@ -80,17 +92,192 @@ static int uclient_do_connect(struct uclient_http *uh, const char *port)
        return 0;
 }
 
+static void uclient_http_disconnect(struct uclient_http *uh)
+{
+       if (!uh->us)
+               return;
+
+       if (uh->ssl)
+               ustream_free(&uh->ussl.stream);
+       ustream_free(&uh->ufd.stream);
+       close(uh->ufd.fd.fd);
+       uh->us = NULL;
+}
+
+static void uclient_http_free_url_state(struct uclient *cl)
+{
+       struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
+
+       uh->auth_type = AUTH_TYPE_UNKNOWN;
+       free(uh->auth_str);
+       uh->auth_str = NULL;
+       uclient_http_disconnect(uh);
+}
+
 static void uclient_notify_eof(struct uclient_http *uh)
 {
        struct ustream *us = uh->us;
 
-       if (!us->eof && !us->write_error)
+       if (!uh->eof) {
+               if (!us->eof && !us->write_error)
+                       return;
+
+               if (ustream_pending_data(us, false))
+                       return;
+       }
+
+       uclient_backend_set_eof(&uh->uc);
+
+       if (uh->connection_close)
+               uclient_http_disconnect(uh);
+}
+
+static void uclient_http_reset_state(struct uclient_http *uh)
+{
+       uclient_backend_reset_state(&uh->uc);
+       uh->read_chunked = -1;
+       uh->content_length = -1;
+       uh->eof = false;
+       uh->connection_close = false;
+       uh->state = HTTP_STATE_INIT;
+
+       if (uh->auth_type == AUTH_TYPE_UNKNOWN && !uh->uc.url->auth)
+               uh->auth_type = AUTH_TYPE_NONE;
+}
+
+static void uclient_http_init_request(struct uclient_http *uh)
+{
+       uclient_http_reset_state(uh);
+       blob_buf_init(&uh->meta, 0);
+}
+
+static enum auth_type
+uclient_http_update_auth_type(struct uclient_http *uh)
+{
+       if (!uh->auth_str)
+               return AUTH_TYPE_NONE;
+
+       if (!strncasecmp(uh->auth_str, "basic", 5))
+               return AUTH_TYPE_BASIC;
+
+       return AUTH_TYPE_NONE;
+}
+
+static void uclient_http_process_headers(struct uclient_http *uh)
+{
+       enum {
+               HTTP_HDR_TRANSFER_ENCODING,
+               HTTP_HDR_CONNECTION,
+               HTTP_HDR_CONTENT_LENGTH,
+               HTTP_HDR_AUTH,
+               __HTTP_HDR_MAX,
+       };
+       static const struct blobmsg_policy hdr_policy[__HTTP_HDR_MAX] = {
+#define hdr(_name) { .name = _name, .type = BLOBMSG_TYPE_STRING }
+               [HTTP_HDR_TRANSFER_ENCODING] = hdr("transfer-encoding"),
+               [HTTP_HDR_CONNECTION] = hdr("connection"),
+               [HTTP_HDR_CONTENT_LENGTH] = hdr("content-length"),
+               [HTTP_HDR_AUTH] = hdr("www-authenticate"),
+#undef hdr
+       };
+       struct blob_attr *tb[__HTTP_HDR_MAX];
+       struct blob_attr *cur;
+
+       blobmsg_parse(hdr_policy, __HTTP_HDR_MAX, tb, blob_data(uh->meta.head), blob_len(uh->meta.head));
+
+       cur = tb[HTTP_HDR_TRANSFER_ENCODING];
+       if (cur && strstr(blobmsg_data(cur), "chunked"))
+               uh->read_chunked = 0;
+
+       cur = tb[HTTP_HDR_CONNECTION];
+       if (cur && strstr(blobmsg_data(cur), "close"))
+               uh->connection_close = true;
+
+       cur = tb[HTTP_HDR_CONTENT_LENGTH];
+       if (cur)
+               uh->content_length = strtoul(blobmsg_data(cur), NULL, 10);
+
+       cur = tb[HTTP_HDR_AUTH];
+       if (cur) {
+               free(uh->auth_str);
+               uh->auth_str = strdup(blobmsg_data(cur));
+       }
+
+       uh->auth_type = uclient_http_update_auth_type(uh);
+}
+
+static void
+uclient_http_add_auth_header(struct uclient_http *uh)
+{
+       struct uclient_url *url = uh->uc.url;
+       char *auth_buf;
+       int auth_len;
+
+       if (!url->auth)
                return;
 
-       if (ustream_pending_data(us, false))
+       auth_len = strlen(url->auth);
+       if (auth_len > 512)
                return;
 
-       uclient_backend_set_eof(&uh->uc);
+       auth_buf = alloca(base64_len(auth_len) + 1);
+       base64_encode(url->auth, auth_len, auth_buf);
+       ustream_printf(uh->us, "Authorization: Basic %s\r\n", auth_buf);
+}
+
+static void
+uclient_http_send_headers(struct uclient_http *uh)
+{
+       struct uclient_url *url = uh->uc.url;
+       struct blob_attr *cur;
+       enum request_type req_type = uh->req_type;
+       int rem;
+
+       if (uh->state >= HTTP_STATE_HEADERS_SENT)
+               return;
+
+       if (uh->auth_type == AUTH_TYPE_UNKNOWN)
+               req_type = REQ_HEAD;
+
+       ustream_printf(uh->us,
+               "%s %s HTTP/1.1\r\n"
+               "Host: %s\r\n",
+               request_types[req_type],
+               url->location, url->host);
+
+       blobmsg_for_each_attr(cur, uh->headers.head, rem)
+               ustream_printf(uh->us, "%s: %s\n", blobmsg_name(cur), (char *) blobmsg_data(cur));
+
+       if (uh->req_type == REQ_POST)
+               ustream_printf(uh->us, "Transfer-Encoding: chunked\r\n");
+
+       uclient_http_add_auth_header(uh);
+
+       ustream_printf(uh->us, "\r\n");
+}
+
+static void uclient_http_headers_complete(struct uclient_http *uh)
+{
+       enum auth_type auth_type = uh->auth_type;
+
+       uh->state = HTTP_STATE_RECV_DATA;
+       uh->uc.meta = uh->meta.head;
+       uclient_http_process_headers(uh);
+
+       if (auth_type == AUTH_TYPE_UNKNOWN) {
+               uclient_http_init_request(uh);
+               uclient_http_send_headers(uh);
+               uh->state = HTTP_STATE_REQUEST_DONE;
+               return;
+       }
+
+       if (uh->uc.cb->header_done)
+               uh->uc.cb->header_done(&uh->uc);
+
+       if (uh->req_type == REQ_HEAD) {
+               uh->eof = true;
+               uclient_notify_eof(uh);
+       }
 }
 
 static void uclient_parse_http_line(struct uclient_http *uh, char *data)
@@ -104,10 +291,7 @@ static void uclient_parse_http_line(struct uclient_http *uh, char *data)
        }
 
        if (!*data) {
-               uh->state = HTTP_STATE_RECV_DATA;
-               uh->uc.meta = uh->meta.head;
-               if (uh->uc.cb->header_done)
-                       uh->uc.cb->header_done(&uh->uc);
+               uclient_http_headers_complete(uh);
                return;
        }
 
@@ -168,7 +352,7 @@ static void __uclient_notify_read(struct uclient_http *uh)
                        len -= cur_len;
 
                        data = ustream_get_read_buf(uh->us, &len);
-               } while (uh->state < HTTP_STATE_RECV_DATA);
+               } while (data && uh->state < HTTP_STATE_RECV_DATA);
 
                if (!len)
                        return;
@@ -246,29 +430,16 @@ static int uclient_setup_https(struct uclient_http *uh)
        return 0;
 }
 
-static void uclient_http_disconnect(struct uclient_http *uh)
-{
-       uclient_backend_reset_state(&uh->uc);
-
-       if (!uh->us)
-               return;
-
-       if (uh->ssl)
-               ustream_free(&uh->ussl.stream);
-       ustream_free(&uh->ufd.stream);
-       close(uh->ufd.fd.fd);
-       uh->us = NULL;
-}
-
 static int uclient_http_connect(struct uclient *cl)
 {
        struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
 
-       uclient_http_disconnect(uh);
-       blob_buf_init(&uh->meta, 0);
+       uclient_http_init_request(uh);
+
+       if (uh->us)
+               return 0;
 
        uh->ssl = cl->url->prefix == PREFIX_HTTPS;
-       uh->state = HTTP_STATE_INIT;
 
        if (uh->ssl)
                return uclient_setup_https(uh);
@@ -290,7 +461,7 @@ static void uclient_http_free(struct uclient *cl)
 {
        struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
 
-       uclient_http_disconnect(uh);
+       uclient_http_free_url_state(cl);
        blob_buf_free(&uh->headers);
        blob_buf_free(&uh->meta);
        free(uh);
@@ -344,67 +515,6 @@ uclient_http_set_header(struct uclient *cl, const char *name, const char *value)
        return 0;
 }
 
-#define ustream_printf(us, ...) do { \
-       fprintf(stderr, "send: " __VA_ARGS__); \
-       ustream_printf(us, __VA_ARGS__); \
-} while (0)
-
-
-static void
-uclient_http_send_headers(struct uclient_http *uh)
-{
-       struct uclient_url *url = uh->uc.url;
-       struct blob_attr *cur;
-       int rem;
-
-       if (uh->state >= HTTP_STATE_HEADERS_SENT)
-               return;
-
-       ustream_printf(uh->us,
-               "%s /%s HTTP/1.0\r\n"
-               "Host: %s\r\n",
-               request_types[uh->req_type],
-               url->location, url->host);
-
-       blobmsg_for_each_attr(cur, uh->headers.head, rem)
-               ustream_printf(uh->us, "%s: %s\n", blobmsg_name(cur), (char *) blobmsg_data(cur));
-
-       if (url->auth) {
-               int auth_len = strlen(url->auth);
-               char *auth_buf;
-
-               if (auth_len > 512)
-                       return;
-
-               auth_buf = alloca(base64_len(auth_len) + 1);
-               base64_encode(url->auth, auth_len, auth_buf);
-               ustream_printf(uh->us, "Authorization: Basic %s\r\n", auth_buf);
-       }
-
-       if (uh->send_len > 0)
-               ustream_printf(uh->us, "Content-Length: %d", uh->send_len);
-
-       ustream_printf(uh->us, "\r\n");
-}
-
-static int
-uclient_http_set_write_len(struct uclient *cl, unsigned int len)
-{
-       struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
-
-       if (uh->state >= HTTP_STATE_HEADERS_SENT)
-               return -1;
-
-       if (uh->req_type == REQ_GET || uh->req_type == REQ_HEAD) {
-               fprintf(stderr, "Sending data is not supported for %s requests\n",
-                       request_types[uh->req_type]);
-               return -1;
-       }
-
-       uh->send_len = len;
-       return 0;
-}
-
 static int
 uclient_http_send_data(struct uclient *cl, char *buf, unsigned int len)
 {
@@ -415,12 +525,9 @@ uclient_http_send_data(struct uclient *cl, char *buf, unsigned int len)
 
        uclient_http_send_headers(uh);
 
-       if (len > uh->send_len) {
-               fprintf(stderr, "%s: ignoring %d extra data bytes\n", __func__, uh->send_len - len);
-               len = uh->send_len;
-       }
-
+       ustream_printf(uh->us, "%X\r\n", len);
        ustream_write(uh->us, buf, len, false);
+       ustream_printf(uh->us, "\r\n");
 
        return len;
 }
@@ -433,9 +540,6 @@ uclient_http_request_done(struct uclient *cl)
        if (uh->state >= HTTP_STATE_REQUEST_DONE)
                return -1;
 
-       if (uh->send_len > 0)
-               fprintf(stderr, "%s: missing %d POST data bytes\n", __func__, uh->send_len);
-
        uclient_http_send_headers(uh);
        uh->state = HTTP_STATE_REQUEST_DONE;
 
@@ -446,21 +550,66 @@ static int
 uclient_http_read(struct uclient *cl, char *buf, unsigned int len)
 {
        struct uclient_http *uh = container_of(cl, struct uclient_http, uc);
-       int data_len;
-       char *data;
+       int read_len = 0;
+       char *data, *data_end;
 
-       if (uh->state < HTTP_STATE_RECV_DATA)
+       if (uh->state < HTTP_STATE_RECV_DATA || !uh->us)
                return 0;
 
-       data = ustream_get_read_buf(uh->us, &data_len);
-       if (!data || !data_len)
+       data = ustream_get_read_buf(uh->us, &read_len);
+       if (!data || !read_len)
                return 0;
 
-       if (len > data_len)
-               len = data_len;
+       data_end = data + read_len;
+       read_len = 0;
+
+       if (uh->read_chunked == 0) {
+               char *sep;
+
+               if (data[0] == '\r' && data[1] == '\n') {
+                       data += 2;
+                       read_len += 2;
+               }
+
+               sep = strstr(data, "\r\n");
+               if (!sep)
+                       return 0;
+
+               *sep = 0;
+               uh->read_chunked = strtoul(data, NULL, 16);
+
+               read_len += sep + 2 - data;
+               data = sep + 2;
+
+               if (!uh->read_chunked)
+                       uh->eof = true;
+       }
+
+       if (len > data_end - data)
+               len = data_end - data;
+
+       if (uh->read_chunked >= 0) {
+               if (len > uh->read_chunked)
+                       len = uh->read_chunked;
+
+               uh->read_chunked -= len;
+       } else if (uh->content_length >= 0) {
+               if (len > uh->content_length)
+                       len = uh->content_length;
+
+               uh->content_length -= len;
+               if (!uh->content_length)
+                       uh->eof = true;
+       }
+
+       if (len > 0) {
+               read_len += len;
+               memcpy(buf, data, len);
+       }
+
+       if (read_len > 0)
+               ustream_consume(uh->us, read_len);
 
-       memcpy(buf, data, len);
-       ustream_consume(uh->us, len);
        uclient_notify_eof(uh);
 
        return len;
@@ -472,10 +621,9 @@ const struct uclient_backend uclient_backend_http __hidden = {
        .alloc = uclient_http_alloc,
        .free = uclient_http_free,
        .connect = uclient_http_connect,
+       .update_url = uclient_http_free_url_state,
 
        .read = uclient_http_read,
        .write = uclient_http_send_data,
        .request = uclient_http_request_done,
-
-       .set_write_len = uclient_http_set_write_len,
 };