http: "absolute-form" target URL parsing 74/41774/2
authorMatus Fabian <[email protected]>
Tue, 29 Oct 2024 14:06:49 +0000 (15:06 +0100)
committerMatus Fabian <[email protected]>
Tue, 29 Oct 2024 16:26:04 +0000 (17:26 +0100)
Type: improvement

Change-Id: If39680a148d39add40433547369b2ddad3c2e226
Signed-off-by: Matus Fabian <[email protected]>
extras/hs-test/docker/Dockerfile.vpp
extras/hs-test/http_test.go
extras/hs-test/infra/vppinstance.go
src/plugins/http/CMakeLists.txt
src/plugins/http/http.h
src/plugins/http/http_test.c [deleted file]
src/plugins/http/test/http_test.c [new file with mode: 0644]

index 82a1a1a..5f5d41c 100644 (file)
@@ -13,6 +13,7 @@ COPY \
    $DIR/af_packet_plugin.so \
    $DIR/hs_apps_plugin.so \
    $DIR/http_plugin.so \
+   $DIR/http_unittest_plugin.so \
    $DIR/unittest_plugin.so \
    $DIR/quic_plugin.so \
    $DIR/http_static_plugin.so \
index 0b345d1..7d0063d 100644 (file)
@@ -31,7 +31,7 @@ func init() {
                HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
                HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
                HttpHeadersTest, HttpStaticFileHandlerTest, HttpStaticFileHandlerDefaultMaxAgeTest, HttpClientTest, HttpClientErrRespTest, HttpClientPostFormTest,
-               HttpClientPostFileTest, HttpClientPostFilePtrTest, AuthorityFormTargetTest, HttpRequestLineTest,
+               HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest, HttpRequestLineTest,
                HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest)
        RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
                PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
@@ -352,24 +352,11 @@ func HttpClientPostFilePtrTest(s *NoTopoSuite) {
        httpClientPostFile(s, true, 131072)
 }
 
-func cliTestAuthority(s *NoTopoSuite, authority string) {
-       o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
-       s.AssertNotContains(o, "error")
-       s.AssertContains(o, authority)
-}
-
-func cliTestAuthorityError(s *NoTopoSuite, authority string) {
-       o := s.GetContainerByName("vpp").VppInstance.Vppctl("test http authority-form " + authority)
-       s.AssertContains(o, "error")
-}
-
-func AuthorityFormTargetTest(s *NoTopoSuite) {
-       cliTestAuthority(s, "10.10.2.45:20")
-       cliTestAuthority(s, "[dead:beef::1234]:443")
-       cliTestAuthorityError(s, "example.com:80")
-       cliTestAuthorityError(s, "10.10.2.45")
-       cliTestAuthorityError(s, "1000.10.2.45:20")
-       cliTestAuthorityError(s, "[xyz0::1234]:443")
+func HttpUnitTest(s *NoTopoSuite) {
+       vpp := s.GetContainerByName("vpp").VppInstance
+       o := vpp.Vppctl("test http all")
+       s.Log(o)
+       s.AssertNotContains(o, "FAIL")
 }
 
 func HttpStaticPromTest(s *NoTopoSuite) {
index dac13bf..d40fbff 100644 (file)
@@ -60,6 +60,7 @@ plugins {
   plugin af_packet_plugin.so { enable }
   plugin hs_apps_plugin.so { enable }
   plugin http_plugin.so { enable }
+  plugin http_unittest_plugin.so { enable }
   plugin http_static_plugin.so { enable }
   plugin prom_plugin.so { enable }
   plugin tlsopenssl_plugin.so { enable }
index c51a7dc..075b8d6 100644 (file)
@@ -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
 )
index b984c95..13118bf 100644 (file)
@@ -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 (file)
index 1f2f21d..0000000
+++ /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 (file)
index 0000000..c21cf85
--- /dev/null
@@ -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,
+};