@scriptc/runtime 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/scr_url.c CHANGED
@@ -7,6 +7,7 @@
7
7
  * - scheme lowercasing; "Invalid URL" TypeError on schemeless input
8
8
  * - special schemes (http/https/ws/wss/ftp/file): authority parsing with
9
9
  * host lowercasing, default-port removal (leading zeros stripped),
10
+ * canonical bracketed IPv6 literals,
10
11
  * backslash-as-slash, and any run of leading slashes tolerated
11
12
  * (http:foo.com works); file: takes an authority only after exactly
12
13
  * "//" (file:/x and file:x are host-less absolute paths, like Node)
@@ -21,8 +22,8 @@
21
22
  * path kept verbatim (data:text/plain,hi there)
22
23
  *
23
24
  * DOCUMENTED DIVERGENCES (SEMANTICS.md): non-ASCII and %-escaped hosts are
24
- * rejected with "Invalid URL" (no IDNA/punycode), IPv6 hosts are rejected,
25
- * and opaque paths skip the spec's C0-encode pass (kept verbatim).
25
+ * rejected with "Invalid URL" (no IDNA/punycode), and opaque paths skip
26
+ * the spec's C0-encode pass (kept verbatim).
26
27
  *
27
28
  * Failures THROW catchable TypeErrors through the exception cell with
28
29
  * Node's messages; callers are compiler-emitted pending checks. */
@@ -33,6 +34,12 @@
33
34
  #include <stdlib.h>
34
35
  #include <string.h>
35
36
  #include <unistd.h>
37
+ #ifdef _WIN32
38
+ #include <winsock2.h>
39
+ #include <ws2tcpip.h>
40
+ #else
41
+ #include <arpa/inet.h>
42
+ #endif
36
43
 
