You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by jr...@apache.org on 2019/01/29 20:31:09 UTC

[trafficserver] branch master updated: Add normalization the URI before cdniuc validation

This is an automated email from the ASF dual-hosted git repository.

jrushford pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new b39b0f7  Add normalization the URI before cdniuc validation
b39b0f7 is described below

commit b39b0f7dd74b66dd11b7d62f49f3432a10dc7fbd
Author: Dylan Souza <dy...@comcast.com>
AuthorDate: Tue Jan 8 20:33:53 2019 +0000

    Add normalization the URI before cdniuc validation
---
 plugins/experimental/uri_signing/Makefile.inc      |   2 +
 plugins/experimental/uri_signing/common.h          |   1 +
 plugins/experimental/uri_signing/jwt.c             |  37 +-
 plugins/experimental/uri_signing/match.c           |   2 +-
 plugins/experimental/uri_signing/match.h           |   2 +-
 plugins/experimental/uri_signing/normalize.c       | 382 +++++++++++++++++++++
 .../uri_signing/{match.h => normalize.h}           |   5 +-
 .../uri_signing/unit_tests/uri_signing_test.cc     | 182 ++++++++++
 8 files changed, 593 insertions(+), 20 deletions(-)

diff --git a/plugins/experimental/uri_signing/Makefile.inc b/plugins/experimental/uri_signing/Makefile.inc
index e9807ba..864d2b4 100644
--- a/plugins/experimental/uri_signing/Makefile.inc
+++ b/plugins/experimental/uri_signing/Makefile.inc
@@ -23,6 +23,7 @@ experimental_uri_signing_uri_signing_la_SOURCES = \
   experimental/uri_signing/jwt.c                  \
   experimental/uri_signing/match.c                \
   experimental/uri_signing/parse.c                \
+  experimental/uri_signing/normalize.c            \
   experimental/uri_signing/timing.c
 
 experimental_uri_signing_uri_signing_la_LIBADD = @LIBJANSSON@ @LIBCJOSE@ @LIBPCRE@ -lm -lcrypto
@@ -39,4 +40,5 @@ experimental_uri_signing_test_uri_signing_SOURCES = \
     experimental/uri_signing/cookie.c \
     experimental/uri_signing/config.c \
     experimental/uri_signing/timing.c \
+    experimental/uri_signing/normalize.c \
     experimental/uri_signing/match.c
diff --git a/plugins/experimental/uri_signing/common.h b/plugins/experimental/uri_signing/common.h
index c930408..10505fa 100644
--- a/plugins/experimental/uri_signing/common.h
+++ b/plugins/experimental/uri_signing/common.h
@@ -28,6 +28,7 @@ void PrintToStdErr(const char *fmt, ...);
 
 #else
 
+#include "ts/ts.h"
 #define PluginDebug(...) TSDebug("uri_signing", PLUGIN_NAME " " __VA_ARGS__)
 #define PluginError(...) PluginDebug(__VA_ARGS__), TSError(PLUGIN_NAME " " __VA_ARGS__)
 
diff --git a/plugins/experimental/uri_signing/jwt.c b/plugins/experimental/uri_signing/jwt.c
index 997448b..a38565c 100644
--- a/plugins/experimental/uri_signing/jwt.c
+++ b/plugins/experimental/uri_signing/jwt.c
@@ -19,6 +19,7 @@
 #include "common.h"
 #include "jwt.h"
 #include "match.h"
+#include "normalize.h"
 #include "ts/ts.h"
 #include <jansson.h>
 #include <cjose/cjose.h>
