From bd8d5d2a52bf565ee80e6a63153d63c0d9db02d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Wed, 17 Jun 2026 06:30:51 +0200 Subject: [PATCH] Extend the isc_url_t unit test with RFC 3986 cases 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 (cherry picked from commit 220a27093098884d2dbb5c16ecfbfaa625bf1e56) --- tests/isc/url_test.c | 137 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 6 deletions(-) diff --git a/tests/isc/url_test.c b/tests/isc/url_test.c index 50d8a481cd..c2b229e8d8 100644 --- a/tests/isc/url_test.c +++ b/tests/isc/url_test.c @@ -31,6 +31,26 @@ #include +/* + * 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 }; @@ -38,7 +58,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. */ @@ -60,20 +80,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(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(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(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(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