]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Extend the isc_url_t unit test with RFC 3986 cases
authorOndřej Surý <ondrej@isc.org>
Wed, 17 Jun 2026 04:30:51 +0000 (06:30 +0200)
committerArаm Sаrgsyаn <aram@isc.org>
Tue, 7 Jul 2026 10:08:04 +0000 (10:08 +0000)
Parse the absolute URIs from RFC 3986 section 5.4 and verify the
component split, and assert the input-length boundary (UINT16_MAX is
accepted, UINT16_MAX + 1 is rejected). isc_url_parse() splits request
targets but does not resolve relative references, so the dot-segments
in path/query/fragment are expected to survive verbatim.

Assisted-by: Claude:claude-opus-4-8
tests/isc/url_test.c

index 5c261f063a5297cfe91b7283de69cd5b718f645d..bff195f9221e7c8dd57c0e456fd4df342c1fc4b5 100644 (file)
 
 #include <tests/isc.h>
 
+/*
+ * Extract the substring of component 'uf' from 'buf' and compare it with
+ * 'expected'. A field that is not set is treated as the empty string.
+ */
+static void
+check_field(const isc_url_parser_t *up, const char *buf, isc_url_field_t uf,
+           const char *expected) {
+       char got[256] = { 0 };
+
+       if ((up->field_set & (1 << uf)) != 0) {
+               uint16_t off = up->field_data[uf].off;
+               uint16_t len = up->field_data[uf].len;
+
+               INSIST(len < sizeof(got));
+               memmove(got, buf + off, len);
+       }
+
+       assert_string_equal(got, expected);
+}
+
 ISC_RUN_TEST_IMPL(parse) {
        isc_result_t result;
        isc_url_parser_t up = { 0 };
@@ -39,7 +59,7 @@ ISC_RUN_TEST_IMPL(parse) {
 
        /* Test an empty buffer. */
        url = "";
-       result = isc_url_parse(url, strlen(url), 0, &up);
+       result = isc_url_parse(url, strlen(url), false, &up);
        assert_int_equal(ISC_R_RANGE, result);
 
        /* Test a buffer with a valid URI. */
@@ -61,20 +81,125 @@ ISC_RUN_TEST_IMPL(parse) {
        assert_int_equal(7, up.field_data[ISC_UF_USERINFO].off);
        assert_int_equal(9, up.field_data[ISC_UF_USERINFO].len);
 
-       /* Test a too big buffer. */
-       url = "https://localhost/";
+       /*
+        * Test the maximum accepted buffer length (UINT16_MAX). A path of
+        * exactly UINT16_MAX bytes also drives ISC_UF_PATH.len to its
+        * uint16_t maximum without overflowing.
+        */
+       size_t max_len = UINT16_MAX;
+       char *max_buf = isc_mem_get(isc_g_mctx, max_len);
+       memset(max_buf, 'a', max_len);
+       max_buf[0] = '/';
+       result = isc_url_parse(max_buf, max_len, false, &up);
+       isc_mem_put(isc_g_mctx, max_buf, max_len);
+       assert_int_equal(ISC_R_SUCCESS, result);
+       assert_int_equal(0, up.field_data[ISC_UF_PATH].off);
+       assert_int_equal(UINT16_MAX, up.field_data[ISC_UF_PATH].len);
+
+       /* Test a too big buffer (UINT16_MAX + 1). */
        size_t buf_len = UINT16_MAX + 2;
        char *buf = isc_mem_get(isc_g_mctx, buf_len);
-       snprintf(buf, buf_len - 1, "%-65535s", url); /* Pad with spaces */
-       buf[buf_len - 1] = '\0'; /* Ensure ending with Null-byte */
-       result = isc_url_parse(buf, buf_len - 1, 0, &up);
+       memset(buf, 'a', buf_len);
+       buf[0] = '/';
+       result = isc_url_parse(buf, UINT16_MAX + 1, false, &up);
        isc_mem_put(isc_g_mctx, buf, buf_len);
        assert_int_equal(ISC_R_RANGE, result);
 }
 
+/*
+ * isc_url_parse() splits an absolute URI / request target into components;
+ * it does NOT resolve relative references or remove dot-segments
+ * (RFC 3986 section 5). The inputs below are the base URI and a
+ * representative subset of the resolved targets from RFC 3986 section 5.4,
+ * parsed as standalone absolute URIs. Note in particular that '.'/'..' and
+ * embedded slashes inside the query and fragment are preserved verbatim
+ * rather than normalized.
+ */
+ISC_RUN_TEST_IMPL(parse_rfc3986) {
+       static const struct {
+               const char *uri;
+               isc_result_t result;
+               /* Expected components (when result is ISC_R_SUCCESS). */
+               const char *scheme;
+               const char *userinfo;
+               const char *host;
+               const char *port;
+               const char *path;
+               const char *query;
+               const char *fragment;
+       } testcases[] = {
+               /* The base URI used throughout RFC 3986 section 5.4. */
+               { "http://a/b/c/d;p?q", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/d;p", "q", "" },
+
+               /* Normal examples (section 5.4.1). */
+               { "http://a/b/c/g", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/g", "", "" },
+               { "http://a/g", ISC_R_SUCCESS, "http", "", "a", "", "/g", "",
+                 "" },
+               { "http://g", ISC_R_SUCCESS, "http", "", "g", "", "", "", "" },
+               { "http://a/", ISC_R_SUCCESS, "http", "", "a", "", "/", "",
+                 "" },
+               { "http://a/b/c/;x", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/;x", "", "" },
+               { "http://a/b/c/g;x?y#s", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/g;x", "y", "s" },
+               { "http://a/b/c/d;p?q#s", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/d;p", "q", "s" },
+
+               /*
+                * Abnormal examples (section 5.4.2): dot-segments and slashes
+                * embedded in the query and fragment are not normalized.
+                */
+               { "http://a/b/c/..g", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/..g", "", "" },
+               { "http://a/b/c/g..", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/g..", "", "" },
+               { "http://a/b/c/g?y/./x", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/g", "y/./x", "" },
+               { "http://a/b/c/g#s/../x", ISC_R_SUCCESS, "http", "", "a", "",
+                 "/b/c/g", "", "s/../x" },
+
+               /* The full generic-syntax example from RFC 3986 section 3. */
+               { "foo://example.com:8042/over/there?name=ferret#nose",
+                 ISC_R_SUCCESS, "foo", "", "example.com", "8042",
+                 "/over/there", "name=ferret", "nose" },
+
+               /*
+                * Relative references cannot be parsed as request targets:
+                * a scheme requires an authority, and a bare reference has no
+                * host.
+                */
+               { "g:h", ISC_R_FAILURE },
+               { "g", ISC_R_FAILURE },
+               { "http:g", ISC_R_FAILURE },
+       };
+
+       for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
+               isc_url_parser_t up = { 0 };
+               const char *uri = testcases[i].uri;
+               isc_result_t result = isc_url_parse(uri, strlen(uri), false,
+                                                   &up);
+
+               assert_int_equal(result, testcases[i].result);
+               if (result != ISC_R_SUCCESS) {
+                       continue;
+               }
+
+               check_field(&up, uri, ISC_UF_SCHEMA, testcases[i].scheme);
+               check_field(&up, uri, ISC_UF_USERINFO, testcases[i].userinfo);
+               check_field(&up, uri, ISC_UF_HOST, testcases[i].host);
+               check_field(&up, uri, ISC_UF_PORT, testcases[i].port);
+               check_field(&up, uri, ISC_UF_PATH, testcases[i].path);
+               check_field(&up, uri, ISC_UF_QUERY, testcases[i].query);
+               check_field(&up, uri, ISC_UF_FRAGMENT, testcases[i].fragment);
+       }
+}
+
 ISC_TEST_LIST_START
 
 ISC_TEST_ENTRY(parse)
+ISC_TEST_ENTRY(parse_rfc3986)
 
 ISC_TEST_LIST_END