@scriptc/runtime 0.0.20 → 0.0.22
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/package.json +1 -1
- package/src/scr_assert.c +23 -0
- package/src/scr_bytes.c +7 -0
- package/src/scr_dyn_invoke.c +58 -6
- package/src/scr_fetch.c +5624 -123
- package/src/scr_fetch_curl.c +21 -9
- package/src/scr_http.c +13 -0
- package/src/scr_inspect.c +6 -0
- package/src/scr_island.c +19 -0
- package/src/scr_json.c +327 -7
- package/src/scr_runtime.h +124 -17
- package/src/scr_tls.c +15 -11
- package/src/scr_tls_ca.c +35 -8
- package/src/scr_url.c +103 -15
- package/src/scr_web.c +77 -1
- package/vendor/README.md +4 -4
package/src/scr_runtime.h
CHANGED
|
@@ -2683,9 +2683,15 @@ typedef enum {
|
|
|
2683
2683
|
* a silent wrong answer. The dyn→cell edge is NOT visible to the cycle
|
|
2684
2684
|
* collector (the dyn→closure stance): a cycle dyn → cell → engine
|
|
2685
2685
|
* object → host closure → dyn is merely never collected (the
|
|
2686
|
-
* documented cross-boundary-cycle divergence).
|
|
2687
|
-
* the LLVM backend hardcodes the preceding kind numbers. */
|
|
2686
|
+
* documented cross-boundary-cycle divergence). */
|
|
2688
2687
|
SCR_DYN_JSVAL,
|
|
2688
|
+
/* A compiler-owned typed reference in transit through a native Web
|
|
2689
|
+
* stream. Unlike an ordinary typed→unknown conversion, this capsule
|
|
2690
|
+
* retains the original value so a statically typed reader can recover
|
|
2691
|
+
* its identity. `materialize` supplies the ordinary deep-copy view for
|
|
2692
|
+
* consumers such as fetch request bodies that need a dyn chunk. Enum
|
|
2693
|
+
* position: LAST — LLVM hardcodes every preceding kind number. */
|
|
2694
|
+
SCR_DYN_TYPED_REF,
|
|
2689
2695
|
} ScrDynKind;
|
|
2690
2696
|
|
|
2691
2697
|
/* The handle-type tags the checked-dynamic tree can carry. The set is deliberately the
|
|
@@ -2701,12 +2707,27 @@ typedef enum {
|
|
|
2701
2707
|
SCR_DYNH_H2_STREAM, /* ScrH2Stream — Http2Stream (client & server) */
|
|
2702
2708
|
SCR_DYNH_HTTP_CLIENT, /* ScrHttpClientReq — http.ClientRequest */
|
|
2703
2709
|
SCR_DYNH_HTTP_AGENT, /* ScrHttpAgent — http.Agent / https.Agent */
|
|
2710
|
+
SCR_DYNH_ABORT_SIGNAL, /* native static-fetch AbortSignal */
|
|
2711
|
+
SCR_DYNH_WEB_STREAM, /* native WHATWG ReadableStream */
|
|
2712
|
+
SCR_DYNH_WEB_READER, /* native ReadableStreamDefaultReader */
|
|
2713
|
+
SCR_DYNH_WEB_CONTROLLER, /* native ReadableStreamDefaultController */
|
|
2714
|
+
SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
|
|
2715
|
+
SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
|
|
2716
|
+
SCR_DYNH_EVENT, /* native static-fetch abort Event */
|
|
2704
2717
|
SCR_DYNH_COUNT,
|
|
2705
2718
|
} ScrDynHandleTag;
|
|
2706
2719
|
|
|
2707
2720
|
typedef struct ScrDyn ScrDyn;
|
|
2708
2721
|
typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
|
|
2709
2722
|
typedef struct ScrClosure ScrClosure; /* full definition below (C11 repeat) */
|
|
2723
|
+
typedef struct ScrDynTypedCast {
|
|
2724
|
+
const char *type_key;
|
|
2725
|
+
size_t type_key_len;
|
|
2726
|
+
void *ptr;
|
|
2727
|
+
void *(*retain)(void *);
|
|
2728
|
+
void (*release)(void *);
|
|
2729
|
+
struct ScrDynTypedCast *next;
|
|
2730
|
+
} ScrDynTypedCast;
|
|
2710
2731
|
typedef struct ScrJsval ScrJsval; /* opaque island cell (C11 repeat; the
|
|
2711
2732
|
* always-linked dyn core never touches
|
|
2712
2733
|
* its engine value — only the gated ops
|
|
@@ -2752,7 +2773,19 @@ struct ScrDyn {
|
|
|
2752
2773
|
ScrStr *str; /* owned */
|
|
2753
2774
|
ScrBytes *bytes; /* owned (SCR_DYN_BYTES) */
|
|
2754
2775
|
struct { size_t len; size_t cap; ScrDyn **items; } arr; /* owned */
|
|
2755
|
-
struct {
|
|
2776
|
+
struct {
|
|
2777
|
+
size_t len;
|
|
2778
|
+
size_t cap;
|
|
2779
|
+
ScrDynEntry *entries;
|
|
2780
|
+
/* Optional identity of the typed record that produced this ordinary
|
|
2781
|
+
* deep-copy snapshot. EventTarget uses it to recognize the same
|
|
2782
|
+
* EventListenerObject across repeated static→dyn crossings without
|
|
2783
|
+
* changing generic dyn-object `===` semantics. */
|
|
2784
|
+
/* Owned through source_access(source_identity, false). Callback
|
|
2785
|
+
* interfaces may request a fresh snapshot with the true arm. */
|
|
2786
|
+
void *source_identity;
|
|
2787
|
+
ScrDyn *(*source_access)(void *, bool materialize);
|
|
2788
|
+
} obj; /* owned */
|
|
2756
2789
|
/* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
|
|
2757
2790
|
* and `name` are static compiler-emitted literals (never freed); name
|
|
2758
2791
|
* may be NULL (anonymous — inspect prints [Function (anonymous)]).
|
|
@@ -2767,6 +2800,23 @@ struct ScrDyn {
|
|
|
2767
2800
|
* so a listener capturing its own boxed handle never cycles past the
|
|
2768
2801
|
* settle — the settle-releases-listeners story. */
|
|
2769
2802
|
struct { void *ptr; ScrDynHandleTag tag; } handle;
|
|
2803
|
+
/* SCR_DYN_TYPED_REF: original static value + its compiler-emitted
|
|
2804
|
+
* identity tag, RC adapters, and ordinary dyn snapshot adapter. */
|
|
2805
|
+
struct {
|
|
2806
|
+
void *ptr;
|
|
2807
|
+
void *(*retain)(void *);
|
|
2808
|
+
void (*release)(void *);
|
|
2809
|
+
const char *type_key;
|
|
2810
|
+
size_t type_key_len;
|
|
2811
|
+
ScrDyn *(*materialize)(void *);
|
|
2812
|
+
void (*commit)(void *, const ScrDyn *);
|
|
2813
|
+
/* Both caches belong to this capsule. ReadableStream canonicalizes
|
|
2814
|
+
* capsules for repeated source references, so the refreshed dyn view
|
|
2815
|
+
* and every structurally converted static view keep JS reference
|
|
2816
|
+
* identity while the source reference remains observable. */
|
|
2817
|
+
ScrDyn *materialized;
|
|
2818
|
+
ScrDynTypedCast *casts;
|
|
2819
|
+
} typed_ref;
|
|
2770
2820
|
/* SCR_DYN_PROMISE: the retained promise. The boundary contract: it
|
|
2771
2821
|
* settles with a dyn payload (SCR_EXC_REF ScrDyn fulfillment or a
|
|
2772
2822
|
* void fulfillment awaiters read as the undefined value) — the
|
|
@@ -2841,6 +2891,14 @@ ScrDyn *scr_dyn_new_num(double n);
|
|
|
2841
2891
|
ScrDyn *scr_dyn_new_str(ScrStr *s);
|
|
2842
2892
|
ScrDyn *scr_dyn_new_arr(void);
|
|
2843
2893
|
ScrDyn *scr_dyn_new_obj(void);
|
|
2894
|
+
/* The ordinary deep-copy object plus a retained typed source. source_access
|
|
2895
|
+
* releases that source when materialize=false and returns a fresh dyn snapshot
|
|
2896
|
+
* when true. Generic dyn member reads and strict equality remain snapshot/node
|
|
2897
|
+
* based; callback-interface consumers opt into the live arm explicitly. */
|
|
2898
|
+
ScrDyn *scr_dyn_new_obj_with_identity(
|
|
2899
|
+
void *source, void *(*source_retain)(void *),
|
|
2900
|
+
ScrDyn *(*source_access)(void *, bool materialize));
|
|
2901
|
+
bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b);
|
|
2844
2902
|
/* Object.create(null): the fresh null-prototype dictionary (see the
|
|
2845
2903
|
* null_proto flavor flag above). */
|
|
2846
2904
|
ScrDyn *scr_dyn_new_obj_null_proto(void);
|
|
@@ -2850,6 +2908,26 @@ ScrDyn *scr_dyn_new_bytes_copy(const ScrBytes *b);
|
|
|
2850
2908
|
/* The Buffer-flavored twin (stream chunks): string coercion/toString
|
|
2851
2909
|
* decode utf8 instead of joining elements. */
|
|
2852
2910
|
ScrDyn *scr_dyn_new_buffer_copy(const ScrBytes *b);
|
|
2911
|
+
/* Identity-preserving transit capsule used by ReadableStream.from over
|
|
2912
|
+
* typed arrays. The constructor retains `ptr`; matching unbox returns +1.
|
|
2913
|
+
* materialize lazily creates and then retains one detached dyn snapshot.
|
|
2914
|
+
* The cast cache lets a non-exact dynCheck reuse one safe, compiler-built
|
|
2915
|
+
* target view instead of raw-casting structurally different layouts. */
|
|
2916
|
+
ScrDyn *scr_dyn_new_typed_ref(
|
|
2917
|
+
void *ptr, void *(*retain)(void *), void (*release)(void *),
|
|
2918
|
+
const char *type_key, size_t type_key_len,
|
|
2919
|
+
ScrDyn *(*materialize)(void *),
|
|
2920
|
+
void (*commit)(void *, const ScrDyn *));
|
|
2921
|
+
bool scr_dyn_typed_ref_is(
|
|
2922
|
+
const ScrDyn *d, const char *type_key, size_t type_key_len);
|
|
2923
|
+
void *scr_dyn_typed_ref_unbox(const ScrDyn *d); /* +1 */
|
|
2924
|
+
ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d); /* +1 */
|
|
2925
|
+
void scr_dyn_typed_ref_commit(ScrDyn *d);
|
|
2926
|
+
void *scr_dyn_typed_ref_cached_cast(
|
|
2927
|
+
const ScrDyn *d, const char *type_key, size_t type_key_len); /* +1/NULL */
|
|
2928
|
+
void scr_dyn_typed_ref_cache_cast(
|
|
2929
|
+
ScrDyn *d, const char *type_key, size_t type_key_len, void *ptr,
|
|
2930
|
+
void *(*retain)(void *), void (*release)(void *));
|
|
2853
2931
|
/* A fresh u8 COPY of a SCR_DYN_BYTES payload (+1) — the dynCheck
|
|
2854
2932
|
* extraction (`u as Uint8Array`). */
|
|
2855
2933
|
ScrBytes *scr_dyn_bytes_copy_out(const ScrDyn *d);
|
|
@@ -3137,9 +3215,10 @@ ScrDyn *scr_dyn_alloc_jsval(ScrJsval *cell, const ScrDynJsvalOps *ops);
|
|
|
3137
3215
|
/* The installed ops (traps on a missing install — impossible unless a
|
|
3138
3216
|
* JSVAL node was forged without the constructor). */
|
|
3139
3217
|
const ScrDynJsvalOps *scr_dyn_jsval_ops(void);
|
|
3140
|
-
/* dynTest arms that need the ENGINE's answer on a JSVAL node
|
|
3141
|
-
*
|
|
3142
|
-
*
|
|
3218
|
+
/* dynTest arms that need the ENGINE's answer on a JSVAL node, or the
|
|
3219
|
+
* materialized answer for a live typed-reference capsule. Each answers
|
|
3220
|
+
* false for every other kind (callers test unconditionally — the emitted
|
|
3221
|
+
* narrowing tests stay branch-free). Never throw. */
|
|
3143
3222
|
bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name);
|
|
3144
3223
|
bool scr_dyn_isl_is_array(const ScrDyn *d);
|
|
3145
3224
|
bool scr_dyn_isl_is_error(const ScrDyn *d);
|
|
@@ -3819,6 +3898,33 @@ ScrGen *scr_gen_of_fiber(ScrFiber *f);
|
|
|
3819
3898
|
long scr_promise_live_count(void);
|
|
3820
3899
|
#endif
|
|
3821
3900
|
|
|
3901
|
+
/* ── fetch (scr_fetch.c) ──────────────────────────────────────────────
|
|
3902
|
+
* The native bridge over scr_net + scr_tls + scr_http's client parser.
|
|
3903
|
+
* Static fetch(url) resolves at the response head with native Response
|
|
3904
|
+
* and ReadableStream handles; Response.json consumes that same stream.
|
|
3905
|
+
* AbortSignal and streaming request bodies stay native too. Under
|
|
3906
|
+
* --dynamic the same TU boots the broader engine web surface. The
|
|
3907
|
+
* emitted main calls scr_fetch_install in either form. */
|
|
3908
|
+
void scr_fetch_install(void);
|
|
3909
|
+
ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init); /* +1 promise<Response handle> */
|
|
3910
|
+
ScrPromise *scr_fetch_response_json(ScrDyn *response); /* +1 promise<dyn> */
|
|
3911
|
+
ScrPromise *scr_fetch_response_text(ScrDyn *response); /* +1 promise<dyn> */
|
|
3912
|
+
ScrPromise *scr_fetch_response_bytes(ScrDyn *response); /* +1 promise<dyn> */
|
|
3913
|
+
/* Borrowed number; +1 AbortSignal handle or NULL pending. */
|
|
3914
|
+
ScrDyn *scr_fetch_abort_timeout(ScrDyn *delay);
|
|
3915
|
+
ScrDyn *scr_fetch_abort_now(ScrDyn *reason); /* borrowed reason; +1 handle */
|
|
3916
|
+
ScrDyn *scr_fetch_abort_any(ScrDyn *signals); /* borrowed array; +1 handle or NULL pending */
|
|
3917
|
+
ScrDyn *scr_fetch_stream_new(ScrDyn *source); /* borrowed source; +1 handle or NULL pending */
|
|
3918
|
+
ScrDyn *scr_fetch_stream_from(ScrDyn *iterable); /* borrowed iterable; +1 handle or NULL pending */
|
|
3919
|
+
ScrDyn *scr_fetch_stream_from_array(
|
|
3920
|
+
ScrArr *iterable,
|
|
3921
|
+
ScrDyn *(*item)(ScrArr *, double)); /* borrowed array/callback; +1 handle */
|
|
3922
|
+
ScrDyn *scr_fetch_stream_from_bytes(
|
|
3923
|
+
ScrBytes *iterable); /* borrowed bytes; +1 handle */
|
|
3924
|
+
ScrDyn *scr_fetch_stream_from_string(
|
|
3925
|
+
ScrStr *iterable); /* borrowed string; +1 handle */
|
|
3926
|
+
ScrPromise *scr_fetch_reader_read(ScrDyn *reader); /* borrowed handle; +1 */
|
|
3927
|
+
|
|
3822
3928
|
/* ── dynamic island (scr_island.c; --dynamic builds ONLY) ────────────
|
|
3823
3929
|
* The embedded QuickJS-ng engine. Compiled and linked only under
|
|
3824
3930
|
* -DSCR_DYNAMIC; static builds never reference these symbols (nor the
|
|
@@ -3948,18 +4054,12 @@ bool scr_island_timers_due(void);
|
|
|
3948
4054
|
bool scr_island_timers_fire_due(void); /* true = fired at least one */
|
|
3949
4055
|
void scr_island_timers_teardown(void);
|
|
3950
4056
|
|
|
3951
|
-
/* ── fetch
|
|
3952
|
-
* The native bridge
|
|
3953
|
-
* scr_http's client parser + zlib), compiled and linked ONLY into
|
|
3954
|
-
* --dynamic builds whose graph references fetch. The emitted main calls
|
|
3955
|
-
* scr_fetch_install BEFORE any island entry; install registers the
|
|
3956
|
-
* bridge's hooks with the island, which boots the fetch glue with the
|
|
3957
|
-
* engine. The native bridge registers NO pending/poll hooks (its
|
|
4057
|
+
/* ── dynamic fetch hooks ──────────────────────────────────────────────
|
|
4058
|
+
* The native bridge registers NO pending/poll hooks (its
|
|
3958
4059
|
* transfers live on real sockets the loop's poller sleeps on); the curl
|
|
3959
4060
|
* reference implementation (scr_fetch_curl.c, SCRIPTC_FETCH_CURL=1 —
|
|
3960
4061
|
* one release as the flip's reference) still registers all four so the
|
|
3961
4062
|
* loop can sleep on curl's fds. */
|
|
3962
|
-
void scr_fetch_install(void);
|
|
3963
4063
|
void scr_island_set_fetch(void (*boot)(void *jsctx), bool (*pending)(void),
|
|
3964
4064
|
void (*poll)(double max_wait_ms), void (*teardown)(void));
|
|
3965
4065
|
|
|
@@ -4078,6 +4178,7 @@ ScrJsval *scr_jsval_destr_check(ScrJsval *v, const char *spell, const char *firs
|
|
|
4078
4178
|
ScrJsval *scr_jsval_iter_n(ScrJsval *v, double n);
|
|
4079
4179
|
int scr_jsval_set_idx(ScrJsval *o, ScrJsval *key, ScrJsval *v);
|
|
4080
4180
|
ScrJsval *scr_jsval_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
|
|
4181
|
+
ScrJsval *scr_jsval_call_this(ScrJsval *f, ScrJsval *receiver, int argc, ScrJsval **argv);
|
|
4081
4182
|
/* `o.name?.(...)`: a nullish member answers the engine's undefined;
|
|
4082
4183
|
* anything else calls with this = o (non-callables throw in the engine). */
|
|
4083
4184
|
ScrJsval *scr_jsval_opt_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
|
|
@@ -4468,6 +4569,9 @@ void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet
|
|
|
4468
4569
|
* toward zero, wrap mod 2^8/2^32), f32 by double→float rounding. */
|
|
4469
4570
|
double scr_bytes_get(const ScrBytes *b, double i);
|
|
4470
4571
|
void scr_bytes_set(ScrBytes *b, double i, double v);
|
|
4572
|
+
/* Replace a live typed-array capsule's fixed-size payload from its dyn
|
|
4573
|
+
* snapshot. Source and target have the same compiler-checked bytes type. */
|
|
4574
|
+
void scr_bytes_copy_contents(ScrBytes *dst, const ScrBytes *src);
|
|
4471
4575
|
|
|
4472
4576
|
/* TypedArray.prototype.slice(start, end): relative indices clamp like
|
|
4473
4577
|
* string/array slice (ToIntegerOrInfinity, negatives from the end); the
|
|
@@ -5125,9 +5229,12 @@ long scr_secure_ctx_live_count(void);
|
|
|
5125
5229
|
* /etc/ssl/cert.pem probe order scr_tls.c documents) stands in for both
|
|
5126
5230
|
* Node's compiled-in Mozilla roots ('bundled', rootCertificates) and the
|
|
5127
5231
|
* platform store ('system') — the established SEMANTICS divergence,
|
|
5128
|
-
* extended to introspection; 'extra'
|
|
5129
|
-
* are cached per type (+1 retained
|
|
5130
|
-
* caching, and the identity the suite pins
|
|
5232
|
+
* extended to introspection; 'extra' uses the NODE_EXTRA_CA_CERTS file
|
|
5233
|
+
* captured before user code runs. Arrays are cached per type (+1 retained
|
|
5234
|
+
* answers each call — Node's own caching, and the identity the suite pins
|
|
5235
|
+
* with strictEqual). */
|
|
5236
|
+
void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */
|
|
5237
|
+
bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */
|
|
5131
5238
|
ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
|
|
5132
5239
|
ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
|
|
5133
5240
|
/* Replaces the 'default' set: entries filter to their PEM certificate
|
package/src/scr_tls.c
CHANGED
|
@@ -336,17 +336,14 @@ typedef struct ScrTlsCli {
|
|
|
336
336
|
} ScrTlsCli;
|
|
337
337
|
|
|
338
338
|
/* The default trust anchors when no `ca` option is given: the system
|
|
339
|
-
* bundle, standing in for Node's compiled-in Mozilla roots
|
|
340
|
-
*
|
|
341
|
-
* Alpine links it),
|
|
342
|
-
*
|
|
343
|
-
* only the first path exists, so
|
|
344
|
-
* A host with none leaves the
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
* shaped) — a no-`ca` client there fails verification like a bundle-less
|
|
348
|
-
* Unix host, which agrees with the oracle wherever the fixtures tread
|
|
349
|
-
* (their CAs are local, never in Node's Mozilla roots either). */
|
|
339
|
+
* bundle, standing in for Node's compiled-in Mozilla roots, plus
|
|
340
|
+
* NODE_EXTRA_CA_CERTS. The system bundle is probed once across the distro
|
|
341
|
+
* spellings — /etc/ssl/cert.pem first (macOS ships it; Alpine links it),
|
|
342
|
+
* then Debian/Ubuntu's ca-certificates.crt, Fedora/RHEL's ca-bundle.crt,
|
|
343
|
+
* and openSUSE's ca-bundle.pem. On macOS only the first path exists, so
|
|
344
|
+
* the probe order changes nothing there. A host with none leaves only the
|
|
345
|
+
* optional extra chain. Windows is that host by construction (no PEM
|
|
346
|
+
* bundle ships; the OS store is cert-database-shaped). */
|
|
350
347
|
static mbedtls_x509_crt scr_tls_system_roots;
|
|
351
348
|
static bool scr_tls_system_roots_loaded = false;
|
|
352
349
|
|
|
@@ -386,6 +383,13 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
|
|
|
386
383
|
for (size_t i = 0; i < sizeof bundles / sizeof bundles[0]; i++) {
|
|
387
384
|
if (mbedtls_x509_crt_parse_file(&scr_tls_system_roots, bundles[i]) == 0) break;
|
|
388
385
|
}
|
|
386
|
+
const char *extra = NULL;
|
|
387
|
+
size_t extra_len = 0;
|
|
388
|
+
if (scr_tls_ca_extra_pem(&extra, &extra_len) && extra_len > 0) {
|
|
389
|
+
(void)mbedtls_x509_crt_parse(
|
|
390
|
+
&scr_tls_system_roots, (const unsigned char *)extra,
|
|
391
|
+
extra_len + 1);
|
|
392
|
+
}
|
|
389
393
|
}
|
|
390
394
|
return &scr_tls_system_roots;
|
|
391
395
|
}
|
package/src/scr_tls_ca.c
CHANGED
|
@@ -32,6 +32,9 @@ static ScrArr *scr_ca_bundled = NULL;
|
|
|
32
32
|
static ScrArr *scr_ca_system = NULL;
|
|
33
33
|
static ScrArr *scr_ca_extra = NULL;
|
|
34
34
|
static ScrArr *scr_ca_default = NULL;
|
|
35
|
+
static char *scr_ca_extra_buf = NULL;
|
|
36
|
+
static size_t scr_ca_extra_len = 0;
|
|
37
|
+
static bool scr_ca_installed = false;
|
|
35
38
|
static char *scr_ca_override_buf = NULL;
|
|
36
39
|
static size_t scr_ca_override_len = 0;
|
|
37
40
|
static uint64_t scr_ca_override_gen = 0; /* 0 = never set */
|
|
@@ -47,6 +50,9 @@ static void scr_ca_teardown(void) {
|
|
|
47
50
|
if (scr_ca_extra != NULL) scr_arr_release(scr_ca_extra);
|
|
48
51
|
if (scr_ca_default != NULL) scr_arr_release(scr_ca_default);
|
|
49
52
|
scr_ca_bundled = scr_ca_system = scr_ca_extra = scr_ca_default = NULL;
|
|
53
|
+
free(scr_ca_extra_buf);
|
|
54
|
+
scr_ca_extra_buf = NULL;
|
|
55
|
+
scr_ca_extra_len = 0;
|
|
50
56
|
free(scr_ca_override_buf);
|
|
51
57
|
scr_ca_override_buf = NULL;
|
|
52
58
|
}
|
|
@@ -123,6 +129,31 @@ static char *scr_ca_read_file(const char *path, size_t *out_len) {
|
|
|
123
129
|
return buf;
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
/*
|
|
133
|
+
* Node consumes NODE_EXTRA_CA_CERTS while the process is initialized:
|
|
134
|
+
* changing process.env or replacing the referenced file later does not
|
|
135
|
+
* alter either TLS trust or getCACertificates("extra"). Generated main
|
|
136
|
+
* calls this install hook before user code; the lazy fallback only serves
|
|
137
|
+
* library/direct-runtime callers that have no executable startup hook.
|
|
138
|
+
*/
|
|
139
|
+
void scr_tls_ca_install(void) {
|
|
140
|
+
if (scr_ca_installed) return;
|
|
141
|
+
scr_ca_installed = true;
|
|
142
|
+
scr_ca_arm_teardown();
|
|
143
|
+
const char *path = getenv("NODE_EXTRA_CA_CERTS");
|
|
144
|
+
if (path != NULL && path[0] != '\0') {
|
|
145
|
+
scr_ca_extra_buf = scr_ca_read_file(path, &scr_ca_extra_len);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
bool scr_tls_ca_extra_pem(const char **pem, size_t *len) {
|
|
150
|
+
scr_tls_ca_install();
|
|
151
|
+
if (scr_ca_extra_buf == NULL) return false;
|
|
152
|
+
*pem = scr_ca_extra_buf;
|
|
153
|
+
*len = scr_ca_extra_len;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
126
157
|
/* The host bundle, probed in scr_tls.c's documented order. */
|
|
127
158
|
static ScrArr *scr_ca_load_host_bundle(void) {
|
|
128
159
|
ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 128);
|
|
@@ -163,14 +194,10 @@ static ScrArr *scr_ca_extra_arr(void) {
|
|
|
163
194
|
if (scr_ca_extra == NULL) {
|
|
164
195
|
scr_ca_arm_teardown();
|
|
165
196
|
scr_ca_extra = scr_arr_new(SCR_ELEM_STR, 4);
|
|
166
|
-
const char *
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (text != NULL) {
|
|
171
|
-
scr_ca_push_pem_blocks(scr_ca_extra, text, len);
|
|
172
|
-
free(text);
|
|
173
|
-
}
|
|
197
|
+
const char *pem = NULL;
|
|
198
|
+
size_t len = 0;
|
|
199
|
+
if (scr_tls_ca_extra_pem(&pem, &len)) {
|
|
200
|
+
scr_ca_push_pem_blocks(scr_ca_extra, pem, len);
|
|
174
201
|
}
|
|
175
202
|
}
|
|
176
203
|
return scr_ca_extra;
|
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),
|
|
25
|
-
*
|
|
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]
|
|
342
|
+
/* host[:port], with IPv6 literals bracketed per WHATWG. */
|
|
277
343
|
long colon = -1;
|
|
278
|
-
|
|
279
|
-
|
|
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)
|
|
362
|
+
* %-escapes (IDNA territory) are rejected outright. */
|
|
284
363
|
UrlBuf hb;
|
|
285
364
|
ub_init(&hb);
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
294
|
-
|
|
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)
|
|
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
|
|
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"
|