Skip to content

Commit b4cfee0

Browse files
rlubosnashif
authored andcommitted
net: lib: http_server: Implement proper CONTINUATION frame processing
CONTINUATION frames are tricky, because individual header fields can be split between HEADERS frame and CONTINUATION frame, or two CONTINUATION frames. Therefore, some extra logic is needed when header parsing returns -EAGAIN, as we may need to remove the CONTINUATION frame header from the stream before proceeding with headers parsing. This commit implements the above logic and additionally adds more checks to detect when CONTINUATION frame is expected. Not receiving a CONTINUATION frame when expect should be treated as a protocol error. Signed-off-by: Robert Lubos <[email protected]>
1 parent 7e22dd4 commit b4cfee0

File tree

4 files changed

+106
-31
lines changed

4 files changed

+106
-31
lines changed

include/zephyr/net/http/server.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,9 @@ struct http_client_ctx {
377377

378378
/** Flag indicating Websocket key is being processed. */
379379
bool websocket_sec_key_next : 1;
380+
381+
/** The next frame on the stream is expectd to be a continuation frame. */
382+
bool expect_continuation : 1;
380383
};
381384

382385
/** @brief Start the HTTP2 server.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ int http_server_sendall(struct http_client_ctx *client, const void *buf, size_t
4242
void http_client_timer_restart(struct http_client_ctx *client);
4343

4444
/* TODO Could be static, but currently used in tests. */
45-
int parse_http_frame_header(struct http_client_ctx *client);
45+
int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffer,
46+
size_t buflen);
4647
const char *get_frame_type_name(enum http2_frame_type type);
4748

4849
#endif /* HTTP_SERVER_INTERNAL_H_ */

subsys/net/lib/http/http_server_http2.c

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -670,13 +670,27 @@ static int enter_http_frame_headers_state(struct http_client_ctx *client)
670670
}
671671
}
672672

673+
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) {
674+
client->expect_continuation = true;
675+
} else {
676+
client->expect_continuation = false;
677+
}
678+
673679
client->server_state = HTTP_SERVER_FRAME_HEADERS_STATE;
674680

675681
return 0;
676682
}
677683

678684
static int enter_http_frame_continuation_state(struct http_client_ctx *client)
679685
{
686+
struct http2_frame *frame = &client->current_frame;
687+
688+
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) {
689+
client->expect_continuation = true;
690+
} else {
691+
client->expect_continuation = false;
692+
}
693+
680694
client->server_state = HTTP_SERVER_FRAME_CONTINUATION_STATE;
681695

682696
return 0;
@@ -712,25 +726,26 @@ static int enter_http_frame_goaway_state(struct http_client_ctx *client)
712726

