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,
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;
};
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)
}
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;
}
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;
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);
{
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);
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)
{
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;
.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,