zephyr/subsys/net/lib/http/http_server_http1.c
Matt Rodgers b7091ed439 net: http_server: serve resources only for their defined services
Ensure that HTTP resources can only be served to a client connected on
the specific service(s) that the resource was registered against using
the HTTP_RESOURCE_DEFINE macro.

This allows different resources to be registered to different services,
for example to make some resources only available via an HTTPS service
and not via unencrypted HTTP.

Signed-off-by: Matt Rodgers <mrodgers@witekio.com>
2025-01-09 14:13:57 +01:00

954 lines
25 KiB
C

/*
* 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 <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <zephyr/fs/fs.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/net/http/service.h>
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->service, client->url_buffer,
&path_len, true);
if (detail == NULL) {
goto not_found;
}
detail->path_len = path_len;
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->service, 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;
}