713727
int handle_http_frame_header(struct http_client_ctx *client)
714728
{
715-
int bytes_consumed;
716-
int parse_result;
729+
int ret;
717730

718731
LOG_DBG("HTTP_SERVER_FRAME_HEADER");
719732

720-
parse_result = parse_http_frame_header(client);
721-
if (parse_result == 0) {
722-
return -EAGAIN;
723-
} else if (parse_result < 0) {
724-
return parse_result;
733+
ret = parse_http_frame_header(client, client->cursor, client->data_len);
734+
if (ret < 0) {
735+
return ret;
725736
}
726737

727-
bytes_consumed = HTTP2_FRAME_HEADER_SIZE;
728-
729-
client->cursor += bytes_consumed;
730-
client->data_len -= bytes_consumed;
738+
client->cursor += HTTP2_FRAME_HEADER_SIZE;
739+
client->data_len -= HTTP2_FRAME_HEADER_SIZE;
731740

732741
print_http_frames(client);
733742

743+
if (client->expect_continuation &&
744+
client->current_frame.type != HTTP2_CONTINUATION_FRAME) {
745+
LOG_ERR("Continuation frame expected");
746+
return -EBADMSG;
747+
}
748+
734749
switch (client->current_frame.type) {
735750
case HTTP2_DATA_FRAME:
736751
return enter_http_frame_data_state(client);
@@ -1032,6 +1047,61 @@ static int process_header(struct http_client_ctx *client,
10321047
return 0;
10331048
}
10341049

1050+
static int handle_incomplete_http_header(struct http_client_ctx *client)
1051+
{
1052+
struct http2_frame *frame = &client->current_frame;
1053+
size_t extra_len, prev_frame_len;
1054+
int ret;
1055+
1056+
if (client->data_len < frame->length) {
1057+
/* Still did not receive entire frame content */
1058+
return -EAGAIN;
1059+
}
1060+
1061+
if (!client->expect_continuation) {
1062+
/* Failed to parse header field while the frame is complete
1063+
* and no continuation frame is expected - report protocol
1064+
* error.
1065+
*/
1066+
LOG_ERR("Incomplete header field");
1067+
return -EBADMSG;
1068+
}
1069+
1070+
/* A header field can be split between two frames, i. e. header and
1071+
* continuation or two continuation frames. Because of this, the
1072+
* continuation frame header can be present in the stream in between
1073+
* header field data, so in such case we need to check for header here
1074+
* and remove it from the stream to unblock further processing of the
1075+
* header field.
1076+
*/
1077+
prev_frame_len = frame->length;
1078+
extra_len = client->data_len - frame->length;
1079+
ret = parse_http_frame_header(client, client->cursor + prev_frame_len,
1080+
extra_len);
1081+
if (ret < 0) {
1082+
return -EAGAIN;
1083+
}
1084+
1085+
/* Continuation frame expected. */
1086+
if (frame->type != HTTP2_CONTINUATION_FRAME) {
1087+
LOG_ERR("Continuation frame expected");
1088+
return -EBADMSG;
1089+
}
1090+
1091+
print_http_frames(client);
1092+
/* Now remove continuation frame header from the stream, and proceed
1093+
* with processing.
1094+
*/
1095+
extra_len -= HTTP2_FRAME_HEADER_SIZE;
1096+
client->data_len -= HTTP2_FRAME_HEADER_SIZE;
1097+
frame->length += prev_frame_len;
1098+
memmove(client->cursor + prev_frame_len,
1099+
client->cursor + prev_frame_len + HTTP2_FRAME_HEADER_SIZE,
1100+
extra_len);
1101+
1102+
return enter_http_frame_continuation_state(client);
1103+
}
1104+
10351105
int handle_http_frame_headers(struct http_client_ctx *client)
10361106
{
10371107
struct http2_frame *frame = &client->current_frame;
@@ -1056,11 +1126,16 @@ int handle_http_frame_headers(struct http_client_ctx *client)
10561126

10571127
while (frame->length > 0) {
10581128
struct http_hpack_header_buf *header = &client->header_field;
1129+
size_t datalen = MIN(client->data_len, frame->length);
10591130

1060-
ret = http_hpack_decode_header(client->cursor, client->data_len,
1061-
header);
1131+
ret = http_hpack_decode_header(client->cursor, datalen, header);
10621132
if (ret <= 0) {
1063-
ret = (ret == 0) ? -EBADMSG : ret;
1133+
if (ret == -EAGAIN) {
1134+
ret = handle_incomplete_http_header(client);
1135+
} else if (ret == 0) {
1136+
ret = -EBADMSG;
1137+
}
1138+
10641139
return ret;
10651140
}
10661141

@@ -1082,12 +1157,9 @@ int handle_http_frame_headers(struct http_client_ctx *client)
10821157
}
10831158
}
10841159

1085-
if (!is_header_flag_set(frame->flags, HTTP2_FLAG_END_HEADERS)) {
1160+
if (client->expect_continuation) {
10861161
/* More headers to come in the continuation frame. */
10871162
client->server_state = HTTP_SERVER_FRAME_HEADER_STATE;
1088-
1089-
/* TODO Implement continuation frame processing. */
1090-
10911163
return 0;
10921164
}
10931165

@@ -1325,16 +1397,13 @@ const char *get_frame_type_name(enum http2_frame_type type)
13251397
}
13261398
}
13271399

1328-
int parse_http_frame_header(struct http_client_ctx *client)
1400+
int parse_http_frame_header(struct http_client_ctx *client, const uint8_t *buffer,
1401+
size_t buflen)
13291402
{
1330-
uint8_t *buffer = client->cursor;
13311403
struct http2_frame *frame = &client->current_frame;
13321404

1333-
frame->length = 0;
1334-
frame->stream_identifier = 0;
1335-
1336-
if (client->data_len < HTTP2_FRAME_HEADER_SIZE) {
1337-
return 0;
1405+
if (buflen < HTTP2_FRAME_HEADER_SIZE) {
1406+
return -EAGAIN;
13381407
}
13391408

13401409
frame->length = sys_get_be24(&buffer[HTTP2_FRAME_LENGTH_OFFSET]);
@@ -1348,5 +1417,5 @@ int parse_http_frame_header(struct http_client_ctx *client)
13481417
LOG_DBG("Frame len %d type 0x%02x flags 0x%02x id %d",
13491418
frame->length, frame->type, frame->flags, frame->stream_identifier);
13501419

1351-
return 1;
1420+
return 0;
13521421
}

tests/net/lib/http_server/prototype/src/main.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,9 +371,10 @@ ZTEST(server_function_tests, test_parse_http_frames)
371371
ctx_client2.data_len = ARRAY_SIZE(buffer2);
372372

373373
/* Test: Buffer with the first frame */
374-
int parser1 = parse_http_frame_header(&ctx_client1);
374+
int parser1 = parse_http_frame_header(&ctx_client1, ctx_client1.cursor,
375+
ctx_client1.data_len);
375376

376-
zassert_equal(parser1, 1, "Failed to parse the first frame");
377+
zassert_equal(parser1, 0, "Failed to parse the first frame");
377378

378379
frame = &ctx_client1.current_frame;
379380

@@ -385,9 +386,10 @@ ZTEST(server_function_tests, test_parse_http_frames)
385386
"Expected stream_identifier for the 1st frame doesn't match");
386387

387388
/* Test: Buffer with the second frame */
388-
int parser2 = parse_http_frame_header(&ctx_client2);
389+
int parser2 = parse_http_frame_header(&ctx_client2, ctx_client2.cursor,
390+
ctx_client2.data_len);
389391

390-
zassert_equal(parser2, 1, "Failed to parse the second frame");
392+
zassert_equal(parser2, 0, "Failed to parse the second frame");
391393

392394
frame = &ctx_client2.current_frame;
393395

0 commit comments

Comments
 (0)