/* * Copyright (c) 2023, Emna Rekik * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include LOG_MODULE_DECLARE(net_http_server, CONFIG_NET_HTTP_SERVER_LOG_LEVEL); #include "headers/server_internal.h" static const char content_404[] = { #ifdef INCLUDE_HTML_CONTENT #include "not_found_page.html.gz.inc" #endif }; static bool is_header_flag_set(uint8_t flags, uint8_t mask) { return (flags & mask) != 0; } static void clear_header_flag(uint8_t *flags, uint8_t mask) { *flags &= ~mask; } static void print_http_frames(struct http_client_ctx *client) { #if defined(PRINT_COLOR) const char *bold = "\033[1m"; const char *reset = "\033[0m"; const char *green = "\033[32m"; const char *blue = "\033[34m"; #else const char *bold = ""; const char *reset = ""; const char *green = ""; const char *blue = ""; #endif struct http2_frame *frame = &client->current_frame; LOG_DBG("%s=====================================%s", green, reset); LOG_DBG("%sReceived %s Frame :%s", bold, get_frame_type_name(frame->type), reset); LOG_DBG(" %sLength:%s %u", blue, reset, frame->length); LOG_DBG(" %sType:%s %u (%s)", blue, reset, frame->type, get_frame_type_name(frame->type)); LOG_DBG(" %sFlags:%s %u", blue, reset, frame->flags); LOG_DBG(" %sStream Identifier:%s %u", blue, reset, frame->stream_identifier); LOG_DBG("%s=====================================%s", green, reset); } static struct http2_stream_ctx *find_http_stream_context( struct http_client_ctx *client, uint32_t stream_id) { ARRAY_FOR_EACH(client->streams, i) { if (client->streams[i].stream_id == stream_id) { return &client->streams[i]; } } return NULL; } static struct http2_stream_ctx *allocate_http_stream_context( struct http_client_ctx *client, uint32_t stream_id) { ARRAY_FOR_EACH(client->streams, i) { if (client->streams[i].stream_state == HTTP2_STREAM_IDLE) { client->streams[i].stream_id = stream_id; client->streams[i].stream_state = HTTP2_STREAM_OPEN; client->streams[i].window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; client->streams[i].headers_sent = false; client->streams[i].end_stream_sent = false; return &client->streams[i]; } } return NULL; } static void release_http_stream_context(struct http_client_ctx *client, uint32_t stream_id) { ARRAY_FOR_EACH(client->streams, i) { if (client->streams[i].stream_id == stream_id) { client->streams[i].stream_id = 0; client->streams[i].stream_state = HTTP2_STREAM_IDLE; client->streams[i].current_detail = NULL; break; } } } static int add_header_field(struct http_client_ctx *client, uint8_t **buf, size_t *buflen, const char *name, const char *value) { int ret; client->header_field.name = name; client->header_field.name_len = strlen(name); client->header_field.value = value; client->header_field.value_len = strlen(value); ret = http_hpack_encode_header(*buf, *buflen, &client->header_field); if (ret < 0) { LOG_DBG("Failed to encode header, err %d", ret); return ret; } *buf += ret; *buflen -= ret; return 0; } static void encode_frame_header(uint8_t *buf, uint32_t payload_len, enum http2_frame_type frame_type, uint8_t flags, uint32_t stream_id) { sys_put_be24(payload_len, &buf[HTTP2_FRAME_LENGTH_OFFSET]); buf[HTTP2_FRAME_TYPE_OFFSET] = frame_type; buf[HTTP2_FRAME_FLAGS_OFFSET] = flags; sys_put_be32(stream_id, &buf[HTTP2_FRAME_STREAM_ID_OFFSET]); } static int send_headers_frame(struct http_client_ctx *client, enum http_status status, uint32_t stream_id, struct http_resource_detail *detail_common, uint8_t flags, const struct http_header *extra_headers, size_t extra_headers_count) { uint8_t headers_frame[CONFIG_HTTP_SERVER_HTTP2_MAX_HEADER_FRAME_LEN]; uint8_t status_str[4]; uint8_t *buf = headers_frame + HTTP2_FRAME_HEADER_SIZE; size_t buflen = sizeof(headers_frame) - HTTP2_FRAME_HEADER_SIZE; bool content_encoding_sent = false; bool content_type_sent = false; size_t payload_len; int ret; ret = snprintf(status_str, sizeof(status_str), "%d", status); if (ret > sizeof(status_str) - 1) { return -EINVAL; } ret = add_header_field(client, &buf, &buflen, ":status", status_str); if (ret < 0) { return ret; } for (size_t i = 0; i < extra_headers_count; i++) { const struct http_header *hdr = &extra_headers[i]; if (strcasecmp(hdr->name, "content-encoding") == 0) { content_encoding_sent = true; } if (strcasecmp(hdr->name, "content-type") == 0) { content_type_sent = true; } ret = add_header_field(client, &buf, &buflen, hdr->name, hdr->value); if (ret < 0) { return ret; } } if (!content_encoding_sent && detail_common && detail_common->content_encoding != NULL) { ret = add_header_field(client, &buf, &buflen, "content-encoding", detail_common->content_encoding); if (ret < 0) { return ret; } } if (!content_type_sent && detail_common && detail_common->content_type != NULL) { ret = add_header_field(client, &buf, &buflen, "content-type", detail_common->content_type); if (ret < 0) { return ret; } } payload_len = sizeof(headers_frame) - buflen - HTTP2_FRAME_HEADER_SIZE; flags |= HTTP2_FLAG_END_HEADERS; encode_frame_header(headers_frame, payload_len, HTTP2_HEADERS_FRAME, flags, stream_id); ret = http_server_sendall(client, headers_frame, payload_len + HTTP2_FRAME_HEADER_SIZE); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } return 0; } static int send_data_frame(struct http_client_ctx *client, const char *payload, size_t length, uint32_t stream_id, uint8_t flags) { uint8_t frame_header[HTTP2_FRAME_HEADER_SIZE]; int ret; encode_frame_header(frame_header, length, HTTP2_DATA_FRAME, is_header_flag_set(flags, HTTP2_FLAG_END_STREAM) ? HTTP2_FLAG_END_STREAM : 0, stream_id); ret = http_server_sendall(client, frame_header, sizeof(frame_header)); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } else { if (payload != NULL && length > 0) { ret = http_server_sendall(client, payload, length); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } } } return ret; } int send_settings_frame(struct http_client_ctx *client, bool ack) { uint8_t settings_frame[HTTP2_FRAME_HEADER_SIZE + 2 * sizeof(struct http2_settings_field)]; struct http2_settings_field *setting; size_t len; int ret; if (ack) { encode_frame_header(settings_frame, 0, HTTP2_SETTINGS_FRAME, HTTP2_FLAG_SETTINGS_ACK, 0); len = HTTP2_FRAME_HEADER_SIZE; } else { encode_frame_header(settings_frame, 2 * sizeof(struct http2_settings_field), HTTP2_SETTINGS_FRAME, 0, 0); setting = (struct http2_settings_field *) (settings_frame + HTTP2_FRAME_HEADER_SIZE); UNALIGNED_PUT(htons(HTTP2_SETTINGS_HEADER_TABLE_SIZE), &setting->id); UNALIGNED_PUT(0, &setting->value); setting++; UNALIGNED_PUT(htons(HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS), &setting->id); UNALIGNED_PUT(htonl(CONFIG_HTTP_SERVER_MAX_STREAMS), &setting->value); len = HTTP2_FRAME_HEADER_SIZE + 2 * sizeof(struct http2_settings_field); } ret = http_server_sendall(client, settings_frame, len); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } return 0; } int send_window_update_frame(struct http_client_ctx *client, struct http2_stream_ctx *stream) { uint8_t window_update_frame[HTTP2_FRAME_HEADER_SIZE + sizeof(uint32_t)]; uint32_t window_update; uint32_t stream_id; int ret; if (stream != NULL) { window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - stream->window_size; stream->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; stream_id = stream->stream_id; } else { window_update = HTTP_SERVER_INITIAL_WINDOW_SIZE - client->window_size; client->window_size = HTTP_SERVER_INITIAL_WINDOW_SIZE; stream_id = 0; } encode_frame_header(window_update_frame, sizeof(uint32_t), HTTP2_WINDOW_UPDATE_FRAME, 0, stream_id); sys_put_be32(window_update, window_update_frame + HTTP2_FRAME_HEADER_SIZE); ret = http_server_sendall(client, window_update_frame, sizeof(window_update_frame)); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } return 0; } static int send_http2_404(struct http_client_ctx *client, struct http2_frame *frame) { int ret; ret = send_headers_frame(client, HTTP_404_NOT_FOUND, frame->stream_identifier, NULL, 0, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } ret = send_data_frame(client, content_404, sizeof(content_404), frame->stream_identifier, HTTP2_FLAG_END_STREAM); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } return ret; } static int send_http2_409(struct http_client_ctx *client, struct http2_frame *frame) { int ret; ret = send_headers_frame(client, HTTP_409_CONFLICT, frame->stream_identifier, NULL, HTTP2_FLAG_END_STREAM, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } return ret; } static int handle_http2_static_resource( struct http_resource_detail_static *static_detail, struct http2_frame *frame, struct http_client_ctx *client) { const char *content_200; size_t content_len; int ret; if (!(static_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET))) { return -ENOTSUP; } if (client->current_stream == NULL) { return -ENOENT; } content_200 = static_detail->static_data; content_len = static_detail->static_data_len; ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, &static_detail->common, 0, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); goto out; } client->current_stream->headers_sent = true; ret = send_data_frame(client, content_200, content_len, frame->stream_identifier, HTTP2_FLAG_END_STREAM); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); goto out; } client->current_stream->end_stream_sent = true; out: return ret; } static int handle_http2_static_fs_resource(struct http_resource_detail_static_fs *static_fs_detail, struct http2_frame *frame, struct http_client_ctx *client) { int ret; struct fs_file_t file; char fname[HTTP_SERVER_MAX_URL_LENGTH]; char content_type[HTTP_SERVER_MAX_CONTENT_TYPE_LEN] = "text/html"; struct http_resource_detail res_detail = { .bitmask_of_supported_http_methods = static_fs_detail->common.bitmask_of_supported_http_methods, .content_type = content_type, .path_len = static_fs_detail->common.path_len, .type = static_fs_detail->common.type, }; bool gzipped; int len; int remaining; char tmp[64]; if (!(static_fs_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET))) { return -ENOTSUP; } if (client->current_stream == NULL) { return -ENOENT; } /* get filename and content-type from url */ len = strlen(client->url_buffer); if (len == 1) { /* url is just the leading slash, use index.html as filename */ snprintk(fname, sizeof(fname), "%s/index.html", static_fs_detail->fs_path); } else { http_server_get_content_type_from_extension(client->url_buffer, content_type, sizeof(content_type)); snprintk(fname, sizeof(fname), "%s%s", static_fs_detail->fs_path, client->url_buffer); } /* open file, if it exists */ ret = http_server_find_file(fname, sizeof(fname), &client->data_len, &gzipped); if (ret < 0) { LOG_ERR("fs_stat %s: %d", fname, ret); ret = send_headers_frame(client, HTTP_404_NOT_FOUND, frame->stream_identifier, NULL, 0, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } return ret; } fs_file_t_init(&file); ret = fs_open(&file, fname, FS_O_READ); if (ret < 0) { LOG_ERR("fs_open %s: %d", fname, ret); if (ret < 0) { return ret; } } /* send headers */ if (gzipped) { res_detail.content_encoding = "gzip"; } ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, &res_detail, 0, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); goto out; } client->current_stream->headers_sent = true; /* read and send file */ remaining = client->data_len; while (remaining > 0) { len = fs_read(&file, tmp, sizeof(tmp)); if (len < 0) { LOG_ERR("Filesystem read error (%d)", len); goto out; } remaining -= len; ret = send_data_frame(client, tmp, len, frame->stream_identifier, (remaining > 0) ? 0 : HTTP2_FLAG_END_STREAM); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); goto out; } } client->current_stream->end_stream_sent = true; out: /* close file */ fs_close(&file); return ret; } static int http2_dynamic_response(struct http_client_ctx *client, struct http2_frame *frame, struct http_response_ctx *rsp, enum http_data_status data_status, struct http_resource_detail_dynamic *dynamic_detail) { int ret; uint8_t flags = 0; bool final_response = http_response_is_final(rsp, data_status); if (client->current_stream->headers_sent && (rsp->header_count > 0 || rsp->status != 0)) { LOG_WRN("Already sent headers, dropping new headers and/or response code"); } /* Send headers and response code if not already sent */ if (!client->current_stream->headers_sent) { /* Use '200 OK' status if not specified by application */ if (rsp->status == 0) { rsp->status = 200; } if (rsp->status < HTTP_100_CONTINUE || rsp->status > HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) { LOG_DBG("Invalid HTTP status code: %d", rsp->status); return -EINVAL; } if (rsp->headers == NULL && rsp->header_count > 0) { LOG_DBG("NULL headers, but count is > 0"); return -EINVAL; } if (final_response && rsp->body_len == 0) { flags |= HTTP2_FLAG_END_STREAM; client->current_stream->end_stream_sent = true; } ret = send_headers_frame(client, rsp->status, frame->stream_identifier, (struct http_resource_detail *)dynamic_detail, flags, rsp->headers, rsp->header_count); if (ret < 0) { return ret; } client->current_stream->headers_sent = true; } /* Send body data if provided */ if (rsp->body != NULL && rsp->body_len > 0) { if (final_response) { flags |= HTTP2_FLAG_END_STREAM; client->current_stream->end_stream_sent = true; } ret = send_data_frame(client, rsp->body, rsp->body_len, frame->stream_identifier, flags); if (ret < 0) { return ret; } } return 0; } static int dynamic_get_del_req_v2(struct http_resource_detail_dynamic *dynamic_detail, struct http_client_ctx *client) { int ret, len; char *ptr; struct http2_frame *frame = &client->current_frame; enum http_data_status status; struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; if (client->current_stream == NULL) { return -ENOENT; } /* Start of GET params */ ptr = &client->url_buffer[dynamic_detail->common.path_len]; len = strlen(ptr); status = HTTP_SERVER_DATA_FINAL; do { memset(&response_ctx, 0, sizeof(response_ctx)); populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx); ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail); if (ret < 0) { return ret; } /* URL params are passed in the first cb only */ len = 0; } while (!http_response_is_final(&response_ctx, status)); if (!client->current_stream->end_stream_sent) { client->current_stream->end_stream_sent = true; ret = send_data_frame(client, NULL, 0, frame->stream_identifier, HTTP2_FLAG_END_STREAM); if (ret < 0) { LOG_DBG("Cannot send last frame (%d)", ret); } } dynamic_detail->holder = NULL; return ret; } static int dynamic_post_put_req_v2(struct http_resource_detail_dynamic *dynamic_detail, struct http_client_ctx *client, bool headers_only) { int ret = 0; char *ptr = client->cursor; size_t data_len; enum http_data_status status; struct http2_frame *frame = &client->current_frame; struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; struct http_header_capture_ctx *request_headers_ctx = headers_only ? &client->header_capture_ctx : NULL; if (dynamic_detail == NULL) { return -ENOENT; } if (client->current_stream == NULL) { return -ENOENT; } if (headers_only) { data_len = 0; } else { data_len = MIN(frame->length, client->data_len); frame->length -= data_len; client->cursor += data_len; client->data_len -= data_len; } if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM) && !headers_only) { status = HTTP_SERVER_DATA_FINAL; } else { status = HTTP_SERVER_DATA_MORE; } memset(&response_ctx, 0, sizeof(response_ctx)); populate_request_ctx(&request_ctx, ptr, data_len, request_headers_ctx); ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } /* For POST the application might not send a response until all data has been received. * Don't send a default response until the application has had a chance to respond. */ if (http_response_is_provided(&response_ctx)) { ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail); if (ret < 0) { return ret; } } /* Once all data is transferred to application, repeat cb until response is complete */ while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) { memset(&response_ctx, 0, sizeof(response_ctx)); populate_request_ctx(&request_ctx, ptr, 0, request_headers_ctx); ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } ret = http2_dynamic_response(client, frame, &response_ctx, status, dynamic_detail); if (ret < 0) { return ret; } } /* At end of stream, ensure response is sent and terminated */ if (frame->length == 0 && !client->current_stream->end_stream_sent && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) { if (client->current_stream->headers_sent) { ret = send_data_frame(client, NULL, 0, frame->stream_identifier, HTTP2_FLAG_END_STREAM); } else { memset(&response_ctx, 0, sizeof(response_ctx)); response_ctx.final_chunk = true; ret = http2_dynamic_response(client, frame, &response_ctx, HTTP_SERVER_DATA_FINAL, dynamic_detail); } if (ret < 0) { LOG_DBG("Cannot send last frame (%d)", ret); } client->current_stream->end_stream_sent = true; dynamic_detail->holder = NULL; } return ret; } static int handle_http2_dynamic_resource( struct http_resource_detail_dynamic *dynamic_detail, struct http2_frame *frame, struct http_client_ctx *client) { uint32_t user_method; int ret; if (dynamic_detail->cb == NULL) { return -ESRCH; } user_method = dynamic_detail->common.bitmask_of_supported_http_methods; if (!(BIT(client->method) & user_method)) { return -ENOPROTOOPT; } if (dynamic_detail->holder != NULL && dynamic_detail->holder != client) { ret = send_http2_409(client, frame); if (ret < 0) { return ret; } return enter_http_done_state(client); } dynamic_detail->holder = client; switch (client->method) { case HTTP_GET: case HTTP_DELETE: if (user_method & BIT(client->method)) { return dynamic_get_del_req_v2(dynamic_detail, client); } goto not_supported; case HTTP_POST: case HTTP_PUT: case HTTP_PATCH: /* The data will come in DATA frames. Remember the detail ptr * which needs to be known when passing data to application. */ if (user_method & BIT(client->method)) { client->current_stream->current_detail = (struct http_resource_detail *)dynamic_detail; /* If there are any header fields to pass to the application, call the * dynamic handler now with the header data so that this may be cleared to * re-use for any other concurrent streams */ if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { ret = dynamic_post_put_req_v2(dynamic_detail, client, true); if (ret < 0) { return ret; } } break; } goto not_supported; not_supported: default: LOG_DBG("HTTP method %s (%d) not supported.", http_method_str(client->method), client->method); return -ENOTSUP; } return 0; } int enter_http2_request(struct http_client_ctx *client) { int ret; client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; client->data_len -= sizeof(HTTP2_PREFACE) - 1; client->cursor += sizeof(HTTP2_PREFACE) - 1; /* HTTP/2 client preface received, send server preface * (settings frame). */ if (!client->preface_sent) { ret = send_settings_frame(client, false); if (ret < 0) { return ret; } client->preface_sent = true; } return 0; } static int enter_http_frame_settings_state(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_FRAME_SETTINGS_STATE; return 0; } static int enter_http_frame_data_state(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; struct http2_stream_ctx *stream; if (frame->stream_identifier == 0) { LOG_DBG("Stream ID 0 is forbidden for data frames."); return -EBADMSG; } stream = find_http_stream_context(client, frame->stream_identifier); if (stream == NULL) { LOG_DBG("No stream context found for ID %d", frame->stream_identifier); return -EBADMSG; } if (stream->stream_state != HTTP2_STREAM_OPEN && stream->stream_state != HTTP2_STREAM_HALF_CLOSED_REMOTE) { LOG_DBG("Stream ID %d in a wrong state %d", stream->stream_id, stream->stream_state); return -EBADMSG; } stream->window_size -= frame->length; client->window_size -= frame->length; client->server_state = HTTP_SERVER_FRAME_DATA_STATE; client->current_stream = stream; return 0; } static int enter_http_frame_headers_state(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; struct http2_stream_ctx *stream; stream = find_http_stream_context(client, frame->stream_identifier); if (!stream) { LOG_DBG("|| stream ID || %d", frame->stream_identifier); stream = allocate_http_stream_context(client, frame->stream_identifier); if (!stream) { LOG_DBG("No available stream slots. Connection closed."); return -ENOMEM; } } client->current_stream = stream; if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) { client->expect_continuation = true; } else { client->expect_continuation = false; } if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { /* Reset header capture state for new headers frame */ client->header_capture_ctx.count = 0; client->header_capture_ctx.cursor = 0; client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK; client->header_capture_ctx.current_stream = stream; } client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; return 0; } static int enter_http_frame_continuation_state(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) { client->expect_continuation = true; } else { client->expect_continuation = false; } client->server_state = HTTP_SERVER_FRAME_CONTINUATION_STATE; return 0; } static int enter_http_frame_window_update_state(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_FRAME_WINDOW_UPDATE_STATE; return 0; } static int enter_http_frame_priority_state(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_FRAME_PRIORITY_STATE; return 0; } static int enter_http_frame_rst_stream_state(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_FRAME_RST_STREAM_STATE; return 0; } static int enter_http_frame_goaway_state(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_FRAME_GOAWAY_STATE; return 0; } int handle_http_frame_header(struct http_client_ctx *client) { int ret; LOG_DBG("HTTP_SERVER_FRAME_HEADER"); ret = parse_http_frame_header(client, client->cursor, client->data_len); if (ret < 0) { return ret; } client->cursor += HTTP2_FRAME_HEADER_SIZE; client->data_len -= HTTP2_FRAME_HEADER_SIZE; print_http_frames(client); if (client->expect_continuation && client->current_frame.type != HTTP2_CONTINUATION_FRAME) { LOG_ERR("Continuation frame expected"); return -EBADMSG; } client->current_stream = NULL; switch (client->current_frame.type) { case HTTP2_DATA_FRAME: return enter_http_frame_data_state(client); case HTTP2_HEADERS_FRAME: return enter_http_frame_headers_state(client); case HTTP2_CONTINUATION_FRAME: return enter_http_frame_continuation_state(client); case HTTP2_SETTINGS_FRAME: return enter_http_frame_settings_state(client); case HTTP2_WINDOW_UPDATE_FRAME: return enter_http_frame_window_update_state(client); case HTTP2_RST_STREAM_FRAME: return enter_http_frame_rst_stream_state(client); case HTTP2_GOAWAY_FRAME: return enter_http_frame_goaway_state(client); case HTTP2_PRIORITY_FRAME: return enter_http_frame_priority_state(client); default: return enter_http_done_state(client); } return 0; } /* This feature is theoretically obsoleted in RFC9113, but curl for instance * still uses it, so implement as described in RFC7540. */ int handle_http1_to_http2_upgrade(struct http_client_ctx *client) { static const char switching_protocols[] = "HTTP/1.1 101 Switching Protocols\r\n" "Connection: Upgrade\r\n" "Upgrade: h2c\r\n" "\r\n"; struct http2_frame *frame = &client->current_frame; struct http_resource_detail *detail; struct http2_stream_ctx *stream; int path_len; int ret; /* Create an artificial Data frame, so that we can proceed with HTTP2 * processing. The HTTP/1.1 request that is sent prior to upgrade is * assigned a stream identifier of 1. */ frame->stream_identifier = 1; frame->type = HTTP2_DATA_FRAME; frame->length = client->http1_frag_data_len; if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { frame->flags = HTTP2_FLAG_END_STREAM; } else { frame->flags = 0; } /* Allocate stream. */ stream = find_http_stream_context(client, frame->stream_identifier); if (stream == NULL) { stream = allocate_http_stream_context(client, frame->stream_identifier); if (!stream) { LOG_DBG("No available stream slots. Connection closed."); return -ENOMEM; } } client->current_stream = stream; if (!client->preface_sent) { ret = http_server_sendall(client, switching_protocols, sizeof(switching_protocols) - 1); if (ret < 0) { goto error; } /* The first HTTP/2 frame sent by the server MUST be a server connection * preface. */ ret = send_settings_frame(client, false); if (ret < 0) { goto error; } client->preface_sent = true; } detail = get_resource_detail(client->url_buffer, &path_len, false); if (detail != NULL) { detail->path_len = path_len; if (detail->type == HTTP_RESOURCE_TYPE_STATIC) { ret = handle_http2_static_resource( (struct http_resource_detail_static *)detail, frame, client); if (ret < 0) { goto error; } } else if (detail->type == HTTP_RESOURCE_TYPE_STATIC_FS) { ret = handle_http2_static_fs_resource( (struct http_resource_detail_static_fs *)detail, frame, client); if (ret < 0) { goto error; } } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { ret = handle_http2_dynamic_resource( (struct http_resource_detail_dynamic *)detail, frame, client); if (ret < 0) { goto error; } if (client->method == HTTP_POST || client->method == HTTP_PUT || client->method == HTTP_PATCH) { ret = dynamic_post_put_req_v2( (struct http_resource_detail_dynamic *)detail, client, false); if (ret < 0) { goto error; } } } } else { ret = send_http2_404(client, frame); if (ret < 0) { goto error; } } /* Only after the complete HTTP1 payload has been processed, switch * to HTTP2. */ if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { release_http_stream_context(client, frame->stream_identifier); client->current_detail = NULL; client->server_state = HTTP_SERVER_PREFACE_STATE; client->cursor += client->data_len; client->data_len = 0; } return 0; error: return ret; } static int parse_http_frame_padded_field(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; if (client->data_len == 0) { return -EAGAIN; } frame->padding_len = *client->cursor; client->cursor++; client->data_len--; frame->length--; if (frame->length <= frame->padding_len) { return -EBADMSG; } /* Subtract the padding length from frame length now to simplify * payload processing. Padding will be handled based on * frame->padding_len in a separate state. */ frame->length -= frame->padding_len; /* Clear the padded flag, this indicates that padding field was * already parsed. */ clear_header_flag(&frame->flags, HTTP2_FLAG_PADDED); return 0; } static int parse_http_frame_priority_field(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; if (client->data_len < HTTP2_HEADERS_FRAME_PRIORITY_LEN) { return -EAGAIN; } /* Priority signalling is deprecated by RFC 9113, however it still * should be expected to receive, just drop the bytes. */ client->cursor += HTTP2_HEADERS_FRAME_PRIORITY_LEN; client->data_len -= HTTP2_HEADERS_FRAME_PRIORITY_LEN; frame->length -= HTTP2_HEADERS_FRAME_PRIORITY_LEN; /* Clear the priority flag, this indicates that priority field was * already parsed. */ clear_header_flag(&frame->flags, HTTP2_FLAG_PRIORITY); return 0; } int handle_http_frame_data(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; int ret; LOG_DBG("HTTP_SERVER_FRAME_DATA_STATE"); if (client->current_stream->current_detail == NULL) { /* There is no handler */ LOG_DBG("No dynamic handler found."); (void)send_http2_404(client, frame); return -ENOENT; } if (is_header_flag_set(frame->flags, HTTP2_FLAG_PADDED)) { ret = parse_http_frame_padded_field(client); if (ret < 0) { return ret; } } ret = dynamic_post_put_req_v2( (struct http_resource_detail_dynamic *)client->current_stream->current_detail, client, false); if (ret < 0 && ret == -ENOENT) { ret = send_http2_404(client, frame); } if (ret < 0) { return ret; } if (frame->length == 0) { struct http2_stream_ctx *stream = find_http_stream_context(client, frame->stream_identifier); if (stream == NULL) { LOG_DBG("No stream context found for ID %d", frame->stream_identifier); return -EBADMSG; } ret = send_window_update_frame(client, stream); if (ret < 0) { return ret; } ret = send_window_update_frame(client, NULL); if (ret < 0) { return ret; } if (is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) { client->current_stream->current_detail = NULL; release_http_stream_context(client, frame->stream_identifier); } /* Whole frame consumed, expect next one. */ if (frame->padding_len > 0) { client->server_state = HTTP_SERVER_FRAME_PADDING_STATE; } else { client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; } } return 0; } static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx, struct http_hpack_header_buf *hdr_buf) { size_t required_len; char *dest = &ctx->buffer[ctx->cursor]; size_t remaining = sizeof(ctx->buffer) - ctx->cursor; struct http_header *current_header = &ctx->headers[ctx->count]; STRUCT_SECTION_FOREACH(http_header_name, header) { required_len = hdr_buf->name_len + hdr_buf->value_len + 2; if (hdr_buf->name_len == strlen(header->name) && (strncasecmp(hdr_buf->name, header->name, hdr_buf->name_len) == 0)) { if (ctx->count == ARRAY_SIZE(ctx->headers)) { LOG_DBG("Header '%s' dropped: not enough slots", header->name); ctx->status = HTTP_HEADER_STATUS_DROPPED; break; } if (remaining < required_len) { LOG_DBG("Header '%s' dropped: buffer too small", header->name); ctx->status = HTTP_HEADER_STATUS_DROPPED; break; } /* Copy header name from user-registered header to make HTTP1/HTTP2 * transparent to the user - they do not need a case-insensitive comparison * to check which header was matched. */ memcpy(dest, header->name, hdr_buf->name_len); dest[hdr_buf->name_len] = '\0'; current_header->name = dest; ctx->cursor += (hdr_buf->name_len + 1); dest += (hdr_buf->name_len + 1); /* Copy header value */ memcpy(dest, hdr_buf->value, hdr_buf->value_len); dest[hdr_buf->value_len] = '\0'; current_header->value = dest; ctx->cursor += (hdr_buf->value_len + 1); ctx->count++; break; } } } static int process_header(struct http_client_ctx *client, struct http_hpack_header_buf *header) { if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { check_user_request_headers_http2(&client->header_capture_ctx, header); } if (header->name_len == (sizeof(":method") - 1) && memcmp(header->name, ":method", header->name_len) == 0) { const enum http_method supported_methods[] = { HTTP_GET, HTTP_DELETE, HTTP_POST, HTTP_PUT, HTTP_PATCH }; for (int i = 0; i < ARRAY_SIZE(supported_methods); i++) { if ((header->value_len == strlen(http_method_str(supported_methods[i]))) && (memcmp(header->value, http_method_str(supported_methods[i]), header->value_len) == 0)) { client->method = supported_methods[i]; goto out; } } /* Unknown method */ return -EBADMSG; } else if (header->name_len == (sizeof(":path") - 1) && memcmp(header->name, ":path", header->name_len) == 0) { if (header->value_len > sizeof(client->url_buffer) - 1) { /* URL too long to handle */ return -ENOBUFS; } memcpy(client->url_buffer, header->value, header->value_len); client->url_buffer[header->value_len] = '\0'; } else if (header->name_len == (sizeof("content-type") - 1) && memcmp(header->name, "content-type", header->name_len) == 0) { if (header->value_len > sizeof(client->content_type) - 1) { /* Content-type too long to handle */ return -ENOBUFS; } memcpy(client->content_type, header->value, header->value_len); client->content_type[header->value_len] = '\0'; } else if (header->name_len == (sizeof("content-length") - 1) && memcmp(header->name, "content-length", header->name_len) == 0) { char len_str[16] = { 0 }; char *endptr; unsigned long len; memcpy(len_str, header->value, MIN(sizeof(len_str), header->value_len)); len_str[sizeof(len_str) - 1] = '\0'; len = strtoul(len_str, &endptr, 10); if (*endptr != '\0') { return -EINVAL; } client->content_len = (size_t)len; } else { /* Just ignore for now. */ LOG_DBG("Ignoring field %.*s", (int)header->name_len, header->name); } out: return 0; } static int handle_incomplete_http_header(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; size_t extra_len, prev_frame_len; int ret; if (client->data_len < frame->length) { /* Still did not receive entire frame content */ return -EAGAIN; } if (!client->expect_continuation) { /* Failed to parse header field while the frame is complete * and no continuation frame is expected - report protocol * error. */ LOG_ERR("Incomplete header field"); return -EBADMSG; } /* A header field can be split between two frames, i. e. header and * continuation or two continuation frames. Because of this, the * continuation frame header can be present in the stream in between * header field data, so in such case we need to check for header here * and remove it from the stream to unblock further processing of the * header field. */ prev_frame_len = frame->length; extra_len = client->data_len - frame->length; ret = parse_http_frame_header(client, client->cursor + prev_frame_len, extra_len); if (ret < 0) { return -EAGAIN; } /* Continuation frame expected. */ if (frame->type != HTTP2_CONTINUATION_FRAME) { LOG_ERR("Continuation frame expected"); return -EBADMSG; } print_http_frames(client); /* Now remove continuation frame header from the stream, and proceed * with processing. */ extra_len -= HTTP2_FRAME_HEADER_SIZE; client->data_len -= HTTP2_FRAME_HEADER_SIZE; frame->length += prev_frame_len; memmove(client->cursor + prev_frame_len, client->cursor + prev_frame_len + HTTP2_FRAME_HEADER_SIZE, extra_len); return enter_http_frame_continuation_state(client); } static int handle_http_frame_headers_end_stream(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; int ret = 0; if (client->current_stream == NULL) { return -ENOENT; } if (client->current_stream->current_detail == NULL) { goto out; } if (client->current_stream->current_detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { struct http_resource_detail_dynamic *dynamic_detail = (struct http_resource_detail_dynamic *) client->current_stream->current_detail; memset(&response_ctx, 0, sizeof(response_ctx)); populate_request_ctx(&request_ctx, NULL, 0, NULL); ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { dynamic_detail->holder = NULL; goto out; } /* Force end stream */ response_ctx.final_chunk = true; ret = http2_dynamic_response(client, frame, &response_ctx, HTTP_SERVER_DATA_FINAL, dynamic_detail); dynamic_detail->holder = NULL; if (ret < 0) { goto out; } } if (!client->current_stream->headers_sent) { ret = send_headers_frame(client, HTTP_200_OK, frame->stream_identifier, client->current_stream->current_detail, HTTP2_FLAG_END_STREAM, NULL, 0); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); goto out; } } else if (!client->current_stream->end_stream_sent) { ret = send_data_frame(client, NULL, 0, frame->stream_identifier, HTTP2_FLAG_END_STREAM); if (ret < 0) { LOG_DBG("Cannot send last frame (%d)", ret); } } client->current_stream->current_detail = NULL; out: release_http_stream_context(client, frame->stream_identifier); return ret; } int handle_http_frame_headers(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; struct http_resource_detail *detail; int ret, path_len; LOG_DBG("HTTP_SERVER_FRAME_HEADERS"); if (is_header_flag_set(frame->flags, HTTP2_FLAG_PADDED)) { ret = parse_http_frame_padded_field(client); if (ret < 0) { return ret; } } if (is_header_flag_set(frame->flags, HTTP2_FLAG_PRIORITY)) { ret = parse_http_frame_priority_field(client); if (ret < 0) { return ret; } } while (frame->length > 0) { struct http_hpack_header_buf *header = &client->header_field; size_t datalen = MIN(client->data_len, frame->length); ret = http_hpack_decode_header(client->cursor, datalen, header); if (ret <= 0) { if (ret == -EAGAIN) { ret = handle_incomplete_http_header(client); } else if (ret == 0) { ret = -EBADMSG; } return ret; } if (ret > frame->length) { LOG_ERR("Protocol error, frame length exceeded"); return -EBADMSG; } frame->length -= ret; client->cursor += ret; client->data_len -= ret; LOG_DBG("Parsed header: %.*s %.*s", (int)header->name_len, header->name, (int)header->value_len, header->value); ret = process_header(client, header); if (ret < 0) { return ret; } } if (client->expect_continuation) { /* More headers to come in the continuation frame. */ client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; return 0; } detail = get_resource_detail(client->url_buffer, &path_len, false); if (detail != NULL) { detail->path_len = path_len; if (detail->type == HTTP_RESOURCE_TYPE_STATIC) { ret = handle_http2_static_resource( (struct http_resource_detail_static *)detail, frame, client); if (ret < 0) { return ret; } } else if (detail->type == HTTP_RESOURCE_TYPE_STATIC_FS) { ret = handle_http2_static_fs_resource( (struct http_resource_detail_static_fs *)detail, frame, client); if (ret < 0) { return ret; } } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { ret = handle_http2_dynamic_resource( (struct http_resource_detail_dynamic *)detail, frame, client); if (ret < 0) { return ret; } } } else { ret = send_http2_404(client, frame); if (ret < 0) { return ret; } } if (is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) { ret = handle_http_frame_headers_end_stream(client); if (ret < 0) { return ret; } } if (frame->padding_len > 0) { client->server_state = HTTP_SERVER_FRAME_PADDING_STATE; } else { client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; } return 0; } int handle_http_frame_priority(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; LOG_DBG("HTTP_SERVER_FRAME_PRIORITY_STATE"); if (frame->length != HTTP2_PRIORITY_FRAME_LEN) { return -EBADMSG; } if (client->data_len < frame->length) { return -EAGAIN; } /* Priority signalling is deprecated by RFC 9113, however it still * should be expected to receive, just drop the bytes. */ client->data_len -= HTTP2_PRIORITY_FRAME_LEN; client->cursor += HTTP2_PRIORITY_FRAME_LEN; client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; return 0; } int handle_http_frame_rst_stream(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; struct http2_stream_ctx *stream_ctx; uint32_t error_code; LOG_DBG("FRAME_RST_STREAM"); if (frame->length != HTTP2_RST_STREAM_FRAME_LEN) { return -EBADMSG; } if (client->data_len < frame->length) { return -EAGAIN; } if (frame->stream_identifier == 0) { return -EBADMSG; } stream_ctx = find_http_stream_context(client, frame->stream_identifier); if (stream_ctx == NULL) { return -EBADMSG; } error_code = sys_get_be32(client->cursor); LOG_DBG("Stream %u reset with error code %u", stream_ctx->stream_id, error_code); release_http_stream_context(client, stream_ctx->stream_id); client->data_len -= HTTP2_RST_STREAM_FRAME_LEN; client->cursor += HTTP2_RST_STREAM_FRAME_LEN; client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; return 0; } int handle_http_frame_settings(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; int bytes_consumed; LOG_DBG("HTTP_SERVER_FRAME_SETTINGS"); if (client->data_len < frame->length) { return -EAGAIN; } bytes_consumed = client->current_frame.length; client->data_len -= bytes_consumed; client->cursor += bytes_consumed; if (!is_header_flag_set(frame->flags, HTTP2_FLAG_SETTINGS_ACK)) { int ret; ret = send_settings_frame(client, true); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } } client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; return 0; } int handle_http_frame_goaway(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; int bytes_consumed; LOG_DBG("HTTP_SERVER_FRAME_GOAWAY"); if (client->data_len < frame->length) { return -EAGAIN; } bytes_consumed = client->current_frame.length; client->data_len -= bytes_consumed; client->cursor += bytes_consumed; enter_http_done_state(client); return 0; } int handle_http_frame_window_update(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; int bytes_consumed; LOG_DBG("HTTP_SERVER_FRAME_WINDOW_UPDATE"); /* TODO Implement flow control, for now just ignore. */ if (client->data_len < frame->length) { return -EAGAIN; } bytes_consumed = client->current_frame.length; client->data_len -= bytes_consumed; client->cursor += bytes_consumed; client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; return 0; } int handle_http_frame_continuation(struct http_client_ctx *client) { LOG_DBG("HTTP_SERVER_FRAME_CONTINUATION_STATE"); client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; return 0; } int handle_http_frame_padding(struct http_client_ctx *client) { struct http2_frame *frame = &client->current_frame; size_t bytes_consumed; if (client->data_len == 0) { return -EAGAIN; } bytes_consumed = MIN(client->data_len, frame->padding_len); client->data_len -= bytes_consumed; client->cursor += bytes_consumed; frame->padding_len -= bytes_consumed; if (frame->padding_len == 0) { client->server_state = HTTP_SERVER_FRAME_HEADER_STATE; } return 0; } const char *get_frame_type_name(enum http2_frame_type type) { switch (type) { case HTTP2_DATA_FRAME: return "DATA"; case HTTP2_HEADERS_FRAME: return "HEADERS"; case HTTP2_PRIORITY_FRAME: return "PRIORITY"; case HTTP2_RST_STREAM_FRAME: return "RST_STREAM"; case HTTP2_SETTINGS_FRAME: return "SETTINGS"; case HTTP2_PUSH_PROMISE_FRAME: return "PUSH_PROMISE"; case HTTP2_PING_FRAME: return "PING"; case HTTP2_GOAWAY_FRAME: return "GOAWAY"; case HTTP2_WINDOW_UPDATE_FRAME: return "WINDOW_UPDATE"; case HTTP2_CONTINUATION_FRAME: return "CONTINUATION"; default: return "UNKNOWN"; } } int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffer, size_t buflen) { struct http2_frame *frame = &client->current_frame; if (buflen < HTTP2_FRAME_HEADER_SIZE) { return -EAGAIN; } frame->length = sys_get_be24(&buffer[HTTP2_FRAME_LENGTH_OFFSET]); frame->type = buffer[HTTP2_FRAME_TYPE_OFFSET]; frame->flags = buffer[HTTP2_FRAME_FLAGS_OFFSET]; frame->stream_identifier = sys_get_be32( &buffer[HTTP2_FRAME_STREAM_ID_OFFSET]); frame->stream_identifier &= HTTP2_FRAME_STREAM_ID_MASK; frame->padding_len = 0; LOG_DBG("Frame len %d type 0x%02x flags 0x%02x id %d", frame->length, frame->type, frame->flags, frame->stream_identifier); return 0; }