From: Matus Fabian Date: Mon, 23 Jun 2025 17:15:04 +0000 (+0000) Subject: http: h2 decoding response X-Git-Tag: v26.02-rc0~218 X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=6b8975e2a158cc84425f269abb4958d37beebfa5;p=vpp.git http: h2 decoding response Type: improvement Change-Id: I5e582e6fec972d6d61683a7a76c2a3f222a9030b Signed-off-by: Matus Fabian --- diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 15e92fe6831..8af061eb753 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -7,6 +7,7 @@ #include #include #include +#include #define HPACK_STATIC_TABLE_SIZE 61 @@ -771,6 +772,30 @@ hpack_parse_scheme (u8 *value, u32 value_len) return HTTP_URL_SCHEME_UNKNOWN; } +static inline int +hpack_parse_status_code (u8 *value, u32 value_len, http_status_code_t *sc) +{ + u16 status_code = 0; + u8 *p; + + if (value_len != 3) + return HTTP2_ERROR_PROTOCOL_ERROR; + + p = value; + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + HTTP_DBG (1, "invalid status code %d", status_code); + return -1; + } + HTTP_DBG (1, "status code: %d", status_code); + *sc = http_sc_by_u16 (status_code); + + return 0; +} + static http2_error_t hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, u32 value_len, @@ -852,6 +877,32 @@ hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, return HTTP2_ERROR_NO_ERROR; } +static http2_error_t +hpack_parse_resp_pseudo_header (u8 *name, u32 name_len, u8 *value, + u32 value_len, + hpack_response_control_data_t *control_data) +{ + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, + value, value_len); + switch (name_len) + { + case 7: + if (!memcmp (name + 1, "status", 6)) + { + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED) + return HTTP2_ERROR_PROTOCOL_ERROR; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_STATUS_PARSED; + if (hpack_parse_status_code (value, value_len, &control_data->sc)) + return HTTP2_ERROR_PROTOCOL_ERROR; + } + break; + default: + return HTTP2_ERROR_PROTOCOL_ERROR; + } + + return HTTP2_ERROR_NO_ERROR; +} + /* Special treatment for headers like: * * RFC9113 8.2.2: any message containing connection-specific header @@ -863,8 +914,7 @@ hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, */ always_inline http2_error_t hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, - uword index, - hpack_request_control_data_t *control_data) + uword index, uword *content_len_header_index) { switch (name_len) { @@ -895,8 +945,8 @@ hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, break; case 14: if (!memcmp (name, "content-length", 14) && - control_data->content_len_header_index == ~0) - control_data->content_len_header_index = index; + *content_len_header_index == ~0) + *content_len_header_index = index; break; case 16: if (!memcmp (name, "proxy-connection", 16)) @@ -987,8 +1037,99 @@ hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, control_data->headers_len += value_len; if (regular_header_parsed) { - rv = hpack_preprocess_header (name, name_len, value, value_len, - header - *headers, control_data); + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "connection-specific header present"); + return rv; + } + } + } + control_data->control_data_len = dst_len - b_left; + HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table); + return HTTP2_ERROR_NO_ERROR; +} + +__clib_export http2_error_t +hpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_response_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table) +{ + u8 *p, *end, *b, *name, *value; + u8 regular_header_parsed = 0; + u32 name_len, value_len; + uword b_left; + http_field_line_t *header; + http2_error_t rv; + + p = src; + end = src + src_len; + b = dst; + b_left = dst_len; + control_data->parsed_bitmap = 0; + control_data->headers_len = 0; + control_data->content_len_header_index = ~0; + + while (p != end) + { + name = b; + rv = hpack_decode_header (&p, end, &b, &b_left, &name_len, &value_len, + dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_decode_header: %U", format_http2_error, rv); + return rv; + } + value = name + name_len; + + /* pseudo header */ + if (name[0] == ':') + { + /* all pseudo-headers must be before regular headers */ + if (regular_header_parsed) + { + HTTP_DBG (1, "pseudo-headers after regular header"); + return HTTP2_ERROR_PROTOCOL_ERROR; + } + rv = hpack_parse_resp_pseudo_header (name, name_len, value, + value_len, control_data); + if (rv != HTTP2_ERROR_NO_ERROR) + { + HTTP_DBG (1, "hpack_parse_resp_pseudo_header: %U", + format_http2_error, rv); + return rv; + } + continue; + } + else + { + if (!hpack_header_name_is_valid (name, name_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + if (!regular_header_parsed) + { + regular_header_parsed = 1; + control_data->headers = name; + } + } + if (!hpack_header_value_is_valid (value, value_len)) + return HTTP2_ERROR_PROTOCOL_ERROR; + vec_add2 (*headers, header, 1); + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, + format_http_bytes, value, value_len); + header->name_offset = name - control_data->headers; + header->name_len = name_len; + header->value_offset = value - control_data->headers; + header->value_len = value_len; + control_data->headers_len += name_len; + control_data->headers_len += value_len; + if (regular_header_parsed) + { + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); if (rv != HTTP2_ERROR_NO_ERROR) { HTTP_DBG (1, "connection-specific header present"); diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index a2fd5faed58..72b9f2b4bee 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -73,6 +73,11 @@ typedef struct u32 server_name_len; u8 *date; u32 date_len; + u16 parsed_bitmap; + uword content_len_header_index; + u8 *headers; + u32 headers_len; + u32 control_data_len; } hpack_response_control_data_t; /** @@ -173,6 +178,25 @@ http2_error_t hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, http_field_line_t **headers, hpack_dynamic_table_t *dynamic_table); +/** + * Response parser + * + * @param src Header block to parse + * @param src_len Length of header block + * @param dst Buffer where headers will be decoded + * @param dst_len Length of buffer for decoded headers + * @param control_data Preparsed pseudo-headers + * @param headers List of regular headers + * @param dynamic_table Decoder dynamic table + * + * @return @c HTTP2_ERROR_NO_ERROR on success, connection error otherwise + */ +http2_error_t +hpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len, + hpack_response_control_data_t *control_data, + http_field_line_t **headers, + hpack_dynamic_table_t *dynamic_table); + /** * Serialize response * diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 73a397aef2f..fbeee4b6b09 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -635,6 +635,147 @@ http_test_parse_request (const char *first_req, uword first_req_len, return 0; } +static int +http_test_parse_response (const char *first_resp, uword first_resp_len, + const char *second_resp, uword second_resp_len, + const char *third_resp, uword third_resp_len, + hpack_dynamic_table_t *dynamic_table) +{ + http2_error_t rv; + u8 *buf = 0; + hpack_response_control_data_t control_data; + http_field_line_t *headers = 0; + u16 parsed_bitmap; + + static http2_error_t (*_hpack_parse_response) ( + u8 * src, u32 src_len, u8 * dst, u32 dst_len, + hpack_response_control_data_t * control_data, http_field_line_t * *headers, + hpack_dynamic_table_t * dynamic_table); + + _hpack_parse_response = + vlib_get_plugin_symbol ("http_plugin.so", "hpack_parse_response"); + + parsed_bitmap = HPACK_PSEUDO_HEADER_STATUS_PARSED; + + /* first request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) first_resp, (u32) first_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_FOUND || vec_len (headers) != 3 || + dynamic_table->used != 222) + return 1; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:21 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + vec_free (headers); + vec_free (buf); + + /* second request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) second_resp, (u32) second_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_TEMPORARY_REDIRECT || + vec_len (headers) != 3 || dynamic_table->used != 222) + return 2; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:21 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + vec_free (headers); + vec_free (buf); + + /* third request */ + vec_validate_init_empty (buf, 254, 0); + memset (&control_data, 0, sizeof (control_data)); + rv = _hpack_parse_response ((u8 *) third_resp, (u32) third_resp_len, buf, + 254, &control_data, &headers, dynamic_table); + if (rv != HTTP2_ERROR_NO_ERROR || + control_data.parsed_bitmap != parsed_bitmap || + control_data.sc != HTTP_STATUS_OK || vec_len (headers) != 5 || + dynamic_table->used != 215) + return 3; + if (headers[0].name_len != 13 || headers[0].value_len != 7) + return 1; + if (memcmp (control_data.headers + headers[0].name_offset, "cache-control", + 13)) + return 1; + if (memcmp (control_data.headers + headers[0].value_offset, "private", 7)) + return 1; + if (headers[1].name_len != 4 || headers[1].value_len != 29) + return 1; + if (memcmp (control_data.headers + headers[1].name_offset, "date", 4)) + return 1; + if (memcmp (control_data.headers + headers[1].value_offset, + "Mon, 21 Oct 2013 20:13:22 GMT", 29)) + return 1; + if (headers[2].name_len != 8 || headers[2].value_len != 23) + return 1; + if (memcmp (control_data.headers + headers[2].name_offset, "location", 8)) + return 1; + if (memcmp (control_data.headers + headers[2].value_offset, + "https://www.example.com", 23)) + return 1; + if (headers[3].name_len != 16 || headers[3].value_len != 4) + return 1; + if (memcmp (control_data.headers + headers[3].name_offset, + "content-encoding", 16)) + return 1; + if (memcmp (control_data.headers + headers[3].value_offset, "gzip", 4)) + return 1; + if (headers[4].name_len != 10 || headers[4].value_len != 56) + return 1; + if (memcmp (control_data.headers + headers[4].name_offset, "set-cookie", 10)) + return 1; + if (memcmp (control_data.headers + headers[4].value_offset, + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", 56)) + return 1; + vec_free (headers); + vec_free (buf); + + return 0; +} + static int http_test_hpack (vlib_main_t *vm) { @@ -956,6 +1097,49 @@ http_test_hpack (vlib_main_t *vm) _hpack_dynamic_table_free (&table); HTTP_TEST ((result == 0), "request with Huffman Coding (result=%d)", result); + vlib_cli_output (vm, "hpack_parse_response"); + + /* C.5. Response Examples without Huffman Coding */ + _hpack_dynamic_table_init (&table, 256); + result = http_test_parse_response ( + http_token_lit ( + "\x48\x03\x33\x30\x32\x58\x07\x70\x72\x69\x76\x61\x74\x65" + "\x61\x1D\x4D\x6F\x6E\x2C\x20\x32\x31\x20\x4F\x63\x74\x20\x32\x30\x31" + "\x33\x20\x32\x30\x3A\x31\x33\x3A\x32\x31\x20\x47\x4D\x54\x6E\x17\x68" + "\x74\x74\x70\x73\x3A\x2F\x2F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C" + "\x65\x2E\x63\x6F\x6D"), + http_token_lit ("\x48\x03\x33\x30\x37\xC1\xC0\xBF"), + http_token_lit ( + "\x88\xC1\x61\x1D\x4D\x6F\x6E\x2C\x20\x32\x31\x20\x4F\x63\x74\x20\x32" + "\x30\x31\x33\x20\x32\x30\x3A\x31\x33\x3A\x32\x32\x20\x47\x4D\x54\xC0" + "\x5A\x04\x67\x7A\x69\x70\x77\x38\x66\x6F\x6F\x3D\x41\x53\x44\x4A\x4B" + "\x48\x51\x4B\x42\x5A\x58\x4F\x51\x57\x45\x4F\x50\x49\x55\x41\x58\x51" + "\x57\x45\x4F\x49\x55\x3B\x20\x6D\x61\x78\x2D\x61\x67\x65\x3D\x33\x36" + "\x30\x30\x3B\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x31"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "response without Huffman Coding (result=%d)", + result); + + /* C.6. Response Examples with Huffman Coding */ + _hpack_dynamic_table_init (&table, 256); + result = http_test_parse_response ( + http_token_lit ("\x48\x82\x64\x02\x58\x85\xAE\xC3\x77\x1A\x4B\x61\x96\xD0" + "\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81" + "\x66\xE0\x82\xA6\x2D\x1B\xFF\x6E\x91\x9D\x29\xAD\x17\x18" + "\x63\xC7\x8F\x0B\x97\xC8\xE9\xAE\x82\xAE\x43\xD3"), + http_token_lit ("\x48\x83\x64\x0E\xFF\xC1\xC0\xBF"), + http_token_lit ( + "\x88\xC1\x61\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04" + "\x0B\x81\x66\xE0\x84\xA6\x2D\x1B\xFF\xC0\x5A\x83\x9B\xD9\xAB\x77\xAD" + "\x94\xE7\x82\x1D\xD7\xF2\xE6\xC7\xB3\x35\xDF\xDF\xCD\x5B\x39\x60\xD5" + "\xAF\x27\x08\x7F\x36\x72\xC1\xAB\x27\x0F\xB5\x29\x1F\x95\x87\x31\x60" + "\x65\xC0\x03\xED\x4E\xE5\xB1\x06\x3D\x50\x07"), + &table); + _hpack_dynamic_table_free (&table); + HTTP_TEST ((result == 0), "response with Huffman Coding (result=%d)", + result); + vlib_cli_output (vm, "hpack_serialize_response"); hpack_response_control_data_t resp_cd;