X-Git-Url: http://git.archive.openwrt.org/?p=project%2Fuclient.git;a=blobdiff_plain;f=uclient-http.c;h=9e7505c84745ccdd51f5463e4a38170d46ee1c9a;hp=8ff2e0ae20fde3e03e30049e37de87495c8279be;hb=5f2e60972941c1ca1a6fa572b39e18cbc58137d9;hpb=9099aff296c1fdc4ba49908018b46f913fe7d9e7 diff --git a/uclient-http.c b/uclient-http.c index 8ff2e0a..9e7505c 100644 --- a/uclient-http.c +++ b/uclient-http.c @@ -13,6 +13,12 @@ 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,9 +50,17 @@ struct uclient_http { struct ustream_ssl ussl; bool ssl; + bool eof; + bool connection_close; enum request_type req_type; enum http_state state; + enum auth_type auth_type; + char *auth_str; + + long read_chunked; + long content_length; + struct blob_buf headers; struct blob_buf meta; }; @@ -78,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) @@ -102,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; } @@ -166,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; @@ -244,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); @@ -288,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); @@ -342,50 +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.1\r\n" - "Host: %s\r\n" - "Connection: close\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->req_type == REQ_POST) - ustream_printf(uh->us, "Transfer-Encoding: chunked\r\n"); - - ustream_printf(uh->us, "\r\n"); -} - static int uclient_http_send_data(struct uclient *cl, char *buf, unsigned int len) { @@ -421,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; @@ -447,6 +621,7 @@ 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,