/* * Copyright (c) 2017 Intel Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #if defined(CONFIG_NET_DEBUG_HTTP) #if defined(CONFIG_HTTPS) #define SYS_LOG_DOMAIN "https/client" #else #define SYS_LOG_DOMAIN "http/client" #endif #define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG #define NET_LOG_ENABLED 1 #endif #include #include #include #include #include #include #include #include #include #include "../../ip/net_private.h" #define BUF_ALLOC_TIMEOUT 100 #define RC_STR(rc) (rc == 0 ? "OK" : "ERROR") #define HTTP_EOF "\r\n\r\n" #define HTTP_HOST "Host" #define HTTP_CONTENT_TYPE "Content-Type" #define HTTP_CONTENT_LEN "Content-Length" #define HTTP_CONT_LEN_SIZE 6 int client_reset(struct http_ctx *ctx) { http_parser_init(&ctx->http.parser, HTTP_RESPONSE); memset(ctx->http.rsp.http_status, 0, sizeof(ctx->http.rsp.http_status)); ctx->http.rsp.cl_present = 0; ctx->http.rsp.content_length = 0; ctx->http.rsp.processed = 0; ctx->http.rsp.body_found = 0; ctx->http.rsp.message_complete = 0; ctx->http.rsp.body_start = NULL; memset(ctx->http.rsp.response_buf, 0, ctx->http.rsp.response_buf_len); ctx->http.rsp.data_len = 0; return 0; } int http_request(struct http_ctx *ctx, struct http_request *req, s32_t timeout, void *user_data) { const char *method = http_method_str(req->method); int ret; if (ctx->pending) { net_pkt_unref(ctx->pending); ctx->pending = NULL; } ret = http_add_header(ctx, method, user_data); if (ret < 0) { goto out; } ret = http_add_header(ctx, " ", user_data); if (ret < 0) { goto out; } ret = http_add_header(ctx, req->url, user_data); if (ret < 0) { goto out; } ret = http_add_header(ctx, req->protocol, user_data); if (ret < 0) { goto out; } ret = http_add_header(ctx, HTTP_CRLF, user_data); if (ret < 0) { goto out; } if (req->host) { ret = http_add_header_field(ctx, HTTP_HOST, req->host, user_data); if (ret < 0) { goto out; } } if (req->header_fields) { ret = http_add_header(ctx, req->header_fields, user_data); if (ret < 0) { goto out; } } if (req->content_type_value) { ret = http_add_header_field(ctx, HTTP_CONTENT_TYPE, req->content_type_value, user_data); if (ret < 0) { goto out; } } if (req->payload && req->payload_size) { char content_len_str[HTTP_CONT_LEN_SIZE]; int i; ret = snprintk(content_len_str, HTTP_CONT_LEN_SIZE, "%u", req->payload_size); if (ret <= 0 || ret >= HTTP_CONT_LEN_SIZE) { ret = -ENOMEM; goto out; } ret = http_add_header_field(ctx, HTTP_CONTENT_LEN, content_len_str, user_data); if (ret < 0) { goto out; } ret = http_add_header(ctx, HTTP_CRLF, user_data); if (ret < 0) { goto out; } for (i = 0; i < req->payload_size;) { ret = http_send_chunk(ctx, req->payload + i, req->payload_size - i, user_data); if (ret < 0) { NET_ERR("Cannot send data to peer (%d)", ret); return ret; } i += ret; } } else { ret = http_add_header(ctx, HTTP_EOF, user_data); if (ret < 0) { goto out; } } http_send_flush(ctx, user_data); out: if (ctx->pending) { net_pkt_unref(ctx->pending); ctx->pending = NULL; } return ret; } #if defined(CONFIG_NET_DEBUG_HTTP) static void sprint_addr(char *buf, int len, sa_family_t family, struct sockaddr *addr) { if (family == AF_INET6) { net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf, len); } else if (family == AF_INET) { net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf, len); } else { NET_DBG("Invalid protocol family"); } } #endif static inline void print_info(struct http_ctx *ctx, enum http_method method) { #if defined(CONFIG_NET_DEBUG_HTTP) char local[NET_IPV6_ADDR_LEN]; char remote[NET_IPV6_ADDR_LEN]; sprint_addr(local, NET_IPV6_ADDR_LEN, ctx->app_ctx.default_ctx->local.sa_family, &ctx->app_ctx.default_ctx->local); sprint_addr(remote, NET_IPV6_ADDR_LEN, ctx->app_ctx.default_ctx->remote.sa_family, &ctx->app_ctx.default_ctx->remote); NET_DBG("HTTP %s (%s) %s -> %s port %d", http_method_str(method), ctx->http.req.host, local, remote, ntohs(net_sin(&ctx->app_ctx.default_ctx->remote)->sin_port)); #endif } int http_client_send_req(struct http_ctx *ctx, struct http_request *req, http_response_cb_t cb, u8_t *response_buf, size_t response_buf_len, void *user_data, s32_t timeout) { int ret; if (!response_buf || response_buf_len == 0) { return -EINVAL; } ctx->http.rsp.response_buf = response_buf; ctx->http.rsp.response_buf_len = response_buf_len; client_reset(ctx); if (!req->host) { req->host = ctx->server; } ctx->http.req.host = req->host; ctx->http.req.method = req->method; ctx->http.req.user_data = user_data; ctx->http.rsp.cb = cb; ret = net_app_connect(&ctx->app_ctx, timeout); if (ret < 0) { NET_DBG("Cannot connect to server (%d)", ret); return ret; } /* We might wait longer than timeout if the first connection * establishment takes long time (like with HTTPS) */ if (k_sem_take(&ctx->http.connect_wait, timeout)) { NET_DBG("Connection timed out"); ret = -ETIMEDOUT; goto out; } print_info(ctx, ctx->http.req.method); ret = http_request(ctx, req, timeout, user_data); if (ret < 0) { NET_DBG("Send error (%d)", ret); goto out; } if (timeout != 0 && k_sem_take(&ctx->http.req.wait, timeout)) { ret = -ETIMEDOUT; goto out; } if (timeout == 0) { return -EINPROGRESS; } return 0; out: return ret; } static void print_header_field(size_t len, const char *str) { #if defined(CONFIG_NET_DEBUG_HTTP) #define MAX_OUTPUT_LEN 128 char output[MAX_OUTPUT_LEN]; /* The value of len does not count \0 so we need to increase it * by one. */ if ((len + 1) > sizeof(output)) { len = sizeof(output) - 1; } snprintk(output, len + 1, "%s", str); NET_DBG("[%zd] %s", len, output); #endif } static int on_url(struct http_parser *parser, const char *at, size_t length) { ARG_UNUSED(parser); print_header_field(length, at); return 0; } static int on_status(struct http_parser *parser, const char *at, size_t length) { u16_t len; struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); len = min(length, sizeof(ctx->http.rsp.http_status) - 1); memcpy(ctx->http.rsp.http_status, at, len); ctx->http.rsp.http_status[len] = 0; NET_DBG("HTTP response status %s", ctx->http.rsp.http_status); return 0; } static int on_header_field(struct http_parser *parser, const char *at, size_t length) { const char *content_len = HTTP_CONTENT_LEN; struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); u16_t len; len = strlen(content_len); if (length >= len && memcmp(at, content_len, len) == 0) { ctx->http.rsp.cl_present = true; } print_header_field(length, at); return 0; } #define MAX_NUM_DIGITS 16 static int on_header_value(struct http_parser *parser, const char *at, size_t length) { char str[MAX_NUM_DIGITS]; struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); if (ctx->http.rsp.cl_present) { if (length <= MAX_NUM_DIGITS - 1) { long int num; memcpy(str, at, length); str[length] = 0; num = strtol(str, NULL, 10); if (num == LONG_MIN || num == LONG_MAX) { return -EINVAL; } ctx->http.rsp.content_length = num; } ctx->http.rsp.cl_present = false; } print_header_field(length, at); return 0; } static int on_body(struct http_parser *parser, const char *at, size_t length) { struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); ctx->http.rsp.body_found = 1; ctx->http.rsp.processed += length; NET_DBG("Processed %zd length %zd", ctx->http.rsp.processed, length); if (!ctx->http.rsp.body_start && (u8_t *)at != (u8_t *)ctx->http.rsp.response_buf) { ctx->http.rsp.body_start = (u8_t *)at; } if (ctx->http.rsp.cb) { NET_DBG("Calling callback for partitioned %zd len data", ctx->http.rsp.data_len); ctx->http.rsp.cb(ctx, ctx->http.rsp.response_buf, ctx->http.rsp.response_buf_len, ctx->http.rsp.data_len, HTTP_DATA_MORE, ctx->http.req.user_data); /* Re-use the result buffer and start to fill it again */ ctx->http.rsp.data_len = 0; ctx->http.rsp.body_start = NULL; } return 0; } static int on_headers_complete(struct http_parser *parser) { struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); if (parser->status_code >= 500 && parser->status_code < 600) { NET_DBG("Status %d, skipping body", parser->status_code); return 1; } if ((ctx->http.req.method == HTTP_HEAD || ctx->http.req.method == HTTP_OPTIONS) && ctx->http.rsp.content_length > 0) { NET_DBG("No body expected"); return 1; } NET_DBG("Headers complete"); return 0; } static int on_message_begin(struct http_parser *parser) { #if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2) struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); NET_DBG("-- HTTP %s response (headers) --", http_method_str(ctx->http.req.method)); #else ARG_UNUSED(parser); #endif return 0; } static int on_message_complete(struct http_parser *parser) { struct http_ctx *ctx = CONTAINER_OF(parser, struct http_ctx, http.parser); NET_DBG("-- HTTP %s response (complete) --", http_method_str(ctx->http.req.method)); if (ctx->http.rsp.cb) { ctx->http.rsp.cb(ctx, ctx->http.rsp.response_buf, ctx->http.rsp.response_buf_len, ctx->http.rsp.data_len, HTTP_DATA_FINAL, ctx->http.req.user_data); } ctx->http.rsp.message_complete = 1; k_sem_give(&ctx->http.req.wait); return 0; } static int on_chunk_header(struct http_parser *parser) { ARG_UNUSED(parser); return 0; } static int on_chunk_complete(struct http_parser *parser) { ARG_UNUSED(parser); return 0; } static void http_received(struct net_app_ctx *app_ctx, struct net_pkt *pkt, int status, void *user_data) { struct http_ctx *ctx = user_data; size_t start = ctx->http.rsp.data_len; u16_t len = 0; struct net_buf *frag, *prev_frag = NULL; size_t recv_len; size_t pkt_len; recv_len = net_pkt_appdatalen(pkt); if (recv_len == 0) { /* don't print info about zero-length app data buffers */ goto quit; } if (status) { NET_DBG("[%p] Status %d <%s>", ctx, status, RC_STR(status)); goto out; } /* Get rid of possible IP headers in the first fragment. */ frag = pkt->frags; pkt_len = net_pkt_get_len(pkt); if (recv_len < pkt_len) { net_buf_pull(frag, pkt_len - recv_len); net_pkt_set_appdata(pkt, frag->data); } NET_DBG("[%p] Received %zd bytes http data", ctx, recv_len); while (frag) { /* If this fragment cannot be copied to result buf, * then parse what we have which will cause the callback to be * called in function on_body(), and continue copying. */ if ((ctx->http.rsp.data_len + frag->len) > ctx->http.rsp.response_buf_len) { /* If the caller has not supplied a callback, then * we cannot really continue if the request buffer * overflows. Set the data_len to mark how many bytes * should be needed in the response_buf. */ if (!ctx->cb.recv) { ctx->http.rsp.data_len = recv_len; goto out; } http_parser_execute(&ctx->http.parser, &ctx->http.parser_settings, ctx->http.rsp.response_buf + start, len); ctx->http.rsp.data_len = 0; len = 0; start = 0; } memcpy(ctx->http.rsp.response_buf + ctx->http.rsp.data_len, frag->data, frag->len); ctx->http.rsp.data_len += frag->len; len += frag->len; prev_frag = frag; frag = frag->frags; pkt->frags = frag; prev_frag->frags = NULL; net_pkt_frag_unref(prev_frag); } out: http_parser_execute(&ctx->http.parser, &ctx->http.parser_settings, ctx->http.rsp.response_buf + start, len); net_pkt_unref(pkt); return; quit: http_parser_init(&ctx->http.parser, HTTP_RESPONSE); ctx->http.rsp.data_len = 0; net_pkt_unref(pkt); } static void http_data_sent(struct net_app_ctx *app_ctx, int status, void *user_data_send, void *user_data) { struct http_ctx *ctx = user_data; if (!user_data_send) { /* This is the token field in the net_context_send(). * If this is not set, then it is TCP ACK messages * that are generated by the stack. We just ignore those. */ return; } if (ctx->cb.send) { ctx->cb.send(ctx, status, user_data_send, ctx->user_data); } } static void http_connected(struct net_app_ctx *app_ctx, int status, void *user_data) { struct http_ctx *ctx = user_data; if (status < 0) { return; } if (ctx->cb.connect) { ctx->cb.connect(ctx, HTTP_CONNECTION, ctx->user_data); } if (ctx->is_connected) { return; } ctx->is_connected = true; k_sem_give(&ctx->http.connect_wait); } static void http_closed(struct net_app_ctx *app_ctx, int status, void *user_data) { struct http_ctx *ctx = user_data; ARG_UNUSED(app_ctx); ARG_UNUSED(status); NET_DBG("[%p] connection closed", ctx); ctx->is_connected = false; if (ctx->cb.close) { ctx->cb.close(ctx, 0, ctx->user_data); } } int http_client_init(struct http_ctx *ctx, const char *server, u16_t server_port, struct sockaddr *server_addr, s32_t timeout) { int ret; memset(ctx, 0, sizeof(*ctx)); ret = net_app_init_tcp_client(&ctx->app_ctx, NULL, /* use any local address */ server_addr, server, server_port, timeout, ctx); if (ret < 0) { NET_DBG("Cannot init HTTP client (%d)", ret); return ret; } ret = net_app_set_cb(&ctx->app_ctx, http_connected, http_received, http_data_sent, http_closed); if (ret < 0) { NET_ERR("Cannot set callbacks (%d)", ret); return ret; } ctx->http.parser_settings.on_body = on_body; ctx->http.parser_settings.on_chunk_complete = on_chunk_complete; ctx->http.parser_settings.on_chunk_header = on_chunk_header; ctx->http.parser_settings.on_headers_complete = on_headers_complete; ctx->http.parser_settings.on_header_field = on_header_field; ctx->http.parser_settings.on_header_value = on_header_value; ctx->http.parser_settings.on_message_begin = on_message_begin; ctx->http.parser_settings.on_message_complete = on_message_complete; ctx->http.parser_settings.on_status = on_status; ctx->http.parser_settings.on_url = on_url; k_sem_init(&ctx->http.req.wait, 0, 1); k_sem_init(&ctx->http.connect_wait, 0, 1); ctx->server = server; ctx->is_init = true; ctx->is_client = true; return 0; } int http_request_cancel(struct http_ctx *ctx) { if (!ctx->is_init) { return -EINVAL; } if (!ctx->is_client) { return -EINVAL; } client_reset(ctx); return 0; } #if defined(CONFIG_HTTPS) int http_client_set_tls(struct http_ctx *ctx, u8_t *request_buf, size_t request_buf_len, u8_t *personalization_data, size_t personalization_data_len, net_app_ca_cert_cb_t cert_cb, const char *cert_host, net_app_entropy_src_cb_t entropy_src_cb, struct k_mem_pool *pool, k_thread_stack_t *https_stack, size_t https_stack_size) { int ret; ret = net_app_client_tls(&ctx->app_ctx, request_buf, request_buf_len, personalization_data, personalization_data_len, cert_cb, cert_host, entropy_src_cb, pool, https_stack, https_stack_size); if (ret < 0) { NET_DBG("Cannot init TLS (%d)", ret); return ret; } ctx->is_tls = true; return 0; } #endif /* CONFIG_HTTPS */