diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/plugins/hs_apps/http_cli.c | 10 | ||||
-rw-r--r-- | src/plugins/hs_apps/proxy.c | 5 | ||||
-rw-r--r-- | src/plugins/http/http.h | 143 | ||||
-rw-r--r-- | src/plugins/http/http_plugin.rst | 11 | ||||
-rw-r--r-- | src/plugins/http/test/http_test.c | 191 |
5 files changed, 315 insertions, 45 deletions
diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c index 89eb49c8ec7..d670ae6b369 100644 --- a/src/plugins/hs_apps/http_cli.c +++ b/src/plugins/hs_apps/http_cli.c @@ -419,14 +419,14 @@ hcs_ts_rx_callback (session_t *ts) msg.data.headers_len, hs->req_headers.buf); ASSERT (rv == msg.data.headers_len); http_build_header_table (&hs->req_headers, msg); - const http_header_t *accept = http_get_header ( + const http_token_t *accept_value = http_get_header ( &hs->req_headers, http_header_name_token (HTTP_HEADER_ACCEPT)); - if (accept) + if (accept_value) { - HCS_DBG ("client accept: %U", format_http_bytes, accept->value.base, - accept->value.len); + HCS_DBG ("client accept: %U", format_http_bytes, accept_value->base, + accept_value->len); /* just for testing purpose, we don't care about precedence */ - if (http_token_contains (accept->value.base, accept->value.len, + if (http_token_contains (accept_value->base, accept_value->len, http_token_lit ("text/plain"))) args.plain_text = 1; } diff --git a/src/plugins/hs_apps/proxy.c b/src/plugins/hs_apps/proxy.c index f3b1fdce48c..7407bcb9f0d 100644 --- a/src/plugins/hs_apps/proxy.c +++ b/src/plugins/hs_apps/proxy.c @@ -592,13 +592,12 @@ proxy_http_connect (session_t *s, vnet_connect_args_t *a) msg.data.headers_len, req_headers.buf); ASSERT (rv == msg.data.headers_len); http_build_header_table (&req_headers, msg); - const http_header_t *capsule_protocol = http_get_header ( + const http_token_t *capsule_protocol = http_get_header ( &req_headers, http_header_name_token (HTTP_HEADER_CAPSULE_PROTOCOL)); if (capsule_protocol) { PROXY_DBG ("Capsule-Protocol header present"); - if (!http_token_is (capsule_protocol->value.base, - capsule_protocol->value.len, + if (!http_token_is (capsule_protocol->base, capsule_protocol->len, http_token_lit (HTTP_BOOLEAN_TRUE))) { PROXY_DBG ("Capsule-Protocol invalid value"); diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 008f6634ea9..637452e0474 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -861,7 +861,7 @@ typedef struct typedef struct { - http_header_t *headers; + http_token_t *values; uword *value_by_name; u8 *buf; char **concatenated_values; @@ -869,7 +869,7 @@ typedef struct #define HTTP_HEADER_TABLE_NULL \ { \ - .headers = 0, .value_by_name = 0, .buf = 0, .concatenated_values = 0, \ + .values = 0, .value_by_name = 0, .buf = 0, .concatenated_values = 0, \ } always_inline u8 @@ -882,17 +882,52 @@ http_token_is (const char *actual, uword actual_len, const char *expected, return memcmp (actual, expected, expected_len) == 0 ? 1 : 0; } +/* Based on searching for a value in a given range from Hacker's Delight */ +always_inline uword +http_tolower_word (uword x) +{ +#if uword_bits == 64 + uword all_bytes = 0x0101010101010101; +#else + uword all_bytes = 0x01010101; +#endif + uword d, y; + d = (x | (0x80 * all_bytes)) - (0x41 * all_bytes); + d = ~((x | (0x7F * all_bytes)) ^ d); + y = (d & (0x7F * all_bytes)) + (0x66 * all_bytes); + y = y | d; + y = y | (0x7F * all_bytes); + y = ~y; + y = (y >> 2) & (0x20 * all_bytes); + return (x | y); +} + always_inline u8 http_token_is_case (const char *actual, uword actual_len, const char *expected, uword expected_len) { - uword i; + uword i, last_a = 0, last_e = 0; + uword *a, *e; ASSERT (actual != 0); if (actual_len != expected_len) return 0; - for (i = 0; i < expected_len; i++) + + i = expected_len; + a = (uword *) actual; + e = (uword *) expected; + while (i >= sizeof (uword)) { - if (tolower (actual[i]) != tolower (expected[i])) + if (http_tolower_word (*a) != http_tolower_word (*e)) + return 0; + a++; + e++; + i -= sizeof (uword); + } + if (i > 0) + { + clib_memcpy_fast (&last_a, a, i); + clib_memcpy_fast (&last_e, e, i); + if (http_tolower_word (last_a) != http_tolower_word (last_e)) return 0; } return 1; @@ -927,7 +962,7 @@ http_reset_header_table (http_header_table_t *ht) for (i = 0; i < vec_len (ht->concatenated_values); i++) vec_free (ht->concatenated_values[i]); vec_reset_length (ht->concatenated_values); - vec_reset_length (ht->headers); + vec_reset_length (ht->values); vec_reset_length (ht->buf); hash_free (ht->value_by_name); } @@ -955,7 +990,7 @@ http_free_header_table (http_header_table_t *ht) for (i = 0; i < vec_len (ht->concatenated_values); i++) vec_free (ht->concatenated_values[i]); vec_free (ht->concatenated_values); - vec_free (ht->headers); + vec_free (ht->values); vec_free (ht->buf); hash_free (ht->value_by_name); } @@ -964,7 +999,37 @@ static uword _http_ht_hash_key_sum (hash_t *h, uword key) { http_token_t *name = uword_to_pointer (key, http_token_t *); - return hash_memory (name->base, name->len, 0); + uword last[3] = {}; + uwordu *q = (uword *) name->base; + u64 a, b, c, n; + + a = b = (uword_bits == 64) ? 0x9e3779b97f4a7c13LL : 0x9e3779b9; + c = 0; + n = name->len; + + while (n >= 3 * sizeof (uword)) + { + a += http_tolower_word (q[0]); + b += http_tolower_word (q[1]); + c += http_tolower_word (q[2]); + hash_mix (a, b, c); + n -= 3 * sizeof (uword); + q += 3; + } + + c += name->len; + + if (n > 0) + { + clib_memcpy_fast (&last, q, n); + a += http_tolower_word (last[0]); + b += http_tolower_word (last[1]); + c += http_tolower_word (last[2]); + } + + hash_mix (a, b, c); + + return c; } static uword @@ -973,7 +1038,22 @@ _http_ht_hash_key_equal (hash_t *h, uword key1, uword key2) http_token_t *name1 = uword_to_pointer (key1, http_token_t *); http_token_t *name2 = uword_to_pointer (key2, http_token_t *); return name1 && name2 && - http_token_is (name1->base, name1->len, name2->base, name2->len); + http_token_is_case (name1->base, name1->len, name2->base, name2->len); +} + +static u8 * +_http_ht_format_pair (u8 *s, va_list *args) +{ + http_header_table_t *ht = va_arg (*args, http_header_table_t *); + void *CLIB_UNUSED (*v) = va_arg (*args, void *); + hash_pair_t *p = va_arg (*args, hash_pair_t *); + http_token_t *name = uword_to_pointer (p->key, http_token_t *); + http_token_t *value = vec_elt_at_index (ht->values, p->value[0]); + + s = format (s, "%U: %U", format_http_bytes, name->base, name->len, + format_http_bytes, value->base, value->len); + + return s; } /** @@ -988,16 +1068,15 @@ _http_ht_hash_key_equal (hash_t *h, uword key1, uword key2) always_inline void http_build_header_table (http_header_table_t *ht, http_msg_t msg) { - http_token_t name; - http_header_t *header; + http_token_t name, *value; http_field_line_t *field_lines, *field_line; uword *p; ASSERT (ht); field_lines = uword_to_pointer (msg.data.headers_ctx, http_field_line_t *); - ht->value_by_name = - hash_create2 (0, 0, sizeof (uword), _http_ht_hash_key_sum, - _http_ht_hash_key_equal, 0, 0); + ht->value_by_name = hash_create2 ( + 0, sizeof (http_token_t), sizeof (uword), _http_ht_hash_key_sum, + _http_ht_hash_key_equal, _http_ht_format_pair, ht); vec_foreach (field_line, field_lines) { @@ -1008,27 +1087,25 @@ http_build_header_table (http_header_table_t *ht, http_msg_t msg) if (p) { char *new_value = 0; - header = vec_elt_at_index (ht->headers, p[0]); - u32 new_len = header->value.len + field_line->value_len + 2; + value = vec_elt_at_index (ht->values, p[0]); + u32 new_len = value->len + field_line->value_len + 2; vec_validate (new_value, new_len - 1); - clib_memcpy (new_value, header->value.base, header->value.len); - new_value[header->value.len] = ','; - new_value[header->value.len + 1] = ' '; - clib_memcpy (new_value + header->value.len + 2, + clib_memcpy (new_value, value->base, value->len); + new_value[value->len] = ','; + new_value[value->len + 1] = ' '; + clib_memcpy (new_value + value->len + 2, ht->buf + field_line->value_offset, field_line->value_len); vec_add1 (ht->concatenated_values, new_value); - header->value.base = new_value; - header->value.len = new_len; + value->base = new_value; + value->len = new_len; continue; } /* or create new record */ - vec_add2 (ht->headers, header, 1); - header->name.base = name.base; - header->name.len = name.len; - header->value.base = (char *) (ht->buf + field_line->value_offset); - header->value.len = field_line->value_len; - hash_set_mem (ht->value_by_name, &header->name, header - ht->headers); + vec_add2 (ht->values, value, 1); + value->base = (char *) (ht->buf + field_line->value_offset); + value->len = field_line->value_len; + hash_set_mem_alloc (&ht->value_by_name, &name, value - ht->values); } } @@ -1038,21 +1115,21 @@ http_build_header_table (http_header_table_t *ht, http_msg_t msg) * @param header_table Header table to search. * @param name Header name to match. * - * @return Header in case of success, @c 0 otherwise. + * @return Header value in case of success, @c 0 otherwise. */ -always_inline const http_header_t * +always_inline const http_token_t * http_get_header (http_header_table_t *header_table, const char *name, uword name_len) { uword *p; - http_header_t *header; + http_token_t *value; http_token_t name_token = { (char *) name, name_len }; p = hash_get_mem (header_table->value_by_name, &name_token); if (p) { - header = vec_elt_at_index (header_table->headers, p[0]); - return header; + value = vec_elt_at_index (header_table->values, p[0]); + return value; } return 0; diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst index 90ffb1919e6..55c5afc3a2d 100644 --- a/src/plugins/http/http_plugin.rst +++ b/src/plugins/http/http_plugin.rst @@ -139,11 +139,14 @@ Following example shows how to parse headers: /* build header table */ http_build_header_table (&ht, msg); /* get Accept header */ - const http_header_t *accept = http_get_header (&ht, + const http_token_t *accept_value = http_get_header (&ht, http_header_name_token (HTTP_HEADER_ACCEPT)); if (accept_value) { - /* do something interesting */ + if (http_token_contains (accept_value->base, accept_value->len, http_token_lit ("text/plain"))) + { + /* do something interesting */ + } } /* free header table */ http_free_header_table (&ht); @@ -187,7 +190,7 @@ Modified example above: /* build header table */ http_build_header_table (&ctx->ht, msg); /* get Accept header */ - const http_header_t *accept = http_get_header (&ctx->ht, + const http_token_t *accept_value = http_get_header (&ctx->ht, http_header_name_token (HTTP_HEADER_ACCEPT)); if (accept_value) { @@ -495,7 +498,7 @@ Following example shows how to parse headers: /* build header table */ http_build_header_table (&ht, msg); /* get Content-Type header */ - const http_header_t *content_type = http_get_header (&ht, + const http_token_t *content_type = http_get_header (&ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); if (content_type) { diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index 7cf36f82577..bfaa285eb35 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -5,6 +5,7 @@ #include <vnet/plugin/plugin.h> #include <vpp/app/version.h> #include <http/http.h> +#include <http/http_header_names.h> #define HTTP_TEST_I(_cond, _comment, _args...) \ ({ \ @@ -350,6 +351,188 @@ http_test_udp_payload_datagram (vlib_main_t *vm) return 0; } +static int +http_test_http_token_is_case (vlib_main_t *vm) +{ + static const char eq_1[] = "content-length"; + static const char eq_2[] = "CONtENT-lenGth"; + static const char eq_3[] = "caPsulE-ProtOcol"; + static const char eq_4[] = "ACCESS-CONTROL-REQUEST-METHOD"; + static const char ne_1[] = "content_length"; + static const char ne_2[] = "content-lengXh"; + static const char ne_3[] = "coNtent-lengXh"; + static const char ne_4[] = "content-len"; + static const char ne_5[] = "comtent-length"; + static const char ne_6[] = "content-lengtR"; + u8 rv; + + rv = http_token_is_case ( + eq_1, strlen (eq_1), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 1), "'%s' and '%s' are equal", eq_1, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + eq_2, strlen (eq_2), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 1), "'%s' and '%s' are equal", eq_2, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = + http_token_is_case (eq_3, strlen (eq_3), + http_header_name_token (HTTP_HEADER_CAPSULE_PROTOCOL)); + HTTP_TEST ((rv == 1), "'%s' and '%s' are equal", eq_3, + http_header_name_str (HTTP_HEADER_CAPSULE_PROTOCOL)) + + rv = http_token_is_case ( + eq_4, strlen (eq_4), + http_header_name_token (HTTP_HEADER_ACCESS_CONTROL_REQUEST_METHOD)); + HTTP_TEST ((rv == 1), "'%s' and '%s' are equal", eq_4, + http_header_name_str (HTTP_HEADER_ACCESS_CONTROL_REQUEST_METHOD)) + + rv = http_token_is_case ( + ne_1, strlen (ne_1), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_1, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + ne_2, strlen (ne_2), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_2, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + ne_3, strlen (ne_3), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_3, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + ne_4, strlen (ne_4), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_4, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + ne_5, strlen (ne_5), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_5, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + rv = http_token_is_case ( + ne_6, strlen (ne_6), http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)); + HTTP_TEST ((rv == 0), "'%s' and '%s' are not equal", ne_6, + http_header_name_str (HTTP_HEADER_CONTENT_LENGTH)) + + return 0; +} + +static int +http_test_http_header_table (vlib_main_t *vm) +{ + http_header_table_t ht = HTTP_HEADER_TABLE_NULL; + const char buf[] = "daTe: Wed, 15 Jan 2025 16:17:33 GMT" + "conTent-tYpE: text/html; charset=utf-8" + "STRICT-transport-security: max-age=31536000" + "sAnDwich: Eggs" + "CONTENT-ENCODING: GZIP" + "sandwich: Spam"; + http_msg_t msg = {}; + http_field_line_t *headers = 0, *field_line; + const http_token_t *value; + u8 rv; + + /* daTe */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 0; + field_line->name_len = 4; + field_line->value_offset = 6; + field_line->value_len = 29; + /* conTent-tYpE */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 35; + field_line->name_len = 12; + field_line->value_offset = 49; + field_line->value_len = 24; + /* STRICT-transport-security */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 73; + field_line->name_len = 25; + field_line->value_offset = 100; + field_line->value_len = 16; + /* sAnDwich */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 116; + field_line->name_len = 8; + field_line->value_offset = 126; + field_line->value_len = 4; + /* CONTENT-ENCODING */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 130; + field_line->name_len = 16; + field_line->value_offset = 148; + field_line->value_len = 4; + /* sandwich */ + vec_add2 (headers, field_line, 1); + field_line->name_offset = 152; + field_line->name_len = 8; + field_line->value_offset = 162; + field_line->value_len = 4; + + msg.data.headers_ctx = pointer_to_uword (headers); + msg.data.headers_len = strlen (buf); + + http_init_header_table_buf (&ht, msg); + memcpy (ht.buf, buf, strlen (buf)); + http_build_header_table (&ht, msg); + + vlib_cli_output (vm, "%U", format_hash, ht.value_by_name, 1); + + value = http_get_header ( + &ht, http_header_name_token (HTTP_HEADER_CONTENT_ENCODING)); + HTTP_TEST ((value != 0), "'%s' is in headers", + http_header_name_str (HTTP_HEADER_CONTENT_ENCODING)); + rv = http_token_is (value->base, value->len, http_token_lit ("GZIP")); + HTTP_TEST ((rv = 1), "header value '%U' should be 'GZIP'", format_http_bytes, + value->base, value->len); + + value = + http_get_header (&ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE)); + HTTP_TEST ((value != 0), "'%s' is in headers", + http_header_name_str (HTTP_HEADER_CONTENT_TYPE)); + + value = http_get_header (&ht, http_header_name_token (HTTP_HEADER_DATE)); + HTTP_TEST ((value != 0), "'%s' is in headers", + http_header_name_str (HTTP_HEADER_DATE)); + + value = http_get_header ( + &ht, http_header_name_token (HTTP_HEADER_STRICT_TRANSPORT_SECURITY)); + HTTP_TEST ((value != 0), "'%s' is in headers", + http_header_name_str (HTTP_HEADER_STRICT_TRANSPORT_SECURITY)); + + value = http_get_header (&ht, http_token_lit ("DATE")); + HTTP_TEST ((value != 0), "'DATE' is in headers"); + + value = http_get_header (&ht, http_token_lit ("date")); + HTTP_TEST ((value != 0), "'date' is in headers"); + + /* repeated header */ + value = http_get_header (&ht, http_token_lit ("sandwich")); + HTTP_TEST ((value != 0), "'sandwich' is in headers"); + rv = http_token_is (value->base, value->len, http_token_lit ("Eggs, Spam")); + HTTP_TEST ((rv = 1), "header value '%U' should be 'Eggs, Spam'", + format_http_bytes, value->base, value->len); + + value = http_get_header (&ht, http_token_lit ("Jade")); + HTTP_TEST ((value == 0), "'Jade' is not in headers"); + + value = http_get_header (&ht, http_token_lit ("CONTENT")); + HTTP_TEST ((value == 0), "'CONTENT' is not in headers"); + + value = + http_get_header (&ht, http_header_name_token (HTTP_HEADER_ACCEPT_CHARSET)); + HTTP_TEST ((value == 0), "'%s' is not in headers", + http_header_name_str (HTTP_HEADER_ACCEPT_CHARSET)); + + http_free_header_table (&ht); + vec_free (headers); + return 0; +} + static clib_error_t * test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -363,6 +546,10 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, res = http_test_parse_masque_host_port (vm); else if (unformat (input, "udp-payload-datagram")) res = http_test_udp_payload_datagram (vm); + else if (unformat (input, "token-is-case")) + res = http_test_http_token_is_case (vm); + else if (unformat (input, "header-table")) + res = http_test_http_header_table (vm); else if (unformat (input, "all")) { if ((res = http_test_parse_authority (vm))) @@ -371,6 +558,10 @@ test_http_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; if ((res = http_test_udp_payload_datagram (vm))) goto done; + if ((res = http_test_http_token_is_case (vm))) + goto done; + if ((res = http_test_http_header_table (vm))) + goto done; } else break; |