net: lib: http_server: allow application to access request headers

Allow application to register certain HTTP request headers to be stored
by the server. These stored headers can then be accessed from a dynamic
resource callback.

Signed-off-by: Matt Rodgers <mrodgers@witekio.com>
This commit is contained in:
Matt Rodgers 2024-08-13 10:59:38 +01:00 committed by Anas Nashif
parent 666559b9d9
commit 0c8bdbc45d
10 changed files with 574 additions and 6 deletions

View file

@ -26,6 +26,7 @@
#include <zephyr/net/http/parser.h>
#include <zephyr/net/http/hpack.h>
#include <zephyr/net/socket.h>
#include <zephyr/sys/iterable_sections.h>
#ifdef __cplusplus
extern "C" {
@ -38,18 +39,15 @@ extern "C" {
#define HTTP_SERVER_MAX_STREAMS CONFIG_HTTP_SERVER_MAX_STREAMS
#define HTTP_SERVER_MAX_CONTENT_TYPE_LEN CONFIG_HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH
#define HTTP_SERVER_MAX_URL_LENGTH CONFIG_HTTP_SERVER_MAX_URL_LENGTH
#define HTTP_SERVER_MAX_HEADER_LEN CONFIG_HTTP_SERVER_MAX_HEADER_LEN
#else
#define HTTP_SERVER_CLIENT_BUFFER_SIZE 0
#define HTTP_SERVER_MAX_STREAMS 0
#define HTTP_SERVER_MAX_CONTENT_TYPE_LEN 0
#define HTTP_SERVER_MAX_URL_LENGTH 0
#define HTTP_SERVER_MAX_HEADER_LEN 0
#endif
/* Maximum header field name / value length. This is only used to detect Upgrade and
* websocket header fields and values in the http1 server so the value is quite short.
*/
#define HTTP_SERVER_MAX_HEADER_LEN 32
#define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
/** @endcond */
@ -329,6 +327,46 @@ struct http2_frame {
uint8_t padding_len; /**< Frame padding length. */
};
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
/** @brief HTTP header representation */
struct http_header {
const char *name; /**< Pointer to header name NULL-terminated string. */
const char *value; /**< Pointer to header value NULL-terminated string. */
};
/** @brief Status of captured headers */
enum http_header_status {
HTTP_HEADER_STATUS_OK, /**< All available headers were successfully captured. */
HTTP_HEADER_STATUS_DROPPED, /**< One or more headers were dropped due to lack of space. */
};
/** @brief Context for capturing HTTP headers */
struct http_header_capture_ctx {
/** Buffer for HTTP headers captured for application use */
unsigned char buffer[CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE];
/** Descriptor of each captured HTTP header */
struct http_header headers[CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT];
/** Status of captured headers */
enum http_header_status status;
/** Number of headers captured */
size_t count;
/** Current position in buffer */
size_t cursor;
/** The next HTTP header value should be stored */
bool store_next_value;
};
/** @brief HTTP header name representation */
struct http_header_name {
const char *name; /**< Pointer to header name NULL-terminated string. */
};
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
/**
* @brief Representation of an HTTP client connected to the server.
*/
@ -372,6 +410,11 @@ struct http_client_ctx {
/** HTTP/1 parser context. */
struct http_parser parser;
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
/** Header capture context */
struct http_header_capture_ctx header_capture_ctx;
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
/** Request URL. */
unsigned char url_buffer[HTTP_SERVER_MAX_URL_LENGTH];
@ -427,6 +470,23 @@ struct http_client_ctx {
bool expect_continuation : 1;
};
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
/**
* @brief Register an HTTP request header to be captured by the server
*
* @param _id variable name for the header capture instance
* @param _header header to be captured, as literal string
*/
#define HTTP_SERVER_REGISTER_HEADER_CAPTURE(_id, _header) \
BUILD_ASSERT(sizeof(_header) <= CONFIG_HTTP_SERVER_MAX_HEADER_LEN, \
"Header is too long to be captured, try increasing " \
"CONFIG_HTTP_SERVER_MAX_HEADER_LEN"); \
static const char *const _id##_str = _header; \
static const STRUCT_SECTION_ITERABLE(http_header_name, _id) = { \
.name = _id##_str, \
}
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
/** @brief Start the HTTP2 server.
*
* The server runs in a background thread. Once started, the server will create

View file

@ -22,5 +22,9 @@ if(CONFIG_HTTP_SERVER AND CONFIG_WEBSOCKET)
endif()
if(CONFIG_HTTP_SERVER AND CONFIG_FILE_SYSTEM)
zephyr_linker_sources(SECTIONS iterables.ld)
zephyr_linker_sources(SECTIONS iterables_content_type.ld)
endif()
if(CONFIG_HTTP_SERVER AND CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
zephyr_linker_sources(SECTIONS iterables_header_capture.ld)
endif()

View file

@ -96,6 +96,40 @@ config HTTP_SERVER_MAX_CONTENT_TYPE_LENGTH
help
This setting determines the maximum length of the HTTP Content-Length field.
config HTTP_SERVER_MAX_HEADER_LEN
int "Maximum HTTP Header Field/Value Length"
default 32
range 32 512
help
This setting determines the maximum length of HTTP header field or value
that can be parsed. The default value is sufficient for HTTP server
internal header processing, and only needs to be increased if the
application wishes to access headers of a greater length.
config HTTP_SERVER_CAPTURE_HEADERS
bool "Allow capturing HTTP headers for application use"
help
This setting enables the HTTP server to capture selected headers that have
been registered by the application.
config HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE
int "Size of buffer for capturing HTTP headers for application use"
default 128
range 32 2048
depends on HTTP_SERVER_CAPTURE_HEADERS
help
This setting determines the size of the (per-client) buffer used to store
HTTP headers for later use by the application.
config HTTP_SERVER_CAPTURE_HEADER_COUNT
int "Maximum number of HTTP headers to be captured for application use"
default 3
range 1 100
depends on HTTP_SERVER_CAPTURE_HEADERS
help
This setting determines the maximum number of HTTP headers it is possible
to capture for application use in a single HTTP request.
config HTTP_SERVER_CLIENT_INACTIVITY_TIMEOUT
int "Client inactivity timeout (seconds)"
default 10

View file

@ -405,6 +405,12 @@ static int handle_http_preface(struct http_client_ctx *client)
return -EAGAIN;
}
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
client->header_capture_ctx.count = 0;
client->header_capture_ctx.cursor = 0;
client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK;
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
if (strncmp(client->cursor, HTTP2_PREFACE, sizeof(HTTP2_PREFACE) - 1) != 0) {
return enter_http1_request(client);
}

View file

@ -433,6 +433,43 @@ not_supported:
return 0;
}
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
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;
}
}
}
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int on_header_field(struct http_parser *parser, const char *at,
size_t length)
{
@ -454,6 +491,10 @@ static int on_header_field(struct http_parser *parser, const char *at,
/* This means that the header field is fully parsed,
* and we can use it directly.
*/
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
check_user_request_headers(&ctx->header_capture_ctx, ctx->header_buffer);
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
if (strcasecmp(ctx->header_buffer, "Upgrade") == 0) {
ctx->has_upgrade_header = true;
} else if (strcasecmp(ctx->header_buffer, "Sec-WebSocket-Key") == 0) {
@ -469,6 +510,37 @@ static int on_header_field(struct http_parser *parser, const char *at,
return 0;
}
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
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++;
}
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int on_header_value(struct http_parser *parser,
const char *at, size_t length)
{
@ -481,12 +553,22 @@ static int on_header_value(struct http_parser *parser,
LOG_DBG("Header %s too long (by %zu bytes)", "value",
offset + length - sizeof(ctx->header_buffer) - 1U);
ctx->header_buffer[0] = '\0';
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
if (ctx->header_capture_ctx.store_next_value) {
ctx->header_capture_ctx.store_next_value = false;
ctx->header_capture_ctx.status = HTTP_HEADER_STATUS_DROPPED;
}
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
} else {
memcpy(ctx->header_buffer + offset, at, length);
offset += length;
ctx->header_buffer[offset] = '\0';
if (parser->state == s_header_almost_done) {
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
populate_user_request_header(&ctx->header_capture_ctx, ctx->header_buffer);
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
if (ctx->has_upgrade_header) {
if (strcasecmp(ctx->header_buffer, "h2c") == 0) {
ctx->http2_upgrade = true;
@ -584,6 +666,10 @@ int enter_http1_request(struct http_client_ctx *client)
client->parser_state = HTTP1_INIT_HEADER_STATE;
client->http1_headers_sent = false;
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
client->header_capture_ctx.store_next_value = false;
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
memset(client->header_buffer, 0, sizeof(client->header_buffer));
memset(client->url_buffer, 0, sizeof(client->url_buffer));

View file

@ -1135,9 +1135,62 @@ int handle_http_frame_data(struct http_client_ctx *client)
return 0;
}
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
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;
}
}
}
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int process_header(struct http_client_ctx *client,
struct http_hpack_header_buf *header)
{
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
check_user_request_headers_http2(&client->header_capture_ctx, header);
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
if (header->name_len == (sizeof(":method") - 1) &&
memcmp(header->name, ":method", header->name_len) == 0) {
/* TODO Improve string to method conversion */

View file

@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 Witekio
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/linker/iterable_sections.h>
ITERABLE_SECTION_ROM(http_header_name, Z_LINK_ITERABLE_SUBALIGN)

View file

@ -41,6 +41,9 @@ CONFIG_JSON_LIBRARY=y
CONFIG_HTTP_PARSER_URL=y
CONFIG_HTTP_PARSER=y
CONFIG_HTTP_SERVER=y
CONFIG_HTTP_SERVER_CAPTURE_HEADERS=y
CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE=64
CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT=2
CONFIG_HTTP_SERVER_MAX_CLIENTS=5
CONFIG_HTTP_SERVER_MAX_STREAMS=5

View file

@ -63,6 +63,32 @@
0x49, 0x7c, 0xa5, 0x8a, 0xe8, 0x19, 0xaa, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00
#define TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE1_STREAM_1 \
0x00, 0x00, 0x39, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \
0xb0, 0xbf, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, \
0x7a, 0x88, 0x25, 0xb6, 0x50, 0xc3, 0xab, 0xbc, 0x15, 0xc1, 0x53, 0x03, \
0x2a, 0x2f, 0x2a, 0x40, 0x88, 0x49, 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, \
0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b
#define TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE2_STREAM_1 \
0x00, 0x00, 0x5a, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \
0xb0, 0xbf, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, \
0x7a, 0xa9, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, \
0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, \
0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x18, 0xc6, 0x31, 0x8c, \
0x63, 0x18, 0xc6, 0x31, 0x8c, 0x63, 0x1f, 0x53, 0x03, 0x2a, 0x2f, 0x2a, \
0x40, 0x88, 0x49, 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, 0x9f, 0x87, 0x49, \
0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b
#define TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE3_STREAM_1 \
0x00, 0x00, 0x4c, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \
0xb0, 0xbf, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, \
0x7a, 0x88, 0x25, 0xb6, 0x50, 0xc3, 0xab, 0xbc, 0x15, 0xc1, 0x53, 0x03, \
0x2a, 0x2f, 0x2a, 0x40, 0x88, 0x49, 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, \
0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b, 0x40, 0x88, 0x49, \
0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, 0x82, 0x88, 0x49, 0x50, 0x98, 0xbb, \
0x8e, 0x8b, 0x4a, 0x2f
#define TEST_HTTP2_HEADERS_POST_DYNAMIC_STREAM_1 \
0x00, 0x00, 0x30, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x83, 0x86, 0x41, 0x87, 0x0b, 0xe2, 0x5c, 0x0b, 0x89, 0x70, 0xff, 0x04, \
@ -204,6 +230,63 @@ struct http_resource_detail_dynamic dynamic_detail = {
HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic",
&dynamic_detail);
static uint8_t dynamic_headers_buffer[32];
static struct http_header_capture_ctx header_capture_ctx_clone;
static int dynamic_headers_cb(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, void *user_data)
{
ptrdiff_t offset;
struct http_header *hdrs_src;
struct http_header *hdrs_dst;
if (status == HTTP_SERVER_DATA_FINAL) {
/* Copy the captured header info to static buffer for later assertions in testcase.
* Don't assume that the buffer inside client context remains valid after return
* from the callback - this is currently the case at the time of writing but could
* change. Also need to update pointers within structure with an offset to point at
* new buffer.
*/
memcpy(&header_capture_ctx_clone, &client->header_capture_ctx,
sizeof(header_capture_ctx_clone));
hdrs_src = client->header_capture_ctx.headers;
hdrs_dst = header_capture_ctx_clone.headers;
offset = header_capture_ctx_clone.buffer - client->header_capture_ctx.buffer;
for (int i = 0; i < CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT; i++) {
if (hdrs_src[i].name != NULL) {
hdrs_dst[i].name = hdrs_src[i].name + offset;
}
if (hdrs_dst[i].value != NULL) {
hdrs_dst[i].value = hdrs_src[i].value + offset;
}
}
}
return 0;
}
struct http_resource_detail_dynamic dynamic_headers_detail = {
.common = {
.type = HTTP_RESOURCE_TYPE_DYNAMIC,
.bitmask_of_supported_http_methods = BIT(HTTP_GET) | BIT(HTTP_POST),
.content_type = "text/plain",
},
.cb = dynamic_headers_cb,
.data_buffer = dynamic_headers_buffer,
.data_buffer_len = sizeof(dynamic_headers_buffer),
.user_data = NULL
};
HTTP_RESOURCE_DEFINE(dynamic_headers_resource, test_http_service, "/header_capture",
&dynamic_headers_detail);
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_user_agent, "User-Agent");
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header, "Test-Header");
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header2, "Test-Header2");
static int client_fd = -1;
static uint8_t buf[BUFFER_SIZE];
@ -904,6 +987,236 @@ ZTEST(server_function_tests, test_http2_rst_stream)
zassert_equal(ret, 0, "Connection should've been closed");
}
static const char http1_header_capture_common_response[] = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"0\r\n\r\n";
static void test_http1_header_capture_common(const char *request)
{
size_t offset = 0;
int ret;
ret = zsock_send(client_fd, request, strlen(request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
test_read_data(&offset, sizeof(http1_header_capture_common_response) - 1);
}
ZTEST(server_function_tests, test_http1_header_capture)
{
static const char request[] = "GET /header_capture HTTP/1.1\r\n"
"User-Agent: curl/7.68.0\r\n"
"Test-Header: test_value\r\n"
"Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n"
"\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 2,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK,
"Header capture status was not OK");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
zassert_not_equal(hdrs[1].name, NULL, "Second header name is NULL");
zassert_not_equal(hdrs[1].value, NULL, "Second header value is NULL");
ret = strcmp(hdrs[0].name, "User-Agent");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "curl/7.68.0");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
ZTEST(server_function_tests, test_http1_header_too_long)
{
static const char request[] =
"GET /header_capture HTTP/1.1\r\n"
"User-Agent: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n"
"Test-Header: test_value\r\n"
"Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n"
"\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 1,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status was OK, but should not have been");
/* First header too long should not stop second header being captured into first slot */
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
ret = strcmp(hdrs[0].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
ZTEST(server_function_tests, test_http1_header_too_many)
{
static const char request[] = "GET /header_capture HTTP/1.1\r\n"
"User-Agent: curl/7.68.0\r\n"
"Test-Header: test_value\r\n"
"Test-Header2: test_value2\r\n"
"Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n"
"\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 2,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status OK, but should not have been");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
zassert_not_equal(hdrs[1].name, NULL, "Second header name is NULL");
zassert_not_equal(hdrs[1].value, NULL, "Second header value is NULL");
ret = strcmp(hdrs[0].name, "User-Agent");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "curl/7.68.0");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
static void common_verify_http2_get_header_capture_request(const uint8_t *request,
size_t request_len)
{
size_t offset = 0;
int ret;
dynamic_payload_len = strlen(TEST_DYNAMIC_GET_PAYLOAD);
memcpy(dynamic_payload, TEST_DYNAMIC_GET_PAYLOAD, dynamic_payload_len);
ret = zsock_send(client_fd, request, request_len, 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
memset(buf, 0, sizeof(buf));
expect_http2_settings_frame(&offset, false);
expect_http2_settings_frame(&offset, true);
expect_http2_headers_frame(&offset, TEST_STREAM_ID_1, HTTP2_FLAG_END_HEADERS);
expect_http2_data_frame(&offset, TEST_STREAM_ID_1, NULL, 0, HTTP2_FLAG_END_STREAM);
}
ZTEST(server_function_tests, test_http2_header_capture)
{
static const uint8_t request[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE1_STREAM_1,
TEST_HTTP2_GOAWAY,
};
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 2,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK,
"Header capture status was not OK");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
zassert_not_equal(hdrs[1].name, NULL, "Second header name is NULL");
zassert_not_equal(hdrs[1].value, NULL, "Second header value is NULL");
ret = strcmp(hdrs[0].name, "User-Agent");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "curl/7.81.0");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
ZTEST(server_function_tests, test_http2_header_too_long)
{
static const uint8_t request[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE2_STREAM_1,
TEST_HTTP2_GOAWAY,
};
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 1,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status was OK, but should not have been");
/* First header too long should not stop second header being captured into first slot */
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
ret = strcmp(hdrs[0].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
ZTEST(server_function_tests, test_http2_header_too_many)
{
static const uint8_t request[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE3_STREAM_1,
TEST_HTTP2_GOAWAY,
};
struct http_header *hdrs = header_capture_ctx_clone.headers;
int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 2,
"Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status OK, but should not have been");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs[0].value, NULL, "First header value is NULL");
zassert_not_equal(hdrs[1].name, NULL, "Second header name is NULL");
zassert_not_equal(hdrs[1].value, NULL, "Second header value is NULL");
ret = strcmp(hdrs[0].name, "User-Agent");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[0].value, "curl/7.81.0");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].name, "Test-Header");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs[1].value, "test_value");
zassert_equal(0, ret, "Header strings did not match");
}
ZTEST(server_function_tests_no_init, test_http_server_start_stop)
{
struct sockaddr_in sa = { 0 };