http: "absolute-form" target URL parsing

Type: improvement

Change-Id: If39680a148d39add40433547369b2ddad3c2e226
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/extras/hs-test/docker/Dockerfile.vpp b/extras/hs-test/docker/Dockerfile.vpp
index 82a1a1a..5f5d41c 100644
--- a/extras/hs-test/docker/Dockerfile.vpp
+++ b/extras/hs-test/docker/Dockerfile.vpp
@@ -13,6 +13,7 @@
    $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 \
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index 0b345d1..7d0063d 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -31,7 +31,7 @@
 		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 @@
 	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) {
diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go
index dac13bf..d40fbff 100644
--- a/extras/hs-test/infra/vppinstance.go
+++ b/extras/hs-test/infra/vppinstance.go
@@ -60,6 +60,7 @@
   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 }
diff --git a/src/plugins/http/CMakeLists.txt b/src/plugins/http/CMakeLists.txt
index c51a7dc..075b8d6 100644
--- a/src/plugins/http/CMakeLists.txt
+++ b/src/plugins/http/CMakeLists.txt
@@ -16,5 +16,9 @@
   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 b984c95..13118bf 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -447,9 +447,10 @@
 } 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 @@
     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 @@
       /* 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 @@
 /**
  * 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 @@
 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 @@
 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 @@
   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 1f2f21d..0000000
--- 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 0000000..c21cf85
--- /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,
+};