A gopher client for your terminal.
git clone git://git.skec.site/pub/sr71.git
log | files | refs | readme | license

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:
Mstr.h | 2+-
Mtest.c | 463++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Muri.c | 138++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Muri.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);