http: h2 decoding response 78/43278/3
authorMatus Fabian <[email protected]>
Mon, 23 Jun 2025 17:15:04 +0000 (17:15 +0000)
committerFlorin Coras <[email protected]>
Tue, 24 Jun 2025 17:48:19 +0000 (17:48 +0000)
Type: improvement

Change-Id: I5e582e6fec972d6d61683a7a76c2a3f222a9030b
Signed-off-by: Matus Fabian <[email protected]>
src/plugins/http/http2/hpack.c
src/plugins/http/http2/hpack.h
src/plugins/http/test/http_test.c

index 15e92fe..8af061e 100644 (file)
@@ -7,6 +7,7 @@
 #include <http/http2/hpack.h>
 #include <http/http2/huffman_table.h>
 #include <http/http_status_codes.h>
+#include <http/http_private.h>
 
 #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");
index a2fd5fa..72b9f2b 100644 (file)
@@ -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
  *
index 73a397a..fbeee4b 100644 (file)
@@ -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;