+
+ if (uh->connection_close)
+ uclient_http_request_disconnect(&uh->uc);
+}
+
+static void uclient_http_reset_state(struct uclient_http *uh)
+{
+ uh->seq++;
+ uclient_backend_reset_state(&uh->uc);
+ uh->read_chunked = -1;
+ uh->content_length = -1;
+ uh->eof = false;
+ uh->disconnect = 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)
+{
+ uh->seq++;
+ 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;
+
+ if (!strncasecmp(uh->auth_str, "digest", 6))
+ return AUTH_TYPE_DIGEST;
+
+ 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 bool uclient_request_supports_body(enum request_type req_type)
+{
+ switch (req_type) {
+ case REQ_POST:
+ case REQ_PUT:
+ case REQ_DELETE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int
+uclient_http_add_auth_basic(struct uclient_http *uh)
+{
+ struct uclient_url *url = uh->uc.url;
+ int auth_len = strlen(url->auth);
+ char *auth_buf;
+
+ if (auth_len > 512)
+ return -EINVAL;
+
+ auth_buf = alloca(base64_len(auth_len) + 1);
+ if (!auth_buf)
+ return -ENOMEM;
+
+ base64_encode(url->auth, auth_len, auth_buf);
+ ustream_printf(uh->us, "Authorization: Basic %s\r\n", auth_buf);
+
+ return 0;
+}
+
+static char *digest_unquote_sep(char **str)
+{
+ char *cur = *str + 1;
+ char *start = cur;
+ char *out;
+
+ if (**str != '"')
+ return NULL;
+
+ out = cur;
+ while (1) {
+ if (!*cur)
+ return NULL;
+
+ if (*cur == '"') {
+ cur++;
+ break;
+ }
+
+ if (*cur == '\\')
+ cur++;
+
+ *(out++) = *(cur++);
+ }
+
+ if (*cur == ',')
+ cur++;
+
+ *out = 0;
+ *str = cur;
+
+ return start;
+}
+
+static char *digest_sep(char **str)
+{
+ char *cur, *next;
+
+ cur = *str;
+ next = strchr(*str, ',');
+ if (next) {
+ *str = next + 1;
+ *next = 0;
+ } else {
+ *str += strlen(*str);
+ }
+
+ return cur;
+}
+
+static bool strmatch(char **str, const char *prefix)
+{
+ int len = strlen(prefix);
+
+ if (strncmp(*str, prefix, len) != 0 || (*str)[len] != '=')
+ return false;
+
+ *str += len + 1;
+ return true;
+}
+
+static void
+get_cnonce(char *dest)
+{
+ uint32_t val = 0;
+ FILE *f;
+ size_t n;
+
+ f = fopen("/dev/urandom", "r");
+ if (f) {
+ n = fread(&val, sizeof(val), 1, f);
+ fclose(f);
+ if (n != 1)
+ return;
+ }
+
+ bin_to_hex(dest, &val, sizeof(val));
+}
+
+static void add_field(char **buf, int *ofs, int *len, const char *name, const char *val)
+{
+ int available = *len - *ofs;
+ int required;
+ const char *next;
+ char *cur;
+
+ if (*len && !*buf)
+ return;
+
+ required = strlen(name) + 4 + strlen(val) * 2;
+ if (required > available)
+ *len += required - available + 64;
+
+ *buf = realloc(*buf, *len);
+ if (!*buf)
+ return;
+
+ cur = *buf + *ofs;
+ cur += sprintf(cur, ", %s=\"", name);
+
+ while ((next = strchr(val, '"'))) {
+ if (next > val) {
+ memcpy(cur, val, next - val);
+ cur += next - val;
+ }
+
+ cur += sprintf(cur, "\\\"");
+ val = next + 1;
+ }
+
+ cur += sprintf(cur, "%s\"", val);
+ *ofs = cur - *buf;
+}
+
+static int
+uclient_http_add_auth_digest(struct uclient_http *uh)
+{
+ struct uclient_url *url = uh->uc.url;
+ const char *realm = NULL, *opaque = NULL;
+ const char *user, *password;
+ char *buf, *next;
+ int len, ofs;
+ int err = 0;
+
+ char cnonce_str[9];
+ char nc_str[9];
+ char ahash[33];
+ char hash[33];
+
+ struct http_digest_data data = {
+ .nc = nc_str,
+ .cnonce = cnonce_str,
+ .auth_hash = ahash,
+ };
+
+ len = strlen(uh->auth_str) + 1;
+ if (len > 512) {
+ err = -EINVAL;
+ goto fail;
+ }
+
+ buf = alloca(len);
+ if (!buf) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ strcpy(buf, uh->auth_str);
+
+ /* skip auth type */
+ strsep(&buf, " ");
+
+ next = buf;
+ while (*next) {
+ const char **dest = NULL;
+ const char *tmp;
+
+ while (*next && isspace(*next))
+ next++;
+
+ if (strmatch(&next, "realm"))
+ dest = &realm;
+ else if (strmatch(&next, "qop"))
+ dest = &data.qop;
+ else if (strmatch(&next, "nonce"))
+ dest = &data.nonce;
+ else if (strmatch(&next, "opaque"))
+ dest = &opaque;
+ else if (strmatch(&next, "stale") ||
+ strmatch(&next, "algorithm") ||
+ strmatch(&next, "auth-param")) {
+ digest_sep(&next);
+ continue;
+ } else if (strmatch(&next, "domain") ||
+ strmatch(&next, "qop-options"))
+ dest = &tmp;
+ else {
+ digest_sep(&next);
+ continue;
+ }
+
+ *dest = digest_unquote_sep(&next);
+ }
+
+ if (!realm || !data.qop || !data.nonce) {
+ err = -EINVAL;
+ goto fail;
+ }
+
+ sprintf(nc_str, "%08x", uh->nc++);
+ get_cnonce(cnonce_str);
+
+ data.qop = "auth";
+ data.uri = url->location;
+ data.method = request_types[uh->req_type];
+
+ password = strchr(url->auth, ':');
+ if (password) {
+ char *user_buf;
+
+ len = password - url->auth;
+ if (len > 256) {
+ err = -EINVAL;
+ goto fail;
+ }
+
+ user_buf = alloca(len + 1);
+ if (!user_buf) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ strncpy(user_buf, url->auth, len);
+ user_buf[len] = 0;
+ user = user_buf;
+ password++;
+ } else {
+ user = url->auth;
+ password = "";
+ }
+
+ http_digest_calculate_auth_hash(ahash, user, realm, password);
+ http_digest_calculate_response(hash, &data);
+
+ buf = NULL;
+ len = 0;
+ ofs = 0;
+
+ add_field(&buf, &ofs, &len, "username", user);
+ add_field(&buf, &ofs, &len, "realm", realm);
+ add_field(&buf, &ofs, &len, "nonce", data.nonce);
+ add_field(&buf, &ofs, &len, "uri", data.uri);
+ add_field(&buf, &ofs, &len, "cnonce", data.cnonce);
+ add_field(&buf, &ofs, &len, "response", hash);
+ if (opaque)
+ add_field(&buf, &ofs, &len, "opaque", opaque);
+
+ ustream_printf(uh->us, "Authorization: Digest nc=%s, qop=%s%s\r\n", data.nc, data.qop, buf);
+
+ free(buf);
+
+ return 0;
+
+fail:
+ return err;
+}
+
+static int
+uclient_http_add_auth_header(struct uclient_http *uh)
+{
+ if (!uh->uc.url->auth)
+ return 0;
+
+ switch (uh->auth_type) {
+ case AUTH_TYPE_UNKNOWN:
+ case AUTH_TYPE_NONE:
+ break;
+ case AUTH_TYPE_BASIC:
+ return uclient_http_add_auth_basic(uh);
+ case AUTH_TYPE_DIGEST:
+ return uclient_http_add_auth_digest(uh);
+ }
+
+ return 0;
+}
+
+static int
+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;
+ bool literal_ipv6;
+ int err;
+ size_t rem;
+
+ if (uh->state >= HTTP_STATE_HEADERS_SENT)
+ return 0;
+
+ if (uh->uc.proxy_url)
+ url = uh->uc.proxy_url;
+
+ literal_ipv6 = strchr(url->host, ':');
+
+ ustream_printf(uh->us,
+ "%s %s HTTP/1.1\r\n"
+ "Host: %s%s%s%s%s\r\n",
+ request_types[req_type], url->location,
+ literal_ipv6 ? "[" : "",
+ url->host,
+ literal_ipv6 ? "]" : "",
+ url->port ? ":" : "",
+ url->port ? url->port : "");
+
+ blobmsg_for_each_attr(cur, uh->headers.head, rem)
+ ustream_printf(uh->us, "%s: %s\r\n", blobmsg_name(cur), (char *) blobmsg_data(cur));
+
+ if (uclient_request_supports_body(uh->req_type))
+ ustream_printf(uh->us, "Transfer-Encoding: chunked\r\n");
+
+ err = uclient_http_add_auth_header(uh);
+ if (err)
+ return err;
+
+ ustream_printf(uh->us, "\r\n");
+
+ uh->state = HTTP_STATE_HEADERS_SENT;
+
+ return 0;
+}
+
+static void uclient_http_headers_complete(struct uclient_http *uh)
+{
+ enum auth_type auth_type = uh->auth_type;
+ int seq = uh->uc.seq;
+
+ uh->state = HTTP_STATE_RECV_DATA;
+ uh->uc.meta = uh->meta.head;
+ uclient_http_process_headers(uh);
+
+ if (auth_type == AUTH_TYPE_UNKNOWN && uh->uc.status_code == 401 &&
+ (uh->req_type == REQ_HEAD || uh->req_type == REQ_GET)) {
+ uclient_http_connect(&uh->uc);
+ 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->eof || seq != uh->uc.seq)
+ return;
+
+ if (uh->req_type == REQ_HEAD || uh->uc.status_code == 204 ||
+ uh->content_length == 0) {
+ uh->eof = true;
+ uclient_notify_eof(uh);
+ }