Compare commits
7
Commits
url_builder
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e87cbce27 | ||
|
|
389fe5f2f5 | ||
|
|
880236a2b0 | ||
|
|
0182ee2beb | ||
|
|
c12c4c7bbf | ||
|
|
ee4883774e | ||
|
|
c2ffe57e21 |
@@ -1,5 +1,5 @@
|
||||
# url.c
|
||||
This is a small library to parse and manipulate URLs in conformance to RFC 3986 and WHATWG.
|
||||
This is a small library to parse and manipulate URLs in conformance to RFC 3986 and (most of) the WHATWG specification.
|
||||
|
||||
It features
|
||||
* No allocations
|
||||
@@ -15,6 +15,4 @@ You just copy paste `url.h` and `url.c` in your source tree and compile them as
|
||||
Some example programs are provided in the `examples/` folder. To compile them, run `build.bat` on Windows or `build.sh` on Linux.
|
||||
|
||||
# Testing
|
||||
The parser and reference resolver is tested against the URL section of the [web platform tests](https://github.com/web-platform-tests/wpt). Note that not all tests pass. This is due to the parser not doing any normalization and not supporting more niche features such as converting `\` characters to `/` or supporting drive letters like `C:` in the URL's path.
|
||||
|
||||
To run the test suite, `cd` into `tests/` and compile the test runner using `build.sh` (Linux) or `build.bat` (Windows). Then, run `./test.out` (Linux) or `./test.exe` (Windows).
|
||||
The parser and reference resolver is tested against the URL section of the [web platform tests](https://github.com/web-platform-tests/wpt), which validates implementations that adhere to the WHATWG specification (what the major browsers actually do). To run the test suite, `cd` into `tests/` and compile the test runner using `build.sh` (Linux) or `build.bat` (Windows). Then, run `./test.out` (Linux) or `./test.exe` (Windows). Note that not all tests pass, which is to be expected as adherence to the WHATWG specification is a work-in-progress.
|
||||
|
||||
+76
-34
@@ -31,6 +31,10 @@ void test_printf(char *fmt, ...)
|
||||
sizeof(test_output_buffer) - test_output_buffer_used,
|
||||
fmt, args);
|
||||
va_end(args);
|
||||
|
||||
assert(len > -1);
|
||||
if (len > (int) sizeof(test_output_buffer) - test_output_buffer_used)
|
||||
len = sizeof(test_output_buffer) - test_output_buffer_used;
|
||||
test_output_buffer_used += len;
|
||||
}
|
||||
|
||||
@@ -198,15 +202,21 @@ static int json_to_url(cJSON *json, URL *url)
|
||||
url->fragment.len--;
|
||||
}
|
||||
|
||||
char tmp[8];
|
||||
if (port.len >= (int) sizeof(tmp)) {
|
||||
test_printf(" JSON object's port field is longer than expected\n");
|
||||
return -1;
|
||||
}
|
||||
memcpy(tmp, port.ptr, port.len);
|
||||
tmp[port.len] = 0;
|
||||
if (port.len == 0) {
|
||||
url->no_port = 1;
|
||||
url->port = 0;
|
||||
} else {
|
||||
char tmp[8];
|
||||
if (port.len >= (int) sizeof(tmp)) {
|
||||
test_printf(" JSON object's port field is longer than expected\n");
|
||||
return -1;
|
||||
}
|
||||
memcpy(tmp, port.ptr, port.len);
|
||||
tmp[port.len] = 0;
|
||||
|
||||
url->port = atoi(tmp);
|
||||
url->no_port = 0;
|
||||
url->port = atoi(tmp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -217,73 +227,103 @@ static bool streq(URL_String a, URL_String b)
|
||||
return !memcmp(a.ptr, b.ptr, a.len);
|
||||
}
|
||||
|
||||
static int compare_urls(URL url_1, URL url_2, char *label_1, char *label_2)
|
||||
static int compare_urls(URL parsed, URL expected)
|
||||
{
|
||||
#define UNPACK(s) (s).len, (s).ptr
|
||||
|
||||
bool ok = true;
|
||||
|
||||
if (!streq(url_1.scheme, url_2.scheme)) {
|
||||
if (!streq(parsed.scheme, expected.scheme)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" scheme mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.scheme));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.scheme));
|
||||
test_printf(" parsed [%.*s]\n", UNPACK(parsed.scheme));
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.scheme));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.username, url_2.username)) {
|
||||
char tmp[1<<9];
|
||||
int ret = url_percent_decode(parsed.username, tmp, sizeof(tmp));
|
||||
assert(ret > -1 && ret < (int) sizeof(tmp));
|
||||
|
||||
if (!streq((URL_String) { tmp, ret }, expected.username)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" username mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.username));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.username));
|
||||
test_printf(" parsed [%.*s]\n", ret, tmp);
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.username));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.password, url_2.password)) {
|
||||
ret = url_percent_decode(parsed.password, tmp, sizeof(tmp));
|
||||
assert(ret > -1 && ret < (int) sizeof(tmp));
|
||||
|
||||
if (!streq((URL_String) { tmp, ret }, expected.password)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" password mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.password));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.password));
|
||||
test_printf(" parsed [%.*s]\n", ret, tmp);
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.password));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.host_text, url_2.host_text)) {
|
||||
ret = url_percent_decode(parsed.host_text, tmp, sizeof(tmp));
|
||||
assert(ret > -1 && ret < (int) sizeof(tmp));
|
||||
|
||||
if (!streq((URL_String) { tmp, ret }, expected.host_text)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" host mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.host_text));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.host_text));
|
||||
test_printf(" parsed [%.*s]\n", ret, tmp);
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.host_text));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (url_1.port != url_2.port) {
|
||||
if (parsed.no_port != expected.no_port) {
|
||||
if (ok) test_printf("\n");
|
||||
if (parsed.no_port) {
|
||||
test_printf(" port mismatch\n");
|
||||
test_printf(" parsed (empty)\n");
|
||||
test_printf(" expected [%d]\n", expected.port);
|
||||
} else {
|
||||
test_printf(" port mismatch\n");
|
||||
test_printf(" parsed [%d]\n", parsed.port);
|
||||
test_printf(" expected (empty)\n");
|
||||
}
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (parsed.port != expected.port) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" port mismatch\n");
|
||||
test_printf(" %s [%d]\n", label_1, url_1.port);
|
||||
test_printf(" %s [%d]\n", label_2, url_2.port);
|
||||
test_printf(" parsed [%d]\n", parsed.port);
|
||||
test_printf(" expected [%d]\n", expected.port);
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.path, url_2.path)) {
|
||||
if (!streq(parsed.path, expected.path)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" path mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.path));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.path));
|
||||
test_printf(" parsed [%.*s]\n", UNPACK(parsed.path));
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.path));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.query, url_2.query)) {
|
||||
ret = url_percent_decode(parsed.query, tmp, sizeof(tmp));
|
||||
assert(ret > -1 && ret < (int) sizeof(tmp));
|
||||
|
||||
if (!streq((URL_String) { tmp, ret }, expected.query)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" query mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.query));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.query));
|
||||
test_printf(" parsed [%.*s]\n", ret, tmp);
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.query));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
if (!streq(url_1.fragment, url_2.fragment)) {
|
||||
ret = url_percent_decode(parsed.fragment, tmp, sizeof(tmp));
|
||||
assert(ret > -1 && ret < (int) sizeof(tmp));
|
||||
|
||||
if (!streq((URL_String) { tmp, ret }, expected.fragment)) {
|
||||
if (ok) test_printf("\n");
|
||||
test_printf(" fragment mismatch\n");
|
||||
test_printf(" %s [%.*s]\n", label_1, UNPACK(url_1.fragment));
|
||||
test_printf(" %s [%.*s]\n", label_2, UNPACK(url_2.fragment));
|
||||
test_printf(" parsed [%.*s]\n", ret, tmp);
|
||||
test_printf(" expected [%.*s]\n", UNPACK(expected.fragment));
|
||||
ok = false;
|
||||
}
|
||||
|
||||
@@ -394,7 +434,7 @@ static int run_test(cJSON *json)
|
||||
if (json_to_url(json, &expected) < 0)
|
||||
return -1;
|
||||
|
||||
if (!compare_urls(parsed, expected, "parsed", "expected")) {
|
||||
if (!compare_urls(parsed, expected)) {
|
||||
test_printf(" input: ");
|
||||
test_print_str(src);
|
||||
test_printf("\n");
|
||||
@@ -472,6 +512,7 @@ int main(void)
|
||||
|
||||
if (!cJSON_IsArray(root)) {
|
||||
fprintf(stderr, "Error: %s doesn't contain a JSON array as expected", path);
|
||||
cJSON_Delete(root);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
@@ -524,6 +565,7 @@ int main(void)
|
||||
printf(" failed : %d\n", num_failed);
|
||||
printf(" aborted: %d\n", num_aborted);
|
||||
|
||||
cJSON_Delete(root);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -524,9 +524,20 @@ int url_parse(char *src, int len, int *pcur, URL *out, int flags)
|
||||
// First, try the IPv4 rule
|
||||
if (cur < len && is_digit(src[cur])) {
|
||||
if (url_parse_ipv4(src, len, &cur, &out->host_ipv4) == 0) {
|
||||
out->host_type = URL_HOST_IPV4;
|
||||
is_ipv4 = 1;
|
||||
// If we managed to parse an IPv4 address but the
|
||||
// following character si a valid character for a
|
||||
// registered name, then it wasn't an IPv4 after
|
||||
// all. For instance this should not be parsed as
|
||||
// an IPv4:
|
||||
// 127.0.0.1.com
|
||||
if (cur == len || !is_reg_name(src[cur])) {
|
||||
out->host_type = URL_HOST_IPV4;
|
||||
is_ipv4 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_ipv4)
|
||||
cur = host_off;
|
||||
}
|
||||
|
||||
if (!is_ipv4) {
|
||||
@@ -569,25 +580,26 @@ int url_parse(char *src, int len, int *pcur, URL *out, int flags)
|
||||
out->no_port = 1;
|
||||
out->port = 0;
|
||||
if (cur < len && src[cur] == ':') {
|
||||
|
||||
// The WHATWG standard forbids port numbers with the
|
||||
// "file" protocol.
|
||||
if ((flags & URL_FLAG_RFC3986) == 0) {
|
||||
if (streqcase(out->scheme, S("file")))
|
||||
return -1;
|
||||
}
|
||||
|
||||
cur++; // Consume the ':'
|
||||
out->no_port = 0;
|
||||
|
||||
while (cur < len && is_digit(src[cur])) {
|
||||
if (cur < len && is_digit(src[cur])) {
|
||||
|
||||
int n = src[cur] - '0';
|
||||
cur++;
|
||||
// The WHATWG standard forbids port numbers with the
|
||||
// "file" protocol.
|
||||
if ((flags & URL_FLAG_RFC3986) == 0) {
|
||||
if (streqcase(out->scheme, S("file")))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out->port > (UINT16_MAX - n) / 10)
|
||||
return -1; // Overflow
|
||||
out->port = out->port * 10 + n;
|
||||
out->no_port = 0;
|
||||
do {
|
||||
int n = src[cur] - '0';
|
||||
cur++;
|
||||
|
||||
if (out->port > (UINT16_MAX - n) / 10)
|
||||
return -1; // Overflow
|
||||
out->port = out->port * 10 + n;
|
||||
} while (cur < len && is_digit(src[cur]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,6 +857,8 @@ static void append_fragment(Builder *b, URL_String fragment)
|
||||
|
||||
int url_serialize(URL url, URL *base, char *dst, int cap)
|
||||
{
|
||||
ASSERT(cap == 0 || dst != NULL);
|
||||
|
||||
if (base != NULL && base->scheme.len == 0)
|
||||
return -1; // Base is not an absolute URL
|
||||
|
||||
@@ -948,6 +962,8 @@ static URL_b32 is_white_space(char c)
|
||||
|
||||
int url_remove_white_space(char *src, int len, char *dst, int cap)
|
||||
{
|
||||
ASSERT(cap == 0 || dst != NULL);
|
||||
|
||||
while (len > 0 && is_white_space(src[0])) {
|
||||
src++;
|
||||
len--;
|
||||
@@ -970,6 +986,8 @@ int url_remove_white_space(char *src, int len, char *dst, int cap)
|
||||
|
||||
int url_percent_decode(URL_String str, char *dst, int cap)
|
||||
{
|
||||
ASSERT(cap == 0 || dst != NULL);
|
||||
|
||||
char *src = str.ptr;
|
||||
int len = str.len;
|
||||
int rd = 0;
|
||||
|
||||
Reference in New Issue
Block a user