#include // IWYU pragma: keep #include #include #include #include #include #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; }