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 220a270930)
This commit is contained in:
Ondřej Surý 2026-06-17 06:30:51 +02:00 committed by Aram Sargsyan
parent bf58c9d9d8
commit bd8d5d2a52

View file

@ -31,6 +31,26 @@
#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 };
@ -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