37
44
  ScrUrl *scr_url_retain(ScrUrl *u) {
38
45
  if (u->rc != SIZE_MAX) u->rc++;
@@ -94,6 +101,65 @@ static ScrStr *ub_take(UrlBuf *b) {
94
101
  return s;
95
102
  }
96
103
 
104
+ /* WHATWG's IPv6 serializer: lowercase hexadecimal with the first longest
105
+ * run of two-or-more zero pieces compressed. inet_pton supplies the parser,
106
+ * while serializing the eight pieces here also avoids the platform-specific
107
+ * dotted-decimal spelling inet_ntop uses for IPv4-mapped addresses. */
108
+ static bool ub_append_ipv6(UrlBuf *out, const char *raw, size_t len) {
109
+ char *text = malloc(len + 1);
110
+ if (!text) scr_trap("scriptc: out of memory\n");
111
+ memcpy(text, raw, len);
112
+ text[len] = '\0';
113
+ struct in6_addr address;
114
+ bool valid = inet_pton(AF_INET6, text, &address) == 1;
115
+ free(text);
116
+ if (!valid) return false;
117
+
118
+ const unsigned char *bytes = (const unsigned char *)&address;
119
+ uint16_t pieces[8];
120
+ for (size_t i = 0; i < 8; i++) {
121
+ pieces[i] = (uint16_t)(((uint16_t)bytes[i * 2] << 8) |
122
+ bytes[i * 2 + 1]);
123
+ }
124
+ size_t best_start = 0;
125
+ size_t best_len = 0;
126
+ for (size_t i = 0; i < 8;) {
127
+ if (pieces[i] != 0) {
128
+ i++;
129
+ continue;
130
+ }
131
+ size_t start = i;
132
+ while (i < 8 && pieces[i] == 0) i++;
133
+ size_t run = i - start;
134
+ if (run > best_len) {
135
+ best_start = start;
136
+ best_len = run;
137
+ }
138
+ }
139
+ if (best_len < 2) best_len = 0;
140
+
141
+ ub_push(out, '[');
142
+ bool first = true;
143
+ size_t compressed_end = best_start + best_len;
144
+ for (size_t i = 0; i < 8;) {
145
+ if (best_len > 0 && i == best_start) {
146
+ ub_append(out, "::", 2);
147
+ first = false;
148
+ i = compressed_end;
149
+ continue;
150
+ }
151
+ if (!first && (best_len == 0 || i != compressed_end)) ub_push(out, ':');
152
+ char hex[5];
153
+ int hex_len = snprintf(hex, sizeof hex, "%x", pieces[i]);
154
+ if (hex_len <= 0 || (size_t)hex_len >= sizeof hex) return false;
155
+ ub_append(out, hex, (size_t)hex_len);
156
+ first = false;
157
+ i++;
158
+ }
159
+ ub_push(out, ']');
160
+ return true;
161
+ }
162
+
97
163
  /* ── percent-encode sets (WHATWG) ────────────────────────────────────── */
98
164
 
99
165
  static bool enc_always(unsigned char c) {
@@ -273,25 +339,48 @@ static bool parse_authority(const char *raw, size_t len, bool special, bool is_f
273
339
  *userinfo = ub_take(&ub);
274
340
  const char *hp = at >= 0 ? raw + at + 1 : raw;
275
341
  size_t hp_len = at >= 0 ? len - (size_t)at - 1 : len;
276
- /* host[:port] no IPv6 ('[' rejected below). */
342
+ /* host[:port], with IPv6 literals bracketed per WHATWG. */
277
343
  long colon = -1;
278
- for (size_t i = 0; i < hp_len; i++) {
279
- if (hp[i] == ':') colon = (long)i;
344
+ size_t host_len = hp_len;
345
+ bool ipv6 = hp_len > 0 && hp[0] == '[';
346
+ size_t ipv6_end = 0;
347
+ if (ipv6) {
348
+ while (ipv6_end < hp_len && hp[ipv6_end] != ']') ipv6_end++;
349
+ if (ipv6_end == hp_len || ipv6_end == 1) return false;
350
+ host_len = ipv6_end + 1;
351
+ if (host_len < hp_len) {
352
+ if (hp[host_len] != ':') return false;
353
+ colon = (long)host_len;
354
+ }
355
+ } else {
356
+ for (size_t i = 0; i < hp_len; i++) {
357
+ if (hp[i] == ':') colon = (long)i;
358
+ }
359
+ host_len = colon >= 0 ? (size_t)colon : hp_len;
280
360
  }
281
- size_t host_len = colon >= 0 ? (size_t)colon : hp_len;
282
361
  /* Validate + lowercase (special) the host. Divergence: non-ASCII and
283
- * %-escapes (IDNA territory) and IPv6 are rejected outright. */
362
+ * %-escapes (IDNA territory) are rejected outright. */
284
363
  UrlBuf hb;
285
364
  ub_init(&hb);
286
- for (size_t i = 0; i < host_len; i++) {
287
- unsigned char c = (unsigned char)hp[i];
288
- if (c >= 0x80 || c == '%' || c == '[' || c == ']' || c <= 0x20 || c == '#' || c == '/' ||
289
- c == '<' || c == '>' || c == '?' || c == '@' || c == '\\' || c == '^' || c == '|') {
365
+ if (ipv6) {
366
+ if (!ub_append_ipv6(&hb, hp + 1, ipv6_end - 1)) {
290
367
  free(hb.data);
291
368
  return false;
292
369
  }
293
- if (special && c >= 'A' && c <= 'Z') c = (unsigned char)(c - 'A' + 'a');
294
- ub_push(&hb, (char)c);
370
+ } else {
371
+ for (size_t i = 0; i < host_len; i++) {
372
+ unsigned char c = (unsigned char)hp[i];
373
+ if (c >= 0x80 || c == '%' || c == ':' || c == '[' || c == ']' ||
374
+ c <= 0x20 || c == '#' || c == '/' || c == '<' || c == '>' ||
375
+ c == '?' || c == '@' || c == '\\' || c == '^' || c == '|') {
376
+ free(hb.data);
377
+ return false;
378
+ }
379
+ if (special && c >= 'A' && c <= 'Z') {
380
+ c = (unsigned char)(c - 'A' + 'a');
381
+ }
382
+ ub_push(&hb, (char)c);
383
+ }
295
384
  }
296
385
  /* file: "localhost" normalizes to "" at parse time (Node). */
297
386
  if (is_file && hb.len == 9 && memcmp(hb.data, "localhost", 9) == 0) hb.len = 0;
@@ -561,8 +650,7 @@ ScrStr *scr_url_host(ScrUrl *u) {
561
650
  }
562
651
 
563
652
  /* WHATWG hostname getter: the stored port-less host verbatim ("" for
564
- * authority-less URLs). Node would keep IPv6 brackets here; the parser
565
- * rejects IPv6 hosts (documented divergence), so none reach this getter. */
653
+ * authority-less URLs); IPv6 literals retain their brackets. */
566
654
  ScrStr *scr_url_hostname(ScrUrl *u) { return scr_str_retain(u->host); }
567
655
 
568
656
  ScrStr *scr_url_href(ScrUrl *u) {
package/src/scr_web.c CHANGED
@@ -294,6 +294,62 @@ static const char web_prelude[] =
294
294
  " return it;\n"
295
295
  " }\n"
296
296
  " [Symbol.asyncIterator](options) { return this.values(options); }\n"
297
+ " static from(iterable) {\n"
298
+ " if (iterable === undefined || iterable === null) {\n"
299
+ " throw new TypeError('ReadableStream.from requires an iterable');\n"
300
+ " }\n"
301
+ " const asyncMethod = iterable[Symbol.asyncIterator];\n"
302
+ " let method;\n"
303
+ " let asyncIterator = false;\n"
304
+ " if (asyncMethod !== undefined && asyncMethod !== null) {\n"
305
+ " if (typeof asyncMethod !== 'function') {\n"
306
+ " throw new TypeError('ReadableStream.from requires an iterable');\n"
307
+ " }\n"
308
+ " method = asyncMethod;\n"
309
+ " asyncIterator = true;\n"
310
+ " } else {\n"
311
+ " method = iterable[Symbol.iterator];\n"
312
+ " if (typeof method !== 'function') {\n"
313
+ " throw new TypeError('ReadableStream.from requires an iterable');\n"
314
+ " }\n"
315
+ " }\n"
316
+ " const iterator = method.call(iterable);\n"
317
+ " if ((typeof iterator !== 'object' || iterator === null) && typeof iterator !== 'function') {\n"
318
+ " throw new TypeError('iterator method must return an object');\n"
319
+ " }\n"
320
+ " const next = iterator.next;\n"
321
+ " let finished = false;\n"
322
+ " let started = false;\n"
323
+ " return new ReadableStream({\n"
324
+ " async pull(controller) {\n"
325
+ " started = true;\n"
326
+ " const result = asyncIterator ? await next.call(iterator) : next.call(iterator);\n"
327
+ " if ((typeof result !== 'object' || result === null) && typeof result !== 'function') {\n"
328
+ " throw new TypeError('iterator result is not an object');\n"
329
+ " }\n"
330
+ " if (result.done) { finished = true; controller.close(); return; }\n"
331
+ " controller.enqueue(asyncIterator ? result.value : await result.value);\n"
332
+ " },\n"
333
+ " async cancel(reason) {\n"
334
+ " if (finished) return;\n"
335
+ " finished = true;\n"
336
+ " if (!started) return;\n"
337
+ " const finish = iterator.return;\n"
338
+ " if (finish === undefined || finish === null) return;\n"
339
+ " if (typeof finish !== 'function') throw new TypeError('iterator return is not a function');\n"
340
+ " const result = finish.call(iterator, reason);\n"
341
+ " if (!asyncIterator && ((typeof result !== 'object' || result === null) && typeof result !== 'function')) {\n"
342
+ " throw new TypeError('iterator result is not an object');\n"
343
+ " }\n"
344
+ " if (asyncIterator) {\n"
345
+ " const awaited = await result;\n"
346
+ " if ((typeof awaited !== 'object' || awaited === null) && typeof awaited !== 'function') {\n"
347
+ " throw new TypeError('iterator result is not an object');\n"
348
+ " }\n"
349
+ " }\n"
350
+ " },\n"
351
+ " });\n"
352
+ " }\n"
297
353
  " pipeThrough(transform, options) {\n"
298
354
  " if (transform === null || typeof transform !== 'object' || !transform.writable || !transform.readable) {\n"
299
355
  " throw new TypeError('pipeThrough requires a { writable, readable } pair');\n"
@@ -1342,12 +1398,14 @@ static const char web_prelude[] =
1342
1398
  " this._headers = new g.Headers(input._headers);\n"
1343
1399
  " this._body = input._body; // shared bytes; fetch copies\n"
1344
1400
  " this._signal = input._signal;\n"
1401
+ " this._redirect = input._redirect;\n"
1345
1402
  " } else {\n"
1346
1403
  " this._url = String(input);\n"
1347
1404
  " this._method = 'GET';\n"
1348
1405
  " this._headers = new g.Headers();\n"
1349
1406
  " this._body = null;\n"
1350
1407
  " this._signal = null;\n"
1408
+ " this._redirect = 'follow';\n"
1351
1409
  " }\n"
1352
1410
  " if (init.method !== undefined) this._method = normalizeMethod(init.method);\n"
1353
1411
  " if (init.headers !== undefined) this._headers = new g.Headers(init.headers);\n"
@@ -1357,19 +1415,35 @@ static const char web_prelude[] =
1357
1415
  " }\n"
1358
1416
  " this._signal = init.signal;\n"
1359
1417
  " }\n"
1418
+ " if (init.redirect !== undefined) {\n"
1419
+ " const redirect = String(init.redirect);\n"
1420
+ " if (redirect !== 'follow' && redirect !== 'error' && redirect !== 'manual') {\n"
1421
+ " throw new TypeError(`undefined: ${redirect} is not an accepted type. Expected one of follow, manual, error.`);\n"
1422
+ " }\n"
1423
+ " this._redirect = redirect;\n"
1424
+ " }\n"
1360
1425
  " if (init.body !== undefined && init.body !== null) {\n"
1361
1426
  " if (this._method === 'GET' || this._method === 'HEAD') {\n"
1362
1427
  " throw new TypeError('Request with GET/HEAD method cannot have body.');\n"
1363
1428
  " }\n"
1364
1429
  " const [bytes, ct] = coerceBodyBytes(init.body);\n"
1430
+ " if (bytes instanceof g.ReadableStream && init.duplex !== 'half') {\n"
1431
+ " throw new TypeError('RequestInit: duplex option is required when sending a body.');\n"
1432
+ " }\n"
1365
1433
  " this._body = bytes;\n"
1366
1434
  " if (ct !== null && !this._headers.has('content-type')) this._headers.set('content-type', ct);\n"
1367
1435
  " }\n"
1436
+ " // A method override still validates a body inherited from the\n"
1437
+ " // input Request, not only a body supplied by this init.\n"
1438
+ " if (this._body !== null && (this._method === 'GET' || this._method === 'HEAD')) {\n"
1439
+ " throw new TypeError('Request with GET/HEAD method cannot have body.');\n"
1440
+ " }\n"
1368
1441
  " this._bodyUsed = false;\n"
1369
1442
  " }\n"
1370
1443
  " get url() { return this._url; }\n"
1371
1444
  " get method() { return this._method; }\n"
1372
1445
  " get headers() { return this._headers; }\n"
1446
+ " get redirect() { return this._redirect; }\n"
1373
1447
  " /* Node's Request.signal is never null — a request built without one\n"
1374
1448
  " * carries an inert signal; mint it lazily on first access. */\n"
1375
1449
  " get signal() {\n"
@@ -1391,6 +1465,7 @@ static const char web_prelude[] =
1391
1465
  " this._headers = new g.Headers(init.headers);\n"
1392
1466
  " this._url = '';\n"
1393
1467
  " this._redirected = false;\n"
1468
+ " this._type = 'default';\n"
1394
1469
  " this._bodyUsed = false;\n"
1395
1470
  " if (body === undefined || body === null) {\n"
1396
1471
  " this._body = null;\n"
@@ -1406,7 +1481,7 @@ static const char web_prelude[] =
1406
1481
  " get headers() { return this._headers; }\n"
1407
1482
  " get url() { return this._url; }\n"
1408
1483
  " get redirected() { return this._redirected; }\n"
1409
- " get type() { return 'default'; }\n"
1484
+ " get type() { return this._type; }\n"
1410
1485
  " static json(data, init) {\n"
1411
1486
  " const r = new Response(JSON.stringify(data), init);\n"
1412
1487
  " r._headers.set('content-type', 'application/json');\n"
@@ -1425,6 +1500,7 @@ static const char web_prelude[] =
1425
1500
  " r._headers = headers;\n"
1426
1501
  " r._url = url;\n"
1427
1502
  " r._redirected = redirected;\n"
1503
+ " r._type = 'basic';\n"
1428
1504
  " r._body = bodyStream;\n"
1429
1505
  " return r;\n"
1430
1506
  " };\n"
package/vendor/README.md CHANGED
@@ -19,7 +19,7 @@ The QuickJS-ng JavaScript engine, embedded by the opt-in `--dynamic` build mode
19
19
 
20
20
  The tree is a plain snapshot of the upstream commit with directories the library build does not need removed (tests/, docs/, examples/, test262 fixtures, CI config, generator scripts). No vendored file is modified; to update, re-clone upstream at the new commit, delete its .git directory, apply the same trim, and update this file.
21
21
 
22
- The engine archive (libqjs.a) is built lazily on the first `--dynamic` compile into `.cache/<commit>-<flavor>/` next to this file — one flavor per lane (plain, asan). The cache directory is gitignored and safe to delete.
22
+ The engine archive (libqjs.a) is built lazily on the first `--dynamic` compile into `.cache/<commit>-<flavor>-<target>-<toolchain>/` next to this file — one flavor per lane (plain, asan), native platform/architecture or explicit cross target, and compiler environment/identity. The cache directory is gitignored and safe to delete.
23
23
 
24
24
  ## zlib/
25
25
 
@@ -29,7 +29,7 @@ The engine archive (libqjs.a) is built lazily on the first `--dynamic` compile i
29
29
 
30
30
  The CROSS-target arm of node:zlib (see packages/runtime/src/scr_zlib.c): host builds keep the historical system `-lz` link (macOS ships libz), but zig's cross sysroots have no libz, so SCRIPTC_TARGET builds compile this vendored copy per target instead. Only the flat `*.c`/`*.h` at the distribution root are vendored (LICENSE beside them) — contrib, tests, build machinery, and docs are not. No vendored file is modified; the gz* file-I/O TUs are vendored for faithfulness but never compiled (nothing references the gzFile API — see ZLIB_SOURCES in packages/compiler/src/backend/cc.ts). To update, re-fetch the release tarball, copy the same flat set, and bump `ZLIB_VERSION` in cc.ts.
31
31
 
32
- The objects build lazily on the first zlib-using cross compile into `.cache/zlib-<version>-<flavor>/` next to this file — one flavor per driver/target (the lre-objects pattern). Zlib-free binaries never compile any of this; compressed OUTPUT bytes are zlib-version-dependent, which is why the corpus only compares round-trips and fixed-blob inflation.
32
+ The objects build lazily on the first zlib-using cross compile into `.cache/zlib-<version>-<flavor>-<target>-<toolchain>/` next to this file — one flavor per driver/target and compiler environment/identity (the lre-objects pattern). Zlib-free binaries never compile any of this; compressed OUTPUT bytes are zlib-version-dependent, which is why the corpus only compares round-trips and fixed-blob inflation.
33
33
 
34
34
  ## curl/
35
35
 
@@ -39,7 +39,7 @@ The objects build lazily on the first zlib-using cross compile into `.cache/zlib
39
39
 
40
40
  HEADERS ONLY — no curl C source is vendored and none is ever compiled. These headers now serve ONLY the RETIRED curl reference implementation of fetch (packages/runtime/src/scr_fetch_curl.c, selected by `SCRIPTC_FETCH_CURL=1`; kept compilable for one release as the native flip's reference — see scr_fetch.c, the default, which rides scr_net/scr_tls/scr_http/zlib and touches nothing here). Under the flag: host builds link the system `-lcurl` (macOS ships libcurl), and linux-gnu CROSS builds compile scr_fetch_curl.c against these headers, then link against a generated STUB `libcurl.so` (soname `libcurl.so.4`, empty definitions of exactly the symbols scr_fetch_curl.c calls — see CURL_STUB_SYMBOLS in packages/compiler/src/backend/cc.ts) so the produced binary records a plain `DT_NEEDED libcurl.so.4` that the TARGET system's real libcurl satisfies at load time, the standard cross-link import-stub technique. The 7.88.1 pin is a floor, not a lock: scr_fetch_curl.c's newest requirement is CURLOPT_PROTOCOLS_STR (7.85.0) and the unversioned symbol references bind to any libcurl.so.4. This whole directory leaves with scr_fetch_curl.c when the reference retires for good.
41
41
 
42
- The stub builds lazily on the first flag-selected fetch cross compile into `.cache/curl-stub-<flavor>/` next to this file (the zlib-objects pattern). Default builds never see any of this, and no build ever compiles curl itself.
42
+ The stub builds lazily on the first flag-selected fetch cross compile into `.cache/curl-stub-<target>-<toolchain>/` next to this file (the zlib-objects pattern). Default builds never see any of this, and no build ever compiles curl itself.
43
43
 
44
44
  ## mbedtls/
45
45
 
@@ -49,4 +49,4 @@ The stub builds lazily on the first flag-selected fetch cross compile into `.cac
49
49
 
50
50
  The TLS provider behind node:tls/node:https (see the design note atop packages/runtime/src/scr_tls.c for why mbedTLS over SecureTransport/BoringSSL/libtls). Only `include/` and `library/` are vendored (LICENSE beside them) — tests, docs, programs, scripts, and CMake machinery are not. No vendored file is modified and no custom config is applied (the stock `mbedtls_config.h` builds every `library/*.c` standalone with clang); to update, re-fetch the release tarball, copy the same two directories, and bump `MBEDTLS_VERSION` in packages/compiler/src/backend/cc.ts.
51
51
 
52
- The archive (libmbedtls.a) is built lazily on the first TLS-using compile into `.cache/mbedtls-<version>-<flavor>/` next to this file — one flavor per lane (plain, asan), the libqjs.a pattern. TLS-free binaries never compile or link any of this.
52
+ The archive (libmbedtls.a) is built lazily on the first TLS-using compile into `.cache/mbedtls-<version>-<flavor>-<target>-<toolchain>/` next to this file — one flavor per lane (plain, asan), native platform/architecture or explicit cross target, and compiler environment/identity, the libqjs.a pattern. TLS-free binaries never compile or link any of this.