MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdlib.h>
4#include <stdio.h>
5#include <string.h>
6#include <ctype.h>
7#include <uriparser/Uri.h>
8
9#include "ant.h"
10#include "errors.h"
11#include "internal.h"
12#include "runtime.h"
13#include "descriptors.h"
14
15#include "silver/engine.h"
16#include "modules/url.h"
17#include "modules/symbol.h"
18
19static ant_value_t g_url_proto = 0;
20static ant_value_t g_usp_proto = 0;
21static ant_value_t g_usp_iter_proto = 0;
22
23enum {
24 USP_ITER_ENTRIES = 0,
25 USP_ITER_KEYS = 1,
26 USP_ITER_VALUES = 2
27};
28
29url_state_t *url_get_state(ant_value_t obj) {
30 ant_value_t slot = js_get_slot(obj, SLOT_DATA);
31 if (vtype(slot) != T_NUM) return NULL;
32 return (url_state_t *)(uintptr_t)(size_t)js_getnum(slot);
33}
34
35bool usp_is_urlsearchparams(ant_t *js, ant_value_t obj) {
36 return js_check_brand(obj, BRAND_URLSEARCHPARAMS);
37}
38
39void url_state_clear(url_state_t *s) {
40 free(s->protocol); free(s->username); free(s->password);
41 free(s->hostname); free(s->port); free(s->pathname);
42 free(s->search); free(s->hash);
43}
44
45void url_free_state(url_state_t *s) {
46 if (!s) return;
47 url_state_clear(s);
48 free(s);
49}
50
51static void url_finalize(ant_t *js, ant_object_t *obj) {
52 if (!obj->extra_slots) return;
53 ant_extra_slot_t *slots = (ant_extra_slot_t *)obj->extra_slots;
54 for (uint8_t i = 0; i < obj->extra_count; i++) {
55 if (slots[i].slot == SLOT_DATA && vtype(slots[i].value) == T_NUM) {
56 url_free_state((url_state_t *)(uintptr_t)(size_t)js_getnum(slots[i].value));
57 return;
58 }}
59}
60
61static int default_port_for(const char *proto) {
62 if (!proto) return -1;
63 if (strcmp(proto, "http:") == 0 || strcmp(proto, "ws:") == 0) return 80;
64 if (strcmp(proto, "https:") == 0 || strcmp(proto, "wss:") == 0) return 443;
65 if (strcmp(proto, "ftp:") == 0) return 21;
66 return -1;
67}
68
69static bool is_special_scheme(const char *proto) {
70 if (!proto) return false;
71 return
72 strcmp(proto, "http:") == 0 || strcmp(proto, "https:") == 0 ||
73 strcmp(proto, "ftp:") == 0 || strcmp(proto, "ws:") == 0 ||
74 strcmp(proto, "wss:") == 0;
75}
76
77static bool uses_authority_syntax(const char *proto) {
78 if (!proto) return false;
79 return is_special_scheme(proto) || strcmp(proto, "file:") == 0;
80}
81
82static bool url_base_is_opaque(const char *base_str, const char *proto) {
83 const char *after_colon = NULL;
84
85 if (!base_str || is_special_scheme(proto)) return false;
86 after_colon = strchr(base_str, ':');
87 if (!after_colon) return false;
88 after_colon++;
89
90 return *after_colon != '/' && *after_colon != '\0';
91}
92
93char *form_urlencode_n(const char *str, size_t len) {
94 if (!str) return strdup("");
95 char *out = malloc(len * 3 + 1);
96
97 if (!out) return strdup("");
98 size_t j = 0;
99
100 for (size_t i = 0; i < len; i++) {
101 unsigned char c = (unsigned char)str[i];
102 if (isalnum(c) || c == '*' || c == '-' || c == '.' || c == '_') out[j++] = (char)c;
103 else if (c == ' ') out[j++] = '+';
104 else {
105 snprintf(out + j, 4, "%%%02X", c);
106 j += 3;
107 }}
108
109 out[j] = '\0';
110 return out;
111}
112
113char *form_urlencode(const char *str) {
114 if (!str) return strdup("");
115 return form_urlencode_n(str, strlen(str));
116}
117
118char *form_urldecode(const char *str) {
119 if (!str) return strdup("");
120 size_t len = strlen(str);
121 char *out = malloc(len + 1);
122
123 if (!out) return strdup("");
124 size_t j = 0;
125
126 for (size_t i = 0; i < len; i++) {
127 if (str[i] == '+') out[j++] = ' ';
128 else if (
129 str[i] == '%' && i + 2 < len &&
130 isxdigit((unsigned char)str[i+1]) &&
131 isxdigit((unsigned char)str[i+2])
132 ) {
133 int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
134 int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
135 out[j++] = (char)((hi << 4) | lo);
136 i += 2;
137 } else out[j++] = str[i];
138 }
139
140 out[j] = '\0';
141 return out;
142}
143
144char *url_decode_component(const char *str) {
145 if (!str) return strdup("");
146 size_t len = strlen(str);
147 char *out = malloc(len + 1);
148
149 if (!out) return strdup("");
150 size_t j = 0;
151
152 for (size_t i = 0; i < len; i++) {
153 if (
154 str[i] == '%' && i + 2 < len &&
155 isxdigit((unsigned char)str[i+1]) &&
156 isxdigit((unsigned char)str[i+2])
157 ) {
158 int hi = isdigit((unsigned char)str[i+1]) ? str[i+1]-'0' : tolower((unsigned char)str[i+1])-'a'+10;
159 int lo = isdigit((unsigned char)str[i+2]) ? str[i+2]-'0' : tolower((unsigned char)str[i+2])-'a'+10;
160 out[j++] = (char)((hi << 4) | lo);
161 i += 2;
162 } else out[j++] = str[i];
163 }
164
165 out[j] = '\0';
166 return out;
167}
168
169static char *userinfo_encode(const char *str) {
170 if (!str) return strdup("");
171 size_t len = strlen(str);
172 char *out = malloc(len * 3 + 1);
173
174 if (!out) return strdup("");
175 size_t j = 0;
176
177 for (size_t i = 0; i < len; i++) {
178 unsigned char c = (unsigned char)str[i];
179 if (
180 isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~' ||
181 c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' ||
182 c == ')' || c == '*' || c == '+' || c == ',' || c == ';' ||
183 c == '=' || c == ':'
184 ) out[j++] = (char)c; else { snprintf(out + j, 4, "%%%02X", c); j += 3; }
185 }
186
187 out[j] = '\0';
188 return out;
189}
190
191static char *uri_range_dup(const UriTextRangeA *r) {
192 if (!r->first || !r->afterLast) return strdup("");
193 return strndup(r->first, (size_t)(r->afterLast - r->first));
194}
195
196static bool url_has_brackets_in_query_or_fragment(const char *url_str) {
197 size_t len = strlen(url_str);
198 bool in_query = false;
199 bool in_fragment = false;
200
201 for (size_t i = 0; i < len; i++) {
202 char c = url_str[i];
203 if (c == '#' && !in_fragment) {
204 in_query = false;
205 in_fragment = true;
206 continue;
207 }
208 if (c == '?' && !in_query && !in_fragment) {
209 in_query = true;
210 continue;
211 }
212 if ((in_query || in_fragment) && (c == '[' || c == ']')) return true;
213 }
214
215 return false;
216}
217
218static char *url_escape_brackets_in_query_or_fragment(const char *url_str) {
219 size_t len = strlen(url_str);
220 size_t extra = 0;
221 bool in_query = false;
222 bool in_fragment = false;
223
224 for (size_t i = 0; i < len; i++) {
225 char c = url_str[i];
226 if (c == '#' && !in_fragment) {
227 in_query = false;
228 in_fragment = true;
229 continue;
230 }
231 if (c == '?' && !in_query && !in_fragment) {
232 in_query = true;
233 continue;
234 }
235 if ((in_query || in_fragment) && (c == '[' || c == ']')) extra += 2;
236 }
237
238 char *escaped = malloc(len + extra + 1);
239 size_t pos = 0;
240 in_query = false;
241 in_fragment = false;
242
243 if (!escaped) return NULL;
244
245 for (size_t i = 0; i < len; i++) {
246 char c = url_str[i];
247 if (c == '#' && !in_fragment) {
248 in_query = false;
249 in_fragment = true;
250 escaped[pos++] = c;
251 continue;
252 }
253 if (c == '?' && !in_query && !in_fragment) {
254 in_query = true;
255 escaped[pos++] = c;
256 continue;
257 }
258 if ((in_query || in_fragment) && c == '[') {
259 memcpy(escaped + pos, "%5B", 3);
260 pos += 3;
261 continue;
262 }
263 if ((in_query || in_fragment) && c == ']') {
264 memcpy(escaped + pos, "%5D", 3);
265 pos += 3;
266 continue;
267 }
268 escaped[pos++] = c;
269 }
270
271 escaped[pos] = '\0';
272 return escaped;
273}
274
275static int url_parse_single_uri_relaxed(
276 UriUriA *uri,
277 const char *url_str,
278 const char **errpos,
279 char **owned_input_out,
280 bool *used_relaxed_out
281) {
282 char *escaped = NULL;
283
284 if (owned_input_out) *owned_input_out = NULL;
285 if (used_relaxed_out) *used_relaxed_out = false;
286 if (uriParseSingleUriA(uri, url_str, errpos) == URI_SUCCESS) return 0;
287
288 if (!url_has_brackets_in_query_or_fragment(url_str)) return -1;
289 escaped = url_escape_brackets_in_query_or_fragment(url_str);
290
291 if (!escaped) return -1;
292 if (owned_input_out) *owned_input_out = escaped;
293 if (used_relaxed_out) *used_relaxed_out = true;
294
295 return uriParseSingleUriA(uri, escaped, errpos) == URI_SUCCESS ? 0 : -1;
296}
297
298static void url_override_search_hash_from_input(url_state_t *s, const char *url_str) {
299 const char *hash = strchr(url_str, '#');
300 const char *query = strchr(url_str, '?');
301 size_t search_len = 0;
302 size_t hash_len = 0;
303
304 if (query && hash && hash < query) query = NULL;
305
306 if (query) {
307 const char *search_end = hash && hash > query ? hash : url_str + strlen(url_str);
308 search_len = (size_t)(search_end - query);
309 }
310 if (hash) hash_len = strlen(hash);
311
312 free(s->search);
313 s->search = search_len > 0 ? strndup(query, search_len) : strdup("");
314
315 free(s->hash);
316 s->hash = hash_len > 0 ? strndup(hash, hash_len) : strdup("");
317}
318
319static void url_override_search_hash_from_reference(url_state_t *s, const char *url_str) {
320 const char *hash = strchr(url_str, '#');
321 const char *query = strchr(url_str, '?');
322 size_t search_len = 0;
323 size_t hash_len = 0;
324
325 if (query && hash && hash < query) query = NULL;
326
327 if (query) {
328 const char *search_end = hash && hash > query ? hash : url_str + strlen(url_str);
329 search_len = (size_t)(search_end - query);
330 free(s->search);
331 s->search = strndup(query, search_len);
332 }
333
334 if (hash) {
335 hash_len = strlen(hash);
336 free(s->hash);
337 s->hash = strndup(hash, hash_len);
338 }
339}
340
341static void uri_to_state(const UriUriA *uri, url_state_t *s) {
342 char *scheme = uri_range_dup(&uri->scheme);
343 size_t slen = strlen(scheme);
344 for (size_t i = 0; i < slen; i++) scheme[i] = (char)tolower((unsigned char)scheme[i]);
345
346 s->protocol = malloc(slen + 2);
347 memcpy(s->protocol, scheme, slen);
348 s->protocol[slen] = ':';
349 s->protocol[slen + 1] = '\0';
350 free(scheme);
351
352 char *userinfo = uri_range_dup(&uri->userInfo);
353 char *colon = strchr(userinfo, ':');
354
355 if (colon) {
356 *colon = '\0';
357 s->username = strdup(userinfo);
358 s->password = strdup(colon + 1);
359 } else {
360 s->username = strdup(userinfo);
361 s->password = strdup("");
362 }
363
364 free(userinfo);
365 s->hostname = uri_range_dup(&uri->hostText);
366
367 char *port = uri_range_dup(&uri->portText);
368 int def = default_port_for(s->protocol);
369 if (def > 0 && *port && atoi(port) == def) {
370 free(port);
371 port = strdup("");
372 } s->port = port;
373
374 size_t path_cap = 2;
375 for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next)
376 path_cap += (size_t)(seg->text.afterLast - seg->text.first) + 1;
377
378 char *path = malloc(path_cap + 1);
379 size_t pos = 0;
380
381 for (UriPathSegmentA *seg = uri->pathHead; seg; seg = seg->next) {
382 path[pos++] = '/';
383 size_t seglen = (size_t)(seg->text.afterLast - seg->text.first);
384 memcpy(path + pos, seg->text.first, seglen);
385 pos += seglen;
386 }
387
388 if (pos == 0) path[pos++] = '/';
389 path[pos] = '\0';
390 s->pathname = path;
391
392 char *query = uri_range_dup(&uri->query);
393 if (*query) {
394 size_t qlen = strlen(query);
395 s->search = malloc(qlen + 2);
396 s->search[0] = '?';
397 memcpy(s->search + 1, query, qlen + 1);
398 } else s->search = strdup("");
399 free(query);
400
401 char *frag = uri_range_dup(&uri->fragment);
402 if (*frag) {
403 size_t flen = strlen(frag);
404 s->hash = malloc(flen + 2);
405 s->hash[0] = '#';
406 memcpy(s->hash + 1, frag, flen + 1);
407 } else s->hash = strdup("");
408 free(frag);
409}
410
411int parse_url_to_state(const char *url_str, const char *base_str, url_state_t *s) {
412 memset(s, 0, sizeof(*s));
413 const char *errpos;
414
415 if (base_str) {
416 UriUriA base_uri, ref_uri, resolved;
417 char *escaped_base = NULL;
418 char *escaped_ref = NULL;
419 bool used_relaxed_ref_parse = false;
420
421 if (url_parse_single_uri_relaxed(&base_uri, base_str, &errpos, &escaped_base, NULL) != 0) {
422 free(escaped_base);
423 return -1;
424 }
425
426 char *base_scheme = uri_range_dup(&base_uri.scheme);
427 size_t bslen = strlen(base_scheme);
428 for (size_t i = 0; i < bslen; i++) base_scheme[i] = (char)tolower((unsigned char)base_scheme[i]);
429
430 char proto_buf[bslen + 2];
431 memcpy(proto_buf, base_scheme, bslen);
432 proto_buf[bslen] = ':';
433 proto_buf[bslen + 1] = '\0';
434 free(base_scheme);
435
436 if (url_base_is_opaque(base_str, proto_buf)) {
437 uriFreeUriMembersA(&base_uri);
438 free(escaped_base);
439 return -1;
440 }
441
442 if (url_parse_single_uri_relaxed(&ref_uri, url_str, &errpos, &escaped_ref, &used_relaxed_ref_parse) != 0) {
443 uriFreeUriMembersA(&base_uri);
444 free(escaped_base);
445 free(escaped_ref);
446 return -1;
447 }
448
449 if (uriAddBaseUriA(&resolved, &ref_uri, &base_uri) != URI_SUCCESS) {
450 uriFreeUriMembersA(&base_uri);
451 uriFreeUriMembersA(&ref_uri);
452 free(escaped_base);
453 free(escaped_ref);
454 return -1;
455 }
456
457 uriNormalizeSyntaxA(&resolved);
458 if (!resolved.scheme.first || resolved.scheme.first == resolved.scheme.afterLast) {
459 uriFreeUriMembersA(&resolved);
460 uriFreeUriMembersA(&ref_uri);
461 uriFreeUriMembersA(&base_uri);
462 free(escaped_base);
463 free(escaped_ref);
464 return -1;
465 }
466
467 uri_to_state(&resolved, s);
468 if (used_relaxed_ref_parse) url_override_search_hash_from_reference(s, url_str);
469 uriFreeUriMembersA(&resolved);
470 uriFreeUriMembersA(&ref_uri);
471 uriFreeUriMembersA(&base_uri);
472 free(escaped_ref);
473 free(escaped_base);
474
475 return 0;
476 }
477
478 UriUriA uri;
479 char *escaped_url = NULL;
480 bool used_relaxed_query_parse = false;
481
482 if (url_parse_single_uri_relaxed(&uri, url_str, &errpos, &escaped_url, &used_relaxed_query_parse) != 0) {
483 free(escaped_url);
484 return -1;
485 }
486
487 if (!uri.scheme.first || uri.scheme.first == uri.scheme.afterLast) {
488 uriFreeUriMembersA(&uri);
489 free(escaped_url);
490 return -1;
491 }
492
493 uriNormalizeSyntaxA(&uri);
494 uri_to_state(&uri, s);
495 if (used_relaxed_query_parse) url_override_search_hash_from_input(s, url_str);
496
497 uriFreeUriMembersA(&uri);
498 free(escaped_url);
499
500 return 0;
501}
502
503char *build_href(const url_state_t *s) {
504 bool has_authority =
505 (s->hostname && *s->hostname) ||
506 (s->username && *s->username) ||
507 (s->password && *s->password) ||
508 (s->port && *s->port);
509
510 bool opaque_like = !has_authority && !uses_authority_syntax(s->protocol);
511 const char *pathname = s->pathname ? s->pathname : "";
512 size_t len = strlen(s->protocol) + strlen(pathname) + strlen(s->search) + strlen(s->hash) + 32;
513
514 if (has_authority) len += strlen(s->hostname) + 2;
515 if (s->username && *s->username) len += strlen(s->username) + 1;
516 if (s->password && *s->password) len += strlen(s->password) + 1;
517 if (s->port && *s->port) len += strlen(s->port) + 1;
518
519 char *href = malloc(len);
520 if (!href) return strdup("");
521
522 size_t pos = 0;
523 pos += (size_t)sprintf(href + pos, "%s", s->protocol);
524
525 if (opaque_like) {
526 if (pathname[0] == '/') pathname++;
527 pos += (size_t)sprintf(href + pos, "%s%s%s", pathname, s->search, s->hash);
528 href[pos] = '\0';
529 return href;
530 }
531
532 pos += (size_t)sprintf(href + pos, "//");
533 if (s->username && *s->username) {
534 pos += (size_t)sprintf(href + pos, "%s", s->username);
535 if (s->password && *s->password)
536 pos += (size_t)sprintf(href + pos, ":%s", s->password);
537 href[pos++] = '@';
538 }
539
540 pos += (size_t)sprintf(href + pos, "%s", s->hostname);
541 if (s->port && *s->port)
542 pos += (size_t)sprintf(href + pos, ":%s", s->port);
543
544 pos += (size_t)sprintf(href + pos, "%s%s%s", pathname, s->search, s->hash);
545 href[pos] = '\0';
546 return href;
547}
548
549static const char *coerce_to_string(ant_t *js, ant_value_t val, size_t *len) {
550 if (vtype(val) == T_STR) return js_getstr(js, val, len);
551 if (is_object_type(val)) {
552 ant_value_t href = js_getprop_fallback(js, val, "href");
553 if (vtype(href) == T_STR) return js_getstr(js, href, len);
554 }
555 return NULL;
556}
557
558static ant_value_t parse_query_to_arr(ant_t *js, const char *query) {
559 ant_value_t arr = js_mkarr(js);
560 if (!query || !*query) return arr;
561 const char *p = query;
562
563 while (*p) {
564 const char *amp = strchr(p, '&');
565 size_t plen = amp ? (size_t)(amp - p) : strlen(p);
566 if (plen == 0) { p = amp ? amp + 1 : p + 1; continue; }
567 char *pair = strndup(p, plen);
568 if (!pair) { p = amp ? amp + 1 : p + plen; continue; }
569
570 char *eq = strchr(pair, '=');
571 char *raw_v = eq ? ((*eq = '\0'), eq + 1) : "";
572 char *k = form_urldecode(pair);
573 char *v = form_urldecode(raw_v);
574
575 ant_value_t entry = js_mkarr(js);
576 js_arr_push(js, entry, js_mkstr(js, k, strlen(k)));
577 js_arr_push(js, entry, js_mkstr(js, v, strlen(v)));
578 js_arr_push(js, arr, entry);
579 free(pair); free(k); free(v);
580 p = amp ? amp + 1 : p + plen;
581 }
582
583 return arr;
584}
585
586char *usp_serialize(ant_t *js, ant_value_t usp) {
587 ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
588 ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
589
590 size_t cap = 256;
591 char *buf = malloc(cap);
592 if (!buf) return strdup("");
593 size_t pos = 0;
594
595 for (ant_offset_t i = 0; i < len; i++) {
596 ant_value_t entry = js_arr_get(js, entries, i);
597 size_t klen = 0, vlen = 0;
598
599 char *k = js_getstr(js, js_arr_get(js, entry, 0), &klen);
600 char *v = js_getstr(js, js_arr_get(js, entry, 1), &vlen);
601 char *ek = k ? form_urlencode_n(k, klen) : strdup("");
602 char *ev = v ? form_urlencode_n(v, vlen) : strdup("");
603
604 size_t needed = strlen(ek) + strlen(ev) + 3;
605 if (pos + needed >= cap) {
606 cap = cap * 2 + needed;
607 buf = realloc(buf, cap);
608 if (!buf) { free(ek); free(ev); return strdup(""); }
609 }
610
611 if (pos > 0) buf[pos++] = '&';
612 pos += (size_t)sprintf(buf + pos, "%s=%s", ek, ev);
613 free(ek); free(ev);
614 }
615
616 buf[pos] = '\0';
617 return buf;
618}
619
620static void usp_push_to_url(ant_t *js, ant_value_t usp) {
621 ant_value_t url_obj = js_get_slot(usp, SLOT_DATA);
622 if (!is_special_object(url_obj)) return;
623 url_state_t *s = url_get_state(url_obj);
624
625 if (!s) return;
626 char *qs = usp_serialize(js, usp);
627 free(s->search);
628
629 if (*qs) {
630 size_t qlen = strlen(qs);
631 s->search = malloc(qlen + 2);
632 s->search[0] = '?';
633 memcpy(s->search + 1, qs, qlen + 1);
634 } else s->search = strdup("");
635
636 free(qs);
637}
638
639static void url_sync_usp(ant_t *js, ant_value_t url_obj, const char *query) {
640 ant_value_t usp = js_get_slot(url_obj, SLOT_ENTRIES);
641 if (!is_special_object(usp)) return;
642 ant_value_t new_entries = parse_query_to_arr(js, query);
643 js_set_slot_wb(js, usp, SLOT_ENTRIES, new_entries);
644}
645
646static ant_value_t make_usp_for_url(ant_t *js, ant_value_t url_obj, const char *query) {
647 ant_value_t usp = js_mkobj(js);
648 js_set_proto_init(usp, g_usp_proto);
649 js_set_slot(usp, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
650 js_set_slot_wb(js, usp, SLOT_DATA, url_obj);
651 js_set_slot(usp, SLOT_ENTRIES, parse_query_to_arr(js, query));
652 return usp;
653}
654
655static ant_value_t url_get_href(ant_t *js, ant_value_t *args, int nargs) {
656 url_state_t *s = url_get_state(js->this_val);
657 if (!s) return js_mkstr(js, "", 0);
658 char *href = build_href(s);
659 ant_value_t ret = js_mkstr(js, href, strlen(href));
660 free(href);
661 return ret;
662}
663
664static ant_value_t url_get_protocol(ant_t *js, ant_value_t *args, int nargs) {
665 url_state_t *s = url_get_state(js->this_val);
666 if (!s) return js_mkstr(js, "", 0);
667 return js_mkstr(js, s->protocol, strlen(s->protocol));
668}
669
670static ant_value_t url_get_username(ant_t *js, ant_value_t *args, int nargs) {
671 url_state_t *s = url_get_state(js->this_val);
672 if (!s) return js_mkstr(js, "", 0);
673 return js_mkstr(js, s->username, strlen(s->username));
674}
675
676static ant_value_t url_get_password(ant_t *js, ant_value_t *args, int nargs) {
677 url_state_t *s = url_get_state(js->this_val);
678 if (!s) return js_mkstr(js, "", 0);
679 return js_mkstr(js, s->password, strlen(s->password));
680}
681
682static ant_value_t url_get_host(ant_t *js, ant_value_t *args, int nargs) {
683 url_state_t *s = url_get_state(js->this_val);
684 if (!s) return js_mkstr(js, "", 0);
685 if (s->port && *s->port) {
686 size_t len = strlen(s->hostname) + strlen(s->port) + 2;
687 char *host = malloc(len);
688 snprintf(host, len, "%s:%s", s->hostname, s->port);
689 ant_value_t ret = js_mkstr(js, host, strlen(host));
690 free(host);
691 return ret;
692 }
693 return js_mkstr(js, s->hostname, strlen(s->hostname));
694}
695
696static ant_value_t url_get_hostname(ant_t *js, ant_value_t *args, int nargs) {
697 url_state_t *s = url_get_state(js->this_val);
698 if (!s) return js_mkstr(js, "", 0);
699 return js_mkstr(js, s->hostname, strlen(s->hostname));
700}
701
702static ant_value_t url_get_port(ant_t *js, ant_value_t *args, int nargs) {
703 url_state_t *s = url_get_state(js->this_val);
704 if (!s) return js_mkstr(js, "", 0);
705 return js_mkstr(js, s->port, strlen(s->port));
706}
707
708static ant_value_t url_get_pathname(ant_t *js, ant_value_t *args, int nargs) {
709 url_state_t *s = url_get_state(js->this_val);
710 if (!s) return js_mkstr(js, "/", 1);
711 return js_mkstr(js, s->pathname, strlen(s->pathname));
712}
713
714static ant_value_t url_get_search(ant_t *js, ant_value_t *args, int nargs) {
715 url_state_t *s = url_get_state(js->this_val);
716 if (!s) return js_mkstr(js, "", 0);
717 return js_mkstr(js, s->search, strlen(s->search));
718}
719
720static ant_value_t url_get_hash(ant_t *js, ant_value_t *args, int nargs) {
721 url_state_t *s = url_get_state(js->this_val);
722 if (!s) return js_mkstr(js, "", 0);
723 return js_mkstr(js, s->hash, strlen(s->hash));
724}
725
726static ant_value_t url_get_origin(ant_t *js, ant_value_t *args, int nargs) {
727 url_state_t *s = url_get_state(js->this_val);
728 if (!s || !is_special_scheme(s->protocol)) return js_mkstr(js, "null", 4);
729
730 size_t proto_len = strlen(s->protocol) - 1;
731 size_t host_len = strlen(s->hostname);
732 size_t port_len = (s->port && *s->port) ? strlen(s->port) + 1 : 0;
733 size_t total = proto_len + 3 + host_len + port_len + 1;
734 char *origin = malloc(total);
735
736 size_t pos = 0;
737 memcpy(origin + pos, s->protocol, proto_len); pos += proto_len;
738 memcpy(origin + pos, "://", 3); pos += 3;
739 memcpy(origin + pos, s->hostname, host_len); pos += host_len;
740
741 if (s->port && *s->port) {
742 origin[pos++] = ':';
743 memcpy(origin + pos, s->port, strlen(s->port));
744 pos += strlen(s->port);
745 }
746
747 origin[pos] = '\0';
748 ant_value_t ret = js_mkstr(js, origin, pos);
749 free(origin);
750
751 return ret;
752}
753
754static ant_value_t url_get_searchParams(ant_t *js, ant_value_t *args, int nargs) {
755 ant_value_t usp = js_get_slot(js->this_val, SLOT_ENTRIES);
756 if (vtype(usp) == T_OBJ) return usp;
757 return js_mkundef();
758}
759
760static ant_value_t url_set_href(ant_t *js, ant_value_t *args, int nargs) {
761 if (nargs < 1) return js_mkundef();
762 url_state_t *s = url_get_state(js->this_val);
763 if (!s) return js_mkundef();
764
765 const char *val = js_getstr(js, args[0], NULL);
766 if (!val) return js_mkerr(js, "TypeError: Invalid URL");
767
768 url_state_t tmp;
769 if (parse_url_to_state(val, NULL, &tmp) != 0)
770 return js_mkerr(js, "TypeError: Invalid URL");
771
772 free(s->protocol); s->protocol = tmp.protocol;
773 free(s->username); s->username = tmp.username;
774 free(s->password); s->password = tmp.password;
775 free(s->hostname); s->hostname = tmp.hostname;
776 free(s->port); s->port = tmp.port;
777 free(s->pathname); s->pathname = tmp.pathname;
778 free(s->search); s->search = tmp.search;
779 free(s->hash); s->hash = tmp.hash;
780
781 const char *q = (s->search[0] == '?') ? s->search + 1 : "";
782 url_sync_usp(js, js->this_val, q);
783
784 return js_mkundef();
785}
786
787static ant_value_t url_set_protocol(ant_t *js, ant_value_t *args, int nargs) {
788 if (nargs < 1) return js_mkundef();
789 url_state_t *s = url_get_state(js->this_val);
790
791 if (!s) return js_mkundef();
792 const char *val = js_getstr(js, args[0], NULL);
793
794 if (!val || !*val) return js_mkundef();
795 const char *colon = strchr(val, ':');
796
797 size_t slen = colon ? (size_t)(colon - val) : strlen(val);
798 if (!slen || !isalpha((unsigned char)val[0])) return js_mkundef();
799
800 for (size_t i = 1; i < slen; i++) {
801 unsigned char c = (unsigned char)val[i];
802 if (!isalnum(c) && c != '+' && c != '-' && c != '.') return js_mkundef();
803 }
804
805 free(s->protocol);
806 s->protocol = malloc(slen + 2);
807 for (size_t i = 0; i < slen; i++) s->protocol[i] = (char)tolower((unsigned char)val[i]);
808 s->protocol[slen] = ':';
809 s->protocol[slen + 1] = '\0';
810
811 if (s->port && *s->port) {
812 int def = default_port_for(s->protocol);
813 if (def > 0 && atoi(s->port) == def) { free(s->port); s->port = strdup(""); }
814 }
815
816 return js_mkundef();
817}
818
819static ant_value_t url_set_username(ant_t *js, ant_value_t *args, int nargs) {
820 if (nargs < 1) return js_mkundef();
821 url_state_t *s = url_get_state(js->this_val);
822 if (!s) return js_mkundef();
823 const char *val = js_getstr(js, args[0], NULL);
824 if (!val) return js_mkundef();
825 free(s->username);
826 s->username = userinfo_encode(val);
827 return js_mkundef();
828}
829
830static ant_value_t url_set_password(ant_t *js, ant_value_t *args, int nargs) {
831 if (nargs < 1) return js_mkundef();
832 url_state_t *s = url_get_state(js->this_val);
833 if (!s) return js_mkundef();
834 const char *val = js_getstr(js, args[0], NULL);
835 if (!val) return js_mkundef();
836 free(s->password);
837 s->password = userinfo_encode(val);
838 return js_mkundef();
839}
840
841static ant_value_t url_set_host(ant_t *js, ant_value_t *args, int nargs) {
842 if (nargs < 1) return js_mkundef();
843 url_state_t *s = url_get_state(js->this_val);
844 if (!s) return js_mkundef();
845 const char *val = js_getstr(js, args[0], NULL);
846 if (!val) return js_mkundef();
847 const char *colon = strchr(val, ':');
848
849 if (colon) {
850 free(s->hostname);
851 s->hostname = strndup(val, (size_t)(colon - val));
852 const char *port_str = colon + 1;
853 free(s->port);
854 if (*port_str) {
855 int p = atoi(port_str);
856 int def = default_port_for(s->protocol);
857 s->port = (def > 0 && p == def) ? strdup("") : strdup(port_str);
858 } else s->port = strdup("");
859 } else {
860 free(s->hostname);
861 s->hostname = strdup(val);
862 }
863
864 return js_mkundef();
865}
866
867static ant_value_t url_set_hostname(ant_t *js, ant_value_t *args, int nargs) {
868 if (nargs < 1) return js_mkundef();
869 url_state_t *s = url_get_state(js->this_val);
870 if (!s) return js_mkundef();
871 const char *val = js_getstr(js, args[0], NULL);
872 if (!val) return js_mkundef();
873 free(s->hostname);
874 const char *colon = strchr(val, ':');
875 s->hostname = colon ? strndup(val, (size_t)(colon - val)) : strdup(val);
876 return js_mkundef();
877}
878
879static ant_value_t url_set_port(ant_t *js, ant_value_t *args, int nargs) {
880 if (nargs < 1) return js_mkundef();
881 url_state_t *s = url_get_state(js->this_val);
882 if (!s) return js_mkundef();
883 const char *val = js_getstr(js, args[0], NULL);
884 if (!val) return js_mkundef();
885 free(s->port);
886 if (!*val) { s->port = strdup(""); return js_mkundef(); }
887 int p = atoi(val);
888 if (p < 0 || p > 65535) { s->port = strdup(""); return js_mkundef(); }
889 int def = default_port_for(s->protocol);
890 if (def > 0 && p == def) s->port = strdup(""); else {
891 char buf[8];
892 snprintf(buf, sizeof(buf), "%d", p);
893 s->port = strdup(buf);
894 }
895 return js_mkundef();
896}
897
898static ant_value_t url_set_pathname(ant_t *js, ant_value_t *args, int nargs) {
899 if (nargs < 1) return js_mkundef();
900 url_state_t *s = url_get_state(js->this_val);
901 if (!s) return js_mkundef();
902 const char *val = js_getstr(js, args[0], NULL);
903 if (!val) return js_mkundef();
904 free(s->pathname);
905 if (is_special_scheme(s->protocol) && val[0] != '/') {
906 size_t vlen = strlen(val);
907 s->pathname = malloc(vlen + 2);
908 s->pathname[0] = '/';
909 memcpy(s->pathname + 1, val, vlen + 1);
910 } else s->pathname = strdup(val);
911 return js_mkundef();
912}
913
914static ant_value_t url_set_search(ant_t *js, ant_value_t *args, int nargs) {
915 if (nargs < 1) return js_mkundef();
916 url_state_t *s = url_get_state(js->this_val);
917 if (!s) return js_mkundef();
918 const char *val = js_getstr(js, args[0], NULL);
919 if (!val) return js_mkundef();
920 const char *q = (val[0] == '?') ? val + 1 : val;
921 free(s->search);
922 if (*q) {
923 size_t qlen = strlen(q);
924 s->search = malloc(qlen + 2);
925 s->search[0] = '?';
926 memcpy(s->search + 1, q, qlen + 1);
927 } else s->search = strdup("");
928 url_sync_usp(js, js->this_val, *q ? q : "");
929 return js_mkundef();
930}
931
932static ant_value_t url_set_hash(ant_t *js, ant_value_t *args, int nargs) {
933 if (nargs < 1) return js_mkundef();
934 url_state_t *s = url_get_state(js->this_val);
935 if (!s) return js_mkundef();
936 const char *val = js_getstr(js, args[0], NULL);
937 if (!val) return js_mkundef();
938 const char *h = (val[0] == '#') ? val + 1 : val;
939 free(s->hash);
940 if (*h) {
941 size_t hlen = strlen(h);
942 s->hash = malloc(hlen + 2);
943 s->hash[0] = '#';
944 memcpy(s->hash + 1, h, hlen + 1);
945 } else s->hash = strdup("");
946 return js_mkundef();
947}
948
949static ant_value_t url_toString(ant_t *js, ant_value_t *args, int nargs) {
950 url_state_t *s = url_get_state(js->this_val);
951 if (!s) return js_mkstr(js, "", 0);
952 char *href = build_href(s);
953 ant_value_t ret = js_mkstr(js, href, strlen(href));
954 free(href);
955 return ret;
956}
957
958static ant_value_t js_URL(ant_t *js, ant_value_t *args, int nargs) {
959 if (is_undefined(js->new_target))
960 return js_mkerr_typed(js, JS_ERR_TYPE,
961 "Failed to construct 'URL': Please use the 'new' operator.");
962 if (nargs < 1)
963 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': 1 argument required.");
964
965 ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
966 if (is_err(url_sv))
967 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
968 const char *url_str = js_getstr(js, url_sv, NULL);
969 if (!url_str)
970 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
971
972 const char *base_str = NULL;
973 if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
974 base_str = coerce_to_string(js, args[1], NULL);
975
976 url_state_t *s = calloc(1, sizeof(url_state_t));
977 if (!s) return js_mkerr(js, "out of memory");
978
979 if (parse_url_to_state(url_str, base_str, s) != 0) {
980 free(s);
981 return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to construct 'URL': Invalid URL.");
982 }
983
984 ant_value_t obj = js_mkobj(js);
985 js_set_proto_init(obj, g_url_proto);
986 js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
987 js_set_finalizer(obj, url_finalize);
988
989 const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
990 ant_value_t usp = make_usp_for_url(js, obj, query);
991 js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
992
993 return obj;
994}
995
996ant_value_t make_url_obj(ant_t *js, url_state_t *s) {
997 ant_value_t obj = js_mkobj(js);
998 js_set_proto_init(obj, g_url_proto);
999 js_set_slot(obj, SLOT_DATA, ANT_PTR(s));
1000 js_set_finalizer(obj, url_finalize);
1001 const char *query = (s->search && s->search[0] == '?') ? s->search + 1 : "";
1002 ant_value_t usp = make_usp_for_url(js, obj, query);
1003 js_set_slot_wb(js, obj, SLOT_ENTRIES, usp);
1004 return obj;
1005}
1006
1007static ant_value_t url_canParse(ant_t *js, ant_value_t *args, int nargs) {
1008 if (nargs < 1) return js_false;
1009 ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1010 if (is_err(url_sv)) return js_false;
1011 const char *url_str = js_getstr(js, url_sv, NULL);
1012 if (!url_str) return js_false;
1013 const char *base_str = NULL;
1014 if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
1015 base_str = coerce_to_string(js, args[1], NULL);
1016 url_state_t s;
1017 if (parse_url_to_state(url_str, base_str, &s) != 0) return js_false;
1018 url_state_clear(&s);
1019 return js_true;
1020}
1021
1022static ant_value_t url_parse(ant_t *js, ant_value_t *args, int nargs) {
1023 if (nargs < 1) return js_mknull();
1024 ant_value_t url_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1025 if (is_err(url_sv)) return js_mknull();
1026 const char *url_str = js_getstr(js, url_sv, NULL);
1027 if (!url_str) return js_mknull();
1028 const char *base_str = NULL;
1029 if (nargs > 1 && !is_undefined(args[1]) && !is_null(args[1]))
1030 base_str = coerce_to_string(js, args[1], NULL);
1031 url_state_t *s = calloc(1, sizeof(url_state_t));
1032 if (!s) return js_mknull();
1033 if (parse_url_to_state(url_str, base_str, s) != 0) {
1034 free(s);
1035 return js_mknull();
1036 }
1037 return make_url_obj(js, s);
1038}
1039
1040static ant_value_t usp_get(ant_t *js, ant_value_t *args, int nargs) {
1041 if (nargs < 1) return js_mknull();
1042 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1043 if (is_err(key_sv)) return js_mknull();
1044 const char *key = js_getstr(js, key_sv, NULL);
1045 if (!key) return js_mknull();
1046 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1047 if (!is_special_object(entries)) return js_mknull();
1048 ant_offset_t len = js_arr_len(js, entries);
1049 for (ant_offset_t i = 0; i < len; i++) {
1050 ant_value_t entry = js_arr_get(js, entries, i);
1051 const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
1052 if (ek && strcmp(ek, key) == 0) return js_arr_get(js, entry, 1);
1053 }
1054 return js_mknull();
1055}
1056
1057static ant_value_t usp_getAll(ant_t *js, ant_value_t *args, int nargs) {
1058 ant_value_t result = js_mkarr(js);
1059 if (nargs < 1) return result;
1060 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1061 if (is_err(key_sv)) return result;
1062 const char *key = js_getstr(js, key_sv, NULL);
1063 if (!key) return result;
1064 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1065 if (!is_special_object(entries)) return result;
1066 ant_offset_t len = js_arr_len(js, entries);
1067 for (ant_offset_t i = 0; i < len; i++) {
1068 ant_value_t entry = js_arr_get(js, entries, i);
1069 const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
1070 if (ek && strcmp(ek, key) == 0)
1071 js_arr_push(js, result, js_arr_get(js, entry, 1));
1072 }
1073 return result;
1074}
1075
1076static ant_value_t usp_has(ant_t *js, ant_value_t *args, int nargs) {
1077 if (nargs < 1) return js_false;
1078 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1079 if (is_err(key_sv)) return js_false;
1080 const char *key = js_getstr(js, key_sv, NULL);
1081 if (!key) return js_false;
1082 const char *match_val = NULL;
1083 if (nargs >= 2 && !is_undefined(args[1])) {
1084 ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
1085 if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
1086 }
1087 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1088 if (!is_special_object(entries)) return js_false;
1089 ant_offset_t len = js_arr_len(js, entries);
1090 for (ant_offset_t i = 0; i < len; i++) {
1091 ant_value_t entry = js_arr_get(js, entries, i);
1092 const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
1093 if (!ek || strcmp(ek, key) != 0) continue;
1094 if (!match_val) return js_true;
1095 const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
1096 if (ev && strcmp(ev, match_val) == 0) return js_true;
1097 }
1098 return js_false;
1099}
1100
1101static ant_value_t usp_set(ant_t *js, ant_value_t *args, int nargs) {
1102 if (nargs < 2) return js_mkundef();
1103 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1104
1105 if (is_err(key_sv)) return js_mkundef();
1106 ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
1107
1108 if (is_err(val_sv)) return js_mkundef();
1109 const char *key = js_getstr(js, key_sv, NULL);
1110
1111 if (!key) return js_mkundef();
1112 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1113 ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
1114 ant_value_t new_entries = js_mkarr(js);
1115
1116 int found = 0;
1117 for (ant_offset_t i = 0; i < len; i++) {
1118 ant_value_t entry = js_arr_get(js, entries, i);
1119 const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
1120
1121 if (ek && strcmp(ek, key) == 0) {
1122 if (!found) {
1123 ant_value_t ne = js_mkarr(js);
1124 js_arr_push(js, ne, key_sv);
1125 js_arr_push(js, ne, val_sv);
1126 js_arr_push(js, new_entries, ne);
1127 found = 1;
1128 }
1129 } else js_arr_push(js, new_entries, entry); }
1130
1131 if (!found) {
1132 ant_value_t ne = js_mkarr(js);
1133 js_arr_push(js, ne, key_sv);
1134 js_arr_push(js, ne, val_sv);
1135 js_arr_push(js, new_entries, ne);
1136 }
1137
1138 js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
1139 usp_push_to_url(js, js->this_val);
1140
1141 return js_mkundef();
1142}
1143
1144static ant_value_t usp_append(ant_t *js, ant_value_t *args, int nargs) {
1145 if (nargs < 2) return js_mkundef();
1146 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1147
1148 if (is_err(key_sv)) return js_mkundef();
1149 ant_value_t val_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
1150
1151 if (is_err(val_sv)) return js_mkundef();
1152 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1153
1154 if (!is_special_object(entries)) return js_mkundef();
1155 ant_value_t entry = js_mkarr(js);
1156
1157 js_arr_push(js, entry, key_sv);
1158 js_arr_push(js, entry, val_sv);
1159 js_arr_push(js, entries, entry);
1160 usp_push_to_url(js, js->this_val);
1161
1162 return js_mkundef();
1163}
1164
1165static ant_value_t usp_delete(ant_t *js, ant_value_t *args, int nargs) {
1166 if (nargs < 1) return js_mkundef();
1167 ant_value_t key_sv = (vtype(args[0]) == T_STR) ? args[0] : js_tostring_val(js, args[0]);
1168
1169 if (is_err(key_sv)) return js_mkundef();
1170 const char *key = js_getstr(js, key_sv, NULL);
1171
1172 if (!key) return js_mkundef();
1173 const char *match_val = NULL;
1174
1175 if (nargs >= 2 && !is_undefined(args[1])) {
1176 ant_value_t mv_sv = (vtype(args[1]) == T_STR) ? args[1] : js_tostring_val(js, args[1]);
1177 if (!is_err(mv_sv)) match_val = js_getstr(js, mv_sv, NULL);
1178 }
1179
1180 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1181 ant_offset_t len = is_special_object(entries) ? js_arr_len(js, entries) : 0;
1182 ant_value_t new_entries = js_mkarr(js);
1183
1184 for (ant_offset_t i = 0; i < len; i++) {
1185 ant_value_t entry = js_arr_get(js, entries, i);
1186 const char *ek = js_getstr(js, js_arr_get(js, entry, 0), NULL);
1187 if (ek && strcmp(ek, key) == 0) {
1188 if (!match_val) continue;
1189 const char *ev = js_getstr(js, js_arr_get(js, entry, 1), NULL);
1190 if (ev && strcmp(ev, match_val) == 0) continue;
1191 }
1192 js_arr_push(js, new_entries, entry);
1193 }
1194
1195 js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
1196 usp_push_to_url(js, js->this_val);
1197
1198 return js_mkundef();
1199}
1200
1201static ant_value_t usp_toString(ant_t *js, ant_value_t *args, int nargs) {
1202 char *s = usp_serialize(js, js->this_val);
1203 ant_value_t ret = js_mkstr(js, s, strlen(s));
1204 free(s);
1205 return ret;
1206}
1207
1208static ant_value_t usp_forEach(ant_t *js, ant_value_t *args, int nargs) {
1209 if (nargs < 1 || !is_callable(args[0])) return js_mkundef();
1210
1211 ant_value_t cb = args[0];
1212 ant_value_t this_arg = (nargs >= 2) ? args[1] : js_mkundef();
1213 ant_value_t self = js->this_val;
1214 ant_value_t entries = js_get_slot(self, SLOT_ENTRIES);
1215
1216 if (!is_special_object(entries)) return js_mkundef();
1217 ant_offset_t len = js_arr_len(js, entries);
1218
1219 for (ant_offset_t i = 0; i < len; i++) {
1220 ant_value_t entry = js_arr_get(js, entries, i);
1221 ant_value_t k = js_arr_get(js, entry, 0);
1222 ant_value_t v = js_arr_get(js, entry, 1);
1223 ant_value_t cb_args[3] = { v, k, self };
1224 ant_value_t r = sv_vm_call(js->vm, js, cb, this_arg, cb_args, 3, NULL, false);
1225 if (is_err(r)) return r;
1226 }
1227
1228 return js_mkundef();
1229}
1230
1231static ant_value_t usp_size_get(ant_t *js, ant_value_t *args, int nargs) {
1232 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1233 if (!is_special_object(entries)) return js_mknum(0);
1234 return js_mknum((double)js_arr_len(js, entries));
1235}
1236
1237static ant_value_t usp_sort(ant_t *js, ant_value_t *args, int nargs) {
1238 ant_value_t entries = js_get_slot(js->this_val, SLOT_ENTRIES);
1239 if (!is_special_object(entries)) return js_mkundef();
1240 ant_offset_t len = js_arr_len(js, entries);
1241 if (len <= 1) return js_mkundef();
1242
1243 ant_value_t *arr = malloc(sizeof(ant_value_t) * (size_t)len);
1244 if (!arr) return js_mkundef();
1245 for (ant_offset_t i = 0; i < len; i++) arr[i] = js_arr_get(js, entries, i);
1246
1247 for (ant_offset_t i = 1; i < len; i++) {
1248 ant_value_t cur = arr[i];
1249 const char *ck = js_getstr(js, js_arr_get(js, cur, 0), NULL);
1250 ant_offset_t j = i;
1251
1252 while (j > 0) {
1253 const char *jk = js_getstr(js, js_arr_get(js, arr[j - 1], 0), NULL);
1254 if (strcmp(jk ? jk : "", ck ? ck : "") <= 0) break;
1255 arr[j] = arr[j - 1]; j--;
1256 }
1257
1258 arr[j] = cur;
1259 }
1260
1261 ant_value_t new_entries = js_mkarr(js);
1262 for (ant_offset_t i = 0; i < len; i++) js_arr_push(js, new_entries, arr[i]);
1263 free(arr);
1264
1265 js_set_slot_wb(js, js->this_val, SLOT_ENTRIES, new_entries);
1266 usp_push_to_url(js, js->this_val);
1267
1268 return js_mkundef();
1269}
1270
1271static ant_value_t usp_iter_next(ant_t *js, ant_value_t *args, int nargs) {
1272 ant_value_t state_v = js_get_slot(js->this_val, SLOT_ITER_STATE);
1273 if (vtype(state_v) != T_NUM) return js_iter_result(js, false, js_mkundef());
1274
1275 uint32_t state = (uint32_t)js_getnum(state_v);
1276 uint32_t kind = ITER_STATE_KIND(state);
1277 uint32_t idx = ITER_STATE_INDEX(state);
1278
1279 ant_value_t usp = js_get_slot(js->this_val, SLOT_DATA);
1280 ant_value_t entries = js_get_slot(usp, SLOT_ENTRIES);
1281 if (!is_special_object(entries) || (ant_offset_t)idx >= js_arr_len(js, entries))
1282 return js_iter_result(js, false, js_mkundef());
1283
1284 js_set_slot(js->this_val, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, idx + 1)));
1285
1286 ant_value_t entry = js_arr_get(js, entries, (ant_offset_t)idx);
1287 ant_value_t k = js_arr_get(js, entry, 0);
1288 ant_value_t v = js_arr_get(js, entry, 1);
1289
1290 ant_value_t out;
1291 switch (kind) {
1292 case USP_ITER_KEYS: out = k; break;
1293 case USP_ITER_VALUES: out = v; break;
1294 default: {
1295 out = js_mkarr(js);
1296 js_arr_push(js, out, k);
1297 js_arr_push(js, out, v);
1298 break;
1299 }}
1300
1301 return js_iter_result(js, true, out);
1302}
1303
1304static ant_value_t make_usp_iter(ant_t *js, ant_value_t usp, int kind) {
1305 ant_value_t iter = js_mkobj(js);
1306 js_set_proto_init(iter, g_usp_iter_proto);
1307 js_set_slot_wb(js, iter, SLOT_DATA, usp);
1308 js_set_slot(iter, SLOT_ITER_STATE, js_mknum((double)ITER_STATE_PACK(kind, 0)));
1309 return iter;
1310}
1311
1312static ant_value_t usp_entries_fn(ant_t *js, ant_value_t *args, int nargs) {
1313 return make_usp_iter(js, js->this_val, USP_ITER_ENTRIES);
1314}
1315
1316static ant_value_t usp_keys_fn(ant_t *js, ant_value_t *args, int nargs) {
1317 return make_usp_iter(js, js->this_val, USP_ITER_KEYS);
1318}
1319
1320static ant_value_t usp_values_fn(ant_t *js, ant_value_t *args, int nargs) {
1321 return make_usp_iter(js, js->this_val, USP_ITER_VALUES);
1322}
1323
1324static ant_value_t js_URLSearchParams(ant_t *js, ant_value_t *args, int nargs) {
1325 if (is_undefined(js->new_target))
1326 return js_mkerr_typed(js, JS_ERR_TYPE,
1327 "Failed to construct 'URLSearchParams': Please use the 'new' operator.");
1328
1329 ant_value_t obj = js_mkobj(js);
1330 js_set_proto_init(obj, g_usp_proto);
1331 js_set_slot(obj, SLOT_BRAND, js_mknum(BRAND_URLSEARCHPARAMS));
1332 js_set_slot(obj, SLOT_DATA, js_mkundef());
1333
1334 ant_value_t entries = js_mkarr(js);
1335 js_set_slot(obj, SLOT_ENTRIES, entries);
1336
1337 if (nargs < 1 || is_undefined(args[0]) || is_null(args[0])) return obj;
1338
1339 ant_value_t init = args[0];
1340 uint8_t t = vtype(init);
1341
1342 if (t == T_STR) {
1343 const char *s = js_getstr(js, init, NULL);
1344 if (s) {
1345 const char *q = (s[0] == '?') ? s + 1 : s;
1346 js_set_slot(obj, SLOT_ENTRIES, parse_query_to_arr(js, q));
1347 }
1348 return obj;
1349 }
1350
1351 if (t == T_ARR) {
1352 ant_offset_t len = js_arr_len(js, init);
1353 for (ant_offset_t i = 0; i < len; i++) {
1354 ant_value_t pair = js_arr_get(js, init, i);
1355 if (vtype(pair) != T_ARR)
1356 return js_mkerr_typed(js, JS_ERR_TYPE,
1357 "Failed to construct 'URLSearchParams': Each element must be an array.");
1358
1359 ant_offset_t plen = js_arr_len(js, pair);
1360 if (plen != 2)
1361 return js_mkerr_typed(js, JS_ERR_TYPE,
1362 "Failed to construct 'URLSearchParams': Each pair must have exactly 2 elements.");
1363
1364 ant_value_t pk = js_arr_get(js, pair, 0);
1365 ant_value_t pv = js_arr_get(js, pair, 1);
1366 ant_value_t ksv = (vtype(pk) == T_STR) ? pk : js_tostring_val(js, pk);
1367
1368 if (is_err(ksv)) return ksv;
1369 ant_value_t vsv = (vtype(pv) == T_STR) ? pv : js_tostring_val(js, pv);
1370
1371 if (is_err(vsv)) return vsv;
1372 ant_value_t entry = js_mkarr(js);
1373
1374 js_arr_push(js, entry, ksv);
1375 js_arr_push(js, entry, vsv);
1376 js_arr_push(js, entries, entry);
1377 }
1378
1379 return obj;
1380 }
1381
1382 if (is_special_object(init)) {
1383 ant_value_t src = js_get_slot(init, SLOT_ENTRIES);
1384 if (vtype(src) == T_ARR) {
1385 ant_offset_t len = js_arr_len(js, src);
1386 for (ant_offset_t i = 0; i < len; i++) {
1387 ant_value_t entry = js_arr_get(js, src, i);
1388 ant_value_t ne = js_mkarr(js);
1389
1390 js_arr_push(js, ne, js_arr_get(js, entry, 0));
1391 js_arr_push(js, ne, js_arr_get(js, entry, 1));
1392 js_arr_push(js, entries, ne);
1393 }
1394
1395 return obj;
1396 }
1397
1398 ant_iter_t it = js_prop_iter_begin(js, init);
1399 const char *key;
1400 size_t key_len;
1401 ant_value_t val;
1402
1403 while (js_prop_iter_next(&it, &key, &key_len, &val)) {
1404 ant_value_t sv = (vtype(val) == T_STR) ? val : js_tostring_val(js, val);
1405 if (is_err(sv)) { js_prop_iter_end(&it); return sv; }
1406 ant_value_t entry = js_mkarr(js);
1407 js_arr_push(js, entry, js_mkstr(js, key, key_len));
1408 js_arr_push(js, entry, sv);
1409 js_arr_push(js, entries, entry);
1410 }
1411
1412 js_prop_iter_end(&it);
1413 }
1414
1415 return obj;
1416}
1417
1418void init_url_module(void) {
1419 ant_t *js = rt->js;
1420 ant_value_t glob = js->global;
1421
1422 g_usp_iter_proto = js_mkobj(js);
1423 js_set_proto_init(g_usp_iter_proto, js->sym.iterator_proto);
1424 js_set(js, g_usp_iter_proto, "next", js_mkfun(usp_iter_next));
1425 js_set_descriptor(js, g_usp_iter_proto, "next", 4, JS_DESC_W | JS_DESC_E | JS_DESC_C);
1426 js_set_sym(js, g_usp_iter_proto, get_iterator_sym(), js_mkfun(sym_this_cb));
1427
1428 g_usp_proto = js_mkobj(js);
1429 js_set(js, g_usp_proto, "get", js_mkfun(usp_get));
1430 js_set(js, g_usp_proto, "getAll", js_mkfun(usp_getAll));
1431 js_set(js, g_usp_proto, "has", js_mkfun(usp_has));
1432 js_set(js, g_usp_proto, "set", js_mkfun(usp_set));
1433 js_set(js, g_usp_proto, "append", js_mkfun(usp_append));
1434 js_set(js, g_usp_proto, "delete", js_mkfun(usp_delete));
1435 js_set(js, g_usp_proto, "sort", js_mkfun(usp_sort));
1436 js_set(js, g_usp_proto, "toString", js_mkfun(usp_toString));
1437 js_set(js, g_usp_proto, "forEach", js_mkfun(usp_forEach));
1438 js_set_getter_desc(js, g_usp_proto, "size", 4, js_mkfun(usp_size_get), JS_DESC_C);
1439
1440 js_set(js, g_usp_proto, "entries", js_mkfun(usp_entries_fn));
1441 js_set(js, g_usp_proto, "keys", js_mkfun(usp_keys_fn));
1442 js_set(js, g_usp_proto, "values", js_mkfun(usp_values_fn));
1443
1444 js_set_sym(js, g_usp_proto, get_iterator_sym(), js_get(js, g_usp_proto, "entries"));
1445 js_set_sym(js, g_usp_proto, get_toStringTag_sym(), js_mkstr(js, "URLSearchParams", 15));
1446
1447 ant_value_t usp_ctor = js_make_ctor(js, js_URLSearchParams, g_usp_proto, "URLSearchParams", 15);
1448 js_set(js, glob, "URLSearchParams", usp_ctor);
1449
1450 g_url_proto = js_mkobj(js);
1451 js_set_accessor_desc(js, g_url_proto, "href", 4, js_mkfun(url_get_href), js_mkfun(url_set_href), JS_DESC_C);
1452 js_set_accessor_desc(js, g_url_proto, "protocol", 8, js_mkfun(url_get_protocol), js_mkfun(url_set_protocol), JS_DESC_C);
1453 js_set_accessor_desc(js, g_url_proto, "username", 8, js_mkfun(url_get_username), js_mkfun(url_set_username), JS_DESC_C);
1454 js_set_accessor_desc(js, g_url_proto, "password", 8, js_mkfun(url_get_password), js_mkfun(url_set_password), JS_DESC_C);
1455 js_set_accessor_desc(js, g_url_proto, "host", 4, js_mkfun(url_get_host), js_mkfun(url_set_host), JS_DESC_C);
1456 js_set_accessor_desc(js, g_url_proto, "hostname", 8, js_mkfun(url_get_hostname), js_mkfun(url_set_hostname), JS_DESC_C);
1457 js_set_accessor_desc(js, g_url_proto, "port", 4, js_mkfun(url_get_port), js_mkfun(url_set_port), JS_DESC_C);
1458 js_set_accessor_desc(js, g_url_proto, "pathname", 8, js_mkfun(url_get_pathname), js_mkfun(url_set_pathname), JS_DESC_C);
1459 js_set_accessor_desc(js, g_url_proto, "search", 6, js_mkfun(url_get_search), js_mkfun(url_set_search), JS_DESC_C);
1460 js_set_accessor_desc(js, g_url_proto, "hash", 4, js_mkfun(url_get_hash), js_mkfun(url_set_hash), JS_DESC_C);
1461 js_set_getter_desc(js, g_url_proto, "origin", 6, js_mkfun(url_get_origin), JS_DESC_C);
1462 js_set_getter_desc(js, g_url_proto, "searchParams", 12, js_mkfun(url_get_searchParams), JS_DESC_C);
1463 js_set(js, g_url_proto, "toString", js_mkfun(url_toString));
1464 js_set(js, g_url_proto, "toJSON", js_mkfun(url_toString));
1465 js_set_sym(js, g_url_proto, get_toStringTag_sym(), js_mkstr(js, "URL", 3));
1466
1467 ant_value_t url_ctor = js_make_ctor(js, js_URL, g_url_proto, "URL", 3);
1468 js_set(js, url_ctor, "canParse", js_mkfun(url_canParse));
1469 js_set(js, url_ctor, "parse", js_mkfun(url_parse));
1470 js_set(js, glob, "URL", url_ctor);
1471}
1472
1473static ant_value_t builtin_fileURLToPath(ant_t *js, ant_value_t *args, int nargs) {
1474 if (nargs < 1) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
1475
1476 size_t len;
1477 const char *str = coerce_to_string(js, args[0], &len);
1478 if (!str) return js_mkerr(js, "fileURLToPath requires a string or URL argument");
1479
1480 url_state_t s;
1481 if (parse_url_to_state(str, NULL, &s) != 0)
1482 return js_mkerr(js, "Invalid URL");
1483 if (strcmp(s.protocol, "file:") != 0) {
1484 url_state_clear(&s);
1485 return js_mkerr(js, "fileURLToPath requires a file: URL");
1486 }
1487
1488 char *decoded = url_decode_component(s.pathname);
1489 url_state_clear(&s);
1490 if (!decoded) return js_mkerr(js, "allocation failure");
1491 ant_value_t ret = js_mkstr(js, decoded, strlen(decoded));
1492
1493 free(decoded);
1494 return ret;
1495}
1496
1497static ant_value_t builtin_pathToFileURL(ant_t *js, ant_value_t *args, int nargs) {
1498 if (nargs < 1 || vtype(args[0]) != T_STR)
1499 return js_mkerr(js, "pathToFileURL requires a string argument");
1500
1501 size_t len;
1502 const char *path = js_getstr(js, args[0], &len);
1503 size_t total = 7 + len;
1504 char *buf = malloc(total + 1);
1505 if (!buf) return js_mkerr(js, "allocation failure");
1506
1507 memcpy(buf, "file://", 7);
1508 memcpy(buf + 7, path, len);
1509 buf[total] = '\0';
1510
1511 url_state_t *s = calloc(1, sizeof(url_state_t));
1512 if (!s) { free(buf); return js_mkerr(js, "allocation failure"); }
1513 if (parse_url_to_state(buf, NULL, s) != 0) {
1514 free(buf); free(s);
1515 return js_mkerr(js, "Invalid file URL");
1516 }
1517
1518 free(buf);
1519 return make_url_obj(js, s);
1520}
1521
1522typedef struct {
1523 char *buf;
1524 size_t len;
1525 size_t cap;
1526} url_fmt_buf_t;
1527
1528static bool url_fmt_reserve(url_fmt_buf_t *b, size_t extra) {
1529 if (extra <= b->cap - b->len) return true;
1530
1531 size_t needed = b->len + extra + 1;
1532 size_t next = b->cap ? b->cap : 128;
1533 while (next < needed) next *= 2;
1534
1535 char *buf = realloc(b->buf, next);
1536 if (!buf) return false;
1537 b->buf = buf;
1538 b->cap = next;
1539
1540 return true;
1541}
1542
1543static bool url_fmt_append_n(url_fmt_buf_t *b, const char *s, size_t n) {
1544 if (!s || n == 0) return true;
1545 if (!url_fmt_reserve(b, n)) return false;
1546 memcpy(b->buf + b->len, s, n);
1547 b->len += n;
1548 b->buf[b->len] = '\0';
1549 return true;
1550}
1551
1552static bool url_fmt_append(url_fmt_buf_t *b, const char *s) {
1553 return url_fmt_append_n(b, s, s ? strlen(s) : 0);
1554}
1555
1556static bool url_fmt_append_c(url_fmt_buf_t *b, char c) {
1557 if (!url_fmt_reserve(b, 1)) return false;
1558 b->buf[b->len++] = c;
1559 b->buf[b->len] = '\0';
1560 return true;
1561}
1562
1563static bool url_fmt_append_value_string(ant_t *js, url_fmt_buf_t *b, ant_value_t value) {
1564 ant_value_t str_val = vtype(value) == T_STR ? value : js_tostring_val(js, value);
1565 if (is_err(str_val)) return false;
1566
1567 size_t len = 0;
1568 const char *str = js_getstr(js, str_val, &len);
1569 return str && url_fmt_append_n(b, str, len);
1570}
1571
1572static bool url_fmt_get_string_prop(
1573 ant_t *js,
1574 ant_value_t obj,
1575 const char *name,
1576 ant_value_t *out,
1577 const char **str,
1578 size_t *len
1579) {
1580 *out = js_get(js, obj, name);
1581 if (is_undefined(*out) || is_null(*out)) return false;
1582
1583 ant_value_t str_val = vtype(*out) == T_STR ? *out : js_tostring_val(js, *out);
1584 if (is_err(str_val)) return false;
1585
1586 *out = str_val;
1587 *str = js_getstr(js, str_val, len);
1588 return *str != NULL;
1589}
1590
1591static bool url_fmt_is_query_unescaped(unsigned char c) {
1592 return isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~';
1593}
1594
1595static bool url_fmt_append_query_component(url_fmt_buf_t *b, const char *s, size_t len) {
1596 static const char hex[] = "0123456789ABCDEF";
1597
1598 for (size_t i = 0; i < len; i++) {
1599 unsigned char c = (unsigned char)s[i];
1600 if (url_fmt_is_query_unescaped(c)) {
1601 if (!url_fmt_append_c(b, (char)c)) return false;
1602 } else {
1603 char esc[3] = { '%', hex[c >> 4], hex[c & 0x0f] };
1604 if (!url_fmt_append_n(b, esc, sizeof(esc))) return false;
1605 }}
1606 return true;
1607}
1608
1609static bool url_fmt_append_query_object(ant_t *js, url_fmt_buf_t *b, ant_value_t query) {
1610 if (!is_special_object(query)) return true;
1611
1612 bool first = true;
1613 ant_iter_t it = js_prop_iter_begin(js, query);
1614
1615 const char *key;
1616 size_t key_len;
1617 ant_value_t val;
1618
1619 while (js_prop_iter_next(&it, &key, &key_len, &val)) {
1620 if (!first && !url_fmt_append_c(b, '&')) {
1621 js_prop_iter_end(&it);
1622 return false;
1623 }
1624 first = false;
1625
1626 if (!url_fmt_append_query_component(b, key, key_len)) {
1627 js_prop_iter_end(&it);
1628 return false;
1629 }
1630
1631 if (!url_fmt_append_c(b, '=')) {
1632 js_prop_iter_end(&it);
1633 return false;
1634 }
1635
1636 ant_value_t str_val = vtype(val) == T_STR ? val : js_tostring_val(js, val);
1637 if (is_err(str_val)) {
1638 js_prop_iter_end(&it);
1639 return false;
1640 }
1641
1642 size_t val_len = 0;
1643 const char *val_str = js_getstr(js, str_val, &val_len);
1644 if (!val_str || !url_fmt_append_query_component(b, val_str, val_len)) {
1645 js_prop_iter_end(&it);
1646 return false;
1647 }
1648 }
1649
1650 js_prop_iter_end(&it);
1651 return true;
1652}
1653
1654static bool url_fmt_protocol_needs_slashes(const char *protocol, size_t len) {
1655 if (len > 0 && protocol[len - 1] == ':') len--;
1656 return
1657 (len == 4 && memcmp(protocol, "http", 4) == 0) ||
1658 (len == 5 && memcmp(protocol, "https", 5) == 0) ||
1659 (len == 3 && memcmp(protocol, "ftp", 3) == 0) ||
1660 (len == 4 && memcmp(protocol, "file", 4) == 0) ||
1661 (len == 2 && memcmp(protocol, "ws", 2) == 0) ||
1662 (len == 3 && memcmp(protocol, "wss", 3) == 0);
1663}
1664
1665static ant_value_t builtin_url_format(ant_t *js, ant_value_t *args, int nargs) {
1666 if (nargs < 1 || !is_object_type(args[0]))
1667 return js_mkerr_typed(js, JS_ERR_TYPE, "url.format() requires a URL or object argument");
1668
1669 url_state_t *state = url_get_state(args[0]);
1670 if (state) {
1671 char *href = build_href(state);
1672 ant_value_t ret = js_mkstr(js, href, strlen(href));
1673 free(href);
1674 return ret;
1675 }
1676
1677 ant_value_t obj = args[0];
1678 ant_value_t tmp;
1679
1680 const char *protocol = NULL, *auth = NULL, *host = NULL, *hostname = NULL;
1681 const char *port = NULL, *pathname = NULL, *search = NULL, *hash = NULL;
1682
1683 size_t protocol_len = 0, auth_len = 0, host_len = 0, hostname_len = 0;
1684 size_t port_len = 0, pathname_len = 0, search_len = 0, hash_len = 0;
1685
1686 url_fmt_get_string_prop(js, obj, "protocol", &tmp, &protocol, &protocol_len);
1687 url_fmt_get_string_prop(js, obj, "auth", &tmp, &auth, &auth_len);
1688 url_fmt_get_string_prop(js, obj, "host", &tmp, &host, &host_len);
1689 url_fmt_get_string_prop(js, obj, "hostname", &tmp, &hostname, &hostname_len);
1690 url_fmt_get_string_prop(js, obj, "port", &tmp, &port, &port_len);
1691 url_fmt_get_string_prop(js, obj, "pathname", &tmp, &pathname, &pathname_len);
1692 url_fmt_get_string_prop(js, obj, "search", &tmp, &search, &search_len);
1693 url_fmt_get_string_prop(js, obj, "hash", &tmp, &hash, &hash_len);
1694
1695 url_fmt_buf_t b = {0};
1696
1697 if (protocol && protocol_len > 0) {
1698 if (!url_fmt_append_n(&b, protocol, protocol_len)) goto oom;
1699 if (protocol[protocol_len - 1] != ':' && !url_fmt_append_c(&b, ':')) goto oom;
1700 }
1701
1702 bool has_host = (host && host_len > 0) || (hostname && hostname_len > 0);
1703 ant_value_t slashes_val = js_get(js, obj, "slashes");
1704
1705 bool needs_slashes =
1706 js_truthy(js, slashes_val) ||
1707 (protocol && url_fmt_protocol_needs_slashes(protocol, protocol_len));
1708
1709 if (needs_slashes && (has_host || (protocol && protocol_len >= 4 && memcmp(protocol, "file", 4) == 0))) {
1710 if (!url_fmt_append(&b, "//")) goto oom;
1711 }
1712
1713 if (auth && auth_len > 0) {
1714 if (!url_fmt_append_n(&b, auth, auth_len)) goto oom;
1715 if (!url_fmt_append_c(&b, '@')) goto oom;
1716 }
1717
1718 if (host && host_len > 0) {
1719 if (!url_fmt_append_n(&b, host, host_len)) goto oom;
1720 } else if (hostname && hostname_len > 0) {
1721 if (!url_fmt_append_n(&b, hostname, hostname_len)) goto oom;
1722 if (port && port_len > 0) {
1723 if (!url_fmt_append_c(&b, ':')) goto oom;
1724 if (!url_fmt_append_n(&b, port, port_len)) goto oom;
1725 }}
1726
1727 if (pathname && pathname_len > 0) {
1728 if (has_host && pathname[0] != '/' && !url_fmt_append_c(&b, '/')) goto oom;
1729 if (!url_fmt_append_n(&b, pathname, pathname_len)) goto oom;
1730 }
1731
1732 if (search && search_len > 0) {
1733 if (search[0] != '?' && !url_fmt_append_c(&b, '?')) goto oom;
1734 if (!url_fmt_append_n(&b, search, search_len)) goto oom;
1735 } else {
1736 ant_value_t query = js_get(js, obj, "query");
1737 if (vtype(query) == T_STR) {
1738 size_t qlen = 0;
1739 const char *q = js_getstr(js, query, &qlen);
1740 if (q && qlen > 0) {
1741 if (!url_fmt_append_c(&b, '?')) goto oom;
1742 if (!url_fmt_append_n(&b, q, qlen)) goto oom;
1743 }
1744 } else if (is_special_object(query)) {
1745 url_fmt_buf_t qb = {0};
1746 if (!url_fmt_append_query_object(js, &qb, query)) {
1747 free(qb.buf);
1748 goto oom;
1749 }
1750 if (qb.len > 0) {
1751 if (!url_fmt_append_c(&b, '?')) {
1752 free(qb.buf);
1753 goto oom;
1754 }
1755 if (!url_fmt_append_n(&b, qb.buf, qb.len)) {
1756 free(qb.buf);
1757 goto oom;
1758 }}
1759 free(qb.buf);
1760 }
1761 }
1762
1763 if (hash && hash_len > 0) {
1764 if (hash[0] != '#' && !url_fmt_append_c(&b, '#')) goto oom;
1765 if (!url_fmt_append_n(&b, hash, hash_len)) goto oom;
1766 }
1767
1768 ant_value_t ret = js_mkstr(js, b.buf ? b.buf : "", b.len);
1769 free(b.buf);
1770 return ret;
1771
1772oom:
1773 free(b.buf);
1774 return js_mkerr(js, "allocation failure");
1775}
1776
1777ant_value_t url_library(ant_t *js) {
1778 ant_value_t lib = js_mkobj(js);
1779 ant_value_t glob = js_glob(js);
1780
1781 js_set(js, lib, "URL", js_get(js, glob, "URL"));
1782 js_set(js, lib, "URLSearchParams",js_get(js, glob, "URLSearchParams"));
1783 js_set(js, lib, "fileURLToPath", js_mkfun(builtin_fileURLToPath));
1784 js_set(js, lib, "pathToFileURL", js_mkfun(builtin_pathToFileURL));
1785 js_set(js, lib, "parse", js_mkfun(url_parse));
1786 js_set(js, lib, "format", js_mkfun(builtin_url_format));
1787 js_set(js, lib, "default", lib);
1788
1789 return lib;
1790}