@scriptc/runtime 0.0.12 → 0.0.13
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_async.c +48 -3
- package/src/scr_async_dyn.c +139 -23
- package/src/scr_dyn_invoke.c +27 -3
- package/src/scr_http.c +1076 -30
- package/src/scr_http2.c +50 -0
- package/src/scr_json.c +21 -0
- package/src/scr_net.c +253 -34
- package/src/scr_runtime.h +135 -7
- package/src/scr_tls.c +40 -3
- package/src/scr_tls_ca.c +288 -0
package/src/scr_runtime.h
CHANGED
|
@@ -1613,7 +1613,7 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams);
|
|
|
1613
1613
|
* and settle at the terminal point (right after 'close', the eos timing
|
|
1614
1614
|
* Node's consumers share) — text answers the utf8 decode, json parses
|
|
1615
1615
|
* the text (malformed input rejects with the parse's SyntaxError; the
|
|
1616
|
-
* result is a +1
|
|
1616
|
+
* result is a +1 dyn tree), buffer the concatenated bytes. Stream
|
|
1617
1617
|
* errors reject; an early close rejects ERR_STREAM_PREMATURE_CLOSE; a
|
|
1618
1618
|
* stream with no readable side rejects Node's async-iterable TypeError.
|
|
1619
1619
|
* Streams borrowed; +1 promises. */
|
|
@@ -2681,6 +2681,8 @@ typedef enum {
|
|
|
2681
2681
|
SCR_DYNH_NET_SERVER, /* ScrNetServer — net.Server (http.Server rides the same handle) */
|
|
2682
2682
|
SCR_DYNH_H2_SESSION, /* ScrH2Session — Http2Session (client & server) */
|
|
2683
2683
|
SCR_DYNH_H2_STREAM, /* ScrH2Stream — Http2Stream (client & server) */
|
|
2684
|
+
SCR_DYNH_HTTP_CLIENT, /* ScrHttpClientReq — http.ClientRequest */
|
|
2685
|
+
SCR_DYNH_HTTP_AGENT, /* ScrHttpAgent — http.Agent / https.Agent */
|
|
2684
2686
|
SCR_DYNH_COUNT,
|
|
2685
2687
|
} ScrDynHandleTag;
|
|
2686
2688
|
|
|
@@ -2861,6 +2863,9 @@ void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value
|
|
|
2861
2863
|
* non-object kinds throw Node's catchable TypeErrors (strict-mode
|
|
2862
2864
|
* wording). All three operands BORROWED (the value is retained in). */
|
|
2863
2865
|
void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value);
|
|
2866
|
+
/* `key in v` with a runtime key — the dynHasKey fold per value (OBJ own
|
|
2867
|
+
* members, ARR length/valid indices, false elsewhere). Never throws. */
|
|
2868
|
+
bool scr_dyn_has_key(const ScrDyn *v, const ScrStr *key);
|
|
2864
2869
|
/* Bare `typeof v` on a dyn value: the dyn kind's JS answer (+1 string;
|
|
2865
2870
|
* null answers "object"). Never throws. */
|
|
2866
2871
|
ScrStr *scr_dyn_typeof(const ScrDyn *d);
|
|
@@ -3378,16 +3383,37 @@ void scr_als_disable(double id);
|
|
|
3378
3383
|
ScrDyn *scr_als_run(double id, ScrDyn *value, ScrDyn *fn, ScrDyn *args);
|
|
3379
3384
|
ScrDyn *scr_als_exit_run(double id, ScrDyn *fn, ScrDyn *args);
|
|
3380
3385
|
|
|
3381
|
-
/* process.on('unhandledRejection', fn): dyn listeners dispatched
|
|
3382
|
-
* never-observed rejection at loop end — (reason, promise),
|
|
3383
|
-
* the default report and the exit-1 (Node's handled-event
|
|
3384
|
-
* end-of-turn vs loop-end timing is a SEMANTICS.md
|
|
3385
|
-
*
|
|
3386
|
-
|
|
3386
|
+
/* process.on/once('unhandledRejection', fn): dyn listeners dispatched
|
|
3387
|
+
* per never-observed rejection at loop end — (reason, promise),
|
|
3388
|
+
* suppressing the default report and the exit-1 (Node's handled-event
|
|
3389
|
+
* contract; the end-of-turn vs loop-end timing is a SEMANTICS.md
|
|
3390
|
+
* divergence). `once` auto-removes after one delivery; off removes by
|
|
3391
|
+
* closure identity (the warning registry's story). Throws Node's
|
|
3392
|
+
* ERR_INVALID_ARG_TYPE on a non-function. */
|
|
3393
|
+
void scr_process_on_unhandled_rejection(ScrDyn *fn, bool once);
|
|
3394
|
+
void scr_process_off_unhandled_rejection(ScrDyn *fn);
|
|
3395
|
+
/* process.on/once/off('rejectionHandled', fn): the sibling registry.
|
|
3396
|
+
* Under the loop-exhaustion model the event's ONE firing window is a
|
|
3397
|
+
* handler attached DURING an unhandledRejection listener (a rejection
|
|
3398
|
+
* handled any earlier never enters the report at all — the same
|
|
3399
|
+
* documented divergence); dispatch is synchronous at the attach, with
|
|
3400
|
+
* the promise, Node's payload. */
|
|
3401
|
+
void scr_process_on_rejection_handled(ScrDyn *fn, bool once);
|
|
3402
|
+
void scr_process_off_rejection_handled(ScrDyn *fn);
|
|
3387
3403
|
/* The loop-end delivery hook the registration above installs
|
|
3388
3404
|
* (scr_async_dyn.c → scr_report_unhandled_rejections; NULL = default
|
|
3389
3405
|
* report). Runtime-internal. */
|
|
3390
3406
|
extern bool (*scr_urj_deliver_fn)(ScrPromise *p);
|
|
3407
|
+
/* The late-handled hook (scr_async_dyn.c installs it when a
|
|
3408
|
+
* 'rejectionHandled' listener registers): scr_async.c calls it when a
|
|
3409
|
+
* promise the report already delivered as unhandled gains a handler.
|
|
3410
|
+
* Runtime-internal. */
|
|
3411
|
+
extern void (*scr_rjh_notify_fn)(ScrPromise *p);
|
|
3412
|
+
/* The attach-time handled mark (a dyn then/catch carrying a rejection
|
|
3413
|
+
* handler): marks an already-rejected source observed — Node's attach
|
|
3414
|
+
* moment — firing the late-handled hook when the report already
|
|
3415
|
+
* delivered it. Runtime-internal. */
|
|
3416
|
+
void scr_promise_mark_handled(ScrPromise *p);
|
|
3391
3417
|
/* A rejected promise's reason as a dyn value (identity-preserving for
|
|
3392
3418
|
* dyn payloads and %Error instances). +1. */
|
|
3393
3419
|
ScrDyn *scr_promise_reason_dyn(const ScrPromise *p);
|
|
@@ -4819,6 +4845,22 @@ void scr_net_sock_end_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/);
|
|
|
4819
4845
|
void scr_net_sock_write_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/);
|
|
4820
4846
|
void scr_net_sock_end_dynv(ScrNetSocket *s, const ScrDyn *d /*borrowed*/);
|
|
4821
4847
|
void scr_net_sock_destroy(ScrNetSocket *s);
|
|
4848
|
+
/* Flow control (pause/resume — see the struct's flag comments), the
|
|
4849
|
+
* FIN-flushed destroy (destroySoon), TCP_NODELAY, the deferred write/
|
|
4850
|
+
* finish callbacks (write(chunk, cb) / end(cb) — sweep-fired), and the
|
|
4851
|
+
* counters/flags the compat surface reads. */
|
|
4852
|
+
ScrNetSocket *scr_net_sock_pause(ScrNetSocket *s); /* +1: chaining */
|
|
4853
|
+
ScrNetSocket *scr_net_sock_resume(ScrNetSocket *s); /* +1: chaining */
|
|
4854
|
+
ScrNetSocket *scr_net_sock_set_nodelay(ScrNetSocket *s, bool enable); /* +1: chaining */
|
|
4855
|
+
void scr_net_sock_destroy_soon(ScrNetSocket *s);
|
|
4856
|
+
void scr_net_sock_on_finish(ScrNetSocket *s, ScrClosure *cb /*moves*/);
|
|
4857
|
+
void scr_net_sock_on_write_flush(ScrNetSocket *s, ScrClosure *cb /*moves*/);
|
|
4858
|
+
double scr_net_sock_bytes_written(ScrNetSocket *s);
|
|
4859
|
+
bool scr_net_sock_readable(ScrNetSocket *s);
|
|
4860
|
+
/* The deferred dial (the http agent's maxSockets queue): the socket
|
|
4861
|
+
* registers "connecting" and buffers writes; dial_start runs the dial. */
|
|
4862
|
+
ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nullable*/); /* +1 */
|
|
4863
|
+
void scr_net_sock_dial_start(ScrNetSocket *s);
|
|
4822
4864
|
void scr_net_sock_set_timeout(ScrNetSocket *s, double ms);
|
|
4823
4865
|
void scr_net_sock_on_timeout(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once);
|
|
4824
4866
|
ScrStr *scr_net_sock_remote_address(ScrNetSocket *s); /* +1 or NULL (undefined arm) */
|
|
@@ -4845,6 +4887,7 @@ void scr_net_sock_unshift_bytes(ScrNetSocket *s, ScrBytes *data /*borrowed*/);
|
|
|
4845
4887
|
void scr_net_server_emit_connection(ScrNetServer *srv, ScrNetSocket *sock /*borrowed*/);
|
|
4846
4888
|
void scr_net_data_thunk0(ScrClosure *cb, ScrBytes *chunk);
|
|
4847
4889
|
void scr_net_data_thunk_bytes(ScrClosure *cb, ScrBytes *chunk);
|
|
4890
|
+
void scr_net_data_thunk_str(ScrClosure *cb, ScrBytes *chunk);
|
|
4848
4891
|
/* The dynCheck-adapted listener's flavor: boxes the chunk as a
|
|
4849
4892
|
* Buffer-flavored dyn (toString decodes utf8, Node's 'data' payload). */
|
|
4850
4893
|
void scr_net_data_thunk_dyn(ScrClosure *cb, ScrBytes *chunk);
|
|
@@ -5025,6 +5068,29 @@ void scr_tls_sni_answer(ScrClosure *self, bool has_err, ScrSecureCtx *ctx /*move
|
|
|
5025
5068
|
long scr_secure_ctx_live_count(void);
|
|
5026
5069
|
#endif
|
|
5027
5070
|
|
|
5071
|
+
/* ── node:tls, the CA-store introspection slice (scr_tls_ca.c — its own
|
|
5072
|
+
* unit and link gate; plain PEM-block bookkeeping, NO mbedTLS, so a
|
|
5073
|
+
* getCACertificates-only binary never builds the archive; cc.ts also
|
|
5074
|
+
* compiles it whenever scr_tls.c does). The HOST bundle (the
|
|
5075
|
+
* /etc/ssl/cert.pem probe order scr_tls.c documents) stands in for both
|
|
5076
|
+
* Node's compiled-in Mozilla roots ('bundled', rootCertificates) and the
|
|
5077
|
+
* platform store ('system') — the established SEMANTICS divergence,
|
|
5078
|
+
* extended to introspection; 'extra' reads NODE_EXTRA_CA_CERTS. Arrays
|
|
5079
|
+
* are cached per type (+1 retained answers each call — Node's own
|
|
5080
|
+
* caching, and the identity the suite pins with strictEqual). */
|
|
5081
|
+
ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
|
|
5082
|
+
ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
|
|
5083
|
+
/* Replaces the 'default' set: entries filter to their PEM certificate
|
|
5084
|
+
* blocks (deduped byte-exactly — Node dedupes by X509 identity); a
|
|
5085
|
+
* non-empty array yielding no blocks throws Node's
|
|
5086
|
+
* ERR_CRYPTO_OPERATION_FAILED and leaves the set unchanged. Borrows. */
|
|
5087
|
+
void scr_tls_ca_set_default(ScrArr *certs);
|
|
5088
|
+
/* scr_tls.c's anchor consult: true iff setDefaultCACertificates ran;
|
|
5089
|
+
* *pem/*len then carry the concatenated NUL-terminated blocks (len 0 =
|
|
5090
|
+
* the empty set — verification fails, Node's own consequence), and *gen
|
|
5091
|
+
* a counter that bumps per set so the parsed chain re-parses on change. */
|
|
5092
|
+
bool scr_tls_ca_default_override(const char **pem, size_t *len, uint64_t *gen);
|
|
5093
|
+
|
|
5028
5094
|
/* ── node:http, the server slice (scr_http.c — compiled only when the
|
|
5029
5095
|
* program uses it; rides scr_net.c wholesale, design note atop the
|
|
5030
5096
|
* file). http.createServer returns an ORDINARY ScrNetServer (listen/
|
|
@@ -5180,6 +5246,23 @@ void scr_http_upgrade_thunk3(ScrClosure *cb, ScrHttpReq *req, ScrNetSocket *sock
|
|
|
5180
5246
|
double scr_http_req_status(ScrHttpReq *r); /* < 0 = the undefined arm (server request) */
|
|
5181
5247
|
ScrNetSocket *scr_http_req_socket(ScrHttpReq *r); /* +1 */
|
|
5182
5248
|
void scr_http_req_resume(ScrHttpReq *r);
|
|
5249
|
+
/* pause()/resume() hold and drain 'data'/'end' delivery (the parser keeps
|
|
5250
|
+
* consuming; the drain rides the emit queue); setTimeout delegates to the
|
|
5251
|
+
* socket's idle timer; the flags back req.destroyed/req.readable. */
|
|
5252
|
+
void scr_http_req_pause(ScrHttpReq *r);
|
|
5253
|
+
void scr_http_req_set_timeout(ScrHttpReq *r, double ms, ScrClosure *cb /*moves, nullable*/);
|
|
5254
|
+
bool scr_http_req_destroyed_flag(ScrHttpReq *r);
|
|
5255
|
+
bool scr_http_req_readable(ScrHttpReq *r);
|
|
5256
|
+
/* flushHeaders/cork/uncork/writableCorked, the res.req backref, the
|
|
5257
|
+
* socket-delegated setTimeout, and write(chunk, cb)'s deferred callback. */
|
|
5258
|
+
void scr_http_res_flush_headers(ScrHttpRes *r);
|
|
5259
|
+
void scr_http_res_cork(ScrHttpRes *r);
|
|
5260
|
+
void scr_http_res_uncork(ScrHttpRes *r);
|
|
5261
|
+
double scr_http_res_writable_corked(ScrHttpRes *r);
|
|
5262
|
+
bool scr_http_res_destroyed_flag(ScrHttpRes *r);
|
|
5263
|
+
void scr_http_res_set_req(ScrHttpRes *r, ScrHttpReq *req /*borrowed, nullable*/);
|
|
5264
|
+
void scr_http_res_set_timeout(ScrHttpRes *r, double ms, ScrClosure *cb /*moves, nullable*/);
|
|
5265
|
+
void scr_http_res_on_write_flush(ScrHttpRes *r, ScrClosure *cb /*moves*/);
|
|
5183
5266
|
/* req.setEncoding(enc) — the socket twin's contract; may throw. */
|
|
5184
5267
|
void scr_http_req_set_encoding(ScrHttpReq *r, ScrStr *enc /*borrowed*/);
|
|
5185
5268
|
void scr_http_req_destroy(ScrHttpReq *r);
|
|
@@ -5244,6 +5327,30 @@ ScrHttpClientReq *scr_http_request_ex(ScrStr *host /*borrowed*/, double port,
|
|
|
5244
5327
|
ScrHttpClientReq *scr_http_request_url(ScrStr *url /*borrowed*/, ScrStr *method /*borrowed*/,
|
|
5245
5328
|
bool auto_end, ScrClosure *cb /*moves, nullable*/,
|
|
5246
5329
|
ScrHttpRespFn fn); /* +1 */
|
|
5330
|
+
/* ── the http Agent (new http.Agent(opts) — option surface, getName, and
|
|
5331
|
+
* the maxSockets queue over one-dial-per-request connections; keep-alive
|
|
5332
|
+
* POOLING is not modeled: keepAlive: true fences at construction).
|
|
5333
|
+
* agent_new answers the SCR_DYNH_HTTP_AGENT handle (+1 dyn) or throws.
|
|
5334
|
+
* max_sockets/max_free arrive < 0 for "unset" (Infinity / 256);
|
|
5335
|
+
* timeout_ms < 0 = no idle timer. request_agent_ex threads the agent dyn
|
|
5336
|
+
* (undefined/null = the default path; false = one-shot with Connection:
|
|
5337
|
+
* close; an Agent handle = queue accounting); port < 0 means "no port
|
|
5338
|
+
* option" — the agent's (settable) defaultPort, then the scheme's. */
|
|
5339
|
+
ScrDyn *scr_http_agent_new(bool secure, bool keep_alive, double ka_msecs,
|
|
5340
|
+
double max_sockets, double max_free, double timeout_ms,
|
|
5341
|
+
double port /* < 0 = unset: the option-merge default */); /* +1 */
|
|
5342
|
+
ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double port,
|
|
5343
|
+
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
|
5344
|
+
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
|
5345
|
+
bool auto_end, const ScrDyn *agent /*borrowed*/,
|
|
5346
|
+
ScrClosure *cb /*moves, nullable*/,
|
|
5347
|
+
ScrHttpRespFn fn, int default_port,
|
|
5348
|
+
void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx); /* +1 */
|
|
5349
|
+
ScrHttpClientReq *scr_http_request_agent(ScrStr *host /*borrowed*/, double port,
|
|
5350
|
+
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
|
5351
|
+
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
|
5352
|
+
bool auto_end, const ScrDyn *agent /*borrowed*/,
|
|
5353
|
+
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
|
5247
5354
|
#ifdef SCR_RC_AUDIT
|
|
5248
5355
|
long scr_http_live_count(void);
|
|
5249
5356
|
#endif
|
|
@@ -5288,6 +5395,19 @@ ScrNetServer *scr_http2_create_server_req(ScrClosure *handler /*moves*/, ScrHttp
|
|
|
5288
5395
|
* the same session machinery behind an mbedTLS handshake whose ALPN
|
|
5289
5396
|
* advertises h2 alone — protocol-identical after establishment. */
|
|
5290
5397
|
ScrNetServer *scr_http2_create_secure_server(const char *cert, size_t cert_len, const char *key, size_t key_len); /* +1 */
|
|
5398
|
+
/* createSecureServer(options, handler): the eager COMPAT handler as the
|
|
5399
|
+
* first 'request' listener over the ALPN=h2 server (the createServerReq
|
|
5400
|
+
* route, secured). */
|
|
5401
|
+
ScrNetServer *scr_http2_create_secure_server_req(const char *cert, size_t cert_len, const char *key, size_t key_len, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn); /* +1 */
|
|
5402
|
+
/* createSecureServer with a RUNTIME options record (the divergence-66
|
|
5403
|
+
* stance): allowHTTP1 picks the flavor at runtime, cert/key ride the
|
|
5404
|
+
* shared TLS server walk (out-of-bounds members throw the catchable
|
|
5405
|
+
* fence), h2 session-tuning keys drop like the literal walk ignores
|
|
5406
|
+
* them. +1, or NULL with the exception pending. */
|
|
5407
|
+
ScrNetServer *scr_http2_create_secure_server_dyn(const struct ScrDyn *opts /*borrowed*/, ScrClosure *handler /*moves, nullable*/, ScrHttpReqFn fn);
|
|
5408
|
+
/* The TLS server-options walk (scr_tls.c) — runtime-internal, shared
|
|
5409
|
+
* with scr_http2.c's createSecureServerDyn. */
|
|
5410
|
+
bool scr_tls_srv_opts_walk(const struct ScrDyn *opts, const char *api, ScrBytes **cert_out, ScrBytes **key_out);
|
|
5291
5411
|
void scr_http2_server_on_stream(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2StreamFn fn, bool once);
|
|
5292
5412
|
void scr_http2_server_on_session(ScrNetServer *s, ScrClosure *cb /*moves*/, ScrH2SessionFn fn, bool once);
|
|
5293
5413
|
|
|
@@ -5394,6 +5514,14 @@ ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port,
|
|
|
5394
5514
|
bool auto_end, bool reject_unauthorized,
|
|
5395
5515
|
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
|
5396
5516
|
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
|
5517
|
+
/* The agent-threaded twin (the scr_http_request_agent story over TLS). */
|
|
5518
|
+
ScrHttpClientReq *scr_https_request_agent(ScrStr *host /*borrowed*/, double port,
|
|
5519
|
+
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
|
5520
|
+
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
|
5521
|
+
bool auto_end, bool reject_unauthorized,
|
|
5522
|
+
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
|
5523
|
+
const struct ScrDyn *agent /*borrowed*/,
|
|
5524
|
+
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */
|
|
5397
5525
|
/* The fetch unit's https leg (defined in scr_tls.c): a client transport
|
|
5398
5526
|
* context (SNI/verify against the URL hostname — the DIALED address may
|
|
5399
5527
|
* be a resolved IP) plus the wrap hook scr_http_request_ex installs on
|
package/src/scr_tls.c
CHANGED
|
@@ -350,7 +350,30 @@ typedef struct ScrTlsCli {
|
|
|
350
350
|
static mbedtls_x509_crt scr_tls_system_roots;
|
|
351
351
|
static bool scr_tls_system_roots_loaded = false;
|
|
352
352
|
|
|
353
|
+
/* setDefaultCACertificates' replacement anchors (scr_tls_ca.c — always
|
|
354
|
+
* compiled alongside this unit): parsed lazily per generation, so each
|
|
355
|
+
* set re-parses once and every later dial reuses the chain. An EMPTY
|
|
356
|
+
* replacement keeps an initialized, certificate-free chain — every
|
|
357
|
+
* verification fails, Node's own consequence of trusting nothing. */
|
|
358
|
+
static mbedtls_x509_crt scr_tls_override_roots;
|
|
359
|
+
static uint64_t scr_tls_override_parsed_gen = 0;
|
|
360
|
+
|
|
353
361
|
static mbedtls_x509_crt *scr_tls_system_ca(void) {
|
|
362
|
+
const char *pem = NULL;
|
|
363
|
+
size_t pem_len = 0;
|
|
364
|
+
uint64_t gen = 0;
|
|
365
|
+
if (scr_tls_ca_default_override(&pem, &pem_len, &gen)) {
|
|
366
|
+
if (gen != scr_tls_override_parsed_gen) {
|
|
367
|
+
if (scr_tls_override_parsed_gen != 0) mbedtls_x509_crt_free(&scr_tls_override_roots);
|
|
368
|
+
mbedtls_x509_crt_init(&scr_tls_override_roots);
|
|
369
|
+
/* +1 for the NUL — mbedTLS's PEM mode wants it counted. A parse
|
|
370
|
+
* failure inside one block leaves the chain with the blocks that
|
|
371
|
+
* did parse (the scr_tls_ca.c divergence note). */
|
|
372
|
+
if (pem_len > 0) (void)mbedtls_x509_crt_parse(&scr_tls_override_roots, (const unsigned char *)pem, pem_len + 1);
|
|
373
|
+
scr_tls_override_parsed_gen = gen;
|
|
374
|
+
}
|
|
375
|
+
return &scr_tls_override_roots;
|
|
376
|
+
}
|
|
354
377
|
if (!scr_tls_system_roots_loaded) {
|
|
355
378
|
scr_tls_system_roots_loaded = true;
|
|
356
379
|
mbedtls_x509_crt_init(&scr_tls_system_roots);
|
|
@@ -983,6 +1006,18 @@ ScrHttpClientReq *scr_https_request(ScrStr *host /*borrowed*/, double port,
|
|
|
983
1006
|
fn, 443, &scr_tls_cli_wrap, cli);
|
|
984
1007
|
}
|
|
985
1008
|
|
|
1009
|
+
ScrHttpClientReq *scr_https_request_agent(ScrStr *host /*borrowed*/, double port,
|
|
1010
|
+
ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/,
|
|
1011
|
+
double timeout_ms, ScrArr *header_pairs /*borrowed*/,
|
|
1012
|
+
bool auto_end, bool reject_unauthorized,
|
|
1013
|
+
const char *ca /*borrowed, len 0 = none*/, size_t ca_len,
|
|
1014
|
+
const ScrDyn *agent /*borrowed*/,
|
|
1015
|
+
ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn) {
|
|
1016
|
+
ScrTlsCli *cli = scr_tls_cli_new(host, reject_unauthorized, ca, ca_len);
|
|
1017
|
+
return scr_http_request_agent_ex(host, port, path, method, timeout_ms, header_pairs, auto_end,
|
|
1018
|
+
agent, cb, fn, 443, &scr_tls_cli_wrap, cli);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
986
1021
|
/* ── RUNTIME options records (the divergence-66 stance) ────────────────
|
|
987
1022
|
* A non-literal options value — the checked-dynamic JS lane's dyn record
|
|
988
1023
|
* — reads its members at RUNTIME. Members the literal path lowers take
|
|
@@ -1219,9 +1254,11 @@ static bool scr_tls_opts_validate(const ScrDyn *opts) {
|
|
|
1219
1254
|
}
|
|
1220
1255
|
|
|
1221
1256
|
/* The server-options walk. Fills the cert/key out-params (+1 each) from a
|
|
1222
|
-
* dyn options record; false = exception pending (partial results released).
|
|
1223
|
-
|
|
1224
|
-
|
|
1257
|
+
* dyn options record; false = exception pending (partial results released).
|
|
1258
|
+
* Exported runtime-internally: scr_http2.c's createSecureServerDyn rides
|
|
1259
|
+
* the same walk (scr_http2.c always links alongside this unit). */
|
|
1260
|
+
bool scr_tls_srv_opts_walk(const ScrDyn *opts, const char *api, ScrBytes **cert_out,
|
|
1261
|
+
ScrBytes **key_out) {
|
|
1225
1262
|
*cert_out = NULL;
|
|
1226
1263
|
*key_out = NULL;
|
|
1227
1264
|
if (opts == NULL || opts->kind != SCR_DYN_OBJ) {
|
package/src/scr_tls_ca.c
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* node:tls — the CA-store introspection slice: getCACertificates,
|
|
2
|
+
* rootCertificates, and setDefaultCACertificates (scr_runtime.h has the
|
|
3
|
+
* contracts). Its own link gate ("tlsca." libCalls), deliberately free
|
|
4
|
+
* of mbedTLS: everything here is PEM-BLOCK bookkeeping over the host
|
|
5
|
+
* bundle, so a program that only inspects the CA store keeps the lean
|
|
6
|
+
* link line. scr_tls.c (when it links — cc.ts compiles this unit
|
|
7
|
+
* alongside it) consults scr_tls_ca_default_override for its client
|
|
8
|
+
* trust anchors, which is what makes setDefaultCACertificates LIVE
|
|
9
|
+
* semantics: the next https/tls dial verifies against the replaced set.
|
|
10
|
+
*
|
|
11
|
+
* Divergences, documented: the host bundle stands in for Node's
|
|
12
|
+
* compiled-in Mozilla roots ('bundled' and rootCertificates) AND for the
|
|
13
|
+
* platform store ('system') — the same /etc/ssl/cert.pem stance
|
|
14
|
+
* scr_tls.c's anchors established; and set-time validation is PEM-BLOCK
|
|
15
|
+
* shaped (a well-formed block with corrupt DER inside is kept here where
|
|
16
|
+
* Node's X509 parse would drop it — such a cert then fails verification
|
|
17
|
+
* instead, the same observable outcome one step later). Deduplication is
|
|
18
|
+
* byte-exact over the extracted block where Node compares parsed X509
|
|
19
|
+
* identities; re-encoded duplicates of one certificate stay distinct
|
|
20
|
+
* entries, which no equality the tests observe can distinguish without a
|
|
21
|
+
* parser. */
|
|
22
|
+
#include "scr_runtime.h"
|
|
23
|
+
|
|
24
|
+
#include <stdio.h>
|
|
25
|
+
#include <stdlib.h>
|
|
26
|
+
#include <string.h>
|
|
27
|
+
|
|
28
|
+
/* The per-type caches: +1 held here for the process lifetime (an atexit
|
|
29
|
+
* teardown releases them so the RC audit stays clean). 'default' also
|
|
30
|
+
* keeps the concatenated PEM buffer scr_tls.c parses into anchors. */
|
|
31
|
+
static ScrArr *scr_ca_bundled = NULL;
|
|
32
|
+
static ScrArr *scr_ca_system = NULL;
|
|
33
|
+
static ScrArr *scr_ca_extra = NULL;
|
|
34
|
+
static ScrArr *scr_ca_default = NULL;
|
|
35
|
+
static char *scr_ca_override_buf = NULL;
|
|
36
|
+
static size_t scr_ca_override_len = 0;
|
|
37
|
+
static uint64_t scr_ca_override_gen = 0; /* 0 = never set */
|
|
38
|
+
|
|
39
|
+
static void scr_ca_oom(void) {
|
|
40
|
+
fputs("scriptc: out of memory\n", stderr);
|
|
41
|
+
abort();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static void scr_ca_teardown(void) {
|
|
45
|
+
if (scr_ca_bundled != NULL) scr_arr_release(scr_ca_bundled);
|
|
46
|
+
if (scr_ca_system != NULL) scr_arr_release(scr_ca_system);
|
|
47
|
+
if (scr_ca_extra != NULL) scr_arr_release(scr_ca_extra);
|
|
48
|
+
if (scr_ca_default != NULL) scr_arr_release(scr_ca_default);
|
|
49
|
+
scr_ca_bundled = scr_ca_system = scr_ca_extra = scr_ca_default = NULL;
|
|
50
|
+
free(scr_ca_override_buf);
|
|
51
|
+
scr_ca_override_buf = NULL;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static bool scr_ca_teardown_armed = false;
|
|
55
|
+
|
|
56
|
+
static void scr_ca_arm_teardown(void) {
|
|
57
|
+
if (!scr_ca_teardown_armed) {
|
|
58
|
+
scr_ca_teardown_armed = true;
|
|
59
|
+
atexit(scr_ca_teardown);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Portable bounded substring search (memmem is GNU/BSD-only; win32 has
|
|
64
|
+
* neither). Needles here are short constant markers — the naive scan is
|
|
65
|
+
* plenty. */
|
|
66
|
+
static const char *scr_ca_find(const char *hay, size_t hay_len, const char *needle, size_t needle_len) {
|
|
67
|
+
if (needle_len == 0 || hay_len < needle_len) return NULL;
|
|
68
|
+
for (size_t i = 0; i + needle_len <= hay_len; i++) {
|
|
69
|
+
if (hay[i] == needle[0] && memcmp(hay + i, needle, needle_len) == 0) return hay + i;
|
|
70
|
+
}
|
|
71
|
+
return NULL;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Every "-----BEGIN CERTIFICATE-----" ... "-----END CERTIFICATE-----"
|
|
75
|
+
* block of `text` (other PEM kinds — keys, CRLs — skip, like Node's CA
|
|
76
|
+
* loaders), pushed onto `arr` as one string each, END line included.
|
|
77
|
+
* Returns the number of blocks found. */
|
|
78
|
+
static size_t scr_ca_push_pem_blocks(ScrArr *arr, const char *text, size_t len) {
|
|
79
|
+
static const char BEGIN[] = "-----BEGIN CERTIFICATE-----";
|
|
80
|
+
static const char END[] = "-----END CERTIFICATE-----";
|
|
81
|
+
size_t found = 0;
|
|
82
|
+
const char *p = text;
|
|
83
|
+
const char *limit = text + len;
|
|
84
|
+
while (p < limit) {
|
|
85
|
+
const char *b = scr_ca_find(p, (size_t)(limit - p), BEGIN, sizeof BEGIN - 1);
|
|
86
|
+
if (b == NULL) break;
|
|
87
|
+
const char *e = scr_ca_find(b, (size_t)(limit - b), END, sizeof END - 1);
|
|
88
|
+
if (e == NULL) break;
|
|
89
|
+
const char *stop = e + (sizeof END - 1);
|
|
90
|
+
/* Node's entries carry the block's trailing newline when the source
|
|
91
|
+
* had one — keep it so round-trips through writeFileSync + parse
|
|
92
|
+
* stay concatenation-safe. */
|
|
93
|
+
if (stop < limit && *stop == '\r') stop++;
|
|
94
|
+
if (stop < limit && *stop == '\n') stop++;
|
|
95
|
+
scr_arr_push_ref(arr, scr_str_new(b, (size_t)(stop - b)));
|
|
96
|
+
found++;
|
|
97
|
+
p = stop;
|
|
98
|
+
}
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* One whole file, malloc'd (NULL on any failure — absent bundles answer
|
|
103
|
+
* empty stores, the same silence Node has on hosts without the file). */
|
|
104
|
+
static char *scr_ca_read_file(const char *path, size_t *out_len) {
|
|
105
|
+
FILE *f = fopen(path, "rb");
|
|
106
|
+
if (f == NULL) return NULL;
|
|
107
|
+
if (fseek(f, 0, SEEK_END) != 0) {
|
|
108
|
+
fclose(f);
|
|
109
|
+
return NULL;
|
|
110
|
+
}
|
|
111
|
+
long sz = ftell(f);
|
|
112
|
+
if (sz < 0) {
|
|
113
|
+
fclose(f);
|
|
114
|
+
return NULL;
|
|
115
|
+
}
|
|
116
|
+
rewind(f);
|
|
117
|
+
char *buf = malloc((size_t)sz + 1);
|
|
118
|
+
if (buf == NULL) scr_ca_oom();
|
|
119
|
+
size_t got = fread(buf, 1, (size_t)sz, f);
|
|
120
|
+
fclose(f);
|
|
121
|
+
buf[got] = '\0';
|
|
122
|
+
*out_len = got;
|
|
123
|
+
return buf;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* The host bundle, probed in scr_tls.c's documented order. */
|
|
127
|
+
static ScrArr *scr_ca_load_host_bundle(void) {
|
|
128
|
+
ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 128);
|
|
129
|
+
static const char *const bundles[] = {
|
|
130
|
+
"/etc/ssl/cert.pem", /* macOS, Alpine */
|
|
131
|
+
"/etc/ssl/certs/ca-certificates.crt", /* Debian/Ubuntu */
|
|
132
|
+
"/etc/pki/tls/certs/ca-bundle.crt", /* Fedora/RHEL */
|
|
133
|
+
"/etc/ssl/ca-bundle.pem", /* openSUSE */
|
|
134
|
+
};
|
|
135
|
+
for (size_t i = 0; i < sizeof bundles / sizeof bundles[0]; i++) {
|
|
136
|
+
size_t len = 0;
|
|
137
|
+
char *text = scr_ca_read_file(bundles[i], &len);
|
|
138
|
+
if (text == NULL) continue;
|
|
139
|
+
scr_ca_push_pem_blocks(arr, text, len);
|
|
140
|
+
free(text);
|
|
141
|
+
break; /* first bundle wins, like the anchor probe */
|
|
142
|
+
}
|
|
143
|
+
return arr;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
static ScrArr *scr_ca_bundled_arr(void) {
|
|
147
|
+
if (scr_ca_bundled == NULL) {
|
|
148
|
+
scr_ca_arm_teardown();
|
|
149
|
+
scr_ca_bundled = scr_ca_load_host_bundle();
|
|
150
|
+
}
|
|
151
|
+
return scr_ca_bundled;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static ScrArr *scr_ca_system_arr(void) {
|
|
155
|
+
if (scr_ca_system == NULL) {
|
|
156
|
+
scr_ca_arm_teardown();
|
|
157
|
+
scr_ca_system = scr_ca_load_host_bundle();
|
|
158
|
+
}
|
|
159
|
+
return scr_ca_system;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static ScrArr *scr_ca_extra_arr(void) {
|
|
163
|
+
if (scr_ca_extra == NULL) {
|
|
164
|
+
scr_ca_arm_teardown();
|
|
165
|
+
scr_ca_extra = scr_arr_new(SCR_ELEM_STR, 4);
|
|
166
|
+
const char *path = getenv("NODE_EXTRA_CA_CERTS");
|
|
167
|
+
if (path != NULL && path[0] != '\0') {
|
|
168
|
+
size_t len = 0;
|
|
169
|
+
char *text = scr_ca_read_file(path, &len);
|
|
170
|
+
if (text != NULL) {
|
|
171
|
+
scr_ca_push_pem_blocks(scr_ca_extra, text, len);
|
|
172
|
+
free(text);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return scr_ca_extra;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* 'default' with no override: bundled ∪ extra (Node's own composition
|
|
180
|
+
* when no --use-*-ca flag redirects it). */
|
|
181
|
+
static ScrArr *scr_ca_default_arr(void) {
|
|
182
|
+
if (scr_ca_default == NULL) {
|
|
183
|
+
scr_ca_arm_teardown();
|
|
184
|
+
ScrArr *bundled = scr_ca_bundled_arr();
|
|
185
|
+
ScrArr *extra = scr_ca_extra_arr();
|
|
186
|
+
size_t nb = (size_t)scr_arr_len(bundled);
|
|
187
|
+
size_t ne = (size_t)scr_arr_len(extra);
|
|
188
|
+
scr_ca_default = scr_arr_new(SCR_ELEM_STR, nb + ne > 0 ? nb + ne : 1);
|
|
189
|
+
for (size_t i = 0; i < nb; i++) scr_arr_push_ref(scr_ca_default, scr_arr_get_ref(bundled, (double)i));
|
|
190
|
+
for (size_t i = 0; i < ne; i++) scr_arr_push_ref(scr_ca_default, scr_arr_get_ref(extra, (double)i));
|
|
191
|
+
}
|
|
192
|
+
return scr_ca_default;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
ScrArr *scr_tls_ca_get(ScrStr *type) {
|
|
196
|
+
ScrArr *arr = NULL;
|
|
197
|
+
if (strcmp(type->data, "default") == 0) arr = scr_ca_default_arr();
|
|
198
|
+
else if (strcmp(type->data, "bundled") == 0) arr = scr_ca_bundled_arr();
|
|
199
|
+
else if (strcmp(type->data, "system") == 0) arr = scr_ca_system_arr();
|
|
200
|
+
else if (strcmp(type->data, "extra") == 0) arr = scr_ca_extra_arr();
|
|
201
|
+
if (arr == NULL) {
|
|
202
|
+
/* Node's ERR_INVALID_ARG_VALUE TypeError, message-exact. */
|
|
203
|
+
char msg[256];
|
|
204
|
+
int n = snprintf(msg, sizeof msg, "The argument 'type' is invalid. Received '%s'", type->data);
|
|
205
|
+
scr_throw_error_msg_code(SCR_ERR_TYPE, msg, n < 0 ? 0 : (size_t)n, "ERR_INVALID_ARG_VALUE");
|
|
206
|
+
return NULL;
|
|
207
|
+
}
|
|
208
|
+
return scr_arr_retain(arr);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
ScrArr *scr_tls_ca_root(void) { return scr_arr_retain(scr_ca_bundled_arr()); }
|
|
212
|
+
|
|
213
|
+
void scr_tls_ca_set_default(ScrArr *certs) {
|
|
214
|
+
size_t n = (size_t)scr_arr_len(certs);
|
|
215
|
+
/* Extract + dedupe first — a throw must leave the current set intact
|
|
216
|
+
* (Node's own error contract, pinned by the suite's error test). */
|
|
217
|
+
ScrArr *next = scr_arr_new(SCR_ELEM_STR, n > 0 ? n : 1);
|
|
218
|
+
size_t blocks = 0;
|
|
219
|
+
for (size_t i = 0; i < n; i++) {
|
|
220
|
+
ScrStr *entry = scr_arr_get_ref(certs, (double)i); /* +1 */
|
|
221
|
+
size_t before = (size_t)scr_arr_len(next);
|
|
222
|
+
blocks += scr_ca_push_pem_blocks(next, entry->data, entry->len);
|
|
223
|
+
/* Dedupe the freshly pushed blocks against everything before them. */
|
|
224
|
+
size_t after = (size_t)scr_arr_len(next);
|
|
225
|
+
for (size_t j = after; j > before; j--) {
|
|
226
|
+
ScrStr *cand = scr_arr_get_ref(next, (double)(j - 1)); /* +1 */
|
|
227
|
+
bool dup = false;
|
|
228
|
+
for (size_t k = 0; k + 1 < j && !dup; k++) {
|
|
229
|
+
ScrStr *prev = scr_arr_get_ref(next, (double)k); /* +1 */
|
|
230
|
+
dup = prev->len == cand->len && memcmp(prev->data, cand->data, cand->len) == 0;
|
|
231
|
+
scr_str_release(prev);
|
|
232
|
+
}
|
|
233
|
+
scr_str_release(cand);
|
|
234
|
+
if (dup) {
|
|
235
|
+
/* Drop the duplicate: rebuild without index j-1 (rare path —
|
|
236
|
+
* a simple splice via slice would copy anyway; do it in place). */
|
|
237
|
+
ScrArr *trimmed = scr_arr_new(SCR_ELEM_STR, after);
|
|
238
|
+
for (size_t k = 0; k < (size_t)scr_arr_len(next); k++) {
|
|
239
|
+
if (k == j - 1) continue;
|
|
240
|
+
scr_arr_push_ref(trimmed, scr_arr_get_ref(next, (double)k));
|
|
241
|
+
}
|
|
242
|
+
scr_arr_release(next);
|
|
243
|
+
next = trimmed;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
scr_str_release(entry);
|
|
247
|
+
}
|
|
248
|
+
if (n > 0 && blocks == 0) {
|
|
249
|
+
scr_arr_release(next);
|
|
250
|
+
static const char MSG[] = "No valid certificates found in the provided array";
|
|
251
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, MSG, sizeof MSG - 1, "ERR_CRYPTO_OPERATION_FAILED");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
/* Commit: the cached 'default' array and the concatenated anchor
|
|
255
|
+
* buffer scr_tls.c parses (NUL-terminated for mbedTLS's PEM mode). */
|
|
256
|
+
scr_ca_arm_teardown();
|
|
257
|
+
if (scr_ca_default != NULL) scr_arr_release(scr_ca_default);
|
|
258
|
+
scr_ca_default = next;
|
|
259
|
+
size_t total = 0;
|
|
260
|
+
size_t count = (size_t)scr_arr_len(next);
|
|
261
|
+
for (size_t i = 0; i < count; i++) {
|
|
262
|
+
ScrStr *s = scr_arr_get_ref(next, (double)i);
|
|
263
|
+
total += s->len;
|
|
264
|
+
scr_str_release(s);
|
|
265
|
+
}
|
|
266
|
+
char *buf = malloc(total + 1);
|
|
267
|
+
if (buf == NULL) scr_ca_oom();
|
|
268
|
+
size_t off = 0;
|
|
269
|
+
for (size_t i = 0; i < count; i++) {
|
|
270
|
+
ScrStr *s = scr_arr_get_ref(next, (double)i);
|
|
271
|
+
memcpy(buf + off, s->data, s->len);
|
|
272
|
+
off += s->len;
|
|
273
|
+
scr_str_release(s);
|
|
274
|
+
}
|
|
275
|
+
buf[off] = '\0';
|
|
276
|
+
free(scr_ca_override_buf);
|
|
277
|
+
scr_ca_override_buf = buf;
|
|
278
|
+
scr_ca_override_len = off;
|
|
279
|
+
scr_ca_override_gen++;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
bool scr_tls_ca_default_override(const char **pem, size_t *len, uint64_t *gen) {
|
|
283
|
+
if (scr_ca_override_gen == 0) return false;
|
|
284
|
+
*pem = scr_ca_override_buf;
|
|
285
|
+
*len = scr_ca_override_len;
|
|
286
|
+
*gen = scr_ca_override_gen;
|
|
287
|
+
return true;
|
|
288
|
+
}
|