From 941b14a0ab8f7c80f94e04762e65a48f9ed02f6e Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Sat, 20 Feb 2021 16:47:55 -0500
Subject: Simplify URI API and implementation

---
 test/test_env.c  |   3 +
 test/test_node.c |   2 +-
 test/test_uri.c  | 171 ++++++++++++++++++++++++++++++++++++++++++-------------
 3 files changed, 134 insertions(+), 42 deletions(-)

(limited to 'test')

diff --git a/test/test_env.c b/test/test_env.c
index 99e16854..c64b5fed 100644
--- a/test/test_env.c
+++ b/test/test_env.c
@@ -41,6 +41,9 @@ test_env(void)
   SerdEnv*  env = serd_env_new(NULL);
   serd_env_set_prefix_from_strings(env, "eg.2", "http://example.org/");
 
+  assert(serd_env_set_prefix_from_strings(env, "eg.3", "rel") ==
+         SERD_ERR_BAD_ARG);
+
   SerdStringView prefix;
   SerdStringView suffix;
   assert(serd_env_expand(env, b, &prefix, &suffix));
diff --git a/test/test_node.c b/test/test_node.c
index 00a24b99..7fd4cb29 100644
--- a/test/test_node.c
+++ b/test/test_node.c
@@ -213,7 +213,7 @@ test_literal(void)
   assert(!strcmp(serd_node_string(lang), "en"));
   serd_node_free(hello_l);
 
-  SerdNode* hello_dt =
+  SerdNode* const hello_dt =
     serd_new_literal("hello_dt\"", "http://example.org/Thing", NULL);
   assert(serd_node_length(hello_dt) == 9);
   assert(!strcmp(serd_node_string(hello_dt), "hello_dt\""));
diff --git a/test/test_uri.c b/test/test_uri.c
index 60e03ad8..d0dba037 100644
--- a/test/test_uri.c
+++ b/test/test_uri.c
@@ -14,12 +14,15 @@
   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
 
+#define _POSIX_C_SOURCE 200809L /* for mkstemp */
+
 #undef NDEBUG
 
 #include "serd/serd.h"
 
 #include <assert.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 static void
@@ -35,7 +38,7 @@ test_file_uri(const char* hostname,
   SerdNode*   node         = serd_new_file_uri(path, hostname, 0);
   const char* node_str     = serd_node_string(node);
   char*       out_hostname = NULL;
-  char*       out_path     = serd_file_uri_parse(node_str, &out_hostname);
+  char*       out_path     = serd_parse_file_uri(node_str, &out_hostname);
   assert(!strcmp(node_str, expected_uri));
   assert((hostname && out_hostname) || (!hostname && !out_hostname));
   assert(!strcmp(out_path, expected_path));
@@ -57,75 +60,161 @@ test_uri_parsing(void)
   test_file_uri("bhost", "/foo/bar", "file://bhost/foo/bar", NULL);
   test_file_uri(NULL, "a/relative <path>", "a/relative%20%3Cpath%3E", NULL);
 
-  // Test tolerance of parsing junk URI escapes
+  // Missing trailing '/' after authority
+  assert(!serd_parse_file_uri("file://truncated", NULL));
+
+  // Check that NULL hostname doesn't crash
+  char* out_path = serd_parse_file_uri("file://me/path", NULL);
+  assert(!strcmp(out_path, "/path"));
+  serd_free(out_path);
 
-  char* out_path = serd_file_uri_parse("file:///foo/%0Xbar", NULL);
+  // Invalid first escape character
+  out_path = serd_parse_file_uri("file:///foo/%0Xbar", NULL);
+  assert(!strcmp(out_path, "/foo/bar"));
+  serd_free(out_path);
+
+  // Invalid second escape character
+  out_path = serd_parse_file_uri("file:///foo/%X0bar", NULL);
   assert(!strcmp(out_path, "/foo/bar"));
   serd_free(out_path);
 }
 
 static void
