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:
parent
666559b9d9
commit
0c8bdbc45d
10 changed files with 574 additions and 6 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
9
subsys/net/lib/http/iterables_header_capture.ld
Normal file
9
subsys/net/lib/http/iterables_header_capture.ld
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
Loading…
Reference in a new issue