Add url_resolve_reference to translare relative references to absolute URLs
This commit is contained in:
+9
-1
@@ -310,6 +310,7 @@ static int run_test(cJSON *json)
|
|||||||
}
|
}
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
char tmp[1<<10];
|
||||||
char buf[1<<10];
|
char buf[1<<10];
|
||||||
URL parsed;
|
URL parsed;
|
||||||
if (cJSON_IsNull(json_base)) {
|
if (cJSON_IsNull(json_base)) {
|
||||||
@@ -335,6 +336,13 @@ static int run_test(cJSON *json)
|
|||||||
}
|
}
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
|
ret = url_remove_white_space(src.ptr, src.len, tmp, (int) sizeof(tmp));
|
||||||
|
if (ret > (int) sizeof(tmp)) {
|
||||||
|
assert(0); // TODO
|
||||||
|
}
|
||||||
|
src.ptr = tmp;
|
||||||
|
src.len = ret;
|
||||||
|
|
||||||
ret = url_resolve_reference(src.ptr, src.len, NULL, &parsed_base, false, buf, (int) sizeof(buf));
|
ret = url_resolve_reference(src.ptr, src.len, NULL, &parsed_base, false, buf, (int) sizeof(buf));
|
||||||
if (ret >= (int) sizeof(buf)) {
|
if (ret >= (int) sizeof(buf)) {
|
||||||
test_printf("\n");
|
test_printf("\n");
|
||||||
@@ -440,7 +448,7 @@ static char *load_file(char *path)
|
|||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
char *path = "data/urltestdata.json";
|
char *path = "wpt/urltestdata.json";
|
||||||
char *buf = load_file(path);
|
char *buf = load_file(path);
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
fprintf(stderr, "Error: Couldn't open %s\n", path);
|
fprintf(stderr, "Error: Couldn't open %s\n", path);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <string.h>
|
||||||
#include "url.h"
|
#include "url.h"
|
||||||
|
|
||||||
// This is free and unencumbered software released into the public domain.
|
// This is free and unencumbered software released into the public domain.
|
||||||
@@ -44,8 +45,8 @@ static URL_b32 streq(URL_String a, URL_String b)
|
|||||||
return 0;
|
return 0;
|
||||||
for (int i = 0; i < a.len; i++)
|
for (int i = 0; i < a.len; i++)
|
||||||
if (a.ptr[i] != b.ptr[i])
|
if (a.ptr[i] != b.ptr[i])
|
||||||
return false;
|
return 0;
|
||||||
return true;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static URL_b32 is_alpha(char c)
|
static URL_b32 is_alpha(char c)
|
||||||
@@ -320,7 +321,7 @@ int url_parse_ipv6(char *src, int len, int *pcur, URL_IPv6 *out)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
static int parse(char *src, int len, int *pcur, URL_b32 strict, URL *out)
|
||||||
{
|
{
|
||||||
int cur = pcur ? *pcur : 0;
|
int cur = pcur ? *pcur : 0;
|
||||||
|
|
||||||
@@ -358,11 +359,12 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
// info or the domain until we hit a @ character or
|
// info or the domain until we hit a @ character or
|
||||||
// a character not allowed in the user info, se we
|
// a character not allowed in the user info, se we
|
||||||
// need to backtrack in a similar way to the scheme
|
// need to backtrack in a similar way to the scheme
|
||||||
URL_b32 authority = 0;
|
out->no_authority = 1;
|
||||||
|
out->no_userinfo = 1;
|
||||||
int authority_off = cur;
|
int authority_off = cur;
|
||||||
if (len - cur > 1 && src[cur] == '/' && src[cur+1] == '/') {
|
if (len - cur > 1 && src[cur] == '/' && src[cur+1] == '/') {
|
||||||
cur += 2; // Consume the //
|
cur += 2; // Consume the //
|
||||||
authority = 1;
|
out->no_authority = 0;
|
||||||
|
|
||||||
out->username = EMPTY;
|
out->username = EMPTY;
|
||||||
out->password = EMPTY;
|
out->password = EMPTY;
|
||||||
@@ -404,12 +406,14 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
if (cur == len || src[cur] != '@') {
|
if (cur == len || src[cur] != '@') {
|
||||||
cur = user_off; // Not userinfo after all
|
cur = user_off; // Not userinfo after all
|
||||||
} else {
|
} else {
|
||||||
|
out->no_userinfo = 0;
|
||||||
out->username = SLICE(src, user_off, pass_off-1);
|
out->username = SLICE(src, user_off, pass_off-1);
|
||||||
out->password = SLICE(src, pass_off, cur);
|
out->password = SLICE(src, pass_off, cur);
|
||||||
cur++; // Consume the '@'
|
cur++; // Consume the '@'
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (cur < len && src[cur] == '@') {
|
} else if (cur < len && src[cur] == '@') {
|
||||||
|
out->no_userinfo = 0;
|
||||||
out->username = SLICE(src, user_off, cur);
|
out->username = SLICE(src, user_off, cur);
|
||||||
out->password = EMPTY;
|
out->password = EMPTY;
|
||||||
cur++; // Consume the '@'
|
cur++; // Consume the '@'
|
||||||
@@ -446,7 +450,7 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
URL_b32 is_ipv4 = 0;
|
URL_b32 is_ipv4 = 0;
|
||||||
|
|
||||||
// First, try the IPv4 rule
|
// First, try the IPv4 rule
|
||||||
if (is_digit(src[cur])) {
|
if (cur < len && is_digit(src[cur])) {
|
||||||
if (url_parse_ipv4(src, len, &cur, &out->host_ipv4) == 0) {
|
if (url_parse_ipv4(src, len, &cur, &out->host_ipv4) == 0) {
|
||||||
out->host_type = URL_HOST_IPV4;
|
out->host_type = URL_HOST_IPV4;
|
||||||
is_ipv4 = 1;
|
is_ipv4 = 1;
|
||||||
@@ -474,10 +478,13 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
cur += 3;
|
cur += 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ASSERT(cur > 0);
|
||||||
|
|
||||||
// Registered names are not allowed to end with a dot,
|
// Registered names are not allowed to end with a dot,
|
||||||
// so unconsume the last character if it is one
|
// so unconsume the last character if it is one
|
||||||
if (cur > 0 && src[cur] == '.')
|
//
|
||||||
|
// TODO: What if the dot is percent-encoded?
|
||||||
|
if (src[cur-1] == '.')
|
||||||
cur--;
|
cur--;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@@ -512,6 +519,21 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
out->port = out->port * 10 + n;
|
out->port = out->port * 10 + n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The WHATWG standard considers the sequence "//"
|
||||||
|
// part of the path and not the authority if userinfo,
|
||||||
|
// host, and port are missing
|
||||||
|
if (!strict) {
|
||||||
|
if ((streq(out->scheme, S("http")) || streq(out->scheme, S("https")))
|
||||||
|
&& out->username.len == 0
|
||||||
|
&& out->password.len == 0
|
||||||
|
&& out->host_type == URL_HOST_EMPTY
|
||||||
|
&& out->no_port) {
|
||||||
|
out->no_authority = 1;
|
||||||
|
cur = authority_off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
out->username = EMPTY;
|
out->username = EMPTY;
|
||||||
out->password = EMPTY;
|
out->password = EMPTY;
|
||||||
@@ -521,27 +543,13 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
out->no_port = 1;
|
out->no_port = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The WHATWG standard considers the sequence "//"
|
|
||||||
// part of the path and not the authority if userinfo,
|
|
||||||
// host, and port are missing
|
|
||||||
if (!strict) {
|
|
||||||
if (authority
|
|
||||||
&& out->username.len == 0
|
|
||||||
&& out->password.len == 0
|
|
||||||
&& out->host_type == URL_HOST_EMPTY
|
|
||||||
&& out->no_port) {
|
|
||||||
authority = false;
|
|
||||||
cur = authority_off;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we parse the path. This is most difficult
|
// Now we parse the path. This is most difficult
|
||||||
// component as it changes based on what comes
|
// component as it changes based on what comes
|
||||||
// before it.
|
// before it.
|
||||||
//
|
//
|
||||||
// If an URL contains an authority component, it
|
// If an URL contains an authority component, it
|
||||||
// must be absolute or empty.
|
// must be absolute or empty.
|
||||||
if (authority && (cur == len || src[cur] != '/')) {
|
if (!out->no_authority && (cur == len || src[cur] != '/')) {
|
||||||
out->path = EMPTY;
|
out->path = EMPTY;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -636,3 +644,260 @@ int url_parse(char *src, int len, int *pcur, bool strict, URL *out)
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int url_parse(char *src, int len, int *pcur, URL_b32 strict, URL *out)
|
||||||
|
{
|
||||||
|
int ret = parse(src, len, pcur, strict, out);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
if (out->scheme.len == 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define PATH_COMPONENT_LIMIT 32
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int count;
|
||||||
|
URL_String stack[PATH_COMPONENT_LIMIT];
|
||||||
|
} PathComps;
|
||||||
|
|
||||||
|
static int
|
||||||
|
resolve_dots_and_append_comps(PathComps *comps, URL_String src)
|
||||||
|
{
|
||||||
|
URL_b32 is_absolute = 0;
|
||||||
|
if (src.len > 0 && src.ptr[0] == '/') {
|
||||||
|
is_absolute = 1;
|
||||||
|
src.ptr++;
|
||||||
|
src.len--;
|
||||||
|
if (src.len == 0)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
int off = i;
|
||||||
|
while (i < src.len && src.ptr[i] != '/')
|
||||||
|
i++;
|
||||||
|
int len = i - off;
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
return -1; // Empty component
|
||||||
|
|
||||||
|
URL_String comp = { src.ptr + off, len };
|
||||||
|
if (comp.len == 2 && comp.ptr[0] == '.' && comp.ptr[1] == '.') {
|
||||||
|
if (comps->count == 0) {
|
||||||
|
// For absolute paths, ".." at root is ignored (stays at root)
|
||||||
|
// For relative paths, ".." with no components references parent, which is invalid
|
||||||
|
if (!is_absolute)
|
||||||
|
return -1;
|
||||||
|
// Otherwise, ignore the ".." (absolute path, already at root)
|
||||||
|
} else {
|
||||||
|
comps->count--;
|
||||||
|
}
|
||||||
|
} else if (comp.len != 1 || comp.ptr[0] != '.') {
|
||||||
|
if (comps->count == PATH_COMPONENT_LIMIT)
|
||||||
|
return -1; // To many components
|
||||||
|
comps->stack[comps->count++] = comp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == src.len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ASSERT(src.ptr[i] == '/');
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i == src.len)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *dst;
|
||||||
|
int cap;
|
||||||
|
int len;
|
||||||
|
} Builder;
|
||||||
|
|
||||||
|
static void append(Builder *b, URL_String s)
|
||||||
|
{
|
||||||
|
int unused = b->cap - b->len;
|
||||||
|
if (unused > 0) {
|
||||||
|
int copy = s.len;
|
||||||
|
if (copy > unused)
|
||||||
|
copy = unused;
|
||||||
|
memcpy(b->dst + b->len, s.ptr, copy);
|
||||||
|
}
|
||||||
|
b->len += s.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_port(Builder *b, URL_u16 port)
|
||||||
|
{
|
||||||
|
char buf[sizeof("65536")-1];
|
||||||
|
URL_u16 magn = 10000;
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
buf[i] = '0' + (port / magn);
|
||||||
|
port %= magn;
|
||||||
|
magn /= 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading zeros
|
||||||
|
char *ptr = buf;
|
||||||
|
while (ptr < buf + sizeof(buf)-1 && *ptr == '0')
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
append(b, (URL_String) { ptr, 5 - (ptr - buf) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_authority(Builder *b, URL url)
|
||||||
|
{
|
||||||
|
if (!url.no_authority) {
|
||||||
|
append(b, S("//"));
|
||||||
|
if (!url.no_userinfo) {
|
||||||
|
append(b, url.username);
|
||||||
|
append(b, S(":"));
|
||||||
|
append(b, url.password);
|
||||||
|
append(b, S("@"));
|
||||||
|
}
|
||||||
|
append(b, url.host_text);
|
||||||
|
if (!url.no_port) {
|
||||||
|
append(b, S(":"));
|
||||||
|
append_port(b, url.port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int url_resolve_reference(char *src, int len, int *pcur,
|
||||||
|
URL *base, URL_b32 strict, char *dst, int cap)
|
||||||
|
{
|
||||||
|
if (base != NULL && base->scheme.len == 0)
|
||||||
|
return -1; // Base is not an absolute URL
|
||||||
|
|
||||||
|
URL ref;
|
||||||
|
int ret = parse(src, len, pcur, strict, &ref);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
Builder b = { dst, cap, 0 };
|
||||||
|
if (ref.scheme.len > 0) {
|
||||||
|
append(&b, ref.scheme);
|
||||||
|
append(&b, S(":"));
|
||||||
|
append_authority(&b, ref);
|
||||||
|
append(&b, ref.path);
|
||||||
|
if (ref.query.len > 0) {
|
||||||
|
append(&b, S("?"));
|
||||||
|
append(&b, ref.query);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (base == NULL) {
|
||||||
|
// No base URL provided, which means the
|
||||||
|
// source is required to be an absolute URL
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT(base->scheme.len > 0);
|
||||||
|
append(&b, base->scheme);
|
||||||
|
append(&b, S(":"));
|
||||||
|
if (!ref.no_authority) {
|
||||||
|
|
||||||
|
append_authority(&b, ref);
|
||||||
|
|
||||||
|
PathComps comps = {0};
|
||||||
|
if (resolve_dots_and_append_comps(&comps, ref.path) < 0)
|
||||||
|
return -1;
|
||||||
|
for (int i = 0; i < comps.count; i++) {
|
||||||
|
append(&b, S("/"));
|
||||||
|
append(&b, comps.stack[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.query.len > 0) {
|
||||||
|
append(&b, S("?"));
|
||||||
|
append(&b, ref.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
append_authority(&b, *base);
|
||||||
|
|
||||||
|
if (ref.path.len == 0) {
|
||||||
|
append(&b, base->path);
|
||||||
|
if (ref.query.len > 0) {
|
||||||
|
append(&b, S("?"));
|
||||||
|
append(&b, ref.query);
|
||||||
|
} else if (base->query.len > 0) {
|
||||||
|
append(&b, S("?"));
|
||||||
|
append(&b, base->query);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ASSERT(ref.path.len > 0);
|
||||||
|
PathComps comps = {0};
|
||||||
|
if (ref.path.ptr[0] == '/') {
|
||||||
|
if (resolve_dots_and_append_comps(&comps, ref.path) < 0)
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (!base->no_authority && base->path.len == 0) {
|
||||||
|
if (resolve_dots_and_append_comps(&comps, ref.path) < 0)
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
if (resolve_dots_and_append_comps(&comps, base->path) < 0)
|
||||||
|
return -1;
|
||||||
|
if (comps.count > 0) {
|
||||||
|
comps.count--;
|
||||||
|
}
|
||||||
|
if (resolve_dots_and_append_comps(&comps, ref.path) < 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < comps.count; i++) {
|
||||||
|
append(&b, S("/"));
|
||||||
|
append(&b, comps.stack[i]);
|
||||||
|
}
|
||||||
|
if (ref.query.len > 0) {
|
||||||
|
append(&b, S("?"));
|
||||||
|
append(&b, ref.query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.fragment.len > 0) {
|
||||||
|
append(&b, S("#"));
|
||||||
|
append(&b, ref.fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static URL_b32 is_white_space(char c)
|
||||||
|
{
|
||||||
|
return c == ' '
|
||||||
|
|| c == '\n'
|
||||||
|
|| c == '\t'
|
||||||
|
|| c == '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
int url_remove_white_space(char *src, int len, char *dst, int cap)
|
||||||
|
{
|
||||||
|
while (len > 0 && is_white_space(src[0])) {
|
||||||
|
src++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (len > 0 && is_white_space(src[len-1]))
|
||||||
|
len--;
|
||||||
|
|
||||||
|
int copied = 0;
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if (!is_white_space(src[i]) || src[i] == ' ') {
|
||||||
|
if (copied < cap)
|
||||||
|
dst[copied] = src[i];
|
||||||
|
copied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ URL_STATIC_ASSERT(sizeof(URL_IPv6) == 16, "");
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
||||||
|
URL_b32 no_authority;
|
||||||
|
URL_b32 no_userinfo;
|
||||||
|
|
||||||
// May be empty
|
// May be empty
|
||||||
URL_String scheme;
|
URL_String scheme;
|
||||||
|
|
||||||
@@ -97,8 +100,14 @@ typedef struct {
|
|||||||
|
|
||||||
} URL;
|
} URL;
|
||||||
|
|
||||||
int url_parse(char *src, int len, int *pcur, bool strict, URL *out);
|
|
||||||
int url_parse_ipv4(char *src, int len, int *pcur, URL_IPv4 *out);
|
int url_parse_ipv4(char *src, int len, int *pcur, URL_IPv4 *out);
|
||||||
int url_parse_ipv6(char *src, int len, int *pcur, URL_IPv6 *out);
|
int url_parse_ipv6(char *src, int len, int *pcur, URL_IPv6 *out);
|
||||||
|
|
||||||
|
int url_parse(char *src, int len, int *pcur, URL_b32 strict, URL *out);
|
||||||
|
|
||||||
|
int url_resolve_reference(char *src, int len, int *pcur,
|
||||||
|
URL *base, URL_b32 strict, char *dst, int cap);
|
||||||
|
|
||||||
|
int url_remove_white_space(char *src, int len, char *dst, int cap);
|
||||||
|
|
||||||
#endif // URL_INCLUDED
|
#endif // URL_INCLUDED
|
||||||
|
|||||||
Reference in New Issue
Block a user