Browse Source

net: http_server: fix header capture on concurrent http2 streams

Concurrent HTTP POST requests on different HTTP2 concurrent streams
require that the client's header_capture_context is re-used to capture
headers on a second stream before all of the body data has been received
(and sent to the application) on the first stream.

As a result, any captured headers must be sent to the application
callback before any headers can be received on a different stream. In
practice this means that for HTTP2 the application callback is called
for the first time on receiving a headers frame, before any data frames
are received. All subsequent application callbacks will not include the
request header data.

While this mechanism is not necessary for HTTP1, it is also updated to
only send headers in the first application callback for consistency.

Fixes #82273

Signed-off-by: Matt Rodgers <mrodgers@witekio.com>
pull/82574/head
Matt Rodgers 7 months ago committed by Anas Nashif
parent
commit
ddaeb1379a
  1. 69
      include/zephyr/net/http/server.h
  2. 4
      samples/net/prometheus/src/main.c
  3. 4
      samples/net/prometheus/src/stats.c
  4. 32
      samples/net/sockets/http_server/src/main.c
  5. 3
      subsys/net/lib/http/headers/server_internal.h
  6. 28
      subsys/net/lib/http/http_server_core.c
  7. 44
      subsys/net/lib/http/http_server_http1.c
  8. 59
      subsys/net/lib/http/http_server_http2.c
  9. 232
      tests/net/lib/http_server/core/src/main.c

69
include/zephyr/net/http/server.h

