diff options
Diffstat (limited to 'src/plugins/http')
-rw-r--r-- | src/plugins/http/CMakeLists.txt | 6 | ||||
-rw-r--r-- | src/plugins/http/http.h | 179 | ||||
-rw-r--r-- | src/plugins/http/http_test.c | 34 | ||||
-rw-r--r-- | src/plugins/http/test/http_test.c | 292 |
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, +}; |