Page MenuHomePhorge

url.c
No OneTemporary

Size
57 KB
Referenced Files
None
Subscribers
None
#include <compat.h> // IWYU pragma: keep
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <uriparser/Uri.h>
#include "ant.h"
#include "errors.h"
#include "internal.h"
#include "runtime.h"
#include "descriptors.h"
#include "silver/engine.h"
#include "modules/url.h"
#include "modules/symbol.h"
static ant_value_t g_url_proto = 0;
static ant_value_t g_usp_proto = 0;
static ant_value_t g_usp_iter_proto = 0;
enum {
USP_ITER_ENTRIES = 0,
USP_ITER_KEYS = 1,
USP_ITER_VALUES = 2
};
url_state_t *url_get_state(ant_value_t obj) {
ant_value_t slot = js_get_slot(obj, SLOT_DATA);
if (vtype(slot) != T_NUM) return NULL;
return (url_state_t *)(uintptr_t)(size_t)js_getnum(slot);
}
bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj) {
return js_check_brand(obj, BRAND_URLSEARCHPARAMS);
}
void url_state_clear(url_state_t *s) {
free(s->protocol); free(s->username); free(s->password);
free(s->hostname); free(s->port); free(s->pathname);
free(s->search); free(s->hash);
}
void url_free_state(url_state_t *s) {
if (!s) return;
url_state_clear(s);
free(s);
}
static void url_finalize(ant_t *js, ant_object_t *obj) {
if (!obj->extra_slots) return;
ant_extra_slot_t *slots = (ant_extra_slot_t *)obj->extra_slots;
for (uint8_t i = 0; i < obj->extra_count; i++) {
if (slots[i].slot == SLOT_DATA && vtype(slots[i].value) == T_NUM) {
url_free_state((url_state_t *)(uintptr_t)(size_t)js_getnum(slots[i].value));
return;
}}
}
static int default_port_for(const char *proto) {
if (!proto) return -1;
if (strcmp(proto, "http:") == 0 || strcmp(proto, "ws:") == 0) return 80;
if (strcmp(proto, "https:") == 0 || strcmp(proto, "wss:") == 0) return 443;
if (strcmp(proto, "ftp:") == 0) return 21;
return -1;
}
static bool is_special_scheme(const char *proto) {
if (!proto) return false;
return
strcmp(proto, "http:") == 0 || strcmp(proto, "https:") == 0 ||
strcmp(proto, "ftp:") == 0 || strcmp(proto, "ws:") == 0 ||
strcmp(proto, "wss:") == 0;
}
static bool uses_authority_syntax(const char *proto) {
if (!proto) return false;
return is_special_scheme(proto) || strcmp(proto, "file:") == 0;
}
static bool url_base_is_opaque(const char *base_str, const char *proto) {
const char *after_colon = NULL;
if (!base_str || is_special_scheme(proto)) return false;
after_colon = strchr(base_str, ':');
if (!after_colon) return false;
after_colon++;
return *after_colon != '/' && *after_colon != '\0';
}
char *form_urlencode_n(const char *str, size_t len) {
if (!str) return strdup("");
char *out = malloc(len * 3 + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (isalnum(c) || c == '*' || c == '-' || c == '.' || c == '_') out[j++] = (char)c;
else if (c == ' ') out[j++] = '+';
else {
snprintf(out + j, 4, "%%%02X", c);
j += 3;
}}
out[j] = '\0';
return out;
}
char *form_urlencode(const char *str) {
if (!str) return strdup("");
return form_urlencode_n(str, strlen(str));
}
char *form_urldecode(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
if (str[i] == '+') out[j++] = ' ';
else if (
str[i] == '%' && i + 2 < len &&
isxdigit((unsigned char)str[i+1]) &&
isxdigit((unsigned char)str[i+2])
) {
int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
out[j++] = (char)((hi << 4) | lo);
i += 2;
} else out[j++] = str[i];
}
out[j] = '\0';
return out;
}
char *url_decode_component(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
if (
str[i] == '%' && i + 2 < len &&
isxdigit((unsigned char)str[i+1]) &&
isxdigit((unsigned char)str[i+2])
) {
int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
out[j++] = (char)((hi << 4) | lo);
i += 2;
} else out[j++] = str[i];
}
out[j] = '\0';
return out;
}
static char *userinfo_encode(const char *str) {
if (!str) return strdup("");
size_t len = strlen(str);
char *out = malloc(len * 3 + 1);
if (!out) return strdup("");
size_t j = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)str[i];
if (
isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~' ||
c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
c == '=' || c == ':'
) out[j++] = (char)c; else { snprintf(out + j, 4, "%%%02X", c); j += 3; }
}
out[j] = '\0';
return out;
}
static char *uri_range_dup(const UriTextRangeA *r) {
if (!r->first || !r->afterLast) return strdup("");
return strndup(r->first, (size_t)(r->afterLast - r->first));
}
static bool url_has_brackets_in_query_or_fragment(const char *url_str) {
size_t len = strlen(url_str);
bool in_query = false;
bool in_fragment = false;
for (size_t i = 0; i < len; i++) {
char c = url_str[i];
if (c == '#' && !in_fragment) {
in_query = false;
in_fragment = true;
continue;
}
if (c == '?' && !in_query && !in_fragment) {
in_query = true;
continue;
}
if ((in_query || in_fragment) && (c == '[' || c == ']')) return true;
}
return false;
}
static char *url_escape_brackets_in_query_or_fragment(const char *url_str) {
size_t len = strlen(url_str);
size_t extra = 0;
bool in_query = false;
bool in_fragment = false;
for (size_t i = 0; i < len; i++) {
char c = url_str[i];
if (c == '#' && !in_fragment) {
in_query = false;
in_fragment = true;
continue;
}
if (c == '?' && !in_query && !in_fragment) {
in_query = true;
continue;
}
if ((in_query || in_fragment) && (c == '[' || c == ']')) extra += 2;
}
char *escaped = malloc(len + extra + 1);
size_t pos = 0;
in_query = false;
in_fragment = false;
if (!escaped) return NULL;
for (size_t i = 0; i < len; i++) {
char c = url_str[i];
if (c == '#' && !in_fragment) {
in_query = false;
in_fragment = true;
escaped[pos++] = c;
continue;
}
if (c == '?' && !in_query && !in_fragment) {
in_query = true;
escaped[pos++] = c;
continue;
}
if ((in_query || in_fragment) && c == '[') {
memcpy(escaped + pos, "%5B", 3);
pos += 3;
continue;
}
if ((in_query || in_fragment) && c == ']') {
memcpy(escaped + pos, "%5D", 3);
pos += 3;
continue;
}
escaped[pos++] = c;
}
escaped[pos] = '\0';
return escaped;
}
static int url_parse_single_uri_relaxed(
UriUriA *uri,
const char *url_str,
const char **errpos,
char **owned_input_out,
bool *used_relaxed_out
) {
char *escaped = NULL;
if (owned_input_out) *owned_input_out = NULL;
if (used_relaxed_out) *used_relaxed_out = false;
if (uriParseSingleUriA(uri, url_str, errpos) == URI_SUCCESS) return 0;
if (!url_has_brackets_in_query_or_fragment(url_str)) return -1;
escaped = url_escape_brackets_in_query_or_fragment(url_str);
if (!escaped) return -1;
if (owned_input_out) *owned_input_out = escaped;
if (used_relaxed_out) *used_relaxed_out = true;
return uriParseSingleUriA(uri, escaped, errpos) == URI_SUCCESS ? 0 : -1;
}
static void url_override_search_hash_from_input(url_state_t *s, const char *url_str) {
const char *hash = strchr(url_str, '#');
const char *query = strchr(url_str, '?');
size_t search_len = 0;
size_t hash_len = 0;
if (query && hash && hash < query) query = NULL;
if (query) {
const char *search_end = hash && hash > query ? hash : url_str + strlen(url_str);
search_len = (size_t)(search_end - query);
}
if (hash) hash_len = strlen(hash);
free(s->search);
s->search = search_len > 0 ? strndup(query, search_len) : strdup("");
free(s->hash);
s->hash = hash_len > 0 ? strndup(hash, hash_len) : strdup("");
}
static void url_override_search_hash_from_reference(url_state_t *s, const char *url_str) {
const char *hash = strchr(url_str, '#');
const char *query = strchr(url_str, '?');
size_t search_len = 0;
size_t hash_len = 0;
if (query && hash && hash < query) query = NULL;
if (query) {
const char *search_end = hash && hash > query ? hash : url_str + strlen(url_str);
search_len = (size_t)(search_end - query);
free(s->search);
s->search = strndup(query, search_len);
}
if (hash) {
hash_len = strlen(hash);
free(s->hash);
s->hash = strndup(hash, hash_len);
}
}
static void uri_to_state(const UriUriA *uri, url_state_t *s) {
char *scheme = uri_range_dup(&uri->scheme);
size_t slen = strlen(scheme);
for (size_t i = 0; i < slen; i++) scheme[i] = (char)tolower((unsigned char)scheme[i]);
s->protocol = malloc(slen + 2);
memcpy(s->protocol, scheme, slen);
s->protocol[slen] = ':';
s->protocol[slen + 1] = '\0';
free(scheme);
char *userinfo = uri_range_dup(&uri->userInfo);
char *colon = strchr(userinfo, ':');
if (colon) {
*colon = '\0';
s->username = strdup(userinfo);
s->password = strdup(colon + 1);
} else {
s->username = strdup(userinfo);
s->password = strdup("");
}
free(userinfo);
s->hostname = uri_range_dup(&uri->hostText);
char *port = uri_range_dup(&uri->portText);
int def = default_port_for(s->protocol);
if (def > 0 && *port && atoi(port) == def) {
free(port);
port = strdup("");
} s->port = port;
size_t path_cap = 2;
for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next)
path_cap += (size_t)(seg->text.afterLast - seg->text.first) + 1;
char *path = malloc(path_cap + 1);
size_t pos = 0;
for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next) {
path[pos++] = '/';
size_t seglen = (size_t)(seg->text.afterLast - seg->text.first);
memcpy(path + pos, seg->text.first, seglen);
pos += seglen;
}
if (pos == 0) path[pos++] = '/';
path[pos] = '\0';
s->pathname = path;
char *query = uri_range_dup(&uri->query);
if (*query) {
size_t qlen = strlen(query);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, query, qlen + 1);
} else s->search = strdup("");
free(query);
char *frag = uri_range_dup(&uri->fragment);
if (*frag) {
size_t flen = strlen(frag);
s->hash = malloc(flen + 2);
s->hash[0] = '#';
memcpy(s->hash + 1, frag, flen + 1);
} else s->hash = strdup("");
free(frag);
}
int parse_url_to_state(const char *url_str, const char *base_str, url_state_t *s) {
memset(s, 0, sizeof(*s));
const char *errpos;
if (base_str) {
UriUriA base_uri, ref_uri, resolved;
char *escaped_base = NULL;
char *escaped_ref = NULL;
bool used_relaxed_ref_parse = false;
if (url_parse_single_uri_relaxed(&base_uri, base_str, &errpos, &escaped_base, NULL) != 0) {
free(escaped_base);
return -1;
}
char *base_scheme = uri_range_dup(&base_uri.scheme);
size_t bslen = strlen(base_scheme);
for (size_t i = 0; i < bslen; i++) base_scheme[i] = (char)tolower((unsigned char)base_scheme[i]);
char proto_buf[bslen + 2];
memcpy(proto_buf, base_scheme, bslen);
proto_buf[bslen] = ':';
proto_buf[bslen + 1] = '\0';
free(base_scheme);
if (url_base_is_opaque(base_str, proto_buf)) {
uriFreeUriMembersA(&base_uri);
free(escaped_base);
return -1;
}
if (url_parse_single_uri_relaxed(&ref_uri, url_str, &errpos, &escaped_ref, &used_relaxed_ref_parse) != 0) {
uriFreeUriMembersA(&base_uri);
free(escaped_base);
free(escaped_ref);
return -1;
}
if (uriAddBaseUriA(&resolved, &ref_uri, &base_uri) != URI_SUCCESS) {
uriFreeUriMembersA(&base_uri);
uriFreeUriMembersA(&ref_uri);
free(escaped_base);
free(escaped_ref);
return -1;
}
uriNormalizeSyntaxA(&resolved);
if (!resolved.scheme.first || resolved.scheme.first == resolved.scheme.afterLast) {
uriFreeUriMembersA(&resolved);
uriFreeUriMembersA(&ref_uri);
uriFreeUriMembersA(&base_uri);
free(escaped_base);
free(escaped_ref);
return -1;
}
uri_to_state(&resolved, s);
if (used_relaxed_ref_parse) url_override_search_hash_from_reference(s, url_str);
uriFreeUriMembersA(&resolved);
uriFreeUriMembersA(&ref_uri);
uriFreeUriMembersA(&base_uri);
free(escaped_ref);
free(escaped_base);
return 0;
}
UriUriA uri;
char *escaped_url = NULL;
bool used_relaxed_query_parse = false;
if (url_parse_single_uri_relaxed(&uri, url_str, &errpos, &escaped_url, &used_relaxed_query_parse) != 0) {
free(escaped_url);
return -1;
}
if (!uri.scheme.first || uri.scheme.first == uri.scheme.afterLast) {
uriFreeUriMembersA(&uri);
free(escaped_url);
return -1;
}
uriNormalizeSyntaxA(&uri);
uri_to_state(&uri, s);
if (used_relaxed_query_parse) url_override_search_hash_from_input(s, url_str);
uriFreeUriMembersA(&uri);
free(escaped_url);
return 0;
}
char *build_href(const url_state_t *s) {
bool has_authority =
(s->hostname && *s->hostname) ||
(s->username && *s->username) ||
(s->password && *s->password) ||
(s->port && *s->port);
bool opaque_like = !has_authority && !uses_authority_syntax(s->protocol);
const char *pathname = s->pathname ? s->pathname : "";
size_t len = strlen(s->protocol) + strlen(pathname) + strlen(s->search) + strlen(s->hash) + 32;
if (has_authority) len += strlen(s->hostname) + 2;
if (s->username && *s->username) len += strlen(s->username) + 1;
if (s->password && *s->password) len += strlen(s->password) + 1;
if (s->port && *s->port) len += strlen(s->port) + 1;
char *href = malloc(len);
if (!href) return strdup("");
size_t pos = 0;
pos += (size_t)sprintf(href + pos, "%s", s->protocol);
if (opaque_like) {
if (pathname[0] == '/') pathname++;
pos += (size_t)sprintf(href + pos, "%s%s%s", pathname, s->search, s->hash);
href[pos] = '\0';
return href;
}
pos += (size_t)sprintf(href + pos, "//");
if (s->username && *s->username) {
pos += (size_t)sprintf(href + pos, "%s", s->username);
if (s->password && *s->password)
pos += (size_t)sprintf(href + pos, ":%s", s->password);
href[pos++] = '@';
}
pos += (size_t)sprintf(href + pos, "%s", s->hostname);
if (s->port && *s->port)
pos += (size_t)sprintf(href + pos, ":%s", s->port);
pos += (size_t)sprintf(href + pos, "%s%s%s", pathname, s->search, s->hash);
href[pos] = '\0';
return href;
}
static const char *coerce_to_string(ant_t *js, ant_value_t val, size_t *len) {
if (vtype(val) == T_STR) return js_getstr(js, val, len);
if (is_object_type(val)) {
ant_value_t href = js_getprop_fallback(js, val, "href");
if (vtype(href) == T_STR) return js_getstr(js, href, len);
}
return NULL;
}
static ant_value_t parse_query_to_arr(ant_t *js, const char *query) {
ant_value_t arr = js_mkarr(js);
if (!query || !*query) return arr;
const char *p = query;
while (*p) {
const char *amp = strchr(p, '&');
size_t plen = amp ? (size_t)(amp - p) : strlen(p);
if (plen == 0) { p = amp ? amp + 1 : p + 1; continue; }
char *pair = strndup(p, plen);
if (!pair) { p = amp ? amp + 1 : p + plen; continue; }
char *eq = strchr(pair, '=');
char *raw_v = eq ? ((*eq = '\0'), eq + 1) : "";
char *k = form_urldecode(pair);
char *v = form_urldecode(raw_v);
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, js_mkstr(js, k, strlen(k)));
js_arr_push(js, entry, js_mkstr(js, v, strlen(v)));
js_arr_push(js, arr, entry);
free(pair); free(k); free(v);
p = amp ? amp + 1 : p + plen;
}
return arr;
}
char *usp_serialize(ant_t *js, ant_value_t usp) {
ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
size_t cap = 256;
char *buf = malloc(cap);
if (!buf) return strdup("");
size_t pos = 0;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
size_t klen = 0, vlen = 0;
char *k = js_getstr(js, js_arr_get(js, entry, 0), &klen);
char *v = js_getstr(js, js_arr_get(js, entry, 1), &vlen);
char *ek = k ? form_urlencode_n(k, klen) : strdup("");
char *ev = v ? form_urlencode_n(v, vlen) : strdup("");
size_t needed = strlen(ek) + strlen(ev) + 3;
if (pos + needed >= cap) {
cap = cap * 2 + needed;
buf = realloc(buf, cap);
if (!buf) { free(ek); free(ev); return strdup(""); }
}
if (pos > 0) buf[pos++] = '&';
pos += (size_t)sprintf(buf + pos, "%s=%s", ek, ev);
free(ek); free(ev);
}
buf[pos] = '\0';
return buf;
}
static void usp_push_to_url(ant_t *js, ant_value_t usp) {
ant_value_t url_obj = js_get_slot(usp, SLOT_DATA);
if (!is_special_object(url_obj)) return;
url_state_t *s = url_get_state(url_obj);
if (!s) return;
char *qs = usp_serialize(js, usp);
free(s->search);
if (*qs) {
size_t qlen = strlen(qs);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, qs, qlen + 1);
} else s->search = strdup("");
free(qs);
}
static void url_sync_usp(ant_t *js, ant_value_t url_obj, const char *query) {
ant_value_t usp = js_get_slot(url_obj, SLOT_ENTRIES);
if (!is_special_object(usp)) return;
ant_value_t new_entries = parse_query_to_arr(js, query);
js_set_slot_wb(js, usp, SLOT_ENTRIES, new_entries);
}
static ant_value_t make_usp_for_url(ant_t *js, ant_value_t url_obj, const char *query) {
ant_value_t usp = js_mkobj(js);
js_set_proto_init(usp, g_usp_proto);
js_set_slot(usp, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
js_set_slot_wb(js, usp, SLOT_DATA, url_obj);
js_set_slot(usp, SLOT_ENTRIES, parse_query_to_arr(js, query));
return usp;
}
static ant_value_t url_get_href(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
char *href = build_href(s);
ant_value_t ret = js_mkstr(js, href, strlen(href));
free(href);
return ret;
}
static ant_value_t url_get_protocol(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->protocol, strlen(s->protocol));
}
static ant_value_t url_get_username(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->username, strlen(s->username));
}
static ant_value_t url_get_password(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->password, strlen(s->password));
}
static ant_value_t url_get_host(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
if (s->port && *s->port) {
size_t len = strlen(s->hostname) + strlen(s->port) + 2;
char *host = malloc(len);
snprintf(host, len, "%s:%s", s->hostname, s->port);
ant_value_t ret = js_mkstr(js, host, strlen(host));
free(host);
return ret;
}
return js_mkstr(js, s->hostname, strlen(s->hostname));
}
static ant_value_t url_get_hostname(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->hostname, strlen(s->hostname));
}
static ant_value_t url_get_port(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->port, strlen(s->port));
}
static ant_value_t url_get_pathname(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "/", 1);
return js_mkstr(js, s->pathname, strlen(s->pathname));
}
static ant_value_t url_get_search(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->search, strlen(s->search));
}
static ant_value_t url_get_hash(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
return js_mkstr(js, s->hash, strlen(s->hash));
}
static ant_value_t url_get_origin(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s || !is_special_scheme(s->protocol)) return js_mkstr(js, "null", 4);
size_t proto_len = strlen(s->protocol) - 1;
size_t host_len = strlen(s->hostname);
size_t port_len = (s->port && *s->port) ? strlen(s->port) + 1 : 0;
size_t total = proto_len + 3 + host_len + port_len + 1;
char *origin = malloc(total);
size_t pos = 0;
memcpy(origin + pos, s->protocol, proto_len); pos += proto_len;
memcpy(origin + pos, "://", 3); pos += 3;
memcpy(origin + pos, s->hostname, host_len); pos += host_len;
if (s->port && *s->port) {
origin[pos++] = ':';
memcpy(origin + pos, s->port, strlen(s->port));
pos += strlen(s->port);
}
origin[pos] = '\0';
ant_value_t ret = js_mkstr(js, origin, pos);
free(origin);
return ret;
}
static ant_value_t url_get_searchParams(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t usp = js_get_slot(js->this_val, SLOT_ENTRIES);
if (vtype(usp) == T_OBJ) return usp;
return js_mkundef();
}
static ant_value_t url_set_href(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkerr(js, "TypeError: Invalid URL");
url_state_t tmp;
if (parse_url_to_state(val, NULL, &tmp) != 0)
return js_mkerr(js, "TypeError: Invalid URL");
free(s->protocol); s->protocol = tmp.protocol;
free(s->username); s->username = tmp.username;
free(s->password); s->password = tmp.password;
free(s->hostname); s->hostname = tmp.hostname;
free(s->port); s->port = tmp.port;
free(s->pathname); s->pathname = tmp.pathname;
free(s->search); s->search = tmp.search;
free(s->hash); s->hash = tmp.hash;
const char *q = (s->search[0] == '?') ? s->search + 1 : "";
url_sync_usp(js, js->this_val, q);
return js_mkundef();
}
static ant_value_t url_set_protocol(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val || !*val) return js_mkundef();
const char *colon = strchr(val, ':');
size_t slen = colon ? (size_t)(colon - val) : strlen(val);
if (!slen || !isalpha((unsigned char)val[0])) return js_mkundef();
for (size_t i = 1; i < slen; i++) {
unsigned char c = (unsigned char)val[i];
if (!isalnum(c) && c != '+' && c != '-' && c != '.') return js_mkundef();
}
free(s->protocol);
s->protocol = malloc(slen + 2);
for (size_t i = 0; i < slen; i++) s->protocol[i] = (char)tolower((unsigned char)val[i]);
s->protocol[slen] = ':';
s->protocol[slen + 1] = '\0';
if (s->port && *s->port) {
int def = default_port_for(s->protocol);
if (def > 0 && atoi(s->port) == def) { free(s->port); s->port = strdup(""); }
}
return js_mkundef();
}
static ant_value_t url_set_username(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->username);
s->username = userinfo_encode(val);
return js_mkundef();
}
static ant_value_t url_set_password(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->password);
s->password = userinfo_encode(val);
return js_mkundef();
}
static ant_value_t url_set_host(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *colon = strchr(val, ':');
if (colon) {
free(s->hostname);
s->hostname = strndup(val, (size_t)(colon - val));
const char *port_str = colon + 1;
free(s->port);
if (*port_str) {
int p = atoi(port_str);
int def = default_port_for(s->protocol);
s->port = (def > 0 && p == def) ? strdup("") : strdup(port_str);
} else s->port = strdup("");
} else {
free(s->hostname);
s->hostname = strdup(val);
}
return js_mkundef();
}
static ant_value_t url_set_hostname(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->hostname);
const char *colon = strchr(val, ':');
s->hostname = colon ? strndup(val, (size_t)(colon - val)) : strdup(val);
return js_mkundef();
}
static ant_value_t url_set_port(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->port);
if (!*val) { s->port = strdup(""); return js_mkundef(); }
int p = atoi(val);
if (p < 0 || p > 65535) { s->port = strdup(""); return js_mkundef(); }
int def = default_port_for(s->protocol);
if (def > 0 && p == def) s->port = strdup(""); else {
char buf[8];
snprintf(buf, sizeof(buf), "%d", p);
s->port = strdup(buf);
}
return js_mkundef();
}
static ant_value_t url_set_pathname(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
free(s->pathname);
if (is_special_scheme(s->protocol) && val[0] != '/') {
size_t vlen = strlen(val);
s->pathname = malloc(vlen + 2);
s->pathname[0] = '/';
memcpy(s->pathname + 1, val, vlen + 1);
} else s->pathname = strdup(val);
return js_mkundef();
}
static ant_value_t url_set_search(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *q = (val[0] == '?') ? val + 1 : val;
free(s->search);
if (*q) {
size_t qlen = strlen(q);
s->search = malloc(qlen + 2);
s->search[0] = '?';
memcpy(s->search + 1, q, qlen + 1);
} else s->search = strdup("");
url_sync_usp(js, js->this_val, *q ? q : "");
return js_mkundef();
}
static ant_value_t url_set_hash(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkundef();
const char *val = js_getstr(js, args[0], NULL);
if (!val) return js_mkundef();
const char *h = (val[0] == '#') ? val + 1 : val;
free(s->hash);
if (*h) {
size_t hlen = strlen(h);
s->hash = malloc(hlen + 2);
s->hash[0] = '#';
memcpy(s->hash + 1, h, hlen + 1);
} else s->hash = strdup("");
return js_mkundef();
}
static ant_value_t url_toString(ant_t *js, ant_value_t *args, int nargs) {
url_state_t *s = url_get_state(js->this_val);
if (!s) return js_mkstr(js, "", 0);
char *href = build_href(s);
ant_value_t ret = js_mkstr(js, href, strlen(href));
free(href);
return ret;
}
static ant_value_t js_URL(ant_t *js, ant_value_t *args, int nargs) {
if (is_undefined(js->new_target))
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URL': Please use the 'new' operator.");
if (nargs < 1)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': 1 argument required.");
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv))
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str)
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) return js_mkerr(js, "out of memory");
if (parse_url_to_state(url_str, base_str, s) != 0) {
free(s);
return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
}
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_url_proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
js_set_finalizer(obj, url_finalize);
const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
ant_value_t usp = make_usp_for_url(js, obj, query);
js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
return obj;
}
ant_value_t make_url_obj(ant_t *js, url_state_t *s) {
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_url_proto);
js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
js_set_finalizer(obj, url_finalize);
const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
ant_value_t usp = make_usp_for_url(js, obj, query);
js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
return obj;
}
static ant_value_t url_canParse(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv)) return js_false;
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str) return js_false;
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t s;
if (parse_url_to_state(url_str, base_str, &s) != 0) return js_false;
url_state_clear(&s);
return js_true;
}
static ant_value_t url_parse(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknull();
ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(url_sv)) return js_mknull();
const char *url_str = js_getstr(js, url_sv, NULL);
if (!url_str) return js_mknull();
const char *base_str = NULL;
if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
base_str = coerce_to_string(js, args[1], NULL);
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) return js_mknull();
if (parse_url_to_state(url_str, base_str, s) != 0) {
free(s);
return js_mknull();
}
return make_url_obj(js, s);
}
static ant_value_t usp_get(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mknull();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mknull();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mknull();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mknull();
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) return js_arr_get(js, entry, 1);
}
return js_mknull();
}
static ant_value_t usp_getAll(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t result = js_mkarr(js);
if (nargs < 1) return result;
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return result;
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return result;
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return result;
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0)
js_arr_push(js, result, js_arr_get(js, entry, 1));
}
return result;
}
static ant_value_t usp_has(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_false;
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_false;
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_false;
const char *match_val = NULL;
if (nargs >= 2 && !is_undefined(args[1])) {
ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
}
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_false;
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (!ek || strcmp(ek, key) != 0) continue;
if (!match_val) return js_true;
const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
if (ev && strcmp(ev, match_val) == 0) return js_true;
}
return js_false;
}
static ant_value_t usp_set(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (is_err(val_sv)) return js_mkundef();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mkundef();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
ant_value_t new_entries = js_mkarr(js);
int found = 0;
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) {
if (!found) {
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, key_sv);
js_arr_push(js, ne, val_sv);
js_arr_push(js, new_entries, ne);
found = 1;
}
} else js_arr_push(js, new_entries, entry); }
if (!found) {
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, key_sv);
js_arr_push(js, ne, val_sv);
js_arr_push(js, new_entries, ne);
}
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_append(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 2) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (is_err(val_sv)) return js_mkundef();
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, key_sv);
js_arr_push(js, entry, val_sv);
js_arr_push(js, entries, entry);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_delete(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkundef();
ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
if (is_err(key_sv)) return js_mkundef();
const char *key = js_getstr(js, key_sv, NULL);
if (!key) return js_mkundef();
const char *match_val = NULL;
if (nargs >= 2 && !is_undefined(args[1])) {
ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
}
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
ant_value_t new_entries = js_mkarr(js);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
if (ek && strcmp(ek, key) == 0) {
if (!match_val) continue;
const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
if (ev && strcmp(ev, match_val) == 0) continue;
}
js_arr_push(js, new_entries, entry);
}
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_toString(ant_t *js, ant_value_t *args, int nargs) {
char *s = usp_serialize(js, js->this_val);
ant_value_t ret = js_mkstr(js, s, strlen(s));
free(s);
return ret;
}
static ant_value_t usp_forEach(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || !is_callable(args[0])) return js_mkundef();
ant_value_t cb = args[0];
ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
ant_value_t self = js->this_val;
ant_value_t entries = js_get_slot(self, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_offset_t len = js_arr_len(js, entries);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, entries, i);
ant_value_t k = js_arr_get(js, entry, 0);
ant_value_t v = js_arr_get(js, entry, 1);
ant_value_t cb_args[3] = { v, k, self };
ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, cb_args, 3, NULL, false);
if (is_err(r)) return r;
}
return js_mkundef();
}
static ant_value_t usp_size_get(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mknum(0);
return js_mknum((double)js_arr_len(js, entries));
}
static ant_value_t usp_sort(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
if (!is_special_object(entries)) return js_mkundef();
ant_offset_t len = js_arr_len(js, entries);
if (len <= 1) return js_mkundef();
ant_value_t *arr = malloc(sizeof(ant_value_t) * (size_t)len);
if (!arr) return js_mkundef();
for (ant_offset_t i = 0; i < len; i++) arr[i] = js_arr_get(js, entries, i);
for (ant_offset_t i = 1; i < len; i++) {
ant_value_t cur = arr[i];
const char *ck = js_getstr(js, js_arr_get(js, cur, 0), NULL);
ant_offset_t j = i;
while (j > 0) {
const char *jk = js_getstr(js, js_arr_get(js, arr[j - 1], 0), NULL);
if (strcmp(jk ? jk : "", ck ? ck : "") <= 0) break;
arr[j] = arr[j - 1]; j--;
}
arr[j] = cur;
}
ant_value_t new_entries = js_mkarr(js);
for (ant_offset_t i = 0; i < len; i++) js_arr_push(js, new_entries, arr[i]);
free(arr);
js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
usp_push_to_url(js, js->this_val);
return js_mkundef();
}
static ant_value_t usp_iter_next(ant_t *js, ant_value_t *args, int nargs) {
ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
uint32_t state = (uint32_t)js_getnum(state_v);
uint32_t kind = ITER_STATE_KIND(state);
uint32_t idx = ITER_STATE_INDEX(state);
ant_value_t usp = js_get_slot(js->this_val, SLOT_DATA);
ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
if (!is_special_object(entries) || (ant_offset_t)idx >= js_arr_len(js, entries))
return js_iter_result(js, false, js_mkundef());
js_set_slot(js->this_val, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, idx + 1)));
ant_value_t entry = js_arr_get(js, entries, (ant_offset_t)idx);
ant_value_t k = js_arr_get(js, entry, 0);
ant_value_t v = js_arr_get(js, entry, 1);
ant_value_t out;
switch (kind) {
case USP_ITER_KEYS: out = k; break;
case USP_ITER_VALUES: out = v; break;
default: {
out = js_mkarr(js);
js_arr_push(js, out, k);
js_arr_push(js, out, v);
break;
}}
return js_iter_result(js, true, out);
}
static ant_value_t make_usp_iter(ant_t *js, ant_value_t usp, int kind) {
ant_value_t iter = js_mkobj(js);
js_set_proto_init(iter, g_usp_iter_proto);
js_set_slot_wb(js, iter, SLOT_DATA, usp);
js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, 0)));
return iter;
}
static ant_value_t usp_entries_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_ENTRIES);
}
static ant_value_t usp_keys_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_KEYS);
}
static ant_value_t usp_values_fn(ant_t *js, ant_value_t *args, int nargs) {
return make_usp_iter(js, js->this_val, USP_ITER_VALUES);
}
static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) {
if (is_undefined(js->new_target))
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URLSearchParams': Please use the 'new' operator.");
ant_value_t obj = js_mkobj(js);
js_set_proto_init(obj, g_usp_proto);
js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
js_set_slot(obj, SLOT_DATA, js_mkundef());
ant_value_t entries = js_mkarr(js);
js_set_slot(obj, SLOT_ENTRIES, entries);
if (nargs < 1 || is_undefined(args[0]) || is_null(args[0])) return obj;
ant_value_t init = args[0];
uint8_t t = vtype(init);
if (t == T_STR) {
const char *s = js_getstr(js, init, NULL);
if (s) {
const char *q = (s[0] == '?') ? s + 1 : s;
js_set_slot(obj, SLOT_ENTRIES, parse_query_to_arr(js, q));
}
return obj;
}
if (t == T_ARR) {
ant_offset_t len = js_arr_len(js, init);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t pair = js_arr_get(js, init, i);
if (vtype(pair) != T_ARR)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URLSearchParams': Each element must be an array.");
ant_offset_t plen = js_arr_len(js, pair);
if (plen != 2)
return js_mkerr_typed(js, JS_ERR_TYPE,
"Failed to construct 'URLSearchParams': Each pair must have exactly 2 elements.");
ant_value_t pk = js_arr_get(js, pair, 0);
ant_value_t pv = js_arr_get(js, pair, 1);
ant_value_t ksv = (vtype(pk) == T_STR) ? pk : js_tostring_val(js, pk);
if (is_err(ksv)) return ksv;
ant_value_t vsv = (vtype(pv) == T_STR) ? pv : js_tostring_val(js, pv);
if (is_err(vsv)) return vsv;
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, ksv);
js_arr_push(js, entry, vsv);
js_arr_push(js, entries, entry);
}
return obj;
}
if (is_special_object(init)) {
ant_value_t src = js_get_slot(init, SLOT_ENTRIES);
if (vtype(src) == T_ARR) {
ant_offset_t len = js_arr_len(js, src);
for (ant_offset_t i = 0; i < len; i++) {
ant_value_t entry = js_arr_get(js, src, i);
ant_value_t ne = js_mkarr(js);
js_arr_push(js, ne, js_arr_get(js, entry, 0));
js_arr_push(js, ne, js_arr_get(js, entry, 1));
js_arr_push(js, entries, ne);
}
return obj;
}
ant_iter_t it = js_prop_iter_begin(js, init);
const char *key;
size_t key_len;
ant_value_t val;
while (js_prop_iter_next(&it, &key, &key_len, &val)) {
ant_value_t sv = (vtype(val) == T_STR) ? val : js_tostring_val(js, val);
if (is_err(sv)) { js_prop_iter_end(&it); return sv; }
ant_value_t entry = js_mkarr(js);
js_arr_push(js, entry, js_mkstr(js, key, key_len));
js_arr_push(js, entry, sv);
js_arr_push(js, entries, entry);
}
js_prop_iter_end(&it);
}
return obj;
}
void init_url_module(void) {
ant_t *js = rt->js;
ant_value_t glob = js->global;
g_usp_iter_proto = js_mkobj(js);
js_set_proto_init(g_usp_iter_proto, js->sym.iterator_proto);
js_set(js, g_usp_iter_proto, "next", js_mkfun(usp_iter_next));
js_set_descriptor(js, g_usp_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
js_set_sym(js, g_usp_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
g_usp_proto = js_mkobj(js);
js_set(js, g_usp_proto, "get", js_mkfun(usp_get));
js_set(js, g_usp_proto, "getAll", js_mkfun(usp_getAll));
js_set(js, g_usp_proto, "has", js_mkfun(usp_has));
js_set(js, g_usp_proto, "set", js_mkfun(usp_set));
js_set(js, g_usp_proto, "append", js_mkfun(usp_append));
js_set(js, g_usp_proto, "delete", js_mkfun(usp_delete));
js_set(js, g_usp_proto, "sort", js_mkfun(usp_sort));
js_set(js, g_usp_proto, "toString", js_mkfun(usp_toString));
js_set(js, g_usp_proto, "forEach", js_mkfun(usp_forEach));
js_set_getter_desc(js, g_usp_proto, "size", 4, js_mkfun(usp_size_get), JS_DESC_C);
js_set(js, g_usp_proto, "entries", js_mkfun(usp_entries_fn));
js_set(js, g_usp_proto, "keys", js_mkfun(usp_keys_fn));
js_set(js, g_usp_proto, "values", js_mkfun(usp_values_fn));
js_set_sym(js, g_usp_proto, get_iterator_sym(), js_get(js, g_usp_proto, "entries"));
js_set_sym(js, g_usp_proto, get_toStringTag_sym(), js_mkstr(js, "URLSearchParams", 15));
ant_value_t usp_ctor = js_make_ctor(js, js_URLSearchParams, g_usp_proto, "URLSearchParams", 15);
js_set(js, glob, "URLSearchParams", usp_ctor);
g_url_proto = js_mkobj(js);
js_set_accessor_desc(js, g_url_proto, "href", 4, js_mkfun(url_get_href), js_mkfun(url_set_href), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "protocol", 8, js_mkfun(url_get_protocol), js_mkfun(url_set_protocol), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "username", 8, js_mkfun(url_get_username), js_mkfun(url_set_username), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "password", 8, js_mkfun(url_get_password), js_mkfun(url_set_password), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "host", 4, js_mkfun(url_get_host), js_mkfun(url_set_host), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "hostname", 8, js_mkfun(url_get_hostname), js_mkfun(url_set_hostname), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "port", 4, js_mkfun(url_get_port), js_mkfun(url_set_port), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "pathname", 8, js_mkfun(url_get_pathname), js_mkfun(url_set_pathname), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "search", 6, js_mkfun(url_get_search), js_mkfun(url_set_search), JS_DESC_C);
js_set_accessor_desc(js, g_url_proto, "hash", 4, js_mkfun(url_get_hash), js_mkfun(url_set_hash), JS_DESC_C);
js_set_getter_desc(js, g_url_proto, "origin", 6, js_mkfun(url_get_origin), JS_DESC_C);
js_set_getter_desc(js, g_url_proto, "searchParams", 12, js_mkfun(url_get_searchParams), JS_DESC_C);
js_set(js, g_url_proto, "toString", js_mkfun(url_toString));
js_set(js, g_url_proto, "toJSON", js_mkfun(url_toString));
js_set_sym(js, g_url_proto, get_toStringTag_sym(), js_mkstr(js, "URL", 3));
ant_value_t url_ctor = js_make_ctor(js, js_URL, g_url_proto, "URL", 3);
js_set(js, url_ctor, "canParse", js_mkfun(url_canParse));
js_set(js, url_ctor, "parse", js_mkfun(url_parse));
js_set(js, glob, "URL", url_ctor);
}
static ant_value_t builtin_fileURLToPath(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
size_t len;
const char *str = coerce_to_string(js, args[0], &len);
if (!str) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
url_state_t s;
if (parse_url_to_state(str, NULL, &s) != 0)
return js_mkerr(js, "Invalid URL");
if (strcmp(s.protocol, "file:") != 0) {
url_state_clear(&s);
return js_mkerr(js, "fileURLToPath requires a file: URL");
}
char *decoded = url_decode_component(s.pathname);
url_state_clear(&s);
if (!decoded) return js_mkerr(js, "allocation failure");
ant_value_t ret = js_mkstr(js, decoded, strlen(decoded));
free(decoded);
return ret;
}
static ant_value_t builtin_pathToFileURL(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || vtype(args[0]) != T_STR)
return js_mkerr(js, "pathToFileURL requires a string argument");
size_t len;
const char *path = js_getstr(js, args[0], &len);
size_t total = 7 + len;
char *buf = malloc(total + 1);
if (!buf) return js_mkerr(js, "allocation failure");
memcpy(buf, "file://", 7);
memcpy(buf + 7, path, len);
buf[total] = '\0';
url_state_t *s = calloc(1, sizeof(url_state_t));
if (!s) { free(buf); return js_mkerr(js, "allocation failure"); }
if (parse_url_to_state(buf, NULL, s) != 0) {
free(buf); free(s);
return js_mkerr(js, "Invalid file URL");
}
free(buf);
return make_url_obj(js, s);
}
typedef struct {
char *buf;
size_t len;
size_t cap;
} url_fmt_buf_t;
static bool url_fmt_reserve(url_fmt_buf_t *b, size_t extra) {
if (extra <= b->cap - b->len) return true;
size_t needed = b->len + extra + 1;
size_t next = b->cap ? b->cap : 128;
while (next < needed) next *= 2;
char *buf = realloc(b->buf, next);
if (!buf) return false;
b->buf = buf;
b->cap = next;
return true;
}
static bool url_fmt_append_n(url_fmt_buf_t *b, const char *s, size_t n) {
if (!s || n == 0) return true;
if (!url_fmt_reserve(b, n)) return false;
memcpy(b->buf + b->len, s, n);
b->len += n;
b->buf[b->len] = '\0';
return true;
}
static bool url_fmt_append(url_fmt_buf_t *b, const char *s) {
return url_fmt_append_n(b, s, s ? strlen(s) : 0);
}
static bool url_fmt_append_c(url_fmt_buf_t *b, char c) {
if (!url_fmt_reserve(b, 1)) return false;
b->buf[b->len++] = c;
b->buf[b->len] = '\0';
return true;
}
static bool url_fmt_append_value_string(ant_t *js, url_fmt_buf_t *b, ant_value_t value) {
ant_value_t str_val = vtype(value) == T_STR ? value : js_tostring_val(js, value);
if (is_err(str_val)) return false;
size_t len = 0;
const char *str = js_getstr(js, str_val, &len);
return str && url_fmt_append_n(b, str, len);
}
static bool url_fmt_get_string_prop(
ant_t *js,
ant_value_t obj,
const char *name,
ant_value_t *out,
const char **str,
size_t *len
) {
*out = js_get(js, obj, name);
if (is_undefined(*out) || is_null(*out)) return false;
ant_value_t str_val = vtype(*out) == T_STR ? *out : js_tostring_val(js, *out);
if (is_err(str_val)) return false;
*out = str_val;
*str = js_getstr(js, str_val, len);
return *str != NULL;
}
static bool url_fmt_is_query_unescaped(unsigned char c) {
return isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~';
}
static bool url_fmt_append_query_component(url_fmt_buf_t *b, const char *s, size_t len) {
static const char hex[] = "0123456789ABCDEF";
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)s[i];
if (url_fmt_is_query_unescaped(c)) {
if (!url_fmt_append_c(b, (char)c)) return false;
} else {
char esc[3] = { '%', hex[c >> 4], hex[c & 0x0f] };
if (!url_fmt_append_n(b, esc, sizeof(esc))) return false;
}}
return true;
}
static bool url_fmt_append_query_object(ant_t *js, url_fmt_buf_t *b, ant_value_t query) {
if (!is_special_object(query)) return true;
bool first = true;
ant_iter_t it = js_prop_iter_begin(js, query);
const char *key;
size_t key_len;
ant_value_t val;
while (js_prop_iter_next(&it, &key, &key_len, &val)) {
if (!first && !url_fmt_append_c(b, '&')) {
js_prop_iter_end(&it);
return false;
}
first = false;
if (!url_fmt_append_query_component(b, key, key_len)) {
js_prop_iter_end(&it);
return false;
}
if (!url_fmt_append_c(b, '=')) {
js_prop_iter_end(&it);
return false;
}
ant_value_t str_val = vtype(val) == T_STR ? val : js_tostring_val(js, val);
if (is_err(str_val)) {
js_prop_iter_end(&it);
return false;
}
size_t val_len = 0;
const char *val_str = js_getstr(js, str_val, &val_len);
if (!val_str || !url_fmt_append_query_component(b, val_str, val_len)) {
js_prop_iter_end(&it);
return false;
}
}
js_prop_iter_end(&it);
return true;
}
static bool url_fmt_protocol_needs_slashes(const char *protocol, size_t len) {
if (len > 0 && protocol[len - 1] == ':') len--;
return
(len == 4 && memcmp(protocol, "http", 4) == 0) ||
(len == 5 && memcmp(protocol, "https", 5) == 0) ||
(len == 3 && memcmp(protocol, "ftp", 3) == 0) ||
(len == 4 && memcmp(protocol, "file", 4) == 0) ||
(len == 2 && memcmp(protocol, "ws", 2) == 0) ||
(len == 3 && memcmp(protocol, "wss", 3) == 0);
}
static ant_value_t builtin_url_format(ant_t *js, ant_value_t *args, int nargs) {
if (nargs < 1 || !is_object_type(args[0]))
return js_mkerr_typed(js, JS_ERR_TYPE, "url.format() requires a URL or object argument");
url_state_t *state = url_get_state(args[0]);
if (state) {
char *href = build_href(state);
ant_value_t ret = js_mkstr(js, href, strlen(href));
free(href);
return ret;
}
ant_value_t obj = args[0];
ant_value_t tmp;
const char *protocol = NULL, *auth = NULL, *host = NULL, *hostname = NULL;
const char *port = NULL, *pathname = NULL, *search = NULL, *hash = NULL;
size_t protocol_len = 0, auth_len = 0, host_len = 0, hostname_len = 0;
size_t port_len = 0, pathname_len = 0, search_len = 0, hash_len = 0;
url_fmt_get_string_prop(js, obj, "protocol", &tmp, &protocol, &protocol_len);
url_fmt_get_string_prop(js, obj, "auth", &tmp, &auth, &auth_len);
url_fmt_get_string_prop(js, obj, "host", &tmp, &host, &host_len);
url_fmt_get_string_prop(js, obj, "hostname", &tmp, &hostname, &hostname_len);
url_fmt_get_string_prop(js, obj, "port", &tmp, &port, &port_len);
url_fmt_get_string_prop(js, obj, "pathname", &tmp, &pathname, &pathname_len);
url_fmt_get_string_prop(js, obj, "search", &tmp, &search, &search_len);
url_fmt_get_string_prop(js, obj, "hash", &tmp, &hash, &hash_len);
url_fmt_buf_t b = {0};
if (protocol && protocol_len > 0) {
if (!url_fmt_append_n(&b, protocol, protocol_len)) goto oom;
if (protocol[protocol_len - 1] != ':' && !url_fmt_append_c(&b, ':')) goto oom;
}
bool has_host = (host && host_len > 0) || (hostname && hostname_len > 0);
ant_value_t slashes_val = js_get(js, obj, "slashes");
bool needs_slashes =
js_truthy(js, slashes_val) ||
(protocol && url_fmt_protocol_needs_slashes(protocol, protocol_len));
if (needs_slashes && (has_host || (protocol && protocol_len >= 4 && memcmp(protocol, "file", 4) == 0))) {
if (!url_fmt_append(&b, "//")) goto oom;
}
if (auth && auth_len > 0) {
if (!url_fmt_append_n(&b, auth, auth_len)) goto oom;
if (!url_fmt_append_c(&b, '@')) goto oom;
}
if (host && host_len > 0) {
if (!url_fmt_append_n(&b, host, host_len)) goto oom;
} else if (hostname && hostname_len > 0) {
if (!url_fmt_append_n(&b, hostname, hostname_len)) goto oom;
if (port && port_len > 0) {
if (!url_fmt_append_c(&b, ':')) goto oom;
if (!url_fmt_append_n(&b, port, port_len)) goto oom;
}}
if (pathname && pathname_len > 0) {
if (has_host && pathname[0] != '/' && !url_fmt_append_c(&b, '/')) goto oom;
if (!url_fmt_append_n(&b, pathname, pathname_len)) goto oom;
}
if (search && search_len > 0) {
if (search[0] != '?' && !url_fmt_append_c(&b, '?')) goto oom;
if (!url_fmt_append_n(&b, search, search_len)) goto oom;
} else {
ant_value_t query = js_get(js, obj, "query");
if (vtype(query) == T_STR) {
size_t qlen = 0;
const char *q = js_getstr(js, query, &qlen);
if (q && qlen > 0) {
if (!url_fmt_append_c(&b, '?')) goto oom;
if (!url_fmt_append_n(&b, q, qlen)) goto oom;
}
} else if (is_special_object(query)) {
url_fmt_buf_t qb = {0};
if (!url_fmt_append_query_object(js, &qb, query)) {
free(qb.buf);
goto oom;
}
if (qb.len > 0) {
if (!url_fmt_append_c(&b, '?')) {
free(qb.buf);
goto oom;
}
if (!url_fmt_append_n(&b, qb.buf, qb.len)) {
free(qb.buf);
goto oom;
}}
free(qb.buf);
}
}
if (hash && hash_len > 0) {
if (hash[0] != '#' && !url_fmt_append_c(&b, '#')) goto oom;
if (!url_fmt_append_n(&b, hash, hash_len)) goto oom;
}
ant_value_t ret = js_mkstr(js, b.buf ? b.buf : "", b.len);
free(b.buf);
return ret;
oom:
free(b.buf);
return js_mkerr(js, "allocation failure");
}
ant_value_t url_library(ant_t *js) {
ant_value_t lib = js_mkobj(js);
ant_value_t glob = js_glob(js);
js_set(js, lib, "URL", js_get(js, glob, "URL"));
js_set(js, lib, "URLSearchParams",js_get(js, glob, "URLSearchParams"));
js_set(js, lib, "fileURLToPath", js_mkfun(builtin_fileURLToPath));
js_set(js, lib, "pathToFileURL", js_mkfun(builtin_pathToFileURL));
js_set(js, lib, "parse", js_mkfun(url_parse));
js_set(js, lib, "format", js_mkfun(builtin_url_format));
js_set(js, lib, "default", lib);
return lib;
}

File Metadata

Mime Type
text/x-c
Expires
Sat, May 2, 1:59 AM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
543166
Default Alt Text
url.c (57 KB)

Event Timeline