@ -49,6 +49,14 @@ extern "C" {
#define HTTP_SERVER_MAX_HEADER_LEN 0 #define HTTP_SERVER_MAX_HEADER_LEN 0
#endif #endif
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
#define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE
#define HTTP_SERVER_CAPTURE_HEADER_COUNT CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT
#else
#define HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE 0
#define HTTP_SERVER_CAPTURE_HEADER_COUNT 0
#endif
#define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" #define HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
/** @endcond */ /** @endcond */
@ -162,20 +170,36 @@ enum http_data_status {
HTTP_SERVER_DATA_FINAL = 1, HTTP_SERVER_DATA_FINAL = 1,
}; };
/** @brief Status of captured request 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. */
HTTP_HEADER_STATUS_NONE, /**< No header status is available. */
};
/** @brief HTTP header representation */ /** @brief HTTP header representation */
struct http_header { struct http_header {
const char *name; /**< Pointer to header name NULL-terminated string. */ const char *name; /**< Pointer to header name NULL-terminated string. */
const char *value; /**< Pointer to header value NULL-terminated string. */ const char *value; /**< Pointer to header value NULL-terminated string. */
}; };
/** @brief HTTP request context */
struct http_request_ctx {
uint8_t *data; /**< HTTP request data */
size_t data_len; /**< Length of HTTP request data */
struct http_header *headers; /**< Array of HTTP request headers */
size_t header_count; /**< Array length of HTTP request headers */
enum http_header_status headers_status; /**< Status of HTTP request headers */
};
/** @brief HTTP response context */ /** @brief HTTP response context */
struct http_response_ctx { struct http_response_ctx {
enum http_status status; /** HTTP status code to include in response */ enum http_status status; /**< HTTP status code to include in response */
const struct http_header *headers; /** Array of HTTP headers */ const struct http_header *headers; /**< Array of HTTP headers */
size_t header_count; /** Length of headers array */ size_t header_count; /**< Length of headers array */
const uint8_t *body; /** Pointer to body data */ const uint8_t *body; /**< Pointer to body data */
size_t body_len; /** Length of body data */ size_t body_len; /**< Length of body data */
bool final_chunk; /** Flag set to true when the application has no more data to send */ bool final_chunk; /**< Flag set to true when the application has no more data to send */
}; };
/** /**
@ -185,20 +209,16 @@ struct http_response_ctx {
* *
* @param client HTTP context information for this client connection. * @param client HTTP context information for this client connection.
* @param status HTTP data status, indicate whether more data is expected or not. * @param status HTTP data status, indicate whether more data is expected or not.
* @param data_buffer Data received. * @param request_ctx Request context structure containing HTTP request data that was received.
* @param data_len Amount of data received. * @param response_ctx Response context structure for application to populate with response data.
* @param response_ctx
* @param user_data User specified data. * @param user_data User specified data.
* *
* @return >0 amount of data to be sent to client, let server to call this * @return 0 success, server can send any response data provided in the response_ctx.
* function again when new data is received.
* 0 nothing to sent to client, close the connection
* <0 error, close the connection. * <0 error, close the connection.
*/ */
typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client, typedef int (*http_resource_dynamic_cb_t)(struct http_client_ctx *client,
enum http_data_status status, enum http_data_status status,
uint8_t *data_buffer, const struct http_request_ctx *request_ctx,
size_t data_len,
struct http_response_ctx *response_ctx, struct http_response_ctx *response_ctx,
void *user_data); void *user_data);
@ -341,20 +361,14 @@ struct http2_frame {
uint8_t padding_len; /**< Frame padding length. */ uint8_t padding_len; /**< Frame padding length. */
}; };
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) /** @cond INTERNAL_HIDDEN */
/** @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 */ /** @brief Context for capturing HTTP headers */
struct http_header_capture_ctx { struct http_header_capture_ctx {
/** Buffer for HTTP headers captured for application use */ /** Buffer for HTTP headers captured for application use */
unsigned char buffer[CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE]; unsigned char buffer[HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE];
/** Descriptor of each captured HTTP header */ /** Descriptor of each captured HTTP header */
struct http_header headers[CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT]; struct http_header headers[HTTP_SERVER_CAPTURE_HEADER_COUNT];
/** Status of captured headers */ /** Status of captured headers */
enum http_header_status status; enum http_header_status status;
@ -365,15 +379,18 @@ struct http_header_capture_ctx {
/** Current position in buffer */ /** Current position in buffer */
size_t cursor; size_t cursor;
/** The HTTP2 stream associated with the current headers */
struct http2_stream_ctx *current_stream;
/** The next HTTP header value should be stored */ /** The next HTTP header value should be stored */
bool store_next_value; bool store_next_value;
}; };
/** @endcond */
/** @brief HTTP header name representation */ /** @brief HTTP header name representation */
struct http_header_name { struct http_header_name {
const char *name; /**< Pointer to header name NULL-terminated string. */ 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. * @brief Representation of an HTTP client connected to the server.
@ -418,10 +435,8 @@ struct http_client_ctx {
/** HTTP/1 parser context. */ /** HTTP/1 parser context. */
struct http_parser parser; struct http_parser parser;
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
/** Header capture context */ /** Header capture context */
struct http_header_capture_ctx header_capture_ctx; struct http_header_capture_ctx header_capture_ctx;
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
/** Request URL. */ /** Request URL. */
unsigned char url_buffer[HTTP_SERVER_MAX_URL_LENGTH]; unsigned char url_buffer[HTTP_SERVER_MAX_URL_LENGTH];
@ -478,7 +493,6 @@ struct http_client_ctx {
bool expect_continuation : 1; bool expect_continuation : 1;
}; };
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
/** /**
* @brief Register an HTTP request header to be captured by the server * @brief Register an HTTP request header to be captured by the server
* *
@ -493,7 +507,6 @@ struct http_client_ctx {
static const STRUCT_SECTION_ITERABLE(http_header_name, _id) = { \ static const STRUCT_SECTION_ITERABLE(http_header_name, _id) = { \
.name = _id##_str, \ .name = _id##_str, \
} }
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
/** @brief Start the HTTP2 server. /** @brief Start the HTTP2 server.
* *

4
samples/net/prometheus/src/main.c

@ -43,8 +43,8 @@ HTTP_SERVICE_DEFINE(test_http_service, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &test_htt
10, NULL); 10, NULL);
static int dyn_handler(struct http_client_ctx *client, enum http_data_status status, static int dyn_handler(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, const struct http_request_ctx *request_ctx,
void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
int ret; int ret;
static uint8_t prom_buffer[256]; static uint8_t prom_buffer[256];

4
samples/net/prometheus/src/stats.c

@ -33,8 +33,8 @@ static struct prometheus_collector *stats_collector;
static struct prometheus_collector_walk_context walk_ctx; static struct prometheus_collector_walk_context walk_ctx;
static int stats_handler(struct http_client_ctx *client, enum http_data_status status, static int stats_handler(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, const struct http_request_ctx *request_ctx,
void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
int ret; int ret;
static uint8_t prom_buffer[1024]; static uint8_t prom_buffer[1024];

32
samples/net/sockets/http_server/src/main.c

@ -68,8 +68,8 @@ static struct http_resource_detail_static main_js_gz_resource_detail = {
}; };
static int echo_handler(struct http_client_ctx *client, enum http_data_status status, static int echo_handler(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, const struct http_request_ctx *request_ctx,
void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
#define MAX_TEMP_PRINT_LEN 32 #define MAX_TEMP_PRINT_LEN 32
static char print_str[MAX_TEMP_PRINT_LEN]; static char print_str[MAX_TEMP_PRINT_LEN];
@ -84,11 +84,11 @@ static int echo_handler(struct http_client_ctx *client, enum http_data_status st
__ASSERT_NO_MSG(buffer != NULL); __ASSERT_NO_MSG(buffer != NULL);
processed += len; processed += request_ctx->data_len;
snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", snprintf(print_str, sizeof(print_str), "%s received (%zd bytes)", http_method_str(method),
http_method_str(method), len); request_ctx->data_len);
LOG_HEXDUMP_DBG(buffer, len, print_str); LOG_HEXDUMP_DBG(request_ctx->data, request_ctx->data_len, print_str);
if (status == HTTP_SERVER_DATA_FINAL) { if (status == HTTP_SERVER_DATA_FINAL) {
LOG_DBG("All data received (%zd bytes).", processed); LOG_DBG("All data received (%zd bytes).", processed);
@ -96,8 +96,8 @@ static int echo_handler(struct http_client_ctx *client, enum http_data_status st
} }
/* Echo data back to client */ /* Echo data back to client */
response_ctx->body = buffer; response_ctx->body = request_ctx->data;
response_ctx->body_len = len; response_ctx->body_len = request_ctx->data_len;
response_ctx->final_chunk = (status == HTTP_SERVER_DATA_FINAL); response_ctx->final_chunk = (status == HTTP_SERVER_DATA_FINAL);
return 0; return 0;
@ -113,8 +113,8 @@ static struct http_resource_detail_dynamic echo_resource_detail = {
}; };
static int uptime_handler(struct http_client_ctx *client, enum http_data_status status, static int uptime_handler(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, const struct http_request_ctx *request_ctx,
void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
int ret; int ret;
static uint8_t uptime_buf[sizeof(STRINGIFY(INT64_MAX))]; static uint8_t uptime_buf[sizeof(STRINGIFY(INT64_MAX))];
@ -172,20 +172,20 @@ static void parse_led_post(uint8_t *buf, size_t len)
} }
static int led_handler(struct http_client_ctx *client, enum http_data_status status, static int led_handler(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, struct http_response_ctx *response_ctx, const struct http_request_ctx *request_ctx,
void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
static uint8_t post_payload_buf[32]; static uint8_t post_payload_buf[32];
static size_t cursor; static size_t cursor;
LOG_DBG("LED handler status %d, size %zu", status, len); LOG_DBG("LED handler status %d, size %zu", status, request_ctx->data_len);
if (status == HTTP_SERVER_DATA_ABORTED) { if (status == HTTP_SERVER_DATA_ABORTED) {
cursor = 0; cursor = 0;
return 0; return 0;
} }
if (len + cursor > sizeof(post_payload_buf)) { if (request_ctx->data_len + cursor > sizeof(post_payload_buf)) {
cursor = 0; cursor = 0;
return -ENOMEM; return -ENOMEM;
} }
@ -194,8 +194,8 @@ static int led_handler(struct http_client_ctx *client, enum http_data_status sta
* chunks (e.g. if the header size was such that the whole HTTP request exceeds the size of * chunks (e.g. if the header size was such that the whole HTTP request exceeds the size of
* the client buffer). * the client buffer).
*/ */
memcpy(post_payload_buf + cursor, buffer, len); memcpy(post_payload_buf + cursor, request_ctx->data, request_ctx->data_len);
cursor += len; cursor += request_ctx->data_len;
if (status == HTTP_SERVER_DATA_FINAL) { if (status == HTTP_SERVER_DATA_FINAL) {
parse_led_post(post_payload_buf, cursor); parse_led_post(post_payload_buf, cursor);

3
subsys/net/lib/http/headers/server_internal.h

@ -51,4 +51,7 @@ int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffe
size_t buflen); size_t buflen);
const char *get_frame_type_name(enum http2_frame_type type); const char *get_frame_type_name(enum http2_frame_type type);
void populate_request_ctx(struct http_request_ctx *req_ctx, uint8_t *data, size_t len,
struct http_header_capture_ctx *header_ctx);
#endif /* HTTP_SERVER_INTERNAL_H_ */ #endif /* HTTP_SERVER_INTERNAL_H_ */

28
subsys/net/lib/http/http_server_core.c

@ -305,6 +305,7 @@ static void client_release_resources(struct http_client_ctx *client)
{ {
struct http_resource_detail *detail; struct http_resource_detail *detail;
struct http_resource_detail_dynamic *dynamic_detail; struct http_resource_detail_dynamic *dynamic_detail;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
HTTP_SERVICE_FOREACH(service) { HTTP_SERVICE_FOREACH(service) {
@ -331,8 +332,10 @@ static void client_release_resources(struct http_client_ctx *client)
continue; continue;
} }
dynamic_detail->cb(client, HTTP_SERVER_DATA_ABORTED, NULL, 0, &response_ctx, populate_request_ctx(&request_ctx, NULL, 0, NULL);
dynamic_detail->user_data);
dynamic_detail->cb(client, HTTP_SERVER_DATA_ABORTED, &request_ctx,
&response_ctx, dynamic_detail->user_data);
} }
} }
} }
@ -421,11 +424,11 @@ static int handle_http_preface(struct http_client_ctx *client)
return -EAGAIN; return -EAGAIN;
} }
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
client->header_capture_ctx.count = 0; client->header_capture_ctx.count = 0;
client->header_capture_ctx.cursor = 0; client->header_capture_ctx.cursor = 0;
client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK; 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) { if (strncmp(client->cursor, HTTP2_PREFACE, sizeof(HTTP2_PREFACE) - 1) != 0) {
return enter_http1_request(client); return enter_http1_request(client);
@ -829,6 +832,23 @@ bool http_response_is_provided(struct http_response_ctx *rsp)
return false; return false;
} }
void populate_request_ctx(struct http_request_ctx *req_ctx, uint8_t *data, size_t len,
struct http_header_capture_ctx *header_ctx)
{
req_ctx->data = data;
req_ctx->data_len = len;
if (NULL == header_ctx || header_ctx->status == HTTP_HEADER_STATUS_NONE) {
req_ctx->headers = NULL;
req_ctx->header_count = 0;
req_ctx->headers_status = HTTP_HEADER_STATUS_NONE;
} else {
req_ctx->headers = header_ctx->headers;
req_ctx->header_count = header_ctx->count;
req_ctx->headers_status = header_ctx->status;
}
}
int http_server_start(void) int http_server_start(void)
{ {
if (server_running) { if (server_running) {

44
subsys/net/lib/http/http_server_http1.c

@ -260,6 +260,7 @@ static int dynamic_get_req(struct http_resource_detail_dynamic *dynamic_detail,
int ret, len; int ret, len;
char *ptr; char *ptr;
enum http_data_status status; enum http_data_status status;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
/* Start of GET params */ /* Start of GET params */
@ -269,8 +270,9 @@ static int dynamic_get_req(struct http_resource_detail_dynamic *dynamic_detail,
do { do {
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx);
ret = dynamic_detail->cb(client, status, ptr, len, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -302,6 +304,7 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail,
int ret; int ret;
char *ptr = client->cursor; char *ptr = client->cursor;
enum http_data_status status; enum http_data_status status;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
if (ptr == NULL) { if (ptr == NULL) {
@ -315,13 +318,19 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail,
} }
memset(&response_ctx, 0, sizeof(response_ctx)); 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, ptr, client->data_len, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; 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. /* 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. * Don't send a default response until the application has had a chance to respond.
*/ */
@ -335,8 +344,9 @@ static int dynamic_post_req(struct http_resource_detail_dynamic *dynamic_detail,
/* Once all data is transferred to application, repeat cb until response is complete */ /* 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) { while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) {
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, 0, &client->header_capture_ctx);
ret = dynamic_detail->cb(client, status, ptr, 0, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -544,7 +554,6 @@ not_supported:
return 0; return 0;
} }
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
static void check_user_request_headers(struct http_header_capture_ctx *ctx, const char *buf) static void check_user_request_headers(struct http_header_capture_ctx *ctx, const char *buf)
{ {
size_t header_len; size_t header_len;
@ -579,7 +588,6 @@ static void check_user_request_headers(struct http_header_capture_ctx *ctx, cons
} }
} }
} }
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int on_header_field(struct http_parser *parser, const char *at, static int on_header_field(struct http_parser *parser, const char *at,
size_t length) size_t length)
@ -602,9 +610,10 @@ static int on_header_field(struct http_parser *parser, const char *at,
/* This means that the header field is fully parsed, /* This means that the header field is fully parsed,
* and we can use it directly. * and we can use it directly.
*/ */
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
check_user_request_headers(&ctx->header_capture_ctx, ctx->header_buffer); check_user_request_headers(&ctx->header_capture_ctx,
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ ctx->header_buffer);
}
if (strcasecmp(ctx->header_buffer, "Upgrade") == 0) { if (strcasecmp(ctx->header_buffer, "Upgrade") == 0) {
ctx->has_upgrade_header = true; ctx->has_upgrade_header = true;
@ -621,7 +630,6 @@ static int on_header_field(struct http_parser *parser, const char *at,
return 0; return 0;
} }
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
static void populate_user_request_header(struct http_header_capture_ctx *ctx, const char *buf) static void populate_user_request_header(struct http_header_capture_ctx *ctx, const char *buf)
{ {
char *dest; char *dest;
@ -650,7 +658,6 @@ static void populate_user_request_header(struct http_header_capture_ctx *ctx, co
ctx->headers[ctx->count].value = dest; ctx->headers[ctx->count].value = dest;
ctx->count++; ctx->count++;
} }
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int on_header_value(struct http_parser *parser, static int on_header_value(struct http_parser *parser,
const char *at, size_t length) const char *at, size_t length)
@ -664,21 +671,22 @@ static int on_header_value(struct http_parser *parser,
LOG_DBG("Header %s too long (by %zu bytes)", "value", LOG_DBG("Header %s too long (by %zu bytes)", "value",
offset + length - sizeof(ctx->header_buffer) - 1U); offset + length - sizeof(ctx->header_buffer) - 1U);
ctx->header_buffer[0] = '\0'; ctx->header_buffer[0] = '\0';
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
if (ctx->header_capture_ctx.store_next_value) { 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.store_next_value = false;
ctx->header_capture_ctx.status = HTTP_HEADER_STATUS_DROPPED; ctx->header_capture_ctx.status = HTTP_HEADER_STATUS_DROPPED;
} }
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
} else { } else {
memcpy(ctx->header_buffer + offset, at, length); memcpy(ctx->header_buffer + offset, at, length);
offset += length; offset += length;
ctx->header_buffer[offset] = '\0'; ctx->header_buffer[offset] = '\0';
if (parser->state == s_header_almost_done) { if (parser->state == s_header_almost_done) {
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
populate_user_request_header(&ctx->header_capture_ctx, ctx->header_buffer); populate_user_request_header(&ctx->header_capture_ctx,
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ ctx->header_buffer);
}
if (ctx->has_upgrade_header) { if (ctx->has_upgrade_header) {
if (strcasecmp(ctx->header_buffer, "h2c") == 0) { if (strcasecmp(ctx->header_buffer, "h2c") == 0) {
@ -777,9 +785,9 @@ int enter_http1_request(struct http_client_ctx *client)
client->parser_state = HTTP1_INIT_HEADER_STATE; client->parser_state = HTTP1_INIT_HEADER_STATE;
client->http1_headers_sent = false; client->http1_headers_sent = false;
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
client->header_capture_ctx.store_next_value = false; 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->header_buffer, 0, sizeof(client->header_buffer));
memset(client->url_buffer, 0, sizeof(client->url_buffer)); memset(client->url_buffer, 0, sizeof(client->url_buffer));

59
subsys/net/lib/http/http_server_http2.c

@ -563,6 +563,7 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai
char *ptr; char *ptr;
struct http2_frame *frame = &client->current_frame; struct http2_frame *frame = &client->current_frame;
enum http_data_status status; enum http_data_status status;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
if (client->current_stream == NULL) { if (client->current_stream == NULL) {
@ -576,8 +577,9 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai
do { do {
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, len, &client->header_capture_ctx);
ret = dynamic_detail->cb(client, status, ptr, len, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -607,14 +609,17 @@ static int dynamic_get_req_v2(struct http_resource_detail_dynamic *dynamic_detai
} }
static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_detail, static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_detail,
struct http_client_ctx *client) struct http_client_ctx *client, bool headers_only)
{ {
int ret = 0; int ret = 0;
char *ptr = client->cursor; char *ptr = client->cursor;
size_t data_len; size_t data_len;
enum http_data_status status; enum http_data_status status;
struct http2_frame *frame = &client->current_frame; struct http2_frame *frame = &client->current_frame;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
struct http_header_capture_ctx *request_headers_ctx =
headers_only ? &client->header_capture_ctx : NULL;
if (dynamic_detail == NULL) { if (dynamic_detail == NULL) {
return -ENOENT; return -ENOENT;
@ -624,20 +629,26 @@ static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_deta
return -ENOENT; return -ENOENT;
} }
if (headers_only) {
data_len = 0;
} else {
data_len = MIN(frame->length, client->data_len); data_len = MIN(frame->length, client->data_len);
frame->length -= data_len; frame->length -= data_len;
client->cursor += data_len; client->cursor += data_len;
client->data_len -= data_len; client->data_len -= data_len;
}
if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM)) { if (frame->length == 0 && is_header_flag_set(frame->flags, HTTP2_FLAG_END_STREAM) &&
!headers_only) {
status = HTTP_SERVER_DATA_FINAL; status = HTTP_SERVER_DATA_FINAL;
} else { } else {
status = HTTP_SERVER_DATA_MORE; status = HTTP_SERVER_DATA_MORE;
} }
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, data_len, request_headers_ctx);
ret = dynamic_detail->cb(client, status, ptr, data_len, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -656,8 +667,9 @@ static int dynamic_post_req_v2(struct http_resource_detail_dynamic *dynamic_deta
/* Once all data is transferred to application, repeat cb until response is complete */ /* 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) { while (!http_response_is_final(&response_ctx, status) && status == HTTP_SERVER_DATA_FINAL) {
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, ptr, 0, request_headers_ctx);
ret = dynamic_detail->cb(client, status, ptr, 0, &response_ctx, ret = dynamic_detail->cb(client, status, &request_ctx, &response_ctx,
dynamic_detail->user_data); dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
@ -736,6 +748,17 @@ static int handle_http2_dynamic_resource(
if (user_method & BIT(HTTP_POST)) { if (user_method & BIT(HTTP_POST)) {
client->current_stream->current_detail = client->current_stream->current_detail =
(struct http_resource_detail *)dynamic_detail; (struct http_resource_detail *)dynamic_detail;
/* If there are any header fields to pass to the application, call the
* dynamic handler now with the header data so that this may be cleared to
* re-use for any other concurrent streams
*/
if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
ret = dynamic_post_req_v2(dynamic_detail, client, true);
if (ret < 0) {
return ret;
}
}
break; break;
} }
@ -840,6 +863,14 @@ static int enter_http_frame_headers_state(struct http_client_ctx *client)
client->expect_continuation = false; client->expect_continuation = false;
} }
if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
/* Reset header capture state for new headers frame */
client->header_capture_ctx.count = 0;
client->header_capture_ctx.cursor = 0;
client->header_capture_ctx.status = HTTP_HEADER_STATUS_OK;
client->header_capture_ctx.current_stream = stream;
}
client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE; client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE;
return 0; return 0;
@ -1022,8 +1053,8 @@ int handle_http1_to_http2_upgrade(struct http_client_ctx *client)
if (client->method == HTTP_POST) { if (client->method == HTTP_POST) {
ret = dynamic_post_req_v2( ret = dynamic_post_req_v2(
(struct http_resource_detail_dynamic *)detail, (struct http_resource_detail_dynamic *)detail, client,
client); false);
if (ret < 0) { if (ret < 0) {
goto error; goto error;
} }
@ -1131,7 +1162,7 @@ int handle_http_frame_data(struct http_client_ctx *client)
ret = dynamic_post_req_v2( ret = dynamic_post_req_v2(
(struct http_resource_detail_dynamic *)client->current_stream->current_detail, (struct http_resource_detail_dynamic *)client->current_stream->current_detail,
client); client, false);
if (ret < 0 && ret == -ENOENT) { if (ret < 0 && ret == -ENOENT) {
ret = send_http2_404(client, frame); ret = send_http2_404(client, frame);
} }
@ -1176,7 +1207,6 @@ int handle_http_frame_data(struct http_client_ctx *client)
return 0; return 0;
} }
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)
static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx, static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx,
struct http_hpack_header_buf *hdr_buf) struct http_hpack_header_buf *hdr_buf)
{ {
@ -1223,14 +1253,13 @@ static void check_user_request_headers_http2(struct http_header_capture_ctx *ctx
} }
} }
} }
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */
static int process_header(struct http_client_ctx *client, static int process_header(struct http_client_ctx *client,
struct http_hpack_header_buf *header) struct http_hpack_header_buf *header)
{ {
#if defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) if (IS_ENABLED(CONFIG_HTTP_SERVER_CAPTURE_HEADERS)) {
check_user_request_headers_http2(&client->header_capture_ctx, header); check_user_request_headers_http2(&client->header_capture_ctx, header);
#endif /* defined(CONFIG_HTTP_SERVER_CAPTURE_HEADERS) */ }
if (header->name_len == (sizeof(":method") - 1) && if (header->name_len == (sizeof(":method") - 1) &&
memcmp(header->name, ":method", header->name_len) == 0) { memcmp(header->name, ":method", header->name_len) == 0) {
@ -1344,6 +1373,7 @@ static int handle_incomplete_http_header(struct http_client_ctx *client)
static int handle_http_frame_headers_end_stream(struct http_client_ctx *client) static int handle_http_frame_headers_end_stream(struct http_client_ctx *client)
{ {
struct http2_frame *frame = &client->current_frame; struct http2_frame *frame = &client->current_frame;
struct http_request_ctx request_ctx;
struct http_response_ctx response_ctx; struct http_response_ctx response_ctx;
int ret = 0; int ret = 0;
@ -1361,9 +1391,10 @@ static int handle_http_frame_headers_end_stream(struct http_client_ctx *client)
client->current_stream->current_detail; client->current_stream->current_detail;
memset(&response_ctx, 0, sizeof(response_ctx)); memset(&response_ctx, 0, sizeof(response_ctx));
populate_request_ctx(&request_ctx, NULL, 0, NULL);
ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, NULL, 0, &response_ctx, ret = dynamic_detail->cb(client, HTTP_SERVER_DATA_FINAL, &request_ctx,
dynamic_detail->user_data); &response_ctx, dynamic_detail->user_data);
if (ret < 0) { if (ret < 0) {
dynamic_detail->holder = NULL; dynamic_detail->holder = NULL;
goto out; goto out;

232
tests/net/lib/http_server/core/src/main.c

@ -44,7 +44,18 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE,
"long_payload should be longer than client buffer to test payload being sent to " "long_payload should be longer than client buffer to test payload being sent to "
"application across multiple calls to dynamic resource callback"); "application across multiple calls to dynamic resource callback");
/* Individual HTTP2 frames, used to compose requests. */ /* Individual HTTP2 frames, used to compose requests.
*
* Headers and data frames can be composed based on a "real" request by copying the frame from a
* wireshark capture (Copy --> ...as a hex stream) and formatting into a C array initializer using
* xxd:
*
* echo "<frame_as_hex_stream>" | xxd -r -p | xxd -i
*
* For example:
* $ echo "01234567" | xxd -r -p | xxd -i
* 0x01, 0x23, 0x45, 0x67
*/
#define TEST_HTTP2_MAGIC \ #define TEST_HTTP2_MAGIC \
0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, \ 0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x32, \
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a 0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
@ -106,6 +117,22 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE,
0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b, 0x40, 0x88, 0x49, \ 0x9f, 0x87, 0x49, 0x50, 0x98, 0xbb, 0x8e, 0x8b, 0x4b, 0x40, 0x88, 0x49, \
0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, 0x82, 0x88, 0x49, 0x50, 0x98, 0xbb, \ 0x50, 0x95, 0xa7, 0x28, 0xe4, 0x2d, 0x82, 0x88, 0x49, 0x50, 0x98, 0xbb, \
0x8e, 0x8b, 0x4a, 0x2f 0x8e, 0x8b, 0x4a, 0x2f
#define TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE_WITH_TESTHEADER_STREAM_1 \
0x00, 0x00, 0x4b, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x83, 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, 0x5f, 0x8b, 0x1d, \
0x75, 0xd0, 0x62, 0x0d, 0x26, 0x3d, 0x4c, 0x74, 0x41, 0xea, 0x0f, 0x0d, \
0x02, 0x31, 0x30
#define TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE2_NO_TESTHEADER_STREAM_2 \
0x00, 0x00, 0x39, 0x01, 0x04, 0x00, 0x00, 0x00, TEST_STREAM_ID_2, \
0x83, 0x04, 0x8b, 0x62, 0x72, 0x8e, 0x42, 0xd9, 0x11, 0x07, 0x5a, 0x6d, \
0xb0, 0xa2, 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, 0x5f, 0x8b, 0x1d, 0x75, 0xd0, 0x62, 0x0d, 0x26, 0x3d, \
0x4c, 0x74, 0x41, 0xea, 0x0f, 0x0d, 0x02, 0x31, 0x30
#define TEST_HTTP2_HEADERS_GET_RESPONSE_HEADERS_STREAM_1 \ #define TEST_HTTP2_HEADERS_GET_RESPONSE_HEADERS_STREAM_1 \
0x00, 0x00, 0x28, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x00, 0x00, 0x28, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x82, 0x04, 0x8c, 0x62, 0xc2, 0xa2, 0xb3, 0xd4, 0x82, 0xc5, 0x39, 0x47, \ 0x82, 0x04, 0x8c, 0x62, 0xc2, 0xa2, 0xb3, 0xd4, 0x82, 0xc5, 0x39, 0x47, \
@ -163,6 +190,12 @@ BUILD_ASSERT(sizeof(long_payload) - 1 > CONFIG_HTTP_SERVER_CLIENT_BUFFER_SIZE,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, \ 0x54, 0x65, 0x73, 0x74, 0x20, 0x64, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, \
0x20, 0x50, 0x4f, 0x53, 0x54 0x20, 0x50, 0x4f, 0x53, 0x54
#define TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_1 \
0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x31, 0x7d
#define TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_2 \
0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, TEST_STREAM_ID_2, \
0x7b, 0x22, 0x74, 0x65, 0x73, 0x74, 0x22, 0x3a, 0x31, 0x7d
#define TEST_HTTP2_TRAILING_HEADER_STREAM_1 \ #define TEST_HTTP2_TRAILING_HEADER_STREAM_1 \
0x00, 0x00, 0x0c, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \ 0x00, 0x00, 0x0c, 0x01, 0x05, 0x00, 0x00, 0x00, TEST_STREAM_ID_1, \
0x40, 0x84, 0x92, 0xda, 0x69, 0xf5, 0x85, 0x9c, 0xa3, 0x90, 0xb6, 0x7f 0x40, 0x84, 0x92, 0xda, 0x69, 0xf5, 0x85, 0x9c, 0xa3, 0x90, 0xb6, 0x7f
@ -190,8 +223,9 @@ HTTP_RESOURCE_DEFINE(static_resource, test_http_service, "/",
static uint8_t dynamic_payload[32]; static uint8_t dynamic_payload[32];
static size_t dynamic_payload_len = sizeof(dynamic_payload); static size_t dynamic_payload_len = sizeof(dynamic_payload);
static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status, uint8_t *buffer, static int dynamic_cb(struct http_client_ctx *client, enum http_data_status status,
size_t len, struct http_response_ctx *response_ctx, void *user_data) const struct http_request_ctx *request_ctx,
struct http_response_ctx *response_ctx, void *user_data)
{ {
static size_t offset; static size_t offset;
@ -207,13 +241,13 @@ static int dynamic_cb(struct http_client_ctx *client, enum http_data_status stat
response_ctx->final_chunk = true; response_ctx->final_chunk = true;
break; break;
case HTTP_POST: case HTTP_POST:
if (len + offset > sizeof(dynamic_payload)) { if (request_ctx->data_len + offset > sizeof(dynamic_payload)) {
return -ENOMEM; return -ENOMEM;
} }
if (len > 0) { if (request_ctx->data_len > 0) {
memcpy(dynamic_payload + offset, buffer, len); memcpy(dynamic_payload + offset, request_ctx->data, request_ctx->data_len);
offset += len; offset += request_ctx->data_len;
} }
if (status == HTTP_SERVER_DATA_FINAL) { if (status == HTTP_SERVER_DATA_FINAL) {
@ -244,36 +278,43 @@ struct http_resource_detail_dynamic dynamic_detail = {
HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic", HTTP_RESOURCE_DEFINE(dynamic_resource, test_http_service, "/dynamic",
&dynamic_detail); &dynamic_detail);
static struct http_header_capture_ctx header_capture_ctx_clone; struct test_headers_clone {
uint8_t buffer[CONFIG_HTTP_SERVER_CAPTURE_HEADER_BUFFER_SIZE];
struct http_header headers[CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT];
size_t count;
enum http_header_status status;
};
static int dynamic_request_headers_cb(struct http_client_ctx *client, enum http_data_status status, static int dynamic_request_headers_cb(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, const struct http_request_ctx *request_ctx,
struct http_response_ctx *response_ctx, void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
ptrdiff_t offset; ptrdiff_t offset;
struct http_header *hdrs_src; struct http_header *hdrs_src;
struct http_header *hdrs_dst; struct http_header *hdrs_dst;
struct test_headers_clone *clone = (struct test_headers_clone *)user_data;
if (status == HTTP_SERVER_DATA_FINAL) { if (request_ctx->header_count != 0) {
/* Copy the captured header info to static buffer for later assertions in testcase. /* 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 * 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 * from the callback. Also need to update pointers within structure with an offset
* change. Also need to update pointers within structure with an offset to point at * to point at new buffer.
* new buffer.
*/ */
memcpy(&header_capture_ctx_clone, &client->header_capture_ctx, memcpy(clone->buffer, &client->header_capture_ctx, sizeof(clone->buffer));
sizeof(header_capture_ctx_clone));
clone->count = request_ctx->header_count;
clone->status = request_ctx->headers_status;
hdrs_src = client->header_capture_ctx.headers; hdrs_src = request_ctx->headers;
hdrs_dst = header_capture_ctx_clone.headers; hdrs_dst = clone->headers;
offset = header_capture_ctx_clone.buffer - client->header_capture_ctx.buffer; offset = clone->buffer - client->header_capture_ctx.buffer;
for (int i = 0; i < CONFIG_HTTP_SERVER_CAPTURE_HEADER_COUNT; i++) { for (int i = 0; i < request_ctx->header_count; i++) {
if (hdrs_src[i].name != NULL) { if (hdrs_src[i].name != NULL) {
hdrs_dst[i].name = hdrs_src[i].name + offset; hdrs_dst[i].name = hdrs_src[i].name + offset;
} }
if (hdrs_dst[i].value != NULL) { if (hdrs_src[i].value != NULL) {
hdrs_dst[i].value = hdrs_src[i].value + offset; hdrs_dst[i].value = hdrs_src[i].value + offset;
} }
} }
@ -282,6 +323,10 @@ static int dynamic_request_headers_cb(struct http_client_ctx *client, enum http_
return 0; return 0;
} }
/* Define two resources for testing header capture, so that we can check concurrent streams */
static struct test_headers_clone request_headers_clone;
static struct test_headers_clone request_headers_clone2;
struct http_resource_detail_dynamic dynamic_request_headers_detail = { struct http_resource_detail_dynamic dynamic_request_headers_detail = {
.common = { .common = {
.type = HTTP_RESOURCE_TYPE_DYNAMIC, .type = HTTP_RESOURCE_TYPE_DYNAMIC,
@ -289,12 +334,25 @@ struct http_resource_detail_dynamic dynamic_request_headers_detail = {
.content_type = "text/plain", .content_type = "text/plain",
}, },
.cb = dynamic_request_headers_cb, .cb = dynamic_request_headers_cb,
.user_data = NULL .user_data = &request_headers_clone,
};
struct http_resource_detail_dynamic dynamic_request_headers_detail2 = {
.common = {
.type = HTTP_RESOURCE_TYPE_DYNAMIC,
.bitmask_of_supported_http_methods = BIT(HTTP_GET) | BIT(HTTP_POST),
.content_type = "text/plain",
},
.cb = dynamic_request_headers_cb,
.user_data = &request_headers_clone2,
}; };
HTTP_RESOURCE_DEFINE(dynamic_request_headers_resource, test_http_service, "/header_capture", HTTP_RESOURCE_DEFINE(dynamic_request_headers_resource, test_http_service, "/header_capture",
&dynamic_request_headers_detail); &dynamic_request_headers_detail);
HTTP_RESOURCE_DEFINE(dynamic_request_headers_resource2, test_http_service, "/header_capture2",
&dynamic_request_headers_detail2);
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_user_agent, "User-Agent"); 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_header, "Test-Header");
HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header2, "Test-Header2"); HTTP_SERVER_REGISTER_HEADER_CAPTURE(capture_test_header2, "Test-Header2");
@ -326,7 +384,7 @@ static uint8_t dynamic_response_headers_variant;
static uint8_t dynamic_response_headers_buffer[sizeof(long_payload)]; static uint8_t dynamic_response_headers_buffer[sizeof(long_payload)];
static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http_data_status status, static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http_data_status status,
uint8_t *buffer, size_t len, const struct http_request_ctx *request_ctx,
struct http_response_ctx *response_ctx, void *user_data) struct http_response_ctx *response_ctx, void *user_data)
{ {
static bool request_continuation; static bool request_continuation;
@ -340,6 +398,14 @@ static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http
{.name = "Content-Type", .value = "application/json"}, {.name = "Content-Type", .value = "application/json"},
}; };
if (status != HTTP_SERVER_DATA_FINAL &&
dynamic_response_headers_variant != DYNAMIC_RESPONSE_HEADERS_VARIANT_BODY_LONG) {
/* Long body variant is the only one which needs to take some action before final
* data has been received from server
*/
return 0;
}
switch (dynamic_response_headers_variant) { switch (dynamic_response_headers_variant) {
case DYNAMIC_RESPONSE_HEADERS_VARIANT_NONE: case DYNAMIC_RESPONSE_HEADERS_VARIANT_NONE:
break; break;
@ -400,10 +466,12 @@ static int dynamic_response_headers_cb(struct http_client_ctx *client, enum http
} }
} else if (client->method == HTTP_POST) { } else if (client->method == HTTP_POST) {
/* Copy POST payload into buffer for later comparison */ /* Copy POST payload into buffer for later comparison */
zassert(offset + len <= sizeof(dynamic_response_headers_buffer), zassert(offset + request_ctx->data_len <=
sizeof(dynamic_response_headers_buffer),
"POST data too long for buffer"); "POST data too long for buffer");
memcpy(dynamic_response_headers_buffer + offset, buffer, len); memcpy(dynamic_response_headers_buffer + offset, request_ctx->data,
offset += len; request_ctx->data_len);
offset += request_ctx->data_len;
if (status == HTTP_SERVER_DATA_FINAL) { if (status == HTTP_SERVER_DATA_FINAL) {
offset = 0; offset = 0;
@ -544,10 +612,12 @@ static void expect_http2_headers_frame(size_t *offset, int stream_id, uint8_t fl
test_get_frame_header(offset, &frame); test_get_frame_header(offset, &frame);
zassert_equal(frame.type, HTTP2_HEADERS_FRAME, "Expected headers frame"); zassert_equal(frame.type, HTTP2_HEADERS_FRAME, "Expected headers frame, got frame type %u",
frame.type);
zassert_equal(frame.stream_identifier, stream_id, zassert_equal(frame.stream_identifier, stream_id,
"Invalid headers frame stream ID"); "Invalid headers frame stream ID");
zassert_equal(frame.flags, flags, "Unexpected flags received"); zassert_equal(frame.flags, flags, "Unexpected flags received (expected %x got %x)", flags,
frame.flags);
/* Consume headers payload */ /* Consume headers payload */
test_read_data(offset, frame.length); test_read_data(offset, frame.length);
@ -588,7 +658,8 @@ static void expect_http2_window_update_frame(size_t *offset, int stream_id)
zassert_equal(frame.type, HTTP2_WINDOW_UPDATE_FRAME, zassert_equal(frame.type, HTTP2_WINDOW_UPDATE_FRAME,
"Expected window update frame"); "Expected window update frame");
zassert_equal(frame.stream_identifier, stream_id, zassert_equal(frame.stream_identifier, stream_id,
"Invalid window update frame stream ID"); "Invalid window update frame stream ID (expected %d got %d)", stream_id,
frame.stream_identifier);
zassert_equal(frame.flags, 0, "Unexpected flags received"); zassert_equal(frame.flags, 0, "Unexpected flags received");
zassert_equal(frame.length, sizeof(uint32_t), zassert_equal(frame.length, sizeof(uint32_t),
"Unexpected window update frame length"); "Unexpected window update frame length");
@ -1173,14 +1244,14 @@ ZTEST(server_function_tests, test_http1_header_capture)
"Accept: */*\r\n" "Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n" "Accept-Encoding: deflate, gzip, br\r\n"
"\r\n"; "\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
test_http1_header_capture_common(request); test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 2, zassert_equal(request_headers_clone.count, 2,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_OK,
"Header capture status was not OK"); "Header capture status was not OK");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
@ -1207,14 +1278,14 @@ ZTEST(server_function_tests, test_http1_header_too_long)
"Accept: */*\r\n" "Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n" "Accept-Encoding: deflate, gzip, br\r\n"
"\r\n"; "\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
test_http1_header_capture_common(request); test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 1, zassert_equal(request_headers_clone.count, 1,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status was OK, but should not have been"); "Header capture status was OK, but should not have been");
/* First header too long should not stop second header being captured into first slot */ /* First header too long should not stop second header being captured into first slot */
@ -1236,14 +1307,14 @@ ZTEST(server_function_tests, test_http1_header_too_many)
"Accept: */*\r\n" "Accept: */*\r\n"
"Accept-Encoding: deflate, gzip, br\r\n" "Accept-Encoding: deflate, gzip, br\r\n"
"\r\n"; "\r\n";
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
test_http1_header_capture_common(request); test_http1_header_capture_common(request);
zassert_equal(header_capture_ctx_clone.count, 2, zassert_equal(request_headers_clone.count, 2,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status OK, but should not have been"); "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].name, NULL, "First header name is NULL");
@ -1290,14 +1361,14 @@ ZTEST(server_function_tests, test_http2_header_capture)
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE1_STREAM_1, TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE1_STREAM_1,
TEST_HTTP2_GOAWAY, TEST_HTTP2_GOAWAY,
}; };
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request)); common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 2, zassert_equal(request_headers_clone.count, 2,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_OK, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_OK,
"Header capture status was not OK"); "Header capture status was not OK");
zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL"); zassert_not_equal(hdrs[0].name, NULL, "First header name is NULL");
@ -1324,14 +1395,14 @@ ZTEST(server_function_tests, test_http2_header_too_long)
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE2_STREAM_1, TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE2_STREAM_1,
TEST_HTTP2_GOAWAY, TEST_HTTP2_GOAWAY,
}; };
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request)); common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 1, zassert_equal(request_headers_clone.count, 1,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status was OK, but should not have been"); "Header capture status was OK, but should not have been");
/* First header too long should not stop second header being captured into first slot */ /* First header too long should not stop second header being captured into first slot */
@ -1353,14 +1424,14 @@ ZTEST(server_function_tests, test_http2_header_too_many)
TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE3_STREAM_1, TEST_HTTP2_HEADERS_GET_HEADER_CAPTURE3_STREAM_1,
TEST_HTTP2_GOAWAY, TEST_HTTP2_GOAWAY,
}; };
struct http_header *hdrs = header_capture_ctx_clone.headers; struct http_header *hdrs = request_headers_clone.headers;
int ret; int ret;
common_verify_http2_get_header_capture_request(request, sizeof(request)); common_verify_http2_get_header_capture_request(request, sizeof(request));
zassert_equal(header_capture_ctx_clone.count, 2, zassert_equal(request_headers_clone.count, 2,
"Didn't capture the expected number of headers"); "Didn't capture the expected number of headers");
zassert_equal(header_capture_ctx_clone.status, HTTP_HEADER_STATUS_DROPPED, zassert_equal(request_headers_clone.status, HTTP_HEADER_STATUS_DROPPED,
"Header capture status OK, but should not have been"); "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].name, NULL, "First header name is NULL");
@ -1378,6 +1449,73 @@ ZTEST(server_function_tests, test_http2_header_too_many)
zassert_equal(0, ret, "Header strings did not match"); zassert_equal(0, ret, "Header strings did not match");
} }
ZTEST(server_function_tests, test_http2_header_concurrent)
{
/* Two POST requests which are concurrent, ie. headers1, headers2, data1, data2 */
static const uint8_t request[] = {
TEST_HTTP2_MAGIC,
TEST_HTTP2_SETTINGS,
TEST_HTTP2_SETTINGS_ACK,
TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE_WITH_TESTHEADER_STREAM_1,
TEST_HTTP2_HEADERS_POST_HEADER_CAPTURE2_NO_TESTHEADER_STREAM_2,
TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_1,
TEST_HTTP2_DATA_POST_HEADER_CAPTURE_STREAM_2,
TEST_HTTP2_GOAWAY,
};
struct http_header *hdrs = request_headers_clone.headers;
struct http_header *hdrs2 = request_headers_clone2.headers;
int ret;
size_t offset = 0;
ret = zsock_send(client_fd, request, sizeof(request), 0);
zassert_not_equal(ret, -1, "send() failed (%d)", errno);
/* Wait for response on both resources before checking captured headers */
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 | HTTP2_FLAG_END_STREAM, NULL, 0);
expect_http2_window_update_frame(&offset, TEST_STREAM_ID_1);
expect_http2_window_update_frame(&offset, 0);
expect_http2_headers_frame(&offset, TEST_STREAM_ID_2,
HTTP2_FLAG_END_HEADERS | HTTP2_FLAG_END_STREAM, NULL, 0);
/* Headers captured on /header_capture path should have two headers including the
* Test-Header
*/
zassert_equal(request_headers_clone.count, 2,
"Didn't capture the expected number of headers");
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");
/* Headers captured on the /header_capture2 path should have only one header, not including
* the Test-Header
*/
zassert_equal(request_headers_clone2.count, 1,
"Didn't capture the expected number of headers");
zassert_not_equal(hdrs2[0].name, NULL, "First header name is NULL");
zassert_not_equal(hdrs2[0].value, NULL, "First header value is NULL");
ret = strcmp(hdrs2[0].name, "User-Agent");
zassert_equal(0, ret, "Header strings did not match");
ret = strcmp(hdrs2[0].value, "curl/7.81.0");
zassert_equal(0, ret, "Header strings did not match");
}
static void test_http1_dynamic_response_headers(const char *request, const char *expected_response) static void test_http1_dynamic_response_headers(const char *request, const char *expected_response)
{ {
int ret; int ret;
@ -1982,6 +2120,8 @@ static void http_server_tests_before(void *fixture)
memset(dynamic_payload, 0, sizeof(dynamic_payload)); memset(dynamic_payload, 0, sizeof(dynamic_payload));
memset(dynamic_response_headers_buffer, 0, sizeof(dynamic_response_headers_buffer)); memset(dynamic_response_headers_buffer, 0, sizeof(dynamic_response_headers_buffer));
memset(&request_headers_clone, 0, sizeof(request_headers_clone));
memset(&request_headers_clone2, 0, sizeof(request_headers_clone2));
dynamic_payload_len = 0; dynamic_payload_len = 0;
ret = http_server_start(); ret = http_server_start();

Loading…
Cancel
Save