/* * Copyright (c) 2023, Emna Rekik * Copyright (c) 2024 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L /* Required for strnlen() */ #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" #define TEMP_BUF_LEN 64 static const char not_found_response[] = "HTTP/1.1 404 Not Found\r\n" "Content-Length: 9\r\n\r\n" "Not Found"; static const char not_allowed_response[] = "HTTP/1.1 405 Method Not Allowed\r\n" "Content-Length: 18\r\n\r\n" "Method Not Allowed"; static const char conflict_response[] = "HTTP/1.1 409 Conflict\r\n\r\n"; static const char final_chunk[] = "0\r\n\r\n"; static const char *crlf = &final_chunk[3]; static int handle_http1_static_resource( struct http_resource_detail_static *static_detail, struct http_client_ctx *client) { #define RESPONSE_TEMPLATE \ "HTTP/1.1 200 OK\r\n" \ "%s%s\r\n" \ "Content-Length: %d\r\n" /* Add couple of bytes to total response */ char http_response[sizeof(RESPONSE_TEMPLATE) + sizeof("Content-Encoding: 01234567890123456789\r\n") + sizeof("Content-Type: \r\n") + HTTP_SERVER_MAX_CONTENT_TYPE_LEN + sizeof("xxxx") + sizeof("\r\n")]; const char *data; int len; int ret; if (static_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET)) { data = static_detail->static_data; len = static_detail->static_data_len; if (static_detail->common.content_encoding != NULL && static_detail->common.content_encoding[0] != '\0') { snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE "Content-Encoding: %s\r\n\r\n", "Content-Type: ", static_detail->common.content_type == NULL ? "text/html" : static_detail->common.content_type, len, static_detail->common.content_encoding); } else { snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE "\r\n", "Content-Type: ", static_detail->common.content_type == NULL ? "text/html" : static_detail->common.content_type, len); } ret = http_server_sendall(client, http_response, strlen(http_response)); if (ret < 0) { return ret; } ret = http_server_sendall(client, data, len); if (ret < 0) { return ret; } } return 0; } #define RESPONSE_TEMPLATE_DYNAMIC \ "HTTP/1.1 200 OK\r\n" \ "%s%s\r\n\r\n" #define SEND_RESPONSE(_template, _content_type) ({ \ char http_response[sizeof(_template) + \ sizeof("Content-Type: \r\n") + \ HTTP_SERVER_MAX_CONTENT_TYPE_LEN + \ sizeof("xxxx") + \ sizeof("\r\n")]; \ snprintk(http_response, sizeof(http_response), \ _template, \ "Content-Type: ", \ _content_type == NULL ? \ "text/html" : _content_type); \ ret = http_server_sendall(client, http_response, \ strnlen(http_response, \ sizeof(http_response) - 1)); \ ret; }) #define RESPONSE_TEMPLATE_DYNAMIC_PART1 \ "HTTP/1.1 %d\r\n" \ "Transfer-Encoding: chunked\r\n" static int http1_send_headers(struct http_client_ctx *client, enum http_status status, const struct http_header *headers, size_t header_count, struct http_resource_detail_dynamic *dynamic_detail) { int ret; bool content_type_sent = false; char http_response[MAX(sizeof(RESPONSE_TEMPLATE_DYNAMIC_PART1) + sizeof("xxx"), CONFIG_HTTP_SERVER_MAX_HEADER_LEN + 2)]; if (status < HTTP_100_CONTINUE || status > HTTP_511_NETWORK_AUTHENTICATION_REQUIRED) { LOG_DBG("Invalid HTTP status code: %d", status); return -EINVAL; } if (headers == NULL && header_count > 0) { LOG_DBG("NULL headers, but count is > 0"); return -EINVAL; } /* Send response code and transfer encoding */ snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE_DYNAMIC_PART1, status); ret = http_server_sendall(client, http_response, strnlen(http_response, sizeof(http_response) - 1)); if (ret < 0) { LOG_DBG("Failed to send HTTP headers part 1"); return ret; } /* Send user-defined headers */ for (size_t i = 0; i < header_count; i++) { const struct http_header *hdr = &headers[i]; if (strcasecmp(hdr->name, "Transfer-Encoding") == 0) { LOG_DBG("Application is not permitted to change Transfer-Encoding header"); return -EACCES; } if (strcasecmp(hdr->name, "Content-Type") == 0) { content_type_sent = true; } snprintk(http_response, sizeof(http_response), "%s: ", hdr->name); ret = http_server_sendall(client, http_response, strnlen(http_response, sizeof(http_response) - 1)); if (ret < 0) { LOG_DBG("Failed to send HTTP header name"); return ret; } ret = http_server_sendall(client, hdr->value, strlen(hdr->value)); if (ret < 0) { LOG_DBG("Failed to send HTTP header value"); return ret; } ret = http_server_sendall(client, crlf, 2); if (ret < 0) { LOG_DBG("Failed to send CRLF"); return ret; } } /* Send content-type header if it was not already sent */ if (!content_type_sent) { const char *content_type = NULL; if (dynamic_detail != NULL) { content_type = dynamic_detail->common.content_type; } snprintk(http_response, sizeof(http_response), "Content-Type: %s\r\n", content_type == NULL ? "text/html" : content_type); ret = http_server_sendall(client, http_response, strnlen(http_response, sizeof(http_response) - 1)); if (ret < 0) { LOG_DBG("Failed to send Content-Type"); return ret; } } /* Send final CRLF */ ret = http_server_sendall(client, crlf, 2); if (ret < 0) { LOG_DBG("Failed to send CRLF"); return ret; } return ret; } static int http1_dynamic_response(struct http_client_ctx *client, struct http_response_ctx *rsp, struct http_resource_detail_dynamic *dynamic_detail) { int ret; char tmp[TEMP_BUF_LEN]; if (client->http1_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->http1_headers_sent) { /* Use '200 OK' status if not specified by application */ if (rsp->status == 0) { rsp->status = 200; } ret = http1_send_headers(client, rsp->status, rsp->headers, rsp->header_count, dynamic_detail); if (ret < 0) { return ret; } client->http1_headers_sent = true; } /* Send body data if provided */ if (rsp->body != NULL && rsp->body_len > 0) { ret = snprintk(tmp, sizeof(tmp), "%zx\r\n", rsp->body_len); ret = http_server_sendall(client, tmp, ret); if (ret < 0) { return ret; } ret = http_server_sendall(client, rsp->body, rsp->body_len); if (ret < 0) { return ret; } (void)http_server_sendall(client, crlf, 2); } return 0; } static int dynamic_get_del_req(struct http_resource_detail_dynamic *dynamic_detail, struct http_client_ctx *client) { int ret, len; char *ptr; enum http_data_status status; struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; /* 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 = http1_dynamic_response(client, &response_ctx, 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)); dynamic_detail->holder = NULL; ret = http_server_sendall(client, final_chunk, sizeof(final_chunk) - 1); if (ret < 0) { return ret; } return 0; } static int dynamic_post_put_req(struct http_resource_detail_dynamic *dynamic_detail, struct http_client_ctx *client) { int ret; char *ptr = client->cursor; enum http_data_status status; struct http_request_ctx request_ctx; struct http_response_ctx response_ctx; if (ptr == NULL) { return -ENOENT; } if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { status = HTTP_SERVER_DATA_FINAL; } else { status = HTTP_SERVER_DATA_MORE; } memset(&response_ctx, 0, sizeof(response_ctx)); populate_request_ctx(&request_ctx, ptr, client->data_len, &client->header_capture_ctx); ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } /* Only send request headers in first callback to application. This is not strictly * necessary for http1, but is done for consistency with the http2 behaviour. */ client->header_capture_ctx.status = HTTP_HEADER_STATUS_NONE; /* 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 = http1_dynamic_response(client, &response_ctx, 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, &client->header_capture_ctx); ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx, dynamic_detail->user_data); if (ret < 0) { return ret; } ret = http1_dynamic_response(client, &response_ctx, dynamic_detail); if (ret < 0) { return ret; } } /* At end of message, ensure response is sent and terminated */ if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { if (!client->http1_headers_sent) { memset(&response_ctx, 0, sizeof(response_ctx)); response_ctx.final_chunk = true; ret = http1_dynamic_response(client, &response_ctx, dynamic_detail); if (ret < 0) { return ret; } } ret = http_server_sendall(client, final_chunk, sizeof(final_chunk) - 1); if (ret < 0) { return ret; } dynamic_detail->holder = NULL; } return 0; } #if defined(CONFIG_FILE_SYSTEM) int handle_http1_static_fs_resource(struct http_resource_detail_static_fs *static_fs_detail, struct http_client_ctx *client) { #define RESPONSE_TEMPLATE_STATIC_FS \ "HTTP/1.1 200 OK\r\n" \ "Content-Type: %s%s\r\n\r\n" #define CONTENT_ENCODING_GZIP "\r\nContent-Encoding: gzip" bool gzipped = false; int len; int remaining; int ret; size_t file_size; struct fs_file_t file; char fname[HTTP_SERVER_MAX_URL_LENGTH]; char content_type[HTTP_SERVER_MAX_CONTENT_TYPE_LEN] = "text/html"; /* Add couple of bytes to response template size to have space * for the content type and encoding */ char http_response[sizeof(RESPONSE_TEMPLATE_STATIC_FS) + HTTP_SERVER_MAX_CONTENT_TYPE_LEN + sizeof(CONTENT_ENCODING_GZIP)]; if (!(static_fs_detail->common.bitmask_of_supported_http_methods & BIT(HTTP_GET))) { ret = http_server_sendall(client, not_allowed_response, sizeof(not_allowed_response) - 1); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); } return ret; } /* 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), &file_size, &gzipped); if (ret < 0) { LOG_ERR("fs_stat %s: %d", fname, ret); ret = http_server_sendall(client, not_found_response, sizeof(not_found_response) - 1); 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; } } LOG_DBG("found %s, file size: %zu", fname, file_size); /* send HTTP header */ len = snprintk(http_response, sizeof(http_response), RESPONSE_TEMPLATE_STATIC_FS, content_type, gzipped ? CONTENT_ENCODING_GZIP : ""); ret = http_server_sendall(client, http_response, len); if (ret < 0) { goto close; } /* read and send file */ remaining = file_size; while (remaining > 0) { len = fs_read(&file, http_response, sizeof(http_response)); if (len < 0) { LOG_ERR("Filesystem read error (%d)", len); goto close; } ret = http_server_sendall(client, http_response, len); if (ret < 0) { goto close; } remaining -= len; } ret = http_server_sendall(client, "\r\n\r\n", 4); close: /* close file */ fs_close(&file); return ret; } #endif static int handle_http1_dynamic_resource( struct http_resource_detail_dynamic *dynamic_detail, 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 = http_server_sendall(client, conflict_response, sizeof(conflict_response) - 1); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } return enter_http_done_state(client); } dynamic_detail->holder = client; switch (client->method) { case HTTP_HEAD: if (user_method & BIT(HTTP_HEAD)) { ret = SEND_RESPONSE(RESPONSE_TEMPLATE_DYNAMIC, dynamic_detail->common.content_type); if (ret < 0) { return ret; } dynamic_detail->holder = NULL; return 0; } case HTTP_GET: case HTTP_DELETE: /* For GET/DELETE request, we do not pass any data to the app * but let the app send data to the peer. */ if (user_method & BIT(client->method)) { return dynamic_get_del_req(dynamic_detail, client); } goto not_supported; case HTTP_POST: case HTTP_PUT: case HTTP_PATCH: if (user_method & BIT(client->method)) { return dynamic_post_put_req(dynamic_detail, client); } 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; } static void check_user_request_headers(struct http_header_capture_ctx *ctx, const char *buf) { size_t header_len; char *dest = &ctx->buffer[ctx->cursor]; size_t remaining = sizeof(ctx->buffer) - ctx->cursor; ctx->store_next_value = false; STRUCT_SECTION_FOREACH(http_header_name, header) { header_len = strlen(header->name); if (strcasecmp(buf, header->name) == 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 < header_len + 1) { LOG_DBG("Header '%s' dropped: buffer too small for name", header->name); ctx->status = HTTP_HEADER_STATUS_DROPPED; break; } strcpy(dest, header->name); ctx->headers[ctx->count].name = dest; ctx->cursor += (header_len + 1); ctx->store_next_value = true; break; } } } static int on_header_field(struct http_parser *parser, const char *at, size_t length) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); size_t offset = strnlen(ctx->header_buffer, sizeof(ctx->header_buffer)); if (offset + length > sizeof(ctx->header_buffer) - 1U) { LOG_DBG("Header %s too long (by %zu bytes)", "field", offset + length - sizeof(ctx->header_buffer) - 1U); ctx->header_buffer[0] = '\0'; } else { memcpy(ctx->header_buffer + offset, at, length); offset += length; ctx->header_buffer[offset] = '\0'; if (parser->state == s_header_value_discard_ws) { /* This means that the header field is fully parsed, * and we can use it directly. */ if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { check_user_request_headers(&ctx->header_capture_ctx, ctx->header_buffer); } if (strcasecmp(ctx->header_buffer, "Upgrade") == 0) { ctx->has_upgrade_header = true; } else if (strcasecmp(ctx->header_buffer, "Sec-WebSocket-Key") == 0) { ctx->websocket_sec_key_next = true; } ctx->header_buffer[0] = '\0'; } } ctx->parser_state = HTTP1_RECEIVING_HEADER_STATE; return 0; } static void populate_user_request_header(struct http_header_capture_ctx *ctx, const char *buf) { char *dest; size_t value_len; size_t remaining; if (ctx->store_next_value == false) { return; } ctx->store_next_value = false; value_len = strlen(buf); remaining = sizeof(ctx->buffer) - ctx->cursor; if (value_len + 1 >= remaining) { LOG_DBG("Header '%s' dropped: buffer too small for value", ctx->headers[ctx->count].name); ctx->status = HTTP_HEADER_STATUS_DROPPED; return; } dest = &ctx->buffer[ctx->cursor]; strcpy(dest, buf); ctx->cursor += (value_len + 1); ctx->headers[ctx->count].value = dest; ctx->count++; } static int on_header_value(struct http_parser *parser, const char *at, size_t length) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); size_t offset = strnlen(ctx->header_buffer, sizeof(ctx->header_buffer)); if (offset + length > sizeof(ctx->header_buffer) - 1U) { LOG_DBG("Header %s too long (by %zu bytes)", "value", offset + length - sizeof(ctx->header_buffer) - 1U); ctx->header_buffer[0] = '\0'; if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) && ctx->header_capture_ctx.store_next_value) { ctx->header_capture_ctx.store_next_value = false; ctx->header_capture_ctx.status = HTTP_HEADER_STATUS_DROPPED; } } else { memcpy(ctx->header_buffer + offset, at, length); offset += length; ctx->header_buffer[offset] = '\0'; if (parser->state == s_header_almost_done) { if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { populate_user_request_header(&ctx->header_capture_ctx, ctx->header_buffer); } if (ctx->has_upgrade_header) { if (strcasecmp(ctx->header_buffer, "h2c") == 0) { ctx->http2_upgrade = true; } else if (strcasecmp(ctx->header_buffer, "websocket") == 0) { ctx->websocket_upgrade = true; } ctx->has_upgrade_header = false; } if (ctx->websocket_sec_key_next) { #if defined(CONFIG_WEBSOCKET) strncpy(ctx->ws_sec_key, ctx->header_buffer, MIN(sizeof(ctx->ws_sec_key), offset)); #endif ctx->websocket_sec_key_next = false; } ctx->header_buffer[0] = '\0'; } } return 0; } static int on_headers_complete(struct http_parser *parser) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); ctx->parser_state = HTTP1_RECEIVED_HEADER_STATE; return 0; } static int on_url(struct http_parser *parser, const char *at, size_t length) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); size_t offset = strlen(ctx->url_buffer); ctx->parser_state = HTTP1_WAITING_HEADER_STATE; if (offset + length > sizeof(ctx->url_buffer) - 1) { LOG_DBG("URL too long to handle"); return -EMSGSIZE; } memcpy(ctx->url_buffer + offset, at, length); offset += length; ctx->url_buffer[offset] = '\0'; return 0; } static int on_body(struct http_parser *parser, const char *at, size_t length) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); ctx->parser_state = HTTP1_RECEIVING_DATA_STATE; ctx->http1_frag_data_len += length; return 0; } static int on_message_complete(struct http_parser *parser) { struct http_client_ctx *ctx = CONTAINER_OF(parser, struct http_client_ctx, parser); ctx->parser_state = HTTP1_MESSAGE_COMPLETE_STATE; return 0; } int enter_http1_request(struct http_client_ctx *client) { client->server_state = HTTP_SERVER_REQUEST_STATE; http_parser_init(&client->parser, HTTP_REQUEST); http_parser_settings_init(&client->parser_settings); client->parser_settings.on_header_field = on_header_field; client->parser_settings.on_header_value = on_header_value; client->parser_settings.on_headers_complete = on_headers_complete; client->parser_settings.on_url = on_url; client->parser_settings.on_body = on_body; client->parser_settings.on_message_complete = on_message_complete; client->parser_state = HTTP1_INIT_HEADER_STATE; client->http1_headers_sent = false; if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) { client->header_capture_ctx.store_next_value = false; } memset(client->header_buffer, 0, sizeof(client->header_buffer)); memset(client->url_buffer, 0, sizeof(client->url_buffer)); return 0; } int handle_http1_request(struct http_client_ctx *client) { int ret, path_len = 0; struct http_resource_detail *detail; bool skip_headers = (client->parser_state < HTTP1_RECEIVING_DATA_STATE); size_t parsed; LOG_DBG("HTTP_SERVER_REQUEST"); client->http1_frag_data_len = 0; parsed = http_parser_execute(&client->parser, &client->parser_settings, client->cursor, client->data_len); if (parsed > client->data_len) { LOG_ERR("HTTP/1 parser error, too much data consumed"); return -EBADMSG; } if (client->parser.http_errno != HPE_OK) { LOG_ERR("HTTP/1 parsing error, %d", client->parser.http_errno); return -EBADMSG; } if (client->parser_state < HTTP1_RECEIVED_HEADER_STATE) { client->cursor += parsed; client->data_len -= parsed; return 0; } client->method = client->parser.method; client->has_upgrade_header = client->parser.upgrade; if (skip_headers) { LOG_DBG("Requested URL: %s", client->url_buffer); size_t frag_headers_len; if (parsed < client->http1_frag_data_len) { return -EBADMSG; } frag_headers_len = parsed - client->http1_frag_data_len; parsed -= frag_headers_len; client->cursor += frag_headers_len; client->data_len -= frag_headers_len; } if (client->has_upgrade_header) { static const char upgrade_required[] = "HTTP/1.1 426 Upgrade required\r\n" "Upgrade: "; static const char upgrade_msg[] = "Content-Length: 13\r\n\r\n" "Wrong upgrade"; const char *needed_upgrade = "h2c\r\n"; if (client->websocket_upgrade) { if (IS_ENABLED(CONFIG_HTTP_SERVER_WEBSOCKET)) { detail = get_resource_detail(client->url_buffer, &path_len, true); if (detail == NULL) { goto not_found; } client->current_detail = detail; return handle_http1_to_websocket_upgrade(client); } goto upgrade_not_found; } if (client->http2_upgrade) { return handle_http1_to_http2_upgrade(client); } upgrade_not_found: ret = http_server_sendall(client, upgrade_required, sizeof(upgrade_required) - 1); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } ret = http_server_sendall(client, needed_upgrade, strlen(needed_upgrade)); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } ret = http_server_sendall(client, upgrade_msg, sizeof(upgrade_msg) - 1); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } } 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_http1_static_resource( (struct http_resource_detail_static *)detail, client); if (ret < 0) { return ret; } #if defined(CONFIG_FILE_SYSTEM) } else if (detail->type == HTTP_RESOURCE_TYPE_STATIC_FS) { ret = handle_http1_static_fs_resource( (struct http_resource_detail_static_fs *)detail, client); if (ret < 0) { return ret; } #endif } else if (detail->type == HTTP_RESOURCE_TYPE_DYNAMIC) { ret = handle_http1_dynamic_resource( (struct http_resource_detail_dynamic *)detail, client); if (ret < 0) { return ret; } } } else { not_found: ; /* Add extra semicolon to make clang to compile when using label */ ret = http_server_sendall(client, not_found_response, sizeof(not_found_response) - 1); if (ret < 0) { LOG_DBG("Cannot write to socket (%d)", ret); return ret; } } client->cursor += parsed; client->data_len -= parsed; if (client->parser_state == HTTP1_MESSAGE_COMPLETE_STATE) { if ((client->parser.flags & F_CONNECTION_CLOSE) == 0) { LOG_DBG("Waiting for another request, client %p", client); client->server_state = HTTP_SERVER_PREFACE_STATE; } else { LOG_DBG("Connection closed, client %p", client); enter_http_done_state(client); } } return 0; }