aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/plugins/hs_apps/http_cli.c10
-rw-r--r--src/plugins/hs_apps/proxy.c5
-rw-r--r--src/plugins/http/http.h143
-rw-r--r--src/plugins/http/http_plugin.rst11
-rw-r--r--src/plugins/http/test/http_test.c191
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;