commit 6c1b6edfc7152b7563a2c08f59ea85bc076144a9
parent 129652abd587f40d96ca9b735d8c552cd2b8ee64
Author: Michael Skec
Date: Fri, 1 Dec 2023 08:28:51 +1100
base uri_str implementation + add query support
uri_str generates string from URI structure. Truncations and overflows
not fully tested yet. URI fragments are completely disregarded at the
moment.
Diffstat:
M | str.h | | | 2 | +- |
M | test.c | | | 463 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- |
M | uri.c | | | 138 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
M | uri.h | | | 6 | +++--- |
4 files changed, 452 insertions(+), 157 deletions(-)
diff --git a/str.h b/str.h
@@ -43,7 +43,7 @@ str_equal(const char *restrict s1, const char *restrict s2, size_t n)
static inline bool
strz_equal(const char *restrict s1, const char *restrict s2)
{
- return strcmp(s1, s2);
+ return strcmp(s1, s2) == 0;
}
/* string copy (implemented as strlcpy) */
diff --git a/test.c b/test.c
@@ -135,26 +135,27 @@ test_str(void)
}
static bool
-test_uri(const char *string, struct uri expected)
+test_uri(const char *uristring_in, struct uri expected)
{
- char uristring[512];
+ char uristring_expected[512];
bool equal;
struct uri parsed;
- parsed = uri_parse(string, strz_len(string));
+ parsed = uri_parse(uristring_in, strz_len(uristring_in));
equal = uri_equal(parsed, expected, 0);
- uri_str(&expected, uristring, sizeof(uristring), 0);
+ uri_str(uristring_expected, &expected, sizeof(uristring_expected), 0);
fprintf(stdout, "test %s:\n"
- "\t'%s' vs '%s'\n",
+ "\t'%s'",
equal ? "passed" : "FAILED",
- string, uristring);
+ uristring_in);
#if !VERBOSE_TEST_RESULTS
if (!equal)
#endif /* !VERBOSE_TEST_RESULTS */
{
+ fprintf(stdout, " not equivalent to '%s':\n", uristring_in);
fprintf(stdout, "\tport %d\t\texpected %d\n",
parsed.port, expected.port);
fprintf(stdout, "\tprotocol %d\t\texpected %d\n",
@@ -168,6 +169,35 @@ test_uri(const char *string, struct uri expected)
fprintf(stdout, "\tquery '%s'\t\texpected '%s'\n",
parsed.query, expected.query);
}
+ else
+ {
+ fputc('\n', stdout);
+ }
+
+ return equal;
+}
+
+static bool
+test_uri_str(const struct uri uri, const char *expected, uint32_t flags)
+{
+ char actual[512];
+ bool equal;
+
+ uri_str(actual, &uri, sizeof(actual), flags);
+ equal = strz_equal(expected, actual);
+
+ if (!equal)
+ {
+ fprintf(stdout, "test failed:\n"
+ "\t'%s' != expected '%s'\n",
+ actual, expected);
+ }
+ else
+ {
+ fprintf(stdout, "test passed:\n"
+ "\t'%s' == expected '%s'\n",
+ actual, expected);
+ }
return equal;
}
@@ -175,143 +205,314 @@ test_uri(const char *string, struct uri expected)
static bool
test_uris(void)
{
+ struct uri uri;
+
fprintf(stdout, "testing URI functions...\n");
fprintf(stdout, " - uri_str...\n");
- char uristring_actual[512];
- char uristring_expect[512];
- struct uri testuri;
-
-#if 0
- str_copy_fixed(uristring_expect, "gopher://");
- memset(&testuri, 0, sizeof(testuri));
- testuri.port = 0;
- testuri.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(testuri.protocol_str, "gopher");
- uri_str(&testuri, uristring_actual, sizeof(uristring_actual), 0);
- if (!str_equal_fixed(uristring_expect, uristring_actual))
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://", 0))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com", 0))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com:70", 0))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com", URISTR_NO_PORT_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com/", 0))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/index/");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com/index/", 0))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/index/");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com/index", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/index");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com/index", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "index/test/");
+ uri.protocol = PROTOCOL_NONE;
+ if (!test_uri_str(uri, "index/test", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "index/test/");
+ uri.protocol = PROTOCOL_FILE;
+ str_copy_fixed(uri.protocol_str, "file");
+ if (!test_uri_str(uri, "file://index/test", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "/index/test/");
+ uri.protocol = PROTOCOL_FILE;
+ str_copy_fixed(uri.protocol_str, "file");
+ if (!test_uri_str(uri, "file:///index/test", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ /* unorthodox URI style (no slash with query) but we allow it anyway */
+ if (!test_uri_str(uri, "gopher://example.com?q=test", URISTR_NOSLASH_BIT))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com",
+ URISTR_NOSLASH_BIT | URISTR_NOQUERY_BIT))
+ {
+ return false;
+ }
+
+ memset(&uri, 0, sizeof(uri));
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ if (!test_uri_str(uri, "gopher://example.com/?q=test", 0))
{
- fprintf(stdout, "test failed:\n"
- "\t'%s' != expected '%s'\n",
- uristring_actual, uristring_expect);
return false;
}
- fprintf(stdout, "test passed:\n"
- "\t'%s' == expected '%s'\n",
- uristring_actual, uristring_expect);
-#endif
+
/* TODO: oversized hostnames, oversized paths, test truncation, etc. */
fprintf(stdout, " - uri_parse...\n");
- struct uri expected;
-
- memset(&expected, 0, sizeof(expected));
- if (!test_uri("", expected))
- return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_FILE;
- str_copy_fixed(expected.protocol_str, "file");
- str_copy_fixed(expected.host, "");
- str_copy_fixed(expected.path, "README/");
- expected.query[0] = '\0';
- if (!test_uri("file://README/", expected))
- return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_FILE;
- str_copy_fixed(expected.protocol_str, "file");
- str_copy_fixed(expected.host, "");
- str_copy_fixed(expected.path, "/home/mike/src/c/sr71/README");
- expected.query[0] = '\0';
- if (!test_uri("file:///home/mike/src/c/sr71/README", expected))
- return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/test.txt");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com/test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 70;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/test.txt");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com:70/test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 70;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/1/test.txt");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com:70/1/test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/1/test.txt");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com/1/test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_NONE;
- str_copy_fixed(expected.protocol_str, "");
- str_copy_fixed(expected.host, "");
- str_copy_fixed(expected.path, "test.txt");
- expected.query[0] = '\0';
- if (!test_uri("test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 0;
- expected.protocol = PROTOCOL_NONE;
- str_copy_fixed(expected.protocol_str, "");
- str_copy_fixed(expected.host, "");
- str_copy_fixed(expected.path, "/test.txt");
- expected.query[0] = '\0';
- if (!test_uri("/test.txt", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 70;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/");
- expected.query[0] = '\0';
- if (!test_uri("gopher://example.com:70/", expected)) return false;
-
- memset(&expected, 0, sizeof(expected));
- expected.port = 70;
- expected.protocol = PROTOCOL_GOPHER;
- str_copy_fixed(expected.protocol_str, "gopher");
- str_copy_fixed(expected.host, "example.com");
- str_copy_fixed(expected.path, "/");
- expected.query[0] = '\0'; /* TODO: query */
- if (!test_uri("gopher://example.com:70/?q=test", expected)) return false;
+ memset(&uri, 0, sizeof(uri));
+ if (!test_uri("", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_FILE;
+ str_copy_fixed(uri.protocol_str, "file");
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "README/");
+ uri.query[0] = '\0';
+ if (!test_uri("file://README/", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_FILE;
+ str_copy_fixed(uri.protocol_str, "file");
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "/home/mike/src/c/sr71/README");
+ uri.query[0] = '\0';
+ if (!test_uri("file:///home/mike/src/c/sr71/README", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com/test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com:70/test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/1/test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com:70/1/test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/1/test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com/1/test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_NONE;
+ str_copy_fixed(uri.protocol_str, "");
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 0;
+ uri.protocol = PROTOCOL_NONE;
+ str_copy_fixed(uri.protocol_str, "");
+ str_copy_fixed(uri.host, "");
+ str_copy_fixed(uri.path, "/test.txt");
+ uri.query[0] = '\0';
+ if (!test_uri("/test.txt", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ uri.query[0] = '\0';
+ if (!test_uri("gopher://example.com:70/", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("gopher://example.com:70/?q=test", uri)) return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("gopher://example.com:70/?q=test#ignored-fragment", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.port = 70;
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "/");
+ if (!test_uri("gopher://example.com:70/#ignored-fragment", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "");
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("gopher://example.com?q=test", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_GOPHER;
+ str_copy_fixed(uri.protocol_str, "gopher");
+ str_copy_fixed(uri.host, "example.com");
+ str_copy_fixed(uri.path, "");
+ if (!test_uri("gopher://example.com#ignored-fragment", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_NONE;
+ str_copy_fixed(uri.path, "example/");
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("example/?q=test", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_NONE;
+ str_copy_fixed(uri.path, "/");
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("/?q=test", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ uri.protocol = PROTOCOL_NONE;
+ str_copy_fixed(uri.query, "q=test");
+ if (!test_uri("?q=test", uri))
+ return false;
+
+ memset(&uri, 0, sizeof(uri));
+ if (!test_uri("#test-fragment", uri))
+ return false;
return true;
}
diff --git a/uri.c b/uri.c
@@ -11,7 +11,10 @@ uri_parse(const char *uristr, int uristr_len)
int colon = 0,
hostname_start = 0,
hostname_len = 0,
- protocol_name_len = 0;
+ protocol_name_len = 0,
+ path_start = 0,
+ path_len = 0,
+ query = 0;
/*
* Look for a colon. We expect a maximum of two colons in a URI:
@@ -136,7 +139,7 @@ uri_parse(const char *uristr, int uristr_len)
{
c = uristr[hostname_len + hostname_start];
- if (c == '\0' || c == ':' || c == '/')
+ if (c == '\0' || c == ':' || c == '/' || c == '?' || c == '#')
break;
}
@@ -176,7 +179,7 @@ uri_parse(const char *uristr, int uristr_len)
*/
if (uri.protocol == PROTOCOL_FILE) /* local URI */
{
- int n_slashes, path_start, path_len, path_size;
+ int n_slashes, path_size;
ASSERT(protocol_name_len > 0);
ASSERT(uristr[protocol_name_len] == ':');
@@ -195,7 +198,9 @@ uri_parse(const char *uristr, int uristr_len)
/* Find length of path. Generally ends at the end of the string. */
for (path_len = 0;
(path_start + path_len < uristr_len) &&
- (uristr[path_start + path_len] != '\0');
+ (uristr[path_start + path_len] != '\0') &&
+ (uristr[path_start + path_len] != '?') &&
+ (uristr[path_start + path_len] != '#'); /* ignore fragment */
++path_len)
;
@@ -209,13 +214,18 @@ uri_parse(const char *uristr, int uristr_len)
}
else if (hostname_len > 0) /* URI with a hostname */
{
- int path_start, path_len, path_size;
+ int path_size;
- /* Start at the end of hostname and after port. */
+ /* Start may start at the end of hostname and after port, at the first
+ * slash. If we encounter the query (?) or fragment (#), we leave the
+ * "path start" point there, even though the path length will be 0, so
+ * that we can still find the query later on. */
for (path_start = hostname_start + hostname_len;
path_start < uristr_len &&
- uristr[path_start] != '\0' &&
- uristr[path_start] != '/';
+ (uristr[path_start] != '\0') &&
+ (uristr[path_start] != '/') &&
+ (uristr[path_start] != '?') &&
+ (uristr[path_start] != '#'); /* ignore fragment */
++path_start)
{
/* Skip past the port if there is one */
@@ -231,8 +241,9 @@ uri_parse(const char *uristr, int uristr_len)
/* Length is to end of the string or to the query. */
for (path_len = 0;
path_start + path_len < uristr_len &&
- uristr[path_start + path_len] != '\0' &&
- uristr[path_start + path_len] != '?';
+ (uristr[path_start + path_len] != '\0') &&
+ (uristr[path_start + path_len] != '?') &&
+ (uristr[path_start + path_len] != '#'); /* ignore fragment */
++path_len)
;
@@ -243,18 +254,19 @@ uri_parse(const char *uristr, int uristr_len)
str_copy(uri.path, uristr + path_start, path_size);
}
- else /* relative URI--we use beginning section as path. */
+ else /* relative URI--we use whole beginning section as path. */
{
- int path_start = 0, path_len, path_size;
+ int path_size;
ASSERT(protocol_name_len == 0);
/* Find length of path. Goes until the end of the string or at
- * query. */
+ * query or fragment. */
for (path_len = 0;
(path_start + path_len < uristr_len) &&
- (uristr[path_start + path_len] != '\0') &&
- (uristr[path_start + path_len] != '?');
+ (uristr[path_start + path_len] != '\0') &&
+ (uristr[path_start + path_len] != '?') &&
+ (uristr[path_start + path_len] != '#'); /* ignore fragment */
++path_len)
;
@@ -267,23 +279,105 @@ uri_parse(const char *uristr, int uristr_len)
str_copy(uri.path, uristr + path_start, path_size);
}
+ /* Find query, which always appears after the path */
+ query = str_ichr(uristr + path_start + path_len, '?',
+ uristr_len - path_start - path_len) +
+ path_start + path_len + 1 /* +1 to read after the '?' */;
+ if (query < uristr_len)
+ {
+ int query_len, query_size;
+
+ /* Find length of query. Goes until the end of the string or at
+ * query. */
+ for (query_len = 0;
+ (query + query_len < uristr_len) &&
+ (uristr[query + query_len] != '\0') &&
+ (uristr[query + query_len] != '#'); /* ignore fragment */
+ ++query_len)
+ ;
+
+ ASSERT(query_len > 0);
+
+ query_size = query_len + 1;
+ ASSERT(query_size < sizeof(uri.query) && "big query");
+ query_size = min(sizeof(uri.query), query_size);
+ /* TODO: log truncation */
+
+ /* Copy query */
+ str_copy(uri.query, uristr + query, query_size);
+ }
return uri;
}
size_t
-uri_str(const struct uri *const u,
- char *buffer,
- size_t buffer_size,
- uint32_t flags)
+uri_str(char *buffer,
+ const struct uri *const u,
+ size_t buffer_size,
+ uint32_t flags)
{
ASSERT(u);
ASSERT(buffer);
ASSERT(buffer_size > 0);
- memset(buffer, 0, buffer_size);
+ int pos = 0;
+ buffer[pos] = '\0';
+
+ /* Write the scheme first */
+ if (u->protocol != PROTOCOL_NONE)
+ {
+ ASSERT(u->protocol_str[0] && "uri has no protocol");
+
+ int protocol_len;
+
+ /* Write protocol string */
+ protocol_len = (int)str_copy(buffer, u->protocol_str, buffer_size);
+
+ /* Write the colon following protocol */
+ protocol_len += (int)str_copy(buffer + protocol_len, ":",
+ buffer_size - protocol_len);
+
+ /* Write slashes if the protocol has them */
+ if (!(u->flags & URI_NO_PROTOCOL_SLASHES_BIT))
+ {
+ protocol_len += (int)str_copy(buffer + protocol_len, "//",
+ buffer_size - protocol_len);
+ }
+
+ pos += protocol_len;
+ }
+
+ /* Write the hostname */
+ if (u->host[0] != '\0')
+ {
+ pos += (int)str_copy(buffer + pos, u->host, buffer_size - pos);
+ }
+
+ /* Write the port */
+ if (u->port > 0 && !(flags & URISTR_NO_PORT_BIT))
+ {
+ pos += (int)snprintf(buffer + pos, buffer_size - pos, ":%d", u->port);
+ }
+
+ /* Write path */
+ if (u->path[0] != '\0')
+ {
+ pos += (int)str_copy(buffer + pos, u->path, buffer_size - pos);
+
+ /* trim trailing slash */
+ if ((flags & URISTR_NOSLASH_BIT) && pos > 0 && buffer[pos - 1] == '/')
+ buffer[--pos] = '\0';
+ }
+
+ /* Write query */
+ if (u->query[0] != '\0' && !(flags & URISTR_NOQUERY_BIT))
+ {
+ /* Write the leading '?' followed by query */
+ pos += (int)str_copy(buffer + pos, "?", buffer_size - pos);
+ pos += (int)str_copy(buffer + pos, u->query, buffer_size - pos);
+ }
- return str_copy(buffer, "(uri_str is unimplemented)", buffer_size);
+ return pos;
}
bool
@@ -300,7 +394,7 @@ uri_equal(struct uri u1, struct uri u2, uint32_t flags)
}
/* Check that query matches */
- if ((flags & URIEQ_QUERY_BIT) &&
+ if (!(flags & URIEQ_IGNORE_QUERY_BIT) &&
!str_equal(u1.query, u2.query, sizeof(u1.query)))
{
return false;
diff --git a/uri.h b/uri.h
@@ -91,14 +91,14 @@ struct uri uri_parse(const char *uristr, int uristr_len);
#define URISTR_NOQUERY_BIT 0x20 /* omit query */
/* Convert URI to string */
-size_t uri_str(const struct uri *const u,
- char *b,
+size_t uri_str(char *b,
+ const struct uri *const u,
size_t b_size,
uint32_t flags);
/* urieq flags */
#define URIEQ_IGNORE_TRAILING_SLASH_BIT 0x01
-#define URIEQ_QUERY_BIT 0x02
+#define URIEQ_IGNORE_QUERY_BIT 0x02
/* @return true if URIs are equal */
bool uri_equal(struct uri u1, struct uri u2, uint32_t flags);