-test_uri_from_string(void)
+test_parse_uri(void)
 {
-  assert(!serd_new_uri_from_string(NULL, NULL, NULL));
+  const SerdStringView base = SERD_STATIC_STRING("http://example.org/a/b/c/");
+
+  const SerdURIView base_uri  = serd_parse_uri(base.buf);
+  const SerdURIView empty_uri = serd_parse_uri("");
+
+  SerdNode* const nil =
+    serd_new_parsed_uri(serd_resolve_uri(empty_uri, base_uri));
 
-  SerdURIView base_uri;
-  SerdNode*   base =
-    serd_new_uri_from_string("http://example.org/", NULL, &base_uri);
-  SerdNode* nil  = serd_new_uri_from_string(NULL, &base_uri, NULL);
-  SerdNode* nil2 = serd_new_uri_from_string("", &base_uri, NULL);
   assert(serd_node_type(nil) == SERD_URI);
-  assert(!strcmp(serd_node_string(nil), serd_node_string(base)));
-  assert(serd_node_type(nil2) == SERD_URI);
-  assert(!strcmp(serd_node_string(nil2), serd_node_string(base)));
+  assert(!strcmp(serd_node_string(nil), base.buf));
+
   serd_node_free(nil);
-  serd_node_free(nil2);
-  serd_node_free(base);
 }
 
 static void
-test_relative_uri(void)
+check_is_within(const char* const uri_string,
+                const char* const base_uri_string,
+                const bool        expected)
 {
-  SerdURIView base_uri;
-  SerdNode*   base =
-    serd_new_uri_from_string("http://example.org/", NULL, &base_uri);
+  const SerdURIView uri      = serd_parse_uri(uri_string);
+  const SerdURIView base_uri = serd_parse_uri(base_uri_string);
 
-  SerdNode*   abs = serd_new_string(SERD_URI, "http://example.org/foo/bar");
-  SerdURIView abs_uri;
-  serd_uri_parse(serd_node_string(abs), &abs_uri);
+  assert(serd_uri_is_within(uri, base_uri) == expected);
+}
 
-  SerdURIView rel_uri;
-  SerdNode*   rel = serd_new_relative_uri(&abs_uri, &base_uri, NULL, &rel_uri);
-  assert(!strcmp(serd_node_string(rel), "/foo/bar"));
+static void
+test_is_within(void)
+{
+  static const char* const base = "http://example.org/base/";
+
+  check_is_within("http://example.org/base/", base, true);
+  check_is_within("http://example.org/base/kid?q", base, true);
+  check_is_within("http://example.org/base/kid", base, true);
+  check_is_within("http://example.org/base/kid#f", base, true);
+  check_is_within("http://example.org/base/kid?q#f", base, true);
+  check_is_within("http://example.org/base/kid/grandkid", base, true);
+
+  check_is_within("http://example.org/base", base, false);
+  check_is_within("http://example.org/based", base, false);
+  check_is_within("http://example.org/bose", base, false);
+  check_is_within("http://example.org/", base, false);
+  check_is_within("http://other.org/base", base, false);
+  check_is_within("ftp://other.org/base", base, false);
+  check_is_within("base", base, false);
+
+  check_is_within("http://example.org/", "rel", false);
+}
 
-  SerdNode* up = serd_new_relative_uri(&base_uri, &abs_uri, NULL, NULL);
-  assert(!strcmp(serd_node_string(up), "../"));
+static void
+check_rel_uri(const char*     uri_string,
+              const SerdNode* base,
+              const SerdNode* root,
+              const char*     expected)
+{
+  const SerdURIView base_uri = serd_node_uri_view(base);
+  const SerdURIView uri      = serd_parse_uri(uri_string);
+  const bool        is_within =
+    !root || serd_uri_is_within(uri, serd_node_uri_view(root));
 
-  SerdNode* noup = serd_new_relative_uri(&base_uri, &abs_uri, &abs_uri, NULL);
-  assert(!strcmp(serd_node_string(noup), "http://example.org/"));
+  SerdNode* const rel =
+    is_within ? serd_new_parsed_uri(serd_relative_uri(uri, base_uri))
+              : serd_new_uri(uri_string);
 
-  SerdNode*   x = serd_new_string(SERD_URI, "http://example.org/foo/x");
-  SerdURIView x_uri;
-  serd_uri_parse(serd_node_string(x), &x_uri);
+  const int ret = strcmp(serd_node_string(rel), expected);
+  serd_node_free(rel);
+  assert(!ret);
+}
 