@@ -161,14 +162,26 @@ jwt_validate(struct jwt *jwt)
 bool
 jwt_check_uri(const char *cdniuc, const char *uri)
 {
-  static const char CONT_URI_STR[]         = "uri";
-  static const char CONT_URI_PATTERN_STR[] = "uri-pattern";
-  static const char CONT_URI_REGEX_STR[]   = "uri-regex";
+  static const char CONT_URI_HASH_STR[]  = "hash";
+  static const char CONT_URI_REGEX_STR[] = "regex";
 
   if (!cdniuc || !*cdniuc || !uri) {
     return false;
   }
 
+  /* Normalize the URI */
+  int uri_ct  = strlen(uri);
+  int buff_ct = uri_ct + 2;
+  int err;
+  char normal_uri[buff_ct];
+
+  memset(normal_uri, 0, buff_ct);
+  err = normalize_uri(uri, uri_ct, normal_uri, buff_ct);
+
+  if (err) {
+    return false;
+  }
+
   const char *kind = cdniuc, *container = cdniuc;
   while (*container && *container != ':') {
     ++container;
@@ -179,23 +192,17 @@ jwt_check_uri(const char *cdniuc, const char *uri)
   ++container;
 
   size_t len = container - kind;
-  PluginDebug("Comparing with match kind \"%.*s\" on \"%s\" to \"%s\"", (int)len - 1, kind, container, uri);
+  PluginDebug("Comparing with match kind \"%.*s\" on \"%s\" to normalized URI \"%s\"", (int)len - 1, kind, container, normal_uri);
   switch (len) {
-  case sizeof CONT_URI_STR:
-    if (!strncmp(CONT_URI_STR, kind, len - 1)) {
-      return !strcmp(container, uri);
-    }
-    PluginDebug("Expected kind %s, but did not find it in \"%.*s\"", CONT_URI_STR, (int)len - 1, kind);
-    break;
-  case sizeof CONT_URI_PATTERN_STR:
-    if (!strncmp(CONT_URI_PATTERN_STR, kind, len - 1)) {
-      return match_glob(container, uri);
+  case sizeof CONT_URI_HASH_STR:
+    if (!strncmp(CONT_URI_HASH_STR, kind, len - 1)) {
+      return match_hash(container, normal_uri);
     }
-    PluginDebug("Expected kind %s, but did not find it in \"%.*s\"", CONT_URI_PATTERN_STR, (int)len - 1, kind);
+    PluginDebug("Expected kind %s, but did not find it in \"%.*s\"", CONT_URI_HASH_STR, (int)len - 1, kind);
     break;
   case sizeof CONT_URI_REGEX_STR:
     if (!strncmp(CONT_URI_REGEX_STR, kind, len - 1)) {
-      return match_regex(container, uri);
+      return match_regex(container, normal_uri);
     }
     PluginDebug("Expected kind %s, but did not find it in \"%.*s\"", CONT_URI_REGEX_STR, (int)len - 1, kind);
     break;
diff --git a/plugins/experimental/uri_signing/match.c b/plugins/experimental/uri_signing/match.c
index b665dc4..18fb31a 100644
--- a/plugins/experimental/uri_signing/match.c
+++ b/plugins/experimental/uri_signing/match.c
@@ -23,7 +23,7 @@
 #include <string.h>
 
 bool
-match_glob(const char *needle, const char *haystack)
+match_hash(const char *needle, const char *haystack)
 {
   return false;
 }
diff --git a/plugins/experimental/uri_signing/match.h b/plugins/experimental/uri_signing/match.h
index 92b906d..38f3eb2 100644
--- a/plugins/experimental/uri_signing/match.h
+++ b/plugins/experimental/uri_signing/match.h
@@ -17,5 +17,5 @@
  */
 
 #include <stdbool.h>
-bool match_glob(const char *needle, const char *haystack);
+bool match_hash(const char *needle, const char *haystack);
 bool match_regex(const char *pattern, const char *uri);
diff --git a/plugins/experimental/uri_signing/normalize.c b/plugins/experimental/uri_signing/normalize.c
new file mode 100644
index 0000000..e514111
--- /dev/null
+++ b/plugins/experimental/uri_signing/normalize.c
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "normalize.h"
+#include "common.h"
+#include <string.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+/* Remove Dot Algorithm outlined in RFC3986 section 5.2.4
+ * Function writes normalizes path and writes to ret_buffer */
+int
+remove_dot_segments(const char *path, int path_ct, char *ret_buffer, int buff_ct)
+{
+  /* Ensure buffer is at least the size of the path */
+  if (buff_ct < path_ct) {
+    PluginDebug("Path buffer not large enough");
+    return -1;
+  }
+
+  /* Create an input buffer that we can change */
+  char inBuff[path_ct + 1];
+  memset(inBuff, 0, path_ct + 1);
+  strcpy(inBuff, path);
+
+  const char *path_end = inBuff + path_ct;
+  char *seg_start      = inBuff;
+  char *seg_end;
+  char *write_buffer = ret_buffer;
+  int seg_len;
+
+  for (;;) {
+    if (seg_start == path_end) {
+      break;
+    }
+    seg_end = seg_start + 1;
+
+    /* Parse such that Seg start/end contain the next full path segment */
+    while (seg_end != path_end && *seg_end != '/') {
+      seg_end++;
+    }
+
+    seg_len = seg_end - seg_start + 1;
+
+    /* Remove starting ../ or ./ from input buffer */
+    if (!strncmp(seg_start, "../", seg_len) || !strncmp(seg_start, "./", seg_len)) {
+      if (seg_end != path_end) {
+        seg_end++;
+      }
+    }
+
+    /* Remove starting /./ or /. from input buffer and replace with '/' in output buffer */
+    else if (!strncmp(seg_start, "/./", seg_len) || !strncmp(seg_start, "/.", seg_len)) {
+      *write_buffer = '/';
+      write_buffer++;
+      if (seg_end != path_end) {
+        seg_end++;
+      }
+    }
+
+    /* Replace /../ or /.. with / in write_buffer and remove preceding segment */
+    else if (!strncmp(seg_start, "/../", seg_len) || !strncmp(seg_start, "/..", seg_len)) {
+      int prev_len = 0;
+      while (*write_buffer != '/' && write_buffer != ret_buffer) {
+        prev_len++;
+        write_buffer--;
+      }
+      memset(write_buffer, 0, prev_len);
+
+      /* Replace segment with '/' in input buffer */
+      if (seg_end != path_end) {
+        seg_start[seg_len - 1] = '/';
+      } else {
+        seg_start[seg_len - 2] = '/';
+        seg_end--;
+      }
+    }
+
+    /* Remove starting '.' or '..' from input buffer */
+    else if (!strncmp(seg_start, ".", seg_len) || !strncmp(seg_start, "..", seg_len)) {
+      if (seg_end != path_end) {
+        seg_end++;
+      }
+    }
+    /* Place the current path segment to the output buffer including initial '/' but not the next '/' */
+    else {
+      /* Write first forward slash to buffer */
+      if (*seg_start == '/') {
+        *write_buffer = *seg_start;
+        write_buffer++;
+        seg_start++;
+      }
+
+      /* Write subsequent characters to buffer */
+      while (*seg_start != '/') {
+        *write_buffer = *seg_start;
+        write_buffer++;
+        if (*seg_start == 0) {
+          break;
+        }
+        seg_start++;
+      }
+    }
+    seg_start = seg_end;
+  }
+
+  PluginDebug("Normalized Path: %s", ret_buffer);
+  return strlen(ret_buffer);
+}
+
+/* Function percent decodes uri_ct characters of the string uri and writes it to the decoded_uri
+ * buffer. If lower is true, it sets all characters including decoded ones to lower case.
+ * The function returns the length of the decoded string or -1 if there was a parsing error
+ * TODO: ADD functionality to ignore unicode non-standard characters and leave them encoded. Read RFC regarding normalization and
+ * determine if this is compliant.
+ */
+int
+percent_decode(const char *uri, int uri_ct, char *decoded_uri, bool lower)
+{
+  static const char *reserved_string = ":/?#[]@!$&\'()*+,;=";
+
+  if (uri_ct <= 0) {
+    return 0;
+  }
+
+  int offset = 0;
+  int i;
+  for (i = 0; i < uri_ct; i++) {
+    if (uri[i] == '%') {
+      /* The next two characters are interpreted as the hex encoded value. Store in encodedVal */
+      if (uri_ct < i + 2) {
+        goto decode_failure;
+      }
+      char encodedVal[2] = {0};
+      int j;
+      for (j = 0; j < 2; j++) {
+        if (isxdigit(uri[i + j + 1])) {
+          encodedVal[j] = uri[i + j + 1];
+        } else {
+          goto decode_failure;
+        }
+      }
+      int hexVal = 0;
+      char decodeChar;
+      sscanf(encodedVal, "%2x", &hexVal);
+      decodeChar = (char)hexVal;
+      /* If encoded value is a reserved char, leave encoded*/
+      if (strchr(reserved_string, decodeChar)) {
+        decoded_uri[i - offset]     = uri[i];
+        decoded_uri[i + 1 - offset] = toupper(uri[i + 1]);
+        decoded_uri[i + 2 - offset] = toupper(uri[i + 2]);
+      }
+      /* If not a reserved char, decode using the decoded_uri buffer */
+      else {
+        if (lower) {
+          decoded_uri[i - offset] = tolower(decodeChar);
+        } else {
+          decoded_uri[i - offset] = decodeChar;
+        }
+        offset = offset + 2;
+      }
+      i = i + 2;
+    }
+    /* Write non-encoded values to decoded buffer */
+    else {
+      if (lower) {
+        decoded_uri[i - offset] = tolower(uri[i]);
+      } else {
+        decoded_uri[i - offset] = uri[i];
+      }
+    }
+  }
+
+  /* Return the size of the newly decoded string */
+  return uri_ct - offset;
+
+decode_failure:
+  PluginDebug("ERROR Decoding URI");
+  return -1;
+}
+
+/* This function takes a uri and an initialized buffer to populate with the normalized uri.
+ * Returns non zero for error
+ *
+ * The buffer provided must be at least the length of the uri + 1 as the normalized uri will
+ * potentially be one char larger than the original uri if a backslash is added to the path.
+ *
+ *   The normalization function returns a string with the following modifications
+ *   1. Lowecase protocol/domain
+ *   2. Path segments .. and . are removed from path
+ *   3. Alphabetical percent encoded octet values are toupper
+ *   4. Non-reserved percent encoded octet values are decoded
+ *   5. The Port is removed if it is default
+ *   6. Defaults to a single backslash for the path segment if path segment is empty
+ */
+int
+normalize_uri(const char *uri, int uri_ct, char *normal_uri, int normal_ct)
+{
+  PluginDebug("Normalizing URI: %s", uri);
+
+  /* Buffer provided must be large enough to store the uri plus one additional char */
+  const char *uri_end  = uri + uri_ct;
+  const char *buff_end = normal_uri + normal_ct;
+
+  if (normal_uri && normal_ct < uri_ct + 1) {
+    PluginDebug("Buffer to Normalize URI not large enough.");
+    return -1;
+  }
+
+  /* Initialize a path buffer to pass to path normalization function later on */
+  char path_buffer[normal_ct];
+  memset(path_buffer, 0, normal_ct);
+
+  /* Comp variables store starting/ending indexes for each uri component as uri is parsed.
+   * Write buffer traverses the normalized uri buffer as we build the normalized string.
+   */
+  const char *comp_start = uri;
+  const char *comp_end   = uri;
+  char *write_buffer     = normal_uri;
+  bool https             = false;
+
+  /* Parse the protocol which will end with a colon */
+  while (*comp_end != ':' && comp_end != uri_end) {
+    *write_buffer = tolower(*comp_end);
+    comp_end++;
+    write_buffer++;
+  }
+
+  if (comp_end == uri_end) {
+    PluginDebug("Reached End of String prematurely");
+    goto normalize_failure;
+  }
+
+  /* Copy the colon */
+  *write_buffer = *comp_end;
+  comp_end++;
+  write_buffer++;
+
+  /* Ensure the protocol is either http or https */
+  if (strcmp("https:", normal_uri) == 0) {
+    https = true;
+  } else if (strcmp("http:", normal_uri)) {
+    PluginDebug("String is neither http or https");
+    goto normalize_failure;
+  }
+
+  /* Protocol must be terminated by two forward slashes */
+  int i;
+  for (i = 0; i < 2; i++) {
+    if (comp_end == uri_end || *comp_end != '/') {
+      goto normalize_failure;
+    }
+    *write_buffer = *comp_end;
+    comp_end++;
+    write_buffer++;
+  }
+
+  if (comp_end == uri_end) {
+    goto normalize_failure;
+  }
+
+  /* Comp_start is index of start of authority component */
+  int comp_ct;
+  comp_start = comp_end;
+
+  /* Set comp start/end to contain authority component */
+  bool userInfo = false;
+  while (comp_end != uri_end && *comp_end != '/' && *comp_end != '?' && *comp_end != '#') {
+    /* If we encounter userinfo, decode it without altering case and set comp_start/end to only include hostname/port */
+    if (*comp_end == '@' && userInfo == false) {
+      comp_ct = comp_end - comp_start;
+      comp_ct = percent_decode(comp_start, comp_ct, write_buffer, false);
+      if (comp_ct < 0) {
+        goto normalize_failure;
+      }
+      comp_start   = comp_end;
+      userInfo     = true;
+      write_buffer = write_buffer + comp_ct;
+    }
+    comp_end++;
+  }
+
+  /* UserInfo without a hostname is invalid */
+  if (userInfo == true && comp_end == uri_end) {
+    goto normalize_failure;
+  }
+
+  comp_ct = comp_end - comp_start;
+
+  /* - comp start/end holds indices in original uri of hostname/port
+   * - write_buffer holds pointer to start of hostname/port written to the decode buffer
+   * - comp_ct holds size of hostname/port in original uri
+   */
+
+  /* Parse and decode the hostname and port and set to lower case */
+  comp_ct = percent_decode(comp_start, comp_ct, write_buffer, true);
+
+  if (comp_ct < 0) {
+    goto normalize_failure;
+  }
+
+  /* Remove the port from the buffer if default */
+  while (*write_buffer != 0) {
+    if (*write_buffer == ':') {
+      if (https == true && !strncmp(write_buffer, ":443", 5)) {
+        memset(write_buffer, 0, 4);
+        break;
+      } else if (https == false && !strncmp(write_buffer, ":80", 4)) {
+        memset(write_buffer, 0, 3);
+        break;
+      }
+    }
+    write_buffer++;
+  }
+
+  comp_start = comp_end;
+
+  /* If we have reached the end of the authority section with an empty path component, add a trailing backslash */
+  if (*comp_end == 0 || *comp_end == '?' || *comp_end == '#') {
+    *write_buffer = '/';
+    write_buffer++;
+  }
+
+  /* If there is a path component, normalize it */
+  else {
+    /* Set comp start/end pointers to contain the path component */
+    while (*comp_end != '?' && *comp_end != '#' && *comp_end != 0) {
+      comp_end++;
+    }
+    /* Decode the path component without altering case and store it to the path_buffer*/
+    comp_ct = comp_end - comp_start;
+    comp_ct = percent_decode(comp_start, comp_ct, path_buffer, false);
+
+    if (comp_ct < 0) {
+      goto normalize_failure;
+    }
+
+    /* Remove the . and .. segments from the path and write the now normalized path to the output buffer */
+    PluginDebug("Removing Dot Segments");
+    int buff_ct = buff_end - write_buffer;
+    comp_ct     = remove_dot_segments(path_buffer, comp_ct, write_buffer, buff_ct);
+
+    if (comp_ct < 0) {
+      PluginDebug("Failure removing dot segments from path");
+      goto normalize_failure;
+    }
+    write_buffer = write_buffer + comp_ct;
+  }
+
+  /* If there is any uri remaining after the path, decode and set case to lower */
+  if (comp_end != uri_end) {
+    comp_start = comp_end;
+    comp_ct    = uri_end - comp_start;
+    comp_ct    = percent_decode(comp_start, comp_ct, write_buffer, false);
+    if (comp_ct < 0) {
+      goto normalize_failure;
+    }
+  }
+
+  PluginDebug("Normalized URI:  %s", normal_uri);
+  return 0;
+
+normalize_failure:
+  PluginDebug("URI Normalization Failure. URI does not fit http or https schemes.");
+  return -1;
+}
diff --git a/plugins/experimental/uri_signing/match.h b/plugins/experimental/uri_signing/normalize.h
similarity index 82%
copy from plugins/experimental/uri_signing/match.h
copy to plugins/experimental/uri_signing/normalize.h
index 92b906d..a84c997 100644
--- a/plugins/experimental/uri_signing/match.h
+++ b/plugins/experimental/uri_signing/normalize.h
@@ -16,6 +16,5 @@
  * limitations under the License.
  */
 
-#include <stdbool.h>
-bool match_glob(const char *needle, const char *haystack);
-bool match_regex(const char *pattern, const char *uri);
+int normalize_uri(const char *uri, int uri_ct, char *uri_normal, int buffer_size);
+int remove_dot_segments(const char *path, int path_ct, char *ret_buffer, int buff_ct);
diff --git a/plugins/experimental/uri_signing/unit_tests/uri_signing_test.cc b/plugins/experimental/uri_signing/unit_tests/uri_signing_test.cc
index d89bf9c..30b8681 100644
--- a/plugins/experimental/uri_signing/unit_tests/uri_signing_test.cc
+++ b/plugins/experimental/uri_signing/unit_tests/uri_signing_test.cc
@@ -27,6 +27,7 @@ extern "C" {
 #include <jansson.h>
 #include <cjose/cjose.h>
 #include "../jwt.h"
+#include "../normalize.h"
 }
 
 bool
@@ -48,6 +49,49 @@ jwt_parsing_helper(const char *jwt_string)
   return resp;
 }
 
+bool
+normalize_uri_helper(const char *uri, const char *expected_normal)
+{
+  size_t uri_ct = strlen(uri);
+  int buff_size = uri_ct + 2;
+  int err;
+  char uri_normal[buff_size];
+  memset(uri_normal, 0, buff_size);
+
+  err = normalize_uri(uri, uri_ct, uri_normal, buff_size);
+
+  if (err) {
+    return false;
+  }
+
+  if (expected_normal && strcmp(expected_normal, uri_normal) == 0) {
+    return true;
+  }
+
+  return false;
+}
+
+bool
+remove_dot_helper(const char *path, const char *expected_path)
+{
+  fprintf(stderr, "Removing Dot Segments from Path: %s\n", path);
+  size_t path_ct = strlen(path);
+  path_ct++;
+  int new_ct;
+  char path_buffer[path_ct];
+  memset(path_buffer, 0, path_ct);
+
+  new_ct = remove_dot_segments(path, path_ct, path_buffer, path_ct);
+
+  if (new_ct < 0) {
+    return false;
+  } else if (strcmp(expected_path, path_buffer) == 0) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
 TEST_CASE("1", "[JWSParsingTest]")
 {
   INFO("TEST 1, Test JWT Parsing From Token Strings");
@@ -89,4 +133,142 @@ TEST_CASE("1", "[JWSParsingTest]")
     REQUIRE(!jwt_parsing_helper("{\"cdniets\":30,\"cdnistt\":1,\"cdnistd\":4,\"iss\":\"Content Access "
                                 "Manager\",\"cdniuc\":\"uri-regex:http://foobar.local/testDir/*\"}"));
   }
+  fprintf(stderr, "\n");
+}
+
+TEST_CASE("3", "[RemoveDotSegmentsTest]")
+{
+  INFO("TEST 3, Test Removal of Dot Segments From Paths");
+
+  SECTION("../bar test") { REQUIRE(remove_dot_helper("../bar", "bar")); }
+
+  SECTION("./bar test") { REQUIRE(remove_dot_helper("./bar", "bar")); }
+
+  SECTION(".././bar test") { REQUIRE(remove_dot_helper(".././bar", "bar")); }
+
+  SECTION("./../bar test") { REQUIRE(remove_dot_helper("./../bar", "bar")); }
+
+  SECTION("/foo/./bar test") { REQUIRE(remove_dot_helper("/foo/./bar", "/foo/bar")); }
+
+  SECTION("/bar/./ test") { REQUIRE(remove_dot_helper("/bar/./", "/bar/")); }
+
+  SECTION("/. test") { REQUIRE(remove_dot_helper("/.", "/")); }
+
+  SECTION("/bar/. test") { REQUIRE(remove_dot_helper("/bar/.", "/bar/")); }
+
+  SECTION("/foo/../bar test") { REQUIRE(remove_dot_helper("/foo/../bar", "/bar")); }
+
+  SECTION("/bar/../ test") { REQUIRE(remove_dot_helper("/bar/../", "/")); }
+
+  SECTION("/.. test") { REQUIRE(remove_dot_helper("/..", "/")); }
+
+  SECTION("/bar/.. test") { REQUIRE(remove_dot_helper("/bar/..", "/")); }
+
+  SECTION("/foo/bar/.. test") { REQUIRE(remove_dot_helper("/foo/bar/..", "/foo/")); }
+
+  SECTION("Single . test") { REQUIRE(remove_dot_helper(".", "")); }
+
+  SECTION("Single .. test") { REQUIRE(remove_dot_helper("..", "")); }
+
+  SECTION("Test foo/bar/.. test") { REQUIRE(remove_dot_helper("foo/bar/..", "foo/")); }
+
+  SECTION("Test Empty Path Segment") { REQUIRE(remove_dot_helper("", "")); }
+
+  SECTION("Test mixed operations") { REQUIRE(remove_dot_helper("/foo/bar/././something/../foobar", "/foo/bar/foobar")); }
+  fprintf(stderr, "\n");
+}
+
+TEST_CASE("4", "[NormalizeTest]")
+{
+  INFO("TEST 4, Test Normalization of URIs");
+
+  SECTION("Testing passing too small of a URI to normalize") { REQUIRE(!normalize_uri_helper("ht", NULL)); }
+
+  SECTION("Testing passing non http/https protocol") { REQUIRE(!normalize_uri_helper("ht:", NULL)); }
+
+  SECTION("Passing a uri with half encoded value at end") { REQUIRE(!normalize_uri_helper("http://www.foobar.co%4", NULL)); }
+
+  SECTION("Passing a uri with half encoded value in the middle")
+  {
+    REQUIRE(!normalize_uri_helper("http://www.foobar.co%4psomethin/Path", NULL));
+  }
+
+  SECTION("Passing a uri with an empty path parameter")
+  {
+    REQUIRE(normalize_uri_helper("http://www.foobar.com", "http://www.foobar.com/"));
+  }
+
+  SECTION("Passing a uri with an empty path parameter and additional query params")
+  {
+    REQUIRE(normalize_uri_helper("http://www.foobar.com?query1=foo&query2=bar", "http://www.foobar.com/?query1=foo&query2=bar"));
+  }
+
+  SECTION("Empty path parameter with port")
+  {
+    REQUIRE(normalize_uri_helper("http://www.foobar.com:9301?query1=foo&query2=bar",
+                                 "http://www.foobar.com:9301/?query1=foo&query2=bar"));
+  }
+
+  SECTION("Passing a uri with a username and password")
+  {
+    REQUIRE(normalize_uri_helper("http://foo%40:PaSsword@www.Foo%42ar.coM:80/", "http://foo%40:PaSsword@www.foobar.com/"));
+  }
+
+  SECTION("Testing Removal of standard http Port")
+  {
+    REQUIRE(normalize_uri_helper("http://foobar.com:80/Something/Here", "http://foobar.com/Something/Here"));
+  }
+
+  SECTION("Testing Removal of standard https Port")
+  {
+    REQUIRE(normalize_uri_helper("https://foobar.com:443/Something/Here", "https://foobar.com/Something/Here"));
+  }
+
+  SECTION("Testing passing of non-standard http Port")
+  {
+    REQUIRE(normalize_uri_helper("http://foobar.com:443/Something/Here", "http://foobar.com:443/Something/Here"));
+  }
+
+  SECTION("Testing passing of non-standard https Port")
+  {
+    REQUIRE(normalize_uri_helper("https://foobar.com:80/Something/Here", "https://foobar.com:80/Something/Here"));
+  }
+
+  SECTION("Testing the removal of . and .. in the path ")
+  {
+    REQUIRE(
+      normalize_uri_helper("https://foobar.com:80/Something/Here/././foobar/../foo", "https://foobar.com:80/Something/Here/foo"));
+  }
+
+  SECTION("Testing . and .. segments in non path components")
+  {
+    REQUIRE(normalize_uri_helper("https://foobar.com:80/Something/Here?query1=/././foo/../bar",
+                                 "https://foobar.com:80/Something/Here?query1=/././foo/../bar"));
+  }
+
+  SECTION("Testing standard decdoing of multiple characters")
+  {
+    REQUIRE(normalize_uri_helper("https://kelloggs%54ester.com/%53omething/Here", "https://kelloggstester.com/Something/Here"));
+  }
+
+  SECTION("Testing passing encoded reserved characters")
+  {
+    REQUIRE(
+      normalize_uri_helper("https://kelloggs%54ester.com/%53omething/Here%3f", "https://kelloggstester.com/Something/Here%3F"));
+  }
+
+  SECTION("Mixed Bag Test case")
+  {
+    REQUIRE(normalize_uri_helper("https://foo:something@kellogs%54ester.com:443/%53omething/.././here",
+                                 "https://foo:something@kellogstester.com/here"));
+  }
+
+  SECTION("Testing empty hostname with userinfon") { REQUIRE(!normalize_uri_helper("https://foo:something@", NULL)); }
+
+  SECTION("Testing empty uri after http://") { REQUIRE(!normalize_uri_helper("http://", NULL)); }
+
+  SECTION("Testing http:///////") { REQUIRE(!normalize_uri_helper("http:///////", NULL)); }
+
+  SECTION("Testing empty uri after http://?/") { REQUIRE(!normalize_uri_helper("http://?/", NULL)); }
+  fprintf(stderr, "\n");
 }