aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-02-20 16:47:55 -0500
committerDavid Robillard <d@drobilla.net>2021-03-07 15:32:24 -0500
commit941b14a0ab8f7c80f94e04762e65a48f9ed02f6e (patch)
treea7e89d7d2bb0150728c5eec3d4d9e6730f9af016
parentd243368f8e2f79a125a5858223f71fb40fe84525 (diff)
downloadserd-941b14a0ab8f7c80f94e04762e65a48f9ed02f6e.tar.gz
serd-941b14a0ab8f7c80f94e04762e65a48f9ed02f6e.tar.bz2
serd-941b14a0ab8f7c80f94e04762e65a48f9ed02f6e.zip
Simplify URI API and implementation
-rw-r--r--include/serd/serd.h172
-rw-r--r--src/env.c32
-rw-r--r--src/node.c121
-rw-r--r--src/node.h17
-rw-r--r--src/reader.c2
-rw-r--r--src/serdi.c5
-rw-r--r--src/uri.c376
-rw-r--r--src/uri_utils.h22
-rw-r--r--src/writer.c27
-rw-r--r--test/test_env.c3
-rw-r--r--test/test_node.c2
-rw-r--r--test/test_uri.c171
12 files changed, 514 insertions, 436 deletions
diff --git a/include/serd/serd.h b/include/serd/serd.h
index 489996a5..acae84fb 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -236,17 +236,28 @@ typedef struct {
/**
A parsed URI.
- This struct directly refers to slices in other strings, it does not own any
- memory itself. This allows some URI operations like resolution to be done
- in-place without allocating memory.
+ This URI representation is designed for fast streaming, it allows creating
+ relative URI references or resolving them into absolute URIs in-place
+ without any string allocation.
+
+ Each component refers to slices in other strings, so a URI view must outlive
+ any strings it was parsed from. The components are not necessarily
+ null-terminated.
+
+ The scheme, authority, path, query, and fragment simply point to the string
+ value of those components, not including any delimiters. The path_prefix is
+ a special component for storing relative or resolved paths. If it points to
+ a string (usually a base URI the URI was resolved against), then this string
+ is prepended to the path. Otherwise, the length is interpret as the number
+ of up-references ("../") that must be prepended to the path.
*/
typedef struct {
- SerdStringView scheme; ///< Scheme
- SerdStringView authority; ///< Authority
- SerdStringView path_base; ///< Path prefix if relative
- SerdStringView path; ///< Path suffix
- SerdStringView query; ///< Query
- SerdStringView fragment; ///< Fragment
+ SerdStringView scheme; ///< Scheme
+ SerdStringView authority; ///< Authority
+ SerdStringView path_prefix; ///< Path prefix for relative/resolved paths
+ SerdStringView path; ///< Path suffix
+ SerdStringView query; ///< Query
+ SerdStringView fragment; ///< Fragment
} SerdURIView;
/**
@@ -374,6 +385,16 @@ typedef size_t (*SerdSink)(const void* SERD_NONNULL buf,
static const SerdURIView SERD_URI_NULL =
{{NULL, 0}, {NULL, 0}, {NULL, 0}, {NULL, 0}, {NULL, 0}, {NULL, 0}};
+/// Return true iff `string` starts with a valid URI scheme
+SERD_PURE_API
+bool
+serd_uri_string_has_scheme(const char* SERD_NONNULL string);
+
+/// Parse `string` and return a URI view that points into it
+SERD_API
+SerdURIView
+serd_parse_uri(const char* SERD_NONNULL string);
+
/**
Get the unescaped path and hostname from a file URI.
@@ -381,55 +402,83 @@ static const SerdURIView SERD_URI_NULL =
@param uri A file URI.
@param hostname If non-NULL, set to the hostname, if present.
- @return The path component of the URI.
+ @return A filesystem path.
*/
SERD_API
char* SERD_NULLABLE
-serd_file_uri_parse(const char* SERD_NONNULL uri,
+serd_parse_file_uri(const char* SERD_NONNULL uri,
char* SERD_NONNULL* SERD_NULLABLE hostname);
-/// Return true iff `utf8` starts with a valid URI scheme
-SERD_PURE_API
-bool
-serd_uri_string_has_scheme(const char* SERD_NULLABLE utf8);
+/**
+ Return reference `r` resolved against `base`.
+
+ This will make `r` an absolute URI if possible.
+
+ @see [RFC3986 5.2.2](http://tools.ietf.org/html/rfc3986#section-5.2.2)
-/// Parse `utf8`, writing result to `out`
+ @param r URI reference to make absolute, for example "child/path".
+
+ @param base Base URI, for example "http://example.org/base/".
+
+ @return An absolute URI, for example "http://example.org/base/child/path",
+ or `r` if it is not a URI reference that can be resolved against `base`.
+*/
SERD_API
-SerdStatus
-serd_uri_parse(const char* SERD_NONNULL utf8, SerdURIView* SERD_NONNULL out);
+SerdURIView
+serd_resolve_uri(SerdURIView r, SerdURIView base);
/**
- Set target `t` to reference `r` resolved against `base`.
+ Return `r` as a reference relative to `base` if possible.
@see [RFC3986 5.2.2](http://tools.ietf.org/html/rfc3986#section-5.2.2)
+
+ @param r URI to make relative, for example
+ "http://example.org/base/child/path".
+
+ @param base Base URI, for example "http://example.org/base".
+
+ @return A relative URI reference, for example "child/path", `r` if it can
+ not be made relative to `base`, or a null URI if `r` could be made relative
+ to base, but the path prefix is already being used (most likely because `r`
+ was previously a relative URI reference that was resolved against some
+ base).
*/
SERD_API
-void
-serd_uri_resolve(const SerdURIView* SERD_NONNULL r,
- const SerdURIView* SERD_NONNULL base,
- SerdURIView* SERD_NONNULL t);
+SerdURIView
+serd_relative_uri(SerdURIView r, SerdURIView base);
+
+/**
+ Return whether `r` can be written as a reference relative to `base`.
-/// Serialise `uri` with a series of calls to `sink`
+ For example, with `base` "http://example.org/base/", this returns true if
+ `r` is also "http://example.org/base/", or something like
+ "http://example.org/base/child" ("child")
+ "http://example.org/base/child/grandchild#fragment"
+ ("child/grandchild#fragment"),
+ "http://example.org/base/child/grandchild?query" ("child/grandchild?query"),
+ and so on.
+
+ @return True if `r` and `base` are equal or if `r` is a child of `base`.
+*/
SERD_API
-size_t
-serd_uri_serialise(const SerdURIView* SERD_NONNULL uri,
- SerdSink SERD_NONNULL sink,
- void* SERD_NONNULL stream);
+bool
+serd_uri_is_within(SerdURIView r, SerdURIView base);
/**
- Serialise `uri` relative to `base` with a series of calls to `sink`
+ Write `uri` as a string to `sink`.
+
+ This will call `sink` several times to emit the serialised URI.
- The `uri` is written as a relative URI iff if it a child of `base` and
- `root`. The optional `root` parameter must be a prefix of `base` and can be
- used keep up-references ("../") within a certain namespace.
+ @param uri URI to write as a string.
+ @param sink Sink to write string output to.
+ @param stream Opaque user argument to pass to `sink`.
+ @return The number of bytes written.
*/
SERD_API
size_t
-serd_uri_serialise_relative(const SerdURIView* SERD_NONNULL uri,
- const SerdURIView* SERD_NULLABLE base,
- const SerdURIView* SERD_NULLABLE root,
- SerdSink SERD_NONNULL sink,
- void* SERD_NONNULL stream);
+serd_write_uri(SerdURIView uri,
+ SerdSink SERD_NONNULL sink,
+ void* SERD_NONNULL stream);
/**
@}
@@ -465,21 +514,15 @@ serd_new_literal(const char* SERD_NONNULL str,
const char* SERD_NULLABLE datatype,
const char* SERD_NULLABLE lang);
-/**
- Simple wrapper for serd_new_uri() to resolve a URI node.
-*/
+/// Create a new URI from a string
SERD_API
SerdNode* SERD_ALLOCATED
-serd_new_uri_from_node(const SerdNode* SERD_NONNULL uri_node,
- const SerdURIView* SERD_NULLABLE base,
- SerdURIView* SERD_NULLABLE out);
+serd_new_uri(const char* SERD_NONNULL str);
-/// Simple wrapper for serd_new_uri() to resolve a URI string
+/// Create a new URI from a URI view
SERD_API
SerdNode* SERD_ALLOCATED
-serd_new_uri_from_string(const char* SERD_NULLABLE str,
- const SerdURIView* SERD_NULLABLE base,
- SerdURIView* SERD_NULLABLE out);
+serd_new_parsed_uri(SerdURIView uri);
/**
Create a new file URI node from a file system path and optional hostname.
@@ -497,41 +540,6 @@ serd_new_file_uri(const char* SERD_NONNULL path,
SerdURIView* SERD_NULLABLE out);
/**
- Create a new node by serialising `uri` into a new string.
-
- @param uri The URI to serialise.
-
- @param base Base URI to resolve `uri` against (or NULL for no resolution).
-
- @param out Set to the parsing of the new URI (i.e. points only to
- memory owned by the new returned node).
-*/
-SERD_API
-SerdNode* SERD_ALLOCATED
-serd_new_uri(const SerdURIView* SERD_NONNULL uri,
- const SerdURIView* SERD_NULLABLE base,
- SerdURIView* SERD_NULLABLE out);
-
-/**
- Create a new node by serialising `uri` into a new relative URI.
-
- @param uri The URI to serialise.
-
- @param base Base URI to make `uri` relative to, if possible.
-
- @param root Root URI for resolution (see serd_uri_serialise_relative()).
-
- @param out Set to the parsing of the new URI (i.e. points only to
- memory owned by the new returned node).
-*/
-SERD_API
-SerdNode* SERD_ALLOCATED
-serd_new_relative_uri(const SerdURIView* SERD_NONNULL uri,
- const SerdURIView* SERD_NULLABLE base,
- const SerdURIView* SERD_NULLABLE root,
- SerdURIView* SERD_NULLABLE out);
-
-/**
Create a new node by serialising `d` into an xsd:decimal string
The resulting node will always contain a `.', start with a digit, and end
diff --git a/src/env.c b/src/env.c
index f88b8ab1..eedb85fa 100644
--- a/src/env.c
+++ b/src/env.c
@@ -85,15 +85,16 @@ serd_env_set_base_uri(SerdEnv* env, const SerdNode* uri)
return SERD_SUCCESS;
}
- // Resolve base URI and create a new node and URI for it
- SerdURIView base_uri;
- SerdNode* base_uri_node =
- serd_new_uri_from_node(uri, &env->base_uri, &base_uri);
+ // Resolve the new base against the current base in case it is relative
+ const SerdURIView new_base_uri =
+ serd_resolve_uri(serd_parse_uri(serd_node_string(uri)), env->base_uri);
+
+ SerdNode* const new_base_node = serd_new_parsed_uri(new_base_uri);
// Replace the current base URI
serd_node_free(env->base_uri_node);
- env->base_uri_node = base_uri_node;
- env->base_uri = base_uri;
+ env->base_uri_node = new_base_node;
+ env->base_uri = serd_node_uri_view(env->base_uri_node);
return SERD_SUCCESS;
}
@@ -142,15 +143,17 @@ serd_env_set_prefix(SerdEnv* env, const SerdNode* name, const SerdNode* uri)
if (serd_uri_string_has_scheme(serd_node_string(uri))) {
// Set prefix to absolute URI
serd_env_add(env, name, uri);
+ } else if (!env->base_uri_node) {
+ return SERD_ERR_BAD_ARG;
} else {
// Resolve relative URI and create a new node and URI for it
- SerdURIView abs_uri;
- SerdNode* abs_uri_node =
- serd_new_uri_from_node(uri, &env->base_uri, &abs_uri);
+ SerdNode* const abs_uri =
+ serd_new_resolved_uri(serd_node_string_view(uri), env->base_uri);
// Set prefix to resolved (absolute) URI
- serd_env_add(env, name, abs_uri_node);
- serd_node_free(abs_uri_node);
+ serd_env_add(env, name, abs_uri);
+
+ serd_node_free(abs_uri);
}
return SERD_SUCCESS;
@@ -224,16 +227,15 @@ serd_env_expand_node(const SerdEnv* env, const SerdNode* node)
switch (node->type) {
case SERD_LITERAL:
break;
- case SERD_URI: {
- SerdURIView ignored;
- return serd_new_uri_from_node(node, &env->base_uri, &ignored);
- }
+ case SERD_URI:
+ return serd_new_resolved_uri(serd_node_string_view(node), env->base_uri);
case SERD_CURIE: {
SerdStringView prefix;
SerdStringView suffix;
if (serd_env_expand(env, node, &prefix, &suffix)) {
return NULL;
}
+
const size_t len = prefix.len + suffix.len;
SerdNode* ret = serd_node_malloc(len, 0, SERD_URI);
char* buf = serd_node_buffer(ret);
diff --git a/src/node.c b/src/node.c
index 7508f45d..34ab3bc0 100644
--- a/src/node.c
+++ b/src/node.c
@@ -191,7 +191,7 @@ serd_node_equals(const SerdNode* a, const SerdNode* b)
static size_t
serd_uri_string_length(const SerdURIView* uri)
{
- size_t len = uri->path_base.len;
+ size_t len = uri->path_prefix.len;
#define ADD_LEN(field, n_delims) \
if ((field).len) { \
@@ -217,29 +217,61 @@ string_sink(const void* buf, size_t len, void* stream)
}
SerdNode*
-serd_new_uri_from_node(const SerdNode* uri_node,
- const SerdURIView* base,
- SerdURIView* out)
+serd_new_uri(const char* str)
{
- const char* uri_str = serd_node_string(uri_node);
- return (uri_node && uri_node->type == SERD_URI && uri_str)
- ? serd_new_uri_from_string(uri_str, base, out)
- : NULL;
+ const size_t n_bytes = strlen(str);
+ SerdNode* node = serd_node_malloc(n_bytes, 0, SERD_URI);
+ memcpy(serd_node_buffer(node), str, n_bytes);
+ node->n_bytes = n_bytes;
+ return node;
+}
+
+SerdNode*
+serd_new_parsed_uri(const SerdURIView uri)
+{
+ const size_t len = serd_uri_string_length(&uri);
+ SerdNode* const node = serd_node_malloc(len, 0, SERD_URI);
+ char* ptr = serd_node_buffer(node);
+ const size_t actual_len = serd_write_uri(uri, string_sink, &ptr);
+
+ serd_node_buffer(node)[actual_len] = '\0';
+ node->n_bytes = actual_len;
+
+ return node;
+}
+
+static SerdNode*
+serd_new_from_uri(const SerdURIView uri, const SerdURIView base)
+{
+ const SerdURIView abs_uri = serd_resolve_uri(uri, base);
+ const size_t len = serd_uri_string_length(&abs_uri);
+ SerdNode* node = serd_node_malloc(len, 0, SERD_URI);
+ char* ptr = serd_node_buffer(node);
+ const size_t actual_len = serd_write_uri(abs_uri, string_sink, &ptr);
+
+ serd_node_buffer(node)[actual_len] = '\0';
+ node->n_bytes = actual_len;
+
+ return node;
}
SerdNode*
-serd_new_uri_from_string(const char* str,
- const SerdURIView* base,
- SerdURIView* out)
+serd_new_resolved_uri(const SerdStringView string, const SerdURIView base)
{
- if (!str || str[0] == '\0') {
+ if (!string.len || !string.buf[0]) {
// Empty URI => Base URI, or nothing if no base is given
- return base ? serd_new_uri(base, NULL, out) : NULL;
+ return base.scheme.buf ? serd_new_from_uri(base, SERD_URI_NULL) : NULL;
}
- SerdURIView uri;
- serd_uri_parse(str, &uri);
- return serd_new_uri(&uri, base, out); // Resolve/Serialise
+ const SerdURIView uri = serd_parse_uri(string.buf);
+ SerdNode* const result = serd_new_from_uri(uri, base);
+
+ if (!serd_uri_string_has_scheme(serd_node_string(result))) {
+ serd_node_free(result);
+ return NULL;
+ }
+
+ return result;
}
static inline bool
@@ -321,59 +353,13 @@ serd_new_file_uri(const char* path, const char* hostname, SerdURIView* out)
SerdNode* node =
serd_new_substring(SERD_URI, (const char*)buffer.buf, buffer.len);
if (out) {
- serd_uri_parse(serd_node_buffer(node), out);
+ *out = serd_parse_uri(serd_node_buffer(node));
}
free(buffer.buf);
return node;
}
-SerdNode*
-serd_new_uri(const SerdURIView* uri, const SerdURIView* base, SerdURIView* out)
-{
- SerdURIView abs_uri = *uri;
- if (base) {
- serd_uri_resolve(uri, base, &abs_uri);
- }
-
- const size_t len = serd_uri_string_length(&abs_uri);
- SerdNode* node = serd_node_malloc(len, 0, SERD_URI);
- char* ptr = serd_node_buffer(node);
- const size_t actual_len = serd_uri_serialise(&abs_uri, string_sink, &ptr);
-
- serd_node_buffer(node)[actual_len] = '\0';
- node->n_bytes = actual_len;
-
- if (out) {
- serd_uri_parse(serd_node_buffer(node), out); // TODO: avoid double parse
- }
-
- return node;
-}
-
-SerdNode*
-serd_new_relative_uri(const SerdURIView* uri,
- const SerdURIView* base,
- const SerdURIView* root,
- SerdURIView* out)
-{
- const size_t uri_len = serd_uri_string_length(uri);
- const size_t base_len = serd_uri_string_length(base);
- SerdNode* node = serd_node_malloc(uri_len + base_len, 0, SERD_URI);
- char* ptr = serd_node_buffer(node);
- const size_t actual_len =
- serd_uri_serialise_relative(uri, base, root, string_sink, &ptr);
-
- serd_node_buffer(node)[actual_len] = '\0';
- node->n_bytes = actual_len;
-
- if (out) {
- serd_uri_parse(serd_node_buffer(node), out); // TODO: avoid double parse
- }
-
- return node;
-}
-
static inline unsigned
serd_digits(double abs)
{
@@ -510,13 +496,8 @@ serd_node_string_view(const SerdNode* SERD_NONNULL node)
SerdURIView
serd_node_uri_view(const SerdNode* SERD_NONNULL node)
{
- SerdURIView result = SERD_URI_NULL;
-
- if (node->type == SERD_URI) {
- serd_uri_parse(serd_node_string(node), &result);
- }
-
- return result;
+ return (node->type == SERD_URI) ? serd_parse_uri(serd_node_string(node))
+ : SERD_URI_NULL;
}
const SerdNode*
diff --git a/src/node.h b/src/node.h
index 1c37c68d..5c6f790e 100644
--- a/src/node.h
+++ b/src/node.h
@@ -27,22 +27,27 @@ struct SerdNodeImpl {
SerdNodeType type; /**< Node type */
};
-static inline char*
-serd_node_buffer(SerdNode* node)
+static inline char* SERD_NONNULL
+serd_node_buffer(SerdNode* SERD_NONNULL node)
{
return (char*)(node + 1);
}
-static inline const char*
-serd_node_buffer_c(const SerdNode* node)
+static inline const char* SERD_NONNULL
+serd_node_buffer_c(const SerdNode* SERD_NONNULL node)
{
return (const char*)(node + 1);
}
-SerdNode*
+SerdNode* SERD_ALLOCATED
serd_node_malloc(size_t n_bytes, SerdNodeFlags flags, SerdNodeType type);
void
-serd_node_set(SerdNode** dst, const SerdNode* src);
+serd_node_set(SerdNode* SERD_NONNULL* SERD_NONNULL dst,
+ const SerdNode* SERD_NONNULL src);
+
+/// Create a new URI from a string, resolved against a base URI
+SerdNode* SERD_ALLOCATED
+serd_new_resolved_uri(SerdStringView string, SerdURIView base_uri);
#endif // SERD_NODE_H
diff --git a/src/reader.c b/src/reader.c
index 06fb25ea..6e814d93 100644
--- a/src/reader.c
+++ b/src/reader.c
@@ -270,7 +270,7 @@ serd_reader_set_default_graph(SerdReader* reader, const SerdNode* graph)
SerdStatus
serd_reader_read_file(SerdReader* reader, const char* uri)
{
- char* const path = serd_file_uri_parse(uri, NULL);
+ char* const path = serd_parse_file_uri(uri, NULL);
if (!path) {
return SERD_ERR_BAD_ARG;
}
diff --git a/src/serdi.c b/src/serdi.c
index 3d3b414c..b391f549 100644
--- a/src/serdi.c
+++ b/src/serdi.c
@@ -286,7 +286,7 @@ main(int argc, char** argv)
in_name = in_name ? in_name : input;
if (!in_fd) {
if (!strncmp(input, "file:", 5)) {
- input_path = serd_file_uri_parse(input, NULL);
+ input_path = serd_parse_file_uri(input, NULL);
input = input_path;
}
if (!input || !(in_fd = serd_fopen(input, "rb"))) {
@@ -312,7 +312,8 @@ main(int argc, char** argv)
SerdURIView base_uri = SERD_URI_NULL;
SerdNode* base = NULL;
if (a < argc) { // Base URI given on command line
- base = serd_new_uri_from_string((const char*)argv[a], NULL, &base_uri);
+ base_uri = serd_parse_uri(argv[a]);
+ base = serd_new_parsed_uri(base_uri);
} else if (from_file && in_fd != stdin) { // Use input file URI
base = serd_new_file_uri(input, NULL, &base_uri);
}
diff --git a/src/uri.c b/src/uri.c
index f40fde83..88e1bd1e 100644
--- a/src/uri.c
+++ b/src/uri.c
@@ -26,12 +26,13 @@
#include <string.h>
char*
-serd_file_uri_parse(const char* uri, char** hostname)
+serd_parse_file_uri(const char* uri, char** hostname)
{
const char* path = uri;
if (hostname) {
*hostname = NULL;
}
+
if (!strncmp(uri, "file://", 7)) {
const char* auth = uri + 7;
if (*auth == '/') { // No hostname
@@ -40,6 +41,7 @@ serd_file_uri_parse(const char* uri, char** hostname)
if (!(path = strchr(auth, '/'))) {
return NULL;
}
+
if (hostname) {
*hostname = (char*)calloc((size_t)(path - auth + 1), 1);
memcpy(*hostname, auth, (size_t)(path - auth));
@@ -69,36 +71,34 @@ serd_file_uri_parse(const char* uri, char** hostname)
serd_buffer_sink(s, 1, &buffer);
}
}
+
return serd_buffer_sink_finish(&buffer);
}
+/// RFC3986: scheme ::= ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
bool
-serd_uri_string_has_scheme(const char* utf8)
+serd_uri_string_has_scheme(const char* const string)
{
- // RFC3986: scheme ::= ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
- if (!utf8 || !is_alpha(utf8[0])) {
- return false; // Invalid scheme initial character, URI is relative
- }
-
- for (char c = 0; (c = *++utf8) != '\0';) {
- if (!is_uri_scheme_char(c)) {
- return false;
- }
+ if (is_alpha(string[0])) {
+ for (size_t i = 1; string[i]; ++i) {
+ if (!is_uri_scheme_char(string[i])) {
+ return false; // Non-scheme character before a ':'
+ }
- if (c == ':') {
- return true; // End of scheme
+ if (string[i] == ':') {
+ return true; // Valid scheme terminated by a ':'
+ }
}
}
- return false;
+ return false; // String doesn't start with a scheme
}
-SerdStatus
-serd_uri_parse(const char* utf8, SerdURIView* out)
+SerdURIView
+serd_parse_uri(const char* const string)
{
- *out = SERD_URI_NULL;
-
- const char* ptr = utf8;
+ SerdURIView result = SERD_URI_NULL;
+ const char* ptr = string;
/* See http://tools.ietf.org/html/rfc3986#section-3
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
@@ -112,11 +112,11 @@ serd_uri_parse(const char* utf8, SerdURIView* out)
case '/':
case '?':
case '#':
- ptr = utf8;
+ ptr = string;
goto path; // Relative URI (starts with path by definition)
case ':':
- out->scheme.buf = utf8;
- out->scheme.len = (size_t)((ptr++) - utf8);
+ result.scheme.buf = string;
+ result.scheme.len = (size_t)((ptr++) - string);
goto maybe_authority; // URI with scheme
case '+':
case '-':
@@ -137,7 +137,7 @@ serd_uri_parse(const char* utf8, SerdURIView* out)
maybe_authority:
if (*ptr == '/' && *(ptr + 1) == '/') {
ptr += 2;
- out->authority.buf = ptr;
+ result.authority.buf = ptr;
for (char c = 0; (c = *ptr) != '\0'; ++ptr) {
switch (c) {
case '/':
@@ -147,7 +147,7 @@ maybe_authority:
case '#':
goto fragment;
default:
- ++out->authority.len;
+ ++result.authority.len;
}
}
}
@@ -166,8 +166,8 @@ path:
default:
break;
}
- out->path.buf = ptr;
- out->path.len = 0;
+ result.path.buf = ptr;
+ result.path.len = 0;
for (char c = 0; (c = *ptr) != '\0'; ++ptr) {
switch (c) {
case '?':
@@ -175,7 +175,7 @@ path:
case '#':
goto fragment;
default:
- ++out->path.len;
+ ++result.path.len;
}
}
@@ -185,12 +185,12 @@ path:
*/
query:
if (*ptr == '?') {
- out->query.buf = ++ptr;
+ result.query.buf = ++ptr;
for (char c = 0; (c = *ptr) != '\0'; ++ptr) {
if (c == '#') {
goto fragment;
}
- ++out->query.len;
+ ++result.query.len;
}
}
@@ -200,14 +200,14 @@ query:
*/
fragment:
if (*ptr == '#') {
- out->fragment.buf = ptr;
+ result.fragment.buf = ptr;
while (*ptr++ != '\0') {
- ++out->fragment.len;
+ ++result.fragment.len;
}
}
end:
- return SERD_SUCCESS;
+ return result;
}
/**
@@ -217,64 +217,33 @@ end:
@return A pointer to the new start of `path`
*/
static const char*
-remove_dot_segments(const char* path, size_t len, size_t* up)
+remove_dot_segments(const char* path, const size_t len, size_t* up)
{
- const char* begin = path;
- const char* const end = path + len;
-
*up = 0;
- while (begin < end) {
- switch (begin[0]) {
- case '.':
- switch (begin[1]) {
- case '/':
- begin += 2; // Chop leading "./"
- break;
- case '.':
- switch (begin[2]) {
- case '\0':
- ++*up;
- begin += 2; // Chop input ".."
- break;
- case '/':
- ++*up;
- begin += 3; // Chop leading "../"
- break;
- default:
- return begin;
- }
- break;
- case '\0':
- ++begin; // Chop input "."
- // fallthru
- default:
- return begin;
- }
- break;
- case '/':
- switch (begin[1]) {
- case '.':
- switch (begin[2]) {
- case '/':
- begin += 2; // Leading "/./" => "/"
- break;
- case '.':
- switch (begin[3]) {
- case '/':
- ++*up;
- begin += 3; // Leading "/../" => "/"
- }
- break;
- default:
- return begin;
- }
- } // else fall through
- default:
- return begin; // Finished chopping dot components
+
+ for (size_t i = 0; i < len;) {
+ const char* const p = path + i;
+ if (!strncmp(p, "./", 2)) {
+ i += 2; // Chop leading "./"
+ } else if (!strncmp(p, "..", 3)) {
+ ++*up;
+ i += 2; // Chop input ".."
+ } else if (!strncmp(p, "../", 3)) {
+ ++*up;
+ i += 3; // Chop leading "../"
+ } else if (!strncmp(p, ".", 2)) {
+ ++i; // Chop input "."
+ } else if (!strncmp(p, "/./", 3)) {
+ i += 2; // Leading "/./" => "/"
+ } else if (!strncmp(p, "/../", 4)) {
+ ++*up;
+ i += 3; // Leading "/../" => "/"
+ } else {
+ return p;
}
}
- return begin;
+ return path + len;
}
/// Merge `base` and `path` in-place
@@ -305,172 +274,189 @@ merge(SerdStringView* base, SerdStringView* path)
}
/// See http://tools.ietf.org/html/rfc3986#section-5.2.2
-void
-serd_uri_resolve(const SerdURIView* r, const SerdURIView* base, SerdURIView* t)
+SerdURIView
+serd_resolve_uri(const SerdURIView r, const SerdURIView base)
{
- if (!base->scheme.len) {
- *t = *r; // Don't resolve against non-absolute URIs
- return;
+ if (r.scheme.len || !base.scheme.len) {
+ return r; // No resolution necessary || possible (respectively)
}
- t->path_base.buf = NULL;
- t->path_base.len = 0;
- if (r->scheme.len) {
- *t = *r;
+ SerdURIView t = SERD_URI_NULL;
+
+ t.path_prefix.buf = NULL;
+ t.path_prefix.len = 0;
+
+ if (r.authority.len) {
+ t.authority = r.authority;
+ t.path = r.path;
+ t.query = r.query;
} else {
- if (r->authority.len) {
- t->authority = r->authority;
- t->path = r->path;
- t->query = r->query;
+ t.path = r.path;
+ if (!r.path.len) {
+ t.path_prefix = base.path;
+ t.query = r.query.len ? r.query : base.query;
} else {
- t->path = r->path;
- if (!r->path.len) {
- t->path_base = base->path;
- if (r->query.len) {
- t->query = r->query;
- } else {
- t->query = base->query;
- }
- } else {
- if (r->path.buf[0] != '/') {
- t->path_base = base->path;
- }
- merge(&t->path_base, &t->path);
- t->query = r->query;
+ if (r.path.buf[0] != '/') {
+ t.path_prefix = base.path;
}
- t->authority = base->authority;
+
+ merge(&t.path_prefix, &t.path);
+ t.query = r.query;
}
- t->scheme = base->scheme;
- t->fragment = r->fragment;
+
+ t.authority = base.authority;
}
+
+ t.scheme = base.scheme;
+ t.fragment = r.fragment;
+
+ return t;
}
-/** Write the path of `uri` starting at index `i` */
-static size_t
-write_path_tail(SerdSink sink, void* stream, const SerdURIView* uri, size_t i)
+SerdURIView
+serd_relative_uri(const SerdURIView uri, const SerdURIView base)
{
- size_t len = 0;
- if (i < uri->path_base.len) {
- len += sink(uri->path_base.buf + i, uri->path_base.len - i, stream);
+ if (!uri_is_related(&uri, &base)) {
+ return uri;
}
- if (uri->path.buf) {
- if (i < uri->path_base.len) {
- len += sink(uri->path.buf, uri->path.len, stream);
- } else {
- const size_t j = (i - uri->path_base.len);
- len += sink(uri->path.buf + j, uri->path.len - j, stream);
- }
- }
+ SerdURIView result = SERD_URI_NULL;
- return len;
-}
+ // Regardless of the path, the query and/or fragment come along
+ result.query = uri.query;
+ result.fragment = uri.fragment;
-/** Write the path of `uri` relative to the path of `base`. */
-static size_t
-write_rel_path(SerdSink sink,
- void* stream,
- const SerdURIView* uri,
- const SerdURIView* base)
-{
- const size_t path_len = uri_path_len(uri);
- const size_t base_len = uri_path_len(base);
+ const size_t path_len = uri_path_len(&uri);
+ const size_t base_len = uri_path_len(&base);
const size_t min_len = (path_len < base_len) ? path_len : base_len;
// Find the last separator common to both paths
size_t last_shared_sep = 0;
size_t i = 0;
- for (; i < min_len && uri_path_at(uri, i) == uri_path_at(base, i); ++i) {
- if (uri_path_at(uri, i) == '/') {
+ for (; i < min_len && uri_path_at(&uri, i) == uri_path_at(&base, i); ++i) {
+ if (uri_path_at(&uri, i) == '/') {
last_shared_sep = i;
}
}
- if (i == path_len && i == base_len) { // Paths are identical
- return 0;
+ // If the URI and base URI have identical paths, the relative path is empty
+ if (i == path_len && i == base_len) {
+ result.path.buf = uri.path.buf;
+ result.path.len = 0;
+ return result;
}
+ // Otherwise, we need to build the relative path out of string slices
+
// Find the number of up references ("..") required
size_t up = 0;
for (size_t s = last_shared_sep + 1; s < base_len; ++s) {
- if (uri_path_at(base, s) == '/') {
+ if (uri_path_at(&base, s) == '/') {
++up;
}
}
- // Write up references
- size_t len = 0;
- for (size_t u = 0; u < up; ++u) {
- len += sink("../", 3, stream);
+ if (up > 0) {
+ if (last_shared_sep < uri.path_prefix.len) {
+ return SERD_URI_NULL;
+ }
+
+ // Special representation: NULL buffer and len set to the depth
+ result.path_prefix.len = up;
}
- if (last_shared_sep == 0 && up == 0) {
- len += sink("/", 1, stream);
+ if (last_shared_sep < uri.path_prefix.len) {
+ result.path_prefix.buf = uri.path_prefix.buf + last_shared_sep + 1;
+ result.path_prefix.len = uri.path_prefix.len - last_shared_sep - 1;
+ result.path = uri.path;
+ } else {
+ result.path.buf = uri.path.buf + last_shared_sep + 1;
+ result.path.len = uri.path.len - last_shared_sep - 1;
}
- // Write suffix
- return len + write_path_tail(sink, stream, uri, last_shared_sep + 1);
+ return result;
}
-static uint8_t
-serd_uri_path_starts_without_slash(const SerdURIView* uri)
+SERD_API
+bool
+serd_uri_is_within(const SerdURIView uri, const SerdURIView base)
{
- return ((uri->path_base.len || uri->path.len) &&
- ((!uri->path_base.len || uri->path_base.buf[0] != '/') &&
- (!uri->path.len || uri->path.buf[0] != '/')));
+ if (!base.scheme.len || !slice_equals(&base.scheme, &uri.scheme) ||
+ !slice_equals(&base.authority, &uri.authority)) {
+ return false;
+ }
+
+ bool differ = false;
+ const size_t path_len = uri_path_len(&uri);
+ const size_t base_len = uri_path_len(&base);
+
+ size_t last_base_slash = 0;
+ for (size_t i = 0; i < path_len && i < base_len; ++i) {
+ const char u = uri_path_at(&uri, i);
+ const char b = uri_path_at(&base, i);
+
+ differ = differ || u != b;
+ if (b == '/') {
+ last_base_slash = i;
+ if (differ) {
+ return false;
+ }
+ }
+ }
+
+ for (size_t i = last_base_slash + 1; i < base_len; ++i) {
+ if (uri_path_at(&base, i) == '/') {
+ return false;
+ }
+ }
+
+ return true;
}
/// See http://tools.ietf.org/html/rfc3986#section-5.3
size_t
-serd_uri_serialise_relative(const SerdURIView* uri,
- const SerdURIView* base,
- const SerdURIView* root,
- SerdSink sink,
- void* stream)
+serd_write_uri(const SerdURIView uri, SerdSink sink, void* stream)
{
- size_t len = 0;
- const bool relative =
- root ? uri_is_under(uri, root) : uri_is_related(uri, base);
+ size_t len = 0;
- if (relative) {
- len = write_rel_path(sink, stream, uri, base);
+ if (uri.scheme.buf) {
+ len += sink(uri.scheme.buf, uri.scheme.len, stream);
+ len += sink(":", 1, stream);
}
- if (!relative || (!len && base->query.buf)) {
- if (uri->scheme.buf) {
- len += sink(uri->scheme.buf, uri->scheme.len, stream);
- len += sink(":", 1, stream);
+ if (uri.authority.buf) {
+ len += sink("//", 2, stream);
+ len += sink(uri.authority.buf, uri.authority.len, stream);
+
+ if (uri.authority.len > 0 && uri_path_len(&uri) > 0 &&
+ uri_path_at(&uri, 0) != '/') {
+ // Special case: ensure path begins with a slash
+ // https://tools.ietf.org/html/rfc3986#section-3.2
+ len += sink("/", 1, stream);
}
- if (uri->authority.buf) {
- len += sink("//", 2, stream);
- len += sink(uri->authority.buf, uri->authority.len, stream);
- if (uri->authority.len > 0 &&
- uri->authority.buf[uri->authority.len - 1] != '/' &&
- serd_uri_path_starts_without_slash(uri)) {
- // Special case: ensure path begins with a slash
- // https://tools.ietf.org/html/rfc3986#section-3.2
- len += sink("/", 1, stream);
- }
+ }
+
+ if (uri.path_prefix.buf) {
+ len += sink(uri.path_prefix.buf, uri.path_prefix.len, stream);
+ } else if (uri.path_prefix.len) {
+ for (size_t i = 0; i < uri.path_prefix.len; ++i) {
+ len += sink("../", 3, stream);
}
- len += write_path_tail(sink, stream, uri, 0);
}
- if (uri->query.buf) {
+ if (uri.path.buf) {
+ len += sink(uri.path.buf, uri.path.len, stream);
+ }
+
+ if (uri.query.buf) {
len += sink("?", 1, stream);
- len += sink(uri->query.buf, uri->query.len, stream);
+ len += sink(uri.query.buf, uri.query.len, stream);
}
- if (uri->fragment.buf) {
- // Note uri->fragment.buf includes the leading `#'
- len += sink(uri->fragment.buf, uri->fragment.len, stream);
+ if (uri.fragment.buf) {
+ // Note that uri.fragment.buf includes the leading `#'
+ len += sink(uri.fragment.buf, uri.fragment.len, stream);
}
return len;
}
-
-/// See http://tools.ietf.org/html/rfc3986#section-5.3
-size_t
-serd_uri_serialise(const SerdURIView* uri, SerdSink sink, void* stream)
-{
- return serd_uri_serialise_relative(uri, NULL, NULL, sink, stream);
-}
diff --git a/src/uri_utils.h b/src/uri_utils.h
index 6b07ec48..fa1619d9 100644
--- a/src/uri_utils.h
+++ b/src/uri_utils.h
@@ -33,17 +33,14 @@ slice_equals(const SerdStringView* a, const SerdStringView* b)
static inline size_t
uri_path_len(const SerdURIView* uri)
{
- return uri->path_base.len + uri->path.len;
+ return uri->path_prefix.len + uri->path.len;
}
static inline char
uri_path_at(const SerdURIView* uri, size_t i)
{
- if (i < uri->path_base.len) {
- return uri->path_base.buf[i];
- }
-
- return uri->path.buf[i - uri->path_base.len];
+ return (i < uri->path_prefix.len) ? uri->path_prefix.buf[i]
+ : uri->path.buf[i - uri->path_prefix.len];
}
/**
@@ -60,10 +57,11 @@ uri_rooted_index(const SerdURIView* uri, const SerdURIView* root)
return 0;
}
- bool differ = false;
- const size_t path_len = uri_path_len(uri);
- const size_t root_len = uri_path_len(root);
- size_t last_root_slash = 0;
+ bool differ = false;
+ const size_t path_len = uri_path_len(uri);
+ const size_t root_len = uri_path_len(root);
+
+ size_t last_root_slash = 0;
for (size_t i = 0; i < path_len && i < root_len; ++i) {
const char u = uri_path_at(uri, i);
const char r = uri_path_at(root, i);
@@ -85,7 +83,9 @@ static inline SERD_PURE_FUNC
bool
uri_is_related(const SerdURIView* uri, const SerdURIView* root)
{
- return uri_rooted_index(uri, root) > 0;
+ return root && root->scheme.len &&
+ slice_equals(&root->scheme, &uri->scheme) &&
+ slice_equals(&root->authority, &uri->authority);
}
/** Return true iff `uri` is within the base of `root` */
diff --git a/src/writer.c b/src/writer.c
index 5437f659..5205c7d1 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -614,29 +614,32 @@ write_uri_node(SerdWriter* const writer,
write_sep(writer, SEP_URI_BEGIN);
if (writer->flags & SERD_WRITE_RESOLVED) {
+ const SerdURIView uri = serd_parse_uri(node_str);
+
SerdURIView in_base_uri;
- SerdURIView uri;
- SerdURIView abs_uri;
serd_env_base_uri(writer->env, &in_base_uri);
- serd_uri_parse(node_str, &uri);
- serd_uri_resolve(&uri, &in_base_uri, &abs_uri);
- bool rooted = uri_is_under(&writer->base_uri, &writer->root_uri);
- SerdURIView* root = rooted ? &writer->root_uri : &writer->base_uri;
- if (!uri_is_under(&abs_uri, root) || writer->syntax == SERD_NTRIPLES ||
- writer->syntax == SERD_NQUADS) {
- serd_uri_serialise(&abs_uri, uri_sink, writer);
+
+ const SerdURIView abs_uri = serd_resolve_uri(uri, in_base_uri);
+ const bool rooted = uri_is_under(&in_base_uri, &writer->root_uri);
+ const SerdURIView* root = rooted ? &writer->root_uri : &in_base_uri;
+
+ if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS ||
+ !uri_is_under(&abs_uri, root) ||
+ !uri_is_related(&abs_uri, &in_base_uri)) {
+ serd_write_uri(abs_uri, uri_sink, writer);
} else {
- serd_uri_serialise_relative(
- &uri, &writer->base_uri, root, uri_sink, writer);
+ serd_write_uri(serd_relative_uri(uri, in_base_uri), uri_sink, writer);
}
} else {
write_uri_from_node(writer, node);
}
write_sep(writer, SEP_URI_END);
+
if (is_inline_start(writer, field, flags)) {
sink(" ;", 2, writer);
write_newline(writer);
}
+
return true;
}
@@ -1031,7 +1034,7 @@ serd_writer_set_root_uri(SerdWriter* writer, const SerdNode* uri)
serd_node_free(writer->root_node);
if (uri) {
writer->root_node = serd_node_copy(uri);
- serd_uri_parse(serd_node_string(writer->root_node), &writer->root_uri);
+ writer->root_uri = serd_parse_uri(serd_node_string(writer->root_node));
} else {
writer->root_node = NULL;
writer->root_uri = SERD_URI_NULL;
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;