aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/plugins/http/CMakeLists.txt6
-rw-r--r--src/plugins/http/http.h179
-rw-r--r--src/plugins/http/http_test.c34
-rw-r--r--src/plugins/http/test/http_test.c292
4 files changed, 468 insertions, 43 deletions
diff --git a/src/plugins/http/CMakeLists.txt b/src/plugins/http/CMakeLists.txt
index c51a7dce36d..075b8d6817b 100644
--- a/src/plugins/http/CMakeLists.txt
+++ b/src/plugins/http/CMakeLists.txt
@@ -16,5 +16,9 @@ add_vpp_plugin(http
http.c
http_buffer.c
http_timer.c
- http_test.c
+)
+
+add_vpp_plugin(http_unittest
+ SOURCES
+ test/http_test.c
)
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h
index b984c9564d6..13118bfde06 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -447,9 +447,10 @@ typedef struct http_main_
} http_main_t;
always_inline int
-_validate_target_syntax (u8 *target, int is_query, int *is_encoded)
+_validate_target_syntax (u8 *target, u32 len, int is_query, int *is_encoded)
{
- int i, encoded = 0;
+ int encoded = 0;
+ u32 i;
static uword valid_chars[4] = {
/* !$&'()*+,-./0123456789:;= */
@@ -460,7 +461,7 @@ _validate_target_syntax (u8 *target, int is_query, int *is_encoded)
0x0000000000000000,
};
- for (i = 0; i < vec_len (target); i++)
+ for (i = 0; i < len; i++)
{
if (clib_bitmap_get_no_check (valid_chars, target[i]))
continue;
@@ -471,7 +472,7 @@ _validate_target_syntax (u8 *target, int is_query, int *is_encoded)
/* pct-encoded = "%" HEXDIG HEXDIG */
if (target[i] == '%')
{
- if ((i + 2) > vec_len (target))
+ if ((i + 2) >= len)
return -1;
if (!isxdigit (target[i + 1]) || !isxdigit (target[i + 2]))
return -1;
@@ -490,7 +491,7 @@ _validate_target_syntax (u8 *target, int is_query, int *is_encoded)
/**
* An "absolute-path" rule validation (RFC9110 section 4.1).
*
- * @param path Target path to validate.
+ * @param path Vector of target path to validate.
* @param is_encoded Return flag that indicates if percent-encoded (optional).
*
* @return @c 0 on success.
@@ -498,13 +499,13 @@ _validate_target_syntax (u8 *target, int is_query, int *is_encoded)
always_inline int
http_validate_abs_path_syntax (u8 *path, int *is_encoded)
{
- return _validate_target_syntax (path, 0, is_encoded);
+ return _validate_target_syntax (path, vec_len (path), 0, is_encoded);
}
/**
* A "query" rule validation (RFC3986 section 2.1).
*
- * @param query Target query to validate.
+ * @param query Vector of target query to validate.
* @param is_encoded Return flag that indicates if percent-encoded (optional).
*
* @return @c 0 on success.
@@ -512,7 +513,7 @@ http_validate_abs_path_syntax (u8 *path, int *is_encoded)
always_inline int
http_validate_query_syntax (u8 *query, int *is_encoded)
{
- return _validate_target_syntax (query, 1, is_encoded);
+ return _validate_target_syntax (query, vec_len (query), 1, is_encoded);
}
#define htoi(x) (isdigit (x) ? (x - '0') : (tolower (x) - 'a' + 10))
@@ -977,6 +978,168 @@ http_serialize_authority_form_target (http_uri_t *authority)
return s;
}
+typedef enum http_url_scheme_
+{
+ HTTP_URL_SCHEME_HTTP,
+ HTTP_URL_SCHEME_HTTPS,
+} http_url_scheme_t;
+
+typedef struct
+{
+ http_url_scheme_t scheme;
+ u16 port;
+ u32 host_offset;
+ u32 host_len;
+ u32 path_offset;
+ u32 path_len;
+ u8 host_is_ip6;
+} http_url_t;
+
+/**
+ * An "absolute-form" URL parsing.
+ *
+ * @param url Vector of target URL to validate.
+ * @param parsed Parsed URL metadata in case of success.
+ *
+ * @return @c 0 on success.
+ */
+always_inline int
+http_parse_absolute_form (u8 *url, http_url_t *parsed)
+{
+ u8 *token_start, *token_end, *end;
+ int is_encoded = 0;
+
+ static uword valid_chars[4] = {
+ /* -.0123456789 */
+ 0x03ff600000000000,
+ /* ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz */
+ 0x07fffffe07fffffe,
+ 0x0000000000000000,
+ 0x0000000000000000,
+ };
+
+ if (vec_len (url) < 9)
+ {
+ clib_warning ("uri too short");
+ return -1;
+ }
+
+ clib_memset (parsed, 0, sizeof (*parsed));
+
+ end = url + vec_len (url);
+
+ /* parse scheme */
+ if (!memcmp (url, "http:// ", 7))
+ {
+ parsed->scheme = HTTP_URL_SCHEME_HTTP;
+ parsed->port = clib_host_to_net_u16 (80);
+ parsed->host_offset = 7;
+ }
+ else if (!memcmp (url, "https:// ", 8))
+ {
+ parsed->scheme = HTTP_URL_SCHEME_HTTPS;
+ parsed->port = clib_host_to_net_u16 (443);
+ parsed->host_offset = 8;
+ }
+ else
+ {
+ clib_warning ("invalid scheme");
+ return -1;
+ }
+ token_start = url + parsed->host_offset;
+
+ /* parse host */
+ if (*token_start == '[')
+ /* IPv6 address */
+ {
+ parsed->host_is_ip6 = 1;
+ parsed->host_offset++;
+ token_end = ++token_start;
+ while (1)
+ {
+ if (token_end == end)
+ {
+ clib_warning ("invalid host, IPv6 addr not terminated with ']'");
+ return -1;
+ }
+ else if (*token_end == ']')
+ {
+ parsed->host_len = token_end - token_start;
+ token_start = token_end + 1;
+ break;
+ }
+ else if (*token_end != ':' && *token_end != '.' &&
+ !isxdigit (*token_end))
+ {
+ clib_warning ("invalid character '%u'", *token_end);
+ return -1;
+ }
+ token_end++;
+ }
+ }
+ else
+ {
+ token_end = token_start;
+ while (token_end != end && *token_end != ':' && *token_end != '/')
+ {
+ if (!clib_bitmap_get_no_check (valid_chars, *token_end))
+ {
+ clib_warning ("invalid character '%u'", *token_end);
+ return -1;
+ }
+ token_end++;
+ }
+ parsed->host_len = token_end - token_start;
+ token_start = token_end;
+ }
+
+ if (!parsed->host_len)
+ {
+ clib_warning ("zero length host");
+ return -1;
+ }
+
+ /* parse port, if any */
+ if (token_start != end && *token_start == ':')
+ {
+ u32 port = 0;
+ token_end = ++token_start;
+ while (token_end != end && *token_end != '/')
+ {
+ if (isdigit (*token_end))
+ {
+ port = port * 10 + *token_end - '0';
+ if (port > 65535)
+ {
+ clib_warning ("invalid port number");
+ return -1;
+ }
+ }
+ else
+ {
+ clib_warning ("expected digit '%u'", *token_end);
+ return -1;
+ }
+ token_end++;
+ }
+ parsed->port = clib_host_to_net_u16 ((u16) port);
+ token_start = token_end;
+ }
+
+ if (token_start == end)
+ return 0;
+
+ token_start++; /* drop leading slash */
+ parsed->path_offset = token_start - url;
+ parsed->path_len = end - token_start;
+
+ if (parsed->path_len)
+ return _validate_target_syntax (token_start, parsed->path_len, 0,
+ &is_encoded);
+
+ return 0;
+}
+
#endif /* SRC_PLUGINS_HTTP_HTTP_H_ */
/*
diff --git a/src/plugins/http/http_test.c b/src/plugins/http/http_test.c
deleted file mode 100644
index 1f2f21dd19a..00000000000
--- a/src/plugins/http/http_test.c
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: Apache-2.0
- * Copyright(c) 2024 Cisco Systems, Inc.
- */
-
-#include <http/http.h>
-
-static clib_error_t *
-test_http_authority_command_fn (vlib_main_t *vm, unformat_input_t *input,
- vlib_cli_command_t *cmd)
-{
- u8 *target = 0;
- http_uri_t authority;
- int rv;
-
- if (!unformat (input, "%v", &target))
- return clib_error_return (0, "error: no input provided");
-
- rv = http_parse_authority_form_target (target, &authority);
- vec_free (target);
- if (rv)
- return clib_error_return (0, "error: parsing failed");
-
- target = http_serialize_authority_form_target (&authority);
- vlib_cli_output (vm, "%v", target);
- vec_free (target);
-
- return 0;
-}
-
-VLIB_CLI_COMMAND (test_http_authority_command) = {
- .path = "test http authority-form",
- .short_help = "test dns authority-form",
- .function = test_http_authority_command_fn,
-};
diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c
new file mode 100644
index 00000000000..c21cf85a1bb
--- /dev/null
+++ b/src/plugins/http/test/http_test.c
@@ -0,0 +1,292 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2024 Cisco Systems, Inc.
+ */
+
+#include <vnet/plugin/plugin.h>
+#include <vpp/app/version.h>
+#include <http/http.h>
+
+#define HTTP_TEST_I(_cond, _comment, _args...) \
+ ({ \
+ int _evald = (_cond); \
+ if (!(_evald)) \
+ { \
+ vlib_cli_output (vm, "FAIL:%d: " _comment "\n", __LINE__, ##_args); \
+ } \
+ else \
+ { \
+ vlib_cli_output (vm, "PASS:%d: " _comment "\n", __LINE__, ##_args); \
+ } \
+ _evald; \
+ })
+
+#define HTTP_TEST(_cond, _comment, _args...) \
+ { \
+ if (!HTTP_TEST_I (_cond, _comment, ##_args)) \
+ { \
+ return 1; \
+ } \
+ }
+
+static int
+http_test_authority_form (vlib_main_t *vm)
+{
+ u8 *target = 0, *formated_target = 0;
+ http_uri_t authority;
+ int rv;
+
+ target = format (0, "10.10.2.45:20");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", target);
+ formated_target = http_serialize_authority_form_target (&authority);
+ rv = vec_cmp (target, formated_target);
+ HTTP_TEST ((rv == 0), "'%v' should match '%v'", target, formated_target);
+ vec_free (target);
+ vec_free (formated_target);
+
+ target = format (0, "[dead:beef::1234]:443");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", target);
+ formated_target = http_serialize_authority_form_target (&authority);
+ rv = vec_cmp (target, formated_target);
+ HTTP_TEST ((rv == 0), "'%v' should match '%v'", target, formated_target);
+ vec_free (target);
+ vec_free (formated_target);
+
+ target = format (0, "example.com:80");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv != 0), "'%v' reg-name not supported", target);
+ vec_free (target);
+
+ target = format (0, "10.10.2.45");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", target);
+ vec_free (target);
+
+ target = format (0, "1000.10.2.45:20");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", target);
+ vec_free (target);
+
+ target = format (0, "[xyz0::1234]:443");
+ rv = http_parse_authority_form_target (target, &authority);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", target);
+ vec_free (target);
+
+ return 0;
+}
+
+static int
+http_test_absolute_form (vlib_main_t *vm)
+{
+ u8 *url = 0;
+ http_url_t parsed_url;
+ int rv;
+
+ url = format (0, "https://example.org/.well-known/masque/udp/1.2.3.4/123/");
+ clib_warning (
+ "strlen %u vec_len %u",
+ strlen ("https://example.org/.well-known/masque/udp/1.2.3.4/123/"),
+ vec_len (url));
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", url);
+ HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTPS),
+ "scheme should be https");
+ HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0",
+ parsed_url.host_is_ip6);
+ HTTP_TEST ((parsed_url.host_offset == strlen ("https://")),
+ "host_offset=%u should be %u", parsed_url.host_offset,
+ strlen ("https://"));
+ HTTP_TEST ((parsed_url.host_len == strlen ("example.org")),
+ "host_len=%u should be %u", parsed_url.host_len,
+ strlen ("example.org"));
+ HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 443),
+ "port=%u should be 443", clib_net_to_host_u16 (parsed_url.port));
+ HTTP_TEST ((parsed_url.path_offset == strlen ("https://example.org/")),
+ "path_offset=%u should be %u", parsed_url.path_offset,
+ strlen ("https://example.org/"));
+ HTTP_TEST (
+ (parsed_url.path_len == strlen (".well-known/masque/udp/1.2.3.4/123/")),
+ "path_len=%u should be %u", parsed_url.path_len,
+ strlen (".well-known/masque/udp/1.2.3.4/123/"));
+ vec_free (url);
+
+ url = format (0, "http://vpp-example.org");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", url);
+ HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP),
+ "scheme should be http");
+ HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0",
+ parsed_url.host_is_ip6);
+ HTTP_TEST ((parsed_url.host_offset == strlen ("http://")),
+ "host_offset=%u should be %u", parsed_url.host_offset,
+ strlen ("http://"));
+ HTTP_TEST ((parsed_url.host_len == strlen ("vpp-example.org")),
+ "host_len=%u should be %u", parsed_url.host_len,
+ strlen ("vpp-example.org"));
+ HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 80),
+ "port=%u should be 80", clib_net_to_host_u16 (parsed_url.port));
+ HTTP_TEST ((parsed_url.path_len == 0), "path_len=%u should be 0",
+ parsed_url.path_len);
+ vec_free (url);
+
+ url = format (0, "http://1.2.3.4:8080/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", url);
+ HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP),
+ "scheme should be http");
+ HTTP_TEST ((parsed_url.host_is_ip6 == 0), "host_is_ip6=%u should be 0",
+ parsed_url.host_is_ip6);
+ HTTP_TEST ((parsed_url.host_offset == strlen ("http://")),
+ "host_offset=%u should be %u", parsed_url.host_offset,
+ strlen ("http://"));
+ HTTP_TEST ((parsed_url.host_len == strlen ("1.2.3.4")),
+ "host_len=%u should be %u", parsed_url.host_len,
+ strlen ("1.2.3.4"));
+ HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 8080),
+ "port=%u should be 8080", clib_net_to_host_u16 (parsed_url.port));
+ HTTP_TEST ((parsed_url.path_offset == strlen ("http://1.2.3.4:8080/")),
+ "path_offset=%u should be %u", parsed_url.path_offset,
+ strlen ("http://1.2.3.4:8080/"));
+ HTTP_TEST ((parsed_url.path_len == strlen ("abcd")),
+ "path_len=%u should be %u", parsed_url.path_len, strlen ("abcd"));
+ vec_free (url);
+
+ url = format (0, "https://[dead:beef::1234]/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", url);
+ HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTPS),
+ "scheme should be https");
+ HTTP_TEST ((parsed_url.host_is_ip6 == 1), "host_is_ip6=%u should be 1",
+ parsed_url.host_is_ip6);
+ HTTP_TEST ((parsed_url.host_offset == strlen ("https://[")),
+ "host_offset=%u should be %u", parsed_url.host_offset,
+ strlen ("https://["));
+ HTTP_TEST ((parsed_url.host_len == strlen ("dead:beef::1234")),
+ "host_len=%u should be %u", parsed_url.host_len,
+ strlen ("dead:beef::1234"));
+ HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 443),
+ "port=%u should be 443", clib_net_to_host_u16 (parsed_url.port));
+ HTTP_TEST ((parsed_url.path_offset == strlen ("https://[dead:beef::1234]/")),
+ "path_offset=%u should be %u", parsed_url.path_offset,
+ strlen ("https://[dead:beef::1234]/"));
+ HTTP_TEST ((parsed_url.path_len == strlen ("abcd")),
+ "path_len=%u should be %u", parsed_url.path_len, strlen ("abcd"));
+ vec_free (url);
+
+ url = format (0, "http://[::ffff:192.0.2.128]:8080/");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv == 0), "'%v' should be valid", url);
+ HTTP_TEST ((parsed_url.scheme == HTTP_URL_SCHEME_HTTP),
+ "scheme should be http");
+ HTTP_TEST ((parsed_url.host_is_ip6 == 1), "host_is_ip6=%u should be 1",
+ parsed_url.host_is_ip6);
+ HTTP_TEST ((parsed_url.host_offset == strlen ("http://[")),
+ "host_offset=%u should be %u", parsed_url.host_offset,
+ strlen ("http://["));
+ HTTP_TEST ((parsed_url.host_len == strlen ("::ffff:192.0.2.128")),
+ "host_len=%u should be %u", parsed_url.host_len,
+ strlen ("::ffff:192.0.2.128"));
+ HTTP_TEST ((clib_net_to_host_u16 (parsed_url.port) == 8080),
+ "port=%u should be 8080", clib_net_to_host_u16 (parsed_url.port));
+ HTTP_TEST ((parsed_url.path_len == 0), "path_len=%u should be 0",
+ parsed_url.path_len);
+ vec_free (url);
+
+ url = format (0, "http://[dead:beef::1234/abc");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://[dead|beef::1234]/abc");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http:example.org:8080/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "htt://example.org:8080/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http:///abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://example.org:808080/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://example.org/a%%3Xbcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://example.org/a%%3");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://example.org/a[b]cd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ url = format (0, "http://exa[m]ple.org/abcd");
+ rv = http_parse_absolute_form (url, &parsed_url);
+ HTTP_TEST ((rv != 0), "'%v' should be invalid", url);
+ vec_free (url);
+
+ return 0;
+}
+
+static clib_error_t *
+test_http_command_fn (vlib_main_t *vm, unformat_input_t *input,
+ vlib_cli_command_t *cmd)
+{
+ int res = 0;
+ while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+ {
+ if (unformat (input, "authority-form"))
+ res = http_test_authority_form (vm);
+ else if (unformat (input, "absolute-form"))
+ res = http_test_absolute_form (vm);
+ else if (unformat (input, "all"))
+ {
+ if ((res = http_test_authority_form (vm)))
+ goto done;
+ if ((res = http_test_absolute_form (vm)))
+ goto done;
+ }
+ else
+ break;
+ }
+
+done:
+ if (res)
+ return clib_error_return (0, "HTTP unit test failed");
+ return 0;
+}
+
+VLIB_CLI_COMMAND (test_http_command) = {
+ .path = "test http",
+ .short_help = "http unit tests",
+ .function = test_http_command_fn,
+};
+
+VLIB_PLUGIN_REGISTER () = {
+ .version = VPP_BUILD_VER,
+ .description = "HTTP - Unit Test",
+ .default_disabled = 1,
+};