-  SerdNode* x_rel = serd_new_relative_uri(&x_uri, &abs_uri, &abs_uri, NULL);
-  assert(!strcmp(serd_node_string(x_rel), "x"));
+static void
+test_relative_uri(void)
+{
+  SerdNode* const root = serd_new_uri("http://example.org/a/b/ignored");
+  SerdNode* const base = serd_new_uri("http://example.org/a/b/c/");
+
+  check_rel_uri("http://example.org/a/b/c/foo", base, NULL, "foo");
+  check_rel_uri("http://example.org/a/", base, NULL, "../../");
+  check_rel_uri("http://example.org/a/", base, root, "http://example.org/a/");
+  check_rel_uri("http://example.org/a/b/x", root, root, "x");
+  check_rel_uri("http://example.org/", base, NULL, "../../../");
+  check_rel_uri("http://drobilla.net/a", base, NULL, "http://drobilla.net/a");
+
+  {
+    // Check making a relative URI from a resolved URI
+    const SerdURIView ref  = serd_parse_uri("child");
+    const SerdURIView abs  = serd_resolve_uri(ref, serd_node_uri_view(base));
+    const SerdURIView rel  = serd_relative_uri(abs, serd_node_uri_view(root));
+    SerdNode* const   node = serd_new_parsed_uri(rel);
+
+    assert(!strcmp(serd_node_string(node), "c/child"));
+    serd_node_free(node);
+  }
+  {
+    // Check failure when path_prefix is not available for use
+    const SerdURIView top   = serd_parse_uri("http://example.org/a/");
+    const SerdURIView ref   = serd_parse_uri("up");
+    const SerdURIView up    = serd_resolve_uri(ref, top);
+    const SerdURIView upref = serd_relative_uri(up, serd_node_uri_view(base));
+
+    assert(!memcmp(&upref, &SERD_URI_NULL, sizeof(ref)));
+  }
 
-  serd_node_free(x_rel);
-  serd_node_free(x);
-  serd_node_free(noup);
-  serd_node_free(up);
-  serd_node_free(abs);
-  serd_node_free(rel);
   serd_node_free(base);
+  serd_node_free(root);
+}
+
+static void
+test_uri_resolution(void)
+{
+  static const SerdStringView base =
+    SERD_STATIC_STRING("http://example.org/a/b/c/");
+
+  static const SerdStringView base_foo =
+    SERD_STATIC_STRING("http://example.org/a/b/c/foo");
+
+  const SerdURIView base_uri     = serd_parse_uri(base.buf);
+  const SerdURIView abs_foo_uri  = serd_parse_uri(base_foo.buf);
+  const SerdURIView rel_foo_uri  = serd_relative_uri(abs_foo_uri, base_uri);
+  const SerdURIView resolved_uri = serd_resolve_uri(rel_foo_uri, base_uri);
+
+  SerdNode* const resolved = serd_new_parsed_uri(resolved_uri);
+  assert(!strcmp(serd_node_string(resolved), "http://example.org/a/b/c/foo"));
+
+  serd_node_free(resolved);
 }
 
 int
 main(void)
 {
   test_uri_parsing();
-  test_uri_from_string();
+  test_parse_uri();
+  test_is_within();
   test_relative_uri();
+  test_uri_resolution();
 
   printf("Success\n");
   return 0;
-- 
cgit v1.2.1