@scriptc/runtime 0.0.9 → 0.0.11
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 +43 -4
- package/src/scr_async.c +24 -4
- package/src/scr_bytes.c +47 -9
- package/src/scr_bytes_io.c +410 -0
- package/src/scr_dc.c +6 -0
- package/src/scr_dgram.c +176 -1
- package/src/scr_dyn_invoke.c +96 -2
- package/src/scr_events_emitter.c +58 -5
- package/src/scr_inspect.c +27 -3
- package/src/scr_island.c +389 -3
- package/src/scr_json.c +488 -7
- package/src/scr_lib.c +21 -1
- package/src/scr_library.c +15 -0
- package/src/scr_net.c +112 -0
- package/src/scr_regex.c +39 -6
- package/src/scr_runtime.h +296 -17
- package/src/scr_stream.c +214 -7
- package/src/scr_tls.c +172 -0
package/src/scr_bytes_io.c
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
#include <stdlib.h>
|
|
13
13
|
#include <string.h>
|
|
14
14
|
#include <time.h>
|
|
15
|
+
#ifdef __APPLE__
|
|
16
|
+
#include <sys/stat.h> /* lchmod(2) — the fs.lchmodSync ladder's real tail */
|
|
17
|
+
#endif
|
|
15
18
|
|
|
16
19
|
static void scr_bytes_io_oom(void) {
|
|
17
20
|
scr_trap("scriptc: out of memory\n");
|
|
@@ -254,3 +257,410 @@ double scr_fs_to_unix_timestamp(const ScrDyn *t) {
|
|
|
254
257
|
scr_dyn_arg_type_fail("time", "an instance of Date or an Time in seconds", t);
|
|
255
258
|
return 0;
|
|
256
259
|
}
|
|
260
|
+
|
|
261
|
+
/* ── the fs argument-validation ladders (checked-dynamic lane) ─────────
|
|
262
|
+
* Each fs.*Chk libCall replicates its API's Node-order validation over
|
|
263
|
+
* DOM values and throws Node's exact typed errors; when every validation
|
|
264
|
+
* passes, the honest tail runs — the real operation where one exists
|
|
265
|
+
* (mkdtempSync, lchmodSync on macOS), the compiler-rendered SC2020 fence
|
|
266
|
+
* otherwise (scr_throw_lowering_fence). All arguments borrowed. */
|
|
267
|
+
|
|
268
|
+
static bool scr_fs_dyn_absent(const ScrDyn *v) {
|
|
269
|
+
return v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_NULL;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static bool scr_fs_str_is(const ScrStr *s, const char *lit) {
|
|
273
|
+
size_t n = strlen(lit);
|
|
274
|
+
return s->len == n && memcmp(s->data, lit, n) == 0;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Node's maybeCallback/validateFunction over a callback slot. */
|
|
278
|
+
static bool scr_fs_cb_chk(const ScrDyn *cb, const char *name) {
|
|
279
|
+
if (cb->kind == SCR_DYN_FUNC) return true;
|
|
280
|
+
scr_dyn_arg_type_fail(name, "of type function", cb);
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* getValidatedPath: strings and Buffers pass (URL instances never reach
|
|
285
|
+
* these ladders — the DOM has no URL kind here, and Node would accept
|
|
286
|
+
* only file: URLs anyway). */
|
|
287
|
+
static bool scr_fs_path_chk(const ScrDyn *p, const char *name) {
|
|
288
|
+
if (p->kind == SCR_DYN_STR || p->kind == SCR_DYN_BYTES) return true;
|
|
289
|
+
scr_dyn_arg_type_fail(name, "of type string or an instance of Buffer or URL", p);
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* assertEncoding over an options slot (a bare encoding string or an
|
|
294
|
+
* options record's `encoding` member): Node throws ERR_INVALID_ARG_VALUE
|
|
295
|
+
* for any truthy value Buffer.isEncoding rejects. */
|
|
296
|
+
static bool scr_fs_encoding_chk(const ScrDyn *opts) {
|
|
297
|
+
const ScrDyn *enc = opts;
|
|
298
|
+
if (opts->kind == SCR_DYN_OBJ) {
|
|
299
|
+
enc = scr_dyn_obj_get(opts, "encoding", 8);
|
|
300
|
+
if (enc == NULL) return true;
|
|
301
|
+
}
|
|
302
|
+
if (scr_fs_dyn_absent(enc)) return true;
|
|
303
|
+
if (enc->kind == SCR_DYN_STR) {
|
|
304
|
+
if (enc->v.str->len == 0) return true; /* falsy: assertEncoding's `encoding &&` gate */
|
|
305
|
+
if (scr_bytes_is_encoding(enc->v.str)) return true;
|
|
306
|
+
}
|
|
307
|
+
if (enc->kind == SCR_DYN_BOOL && !enc->v.b) return true;
|
|
308
|
+
if (enc->kind == SCR_DYN_NUM && enc->v.num == 0) return true;
|
|
309
|
+
scr_dyn_arg_value_fail("encoding", "is invalid encoding", enc);
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* parseFileMode: integers 0..2^32-1 pass, octal strings parse, and the
|
|
314
|
+
* rest throw Node's exact ladder (validateUint32's wording). */
|
|
315
|
+
static bool scr_fs_mode_chk(const ScrDyn *m, const char *name) {
|
|
316
|
+
if (m->kind == SCR_DYN_STR) {
|
|
317
|
+
const ScrStr *s = m->v.str;
|
|
318
|
+
bool octal = s->len > 0;
|
|
319
|
+
for (size_t i = 0; octal && i < s->len; i++) {
|
|
320
|
+
if (s->data[i] < '0' || s->data[i] > '7') octal = false;
|
|
321
|
+
}
|
|
322
|
+
if (octal) return true;
|
|
323
|
+
scr_dyn_arg_value_fail(name, "must be a 32-bit unsigned integer or an octal string", m);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
if (m->kind != SCR_DYN_NUM) {
|
|
327
|
+
scr_dyn_arg_type_fail(name, "of type number", m);
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
double v = m->v.num;
|
|
331
|
+
if (!(isfinite(v) && trunc(v) == v)) {
|
|
332
|
+
char recv[48], msg[160];
|
|
333
|
+
scr_num_received(v, recv);
|
|
334
|
+
int len = snprintf(msg, sizeof msg,
|
|
335
|
+
"The value of \"%s\" is out of range. It must be an integer. Received %s",
|
|
336
|
+
name, recv);
|
|
337
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (v < 0 || v > 4294967295.0) {
|
|
341
|
+
char recv[48], msg[160];
|
|
342
|
+
scr_num_received(v, recv);
|
|
343
|
+
int len = snprintf(msg, sizeof msg,
|
|
344
|
+
"The value of \"%s\" is out of range. It must be >= 0 && <= 4294967295. Received %s",
|
|
345
|
+
name, recv);
|
|
346
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* fs.exists(path, cb) — the REAL deprecated-API shape: the callback
|
|
353
|
+
* validates synchronously (Node's one throwing arm), and the answer
|
|
354
|
+
* arrives asynchronously through it — string/Buffer paths run the
|
|
355
|
+
* existsSync probe at fire time, every other path kind answers `false`
|
|
356
|
+
* (Node swallows getValidatedPath failures there). The loop-held timer
|
|
357
|
+
* matches Node's I/O-completion timing closely enough for the
|
|
358
|
+
* sequential CLI corpus. */
|
|
359
|
+
#ifndef SCR_LIB
|
|
360
|
+
static void scr_fs_exists_fire(ScrClosure *self) {
|
|
361
|
+
ScrDyn *path = scr_box_get_ref(self->caps[0]); /* +1 */
|
|
362
|
+
ScrDyn *cb = scr_box_get_ref(self->caps[1]); /* +1 */
|
|
363
|
+
bool ans = false;
|
|
364
|
+
if (path->kind == SCR_DYN_STR) {
|
|
365
|
+
ScrStr *p = scr_str_retain(path->v.str);
|
|
366
|
+
ans = scr_fs_exists(p);
|
|
367
|
+
scr_str_release(p);
|
|
368
|
+
} else if (path->kind == SCR_DYN_BYTES) {
|
|
369
|
+
ScrStr *p = scr_str_new((const char *)path->v.bytes->data, path->v.bytes->len);
|
|
370
|
+
ans = scr_fs_exists(p);
|
|
371
|
+
scr_str_release(p);
|
|
372
|
+
}
|
|
373
|
+
ScrDyn *arg = scr_dyn_new_bool(ans);
|
|
374
|
+
ScrDyn *r = scr_dyn_call(cb, &arg, 1, "the fs.exists callback");
|
|
375
|
+
scr_dyn_release(arg);
|
|
376
|
+
scr_dyn_release(r);
|
|
377
|
+
scr_dyn_release(path);
|
|
378
|
+
scr_dyn_release(cb);
|
|
379
|
+
}
|
|
380
|
+
#endif
|
|
381
|
+
|
|
382
|
+
ScrDyn *scr_fs_exists_async(const ScrDyn *path, const ScrDyn *cb) {
|
|
383
|
+
if (!scr_fs_cb_chk(cb, "cb")) return NULL;
|
|
384
|
+
if (path->kind != SCR_DYN_STR && path->kind != SCR_DYN_BYTES) {
|
|
385
|
+
/* Node's wart, kept exactly: a path getValidatedPath rejects answers
|
|
386
|
+
* false through the callback SYNCHRONOUSLY (`return callback(false)`
|
|
387
|
+
* in lib/fs.js exists). */
|
|
388
|
+
ScrDyn *arg = scr_dyn_new_bool(false);
|
|
389
|
+
ScrDyn *r = scr_dyn_call(cb, &arg, 1, "the fs.exists callback");
|
|
390
|
+
scr_dyn_release(arg);
|
|
391
|
+
scr_dyn_release(r);
|
|
392
|
+
if (scr_exc_pending()) return NULL;
|
|
393
|
+
return scr_dyn_retain(scr_dyn_undefined());
|
|
394
|
+
}
|
|
395
|
+
#ifdef SCR_LIB
|
|
396
|
+
/* Library links exclude the event-loop unit, and the compile-time scan
|
|
397
|
+
* refuses the fs.exists surface (SC4005's family) — this arm exists
|
|
398
|
+
* only so the TU links without scr_async. Unreachable by construction. */
|
|
399
|
+
scr_trap("scriptc: internal error: fs.exists async fire reached in a library build — please report this");
|
|
400
|
+
return NULL;
|
|
401
|
+
#else
|
|
402
|
+
ScrClosure *clo = scr_closure_new((void *)scr_fs_exists_fire, 2);
|
|
403
|
+
clo->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
404
|
+
scr_box_set_ref(clo->caps[0], scr_dyn_retain((ScrDyn *)path));
|
|
405
|
+
clo->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL);
|
|
406
|
+
scr_box_set_ref(clo->caps[1], scr_dyn_retain((ScrDyn *)cb));
|
|
407
|
+
scr_set_timeout(clo, 0);
|
|
408
|
+
return scr_dyn_retain(scr_dyn_undefined());
|
|
409
|
+
#endif
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/* fs.mkdtemp(prefix, options?, cb): callback first (makeCallback), then
|
|
413
|
+
* the prefix (getValidatedPath's 'prefix' slot); the async op fences. */
|
|
414
|
+
void scr_fs_mkdtemp_chk(const ScrDyn *prefix, const ScrDyn *cb, const ScrStr *fence) {
|
|
415
|
+
if (!scr_fs_cb_chk(cb, "cb")) return;
|
|
416
|
+
if (!scr_fs_path_chk(prefix, "prefix")) return;
|
|
417
|
+
scr_throw_lowering_fence(fence);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/* fs.mkdtempSync(prefix, options?): prefix validates (Node's 'prefix'
|
|
421
|
+
* slot), the options walk accepts only shapes that leave utf8 semantics
|
|
422
|
+
* (absent/empty records, utf8 spellings — invalid encodings throw the
|
|
423
|
+
* assertEncoding ladder, other effectful options fence), then the REAL
|
|
424
|
+
* mkdtemp runs. +1 result or NULL with the exception pending. */
|
|
425
|
+
ScrStr *scr_fs_mkdtemp_sync_chk(const ScrDyn *prefix, const ScrDyn *opts, const ScrStr *fence) {
|
|
426
|
+
if (!scr_fs_path_chk(prefix, "prefix")) return NULL;
|
|
427
|
+
if (!scr_fs_encoding_chk(opts)) return NULL;
|
|
428
|
+
bool utf8 = true;
|
|
429
|
+
if (opts->kind == SCR_DYN_OBJ) {
|
|
430
|
+
for (size_t i = 0; i < opts->v.obj.len; i++) {
|
|
431
|
+
const ScrDynEntry *e = &opts->v.obj.entries[i];
|
|
432
|
+
if (scr_fs_dyn_absent(e->value)) continue;
|
|
433
|
+
if (strcmp(e->key, "encoding") == 0) {
|
|
434
|
+
const ScrDyn *enc = e->value;
|
|
435
|
+
if (!(enc->kind == SCR_DYN_STR &&
|
|
436
|
+
(scr_fs_str_is(enc->v.str, "utf8") || scr_fs_str_is(enc->v.str, "utf-8")))) {
|
|
437
|
+
utf8 = false;
|
|
438
|
+
}
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
utf8 = false; /* an unmodeled effectful option */
|
|
442
|
+
}
|
|
443
|
+
} else if (opts->kind == SCR_DYN_STR) {
|
|
444
|
+
utf8 = scr_fs_str_is(opts->v.str, "utf8") || scr_fs_str_is(opts->v.str, "utf-8");
|
|
445
|
+
} else if (!scr_fs_dyn_absent(opts)) {
|
|
446
|
+
utf8 = false;
|
|
447
|
+
}
|
|
448
|
+
if (!utf8 || prefix->kind != SCR_DYN_STR) {
|
|
449
|
+
scr_throw_lowering_fence(fence);
|
|
450
|
+
return NULL;
|
|
451
|
+
}
|
|
452
|
+
ScrStr *p = scr_str_retain(prefix->v.str);
|
|
453
|
+
ScrStr *r = scr_fs_mkdtemp(p);
|
|
454
|
+
scr_str_release(p);
|
|
455
|
+
return r;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/* fs.readFile(path, options?, cb): Node's order — the callback
|
|
459
|
+
* (maybeCallback), the options walk's assertEncoding, then the path;
|
|
460
|
+
* the async read itself fences. */
|
|
461
|
+
void scr_fs_read_file_chk(const ScrDyn *path, const ScrDyn *opts, const ScrDyn *cb,
|
|
462
|
+
const ScrStr *fence) {
|
|
463
|
+
if (!scr_fs_cb_chk(cb, "cb")) return;
|
|
464
|
+
if (!scr_fs_encoding_chk(opts)) return;
|
|
465
|
+
if (!scr_fs_path_chk(path, "path")) return;
|
|
466
|
+
scr_throw_lowering_fence(fence);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* fs.opendirSync(path, options?): getValidatedPath, then getOptions'
|
|
470
|
+
* assertEncoding; the Dir machinery fences. */
|
|
471
|
+
void scr_fs_opendir_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence) {
|
|
472
|
+
if (!scr_fs_path_chk(path, "path")) return;
|
|
473
|
+
if (!scr_fs_encoding_chk(opts)) return;
|
|
474
|
+
scr_throw_lowering_fence(fence);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* fs.watchFile(path, options?, listener): the path first, the listener's
|
|
478
|
+
* function check second (Node's watchFile order); real watching fences. */
|
|
479
|
+
void scr_fs_watch_file_chk(const ScrDyn *path, const ScrDyn *listener, const ScrStr *fence) {
|
|
480
|
+
if (!scr_fs_path_chk(path, "path")) return;
|
|
481
|
+
if (listener->kind != SCR_DYN_FUNC) {
|
|
482
|
+
scr_dyn_arg_type_fail("listener", "of type function", listener);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
scr_throw_lowering_fence(fence);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/* fs.lchmod / lchmodSync / fs.promises.lchmod — macOS-only in Node (the
|
|
489
|
+
* callback/sync pair is not even exported elsewhere, so non-APPLE builds
|
|
490
|
+
* answer the not-a-function TypeError; the promise form rejects
|
|
491
|
+
* ERR_METHOD_NOT_IMPLEMENTED, Node's own linux shape). On macOS the
|
|
492
|
+
* validation ladder runs in Node's order and the real lchmod(2) applies
|
|
493
|
+
* where an operation survives it. */
|
|
494
|
+
static bool scr_fs_lchmod_defined(const char *api) {
|
|
495
|
+
#ifdef __APPLE__
|
|
496
|
+
(void)api;
|
|
497
|
+
return true;
|
|
498
|
+
#else
|
|
499
|
+
char msg[64];
|
|
500
|
+
int len = snprintf(msg, sizeof msg, "%s is not a function", api);
|
|
501
|
+
scr_throw_error_msg(SCR_ERR_TYPE, msg, (size_t)len);
|
|
502
|
+
return false;
|
|
503
|
+
#endif
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
void scr_fs_lchmod_chk(const ScrDyn *path, const ScrDyn *mode, const ScrDyn *cb,
|
|
507
|
+
const ScrStr *fence) {
|
|
508
|
+
if (!scr_fs_lchmod_defined("fs.lchmod")) return;
|
|
509
|
+
if (!scr_fs_cb_chk(cb, "cb")) return;
|
|
510
|
+
if (!scr_fs_path_chk(path, "path")) return;
|
|
511
|
+
if (!scr_fs_mode_chk(mode, "mode")) return;
|
|
512
|
+
scr_throw_lowering_fence(fence); /* the async op + callback dispatch */
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#ifdef __APPLE__
|
|
516
|
+
static void scr_fs_lchmod_apply(const ScrDyn *path, const ScrDyn *mode) {
|
|
517
|
+
ScrStr *p = path->kind == SCR_DYN_STR ? scr_str_retain(path->v.str)
|
|
518
|
+
: scr_str_new((const char *)path->v.bytes->data, path->v.bytes->len);
|
|
519
|
+
double m = mode->kind == SCR_DYN_NUM ? mode->v.num : (double)strtol(mode->v.str->data, NULL, 8);
|
|
520
|
+
if (lchmod(p->data, (mode_t)m) != 0) scr_fs_throw(errno, "lchmod", p);
|
|
521
|
+
scr_str_release(p);
|
|
522
|
+
}
|
|
523
|
+
#endif
|
|
524
|
+
|
|
525
|
+
/* Answers the DOM undefined (+1) on success — lchmodSync's JS value, so
|
|
526
|
+
* return-position uses lower; NULL with the exception pending. */
|
|
527
|
+
ScrDyn *scr_fs_lchmod_sync_chk(const ScrDyn *path, const ScrDyn *mode) {
|
|
528
|
+
if (!scr_fs_lchmod_defined("fs.lchmodSync")) return NULL;
|
|
529
|
+
if (!scr_fs_path_chk(path, "path")) return NULL;
|
|
530
|
+
if (!scr_fs_mode_chk(mode, "mode")) return NULL;
|
|
531
|
+
#ifdef __APPLE__
|
|
532
|
+
scr_fs_lchmod_apply(path, mode);
|
|
533
|
+
if (scr_exc_pending()) return NULL;
|
|
534
|
+
#endif
|
|
535
|
+
return scr_dyn_retain(scr_dyn_undefined());
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
ScrPromise *scr_fsp_lchmod_chk(const ScrDyn *path, const ScrDyn *mode) {
|
|
539
|
+
#ifndef __APPLE__
|
|
540
|
+
(void)path;
|
|
541
|
+
(void)mode;
|
|
542
|
+
static const char ni[] = "The lchmod() method is not implemented";
|
|
543
|
+
scr_throw_error_msg_code(SCR_ERR_ERROR, ni, sizeof ni - 1, "ERR_METHOD_NOT_IMPLEMENTED");
|
|
544
|
+
return scr_promise_settled_void();
|
|
545
|
+
#else
|
|
546
|
+
if (scr_fs_path_chk(path, "path") && scr_fs_mode_chk(mode, "mode")) {
|
|
547
|
+
scr_fs_lchmod_apply(path, mode);
|
|
548
|
+
}
|
|
549
|
+
return scr_promise_settled_void();
|
|
550
|
+
#endif
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/* fs.read(fd, buffer, offset, length, position, cb) — the full argument
|
|
554
|
+
* ladder in Node's order (buffer, fd, offset, length, position); the
|
|
555
|
+
* async read fences. Bounds follow lib/fs.js read(): offset within the
|
|
556
|
+
* buffer, length within buffer - offset. */
|
|
557
|
+
static bool scr_fs_int_range_chk(const ScrDyn *v, const char *name, double min, double max,
|
|
558
|
+
const char *range) {
|
|
559
|
+
if (v->kind != SCR_DYN_NUM) {
|
|
560
|
+
scr_dyn_arg_type_fail(name, "of type number", v);
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
double n = v->v.num;
|
|
564
|
+
char recv[48], msg[192];
|
|
565
|
+
if (!(isfinite(n) && trunc(n) == n)) {
|
|
566
|
+
scr_num_received(n, recv);
|
|
567
|
+
int len = snprintf(msg, sizeof msg,
|
|
568
|
+
"The value of \"%s\" is out of range. It must be an integer. Received %s",
|
|
569
|
+
name, recv);
|
|
570
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
if (n < min || n > max) {
|
|
574
|
+
scr_num_received(n, recv);
|
|
575
|
+
int len = snprintf(msg, sizeof msg,
|
|
576
|
+
"The value of \"%s\" is out of range. It must be %s. Received %s",
|
|
577
|
+
name, range, recv);
|
|
578
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
void scr_fs_read_chk(const ScrDyn *fd, const ScrDyn *buffer, const ScrDyn *offset,
|
|
585
|
+
const ScrDyn *length, const ScrDyn *position, const ScrStr *fence) {
|
|
586
|
+
if (buffer->kind != SCR_DYN_BYTES) {
|
|
587
|
+
scr_dyn_arg_type_fail("buffer", "an instance of Buffer, TypedArray, or DataView", buffer);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (fd->kind != SCR_DYN_NUM) {
|
|
591
|
+
scr_dyn_arg_type_fail("fd", "of type number", fd);
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
double buflen = (double)buffer->v.bytes->len;
|
|
595
|
+
if (!scr_fs_dyn_absent(offset)) {
|
|
596
|
+
/* validateInteger's MAX_SAFE range first, the buffer bound second —
|
|
597
|
+
* Node renders each with its own max. */
|
|
598
|
+
if (!scr_fs_int_range_chk(offset, "offset", 0, 9007199254740991.0,
|
|
599
|
+
">= 0 && <= 9007199254740991")) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
if (offset->v.num > buflen) {
|
|
603
|
+
char range[64];
|
|
604
|
+
snprintf(range, sizeof range, ">= 0 && <= %.0f", buflen);
|
|
605
|
+
if (!scr_fs_int_range_chk(offset, "offset", 0, buflen, range)) return;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
double off = offset->kind == SCR_DYN_NUM ? offset->v.num : 0;
|
|
609
|
+
if (!scr_fs_dyn_absent(length)) {
|
|
610
|
+
if (length->kind != SCR_DYN_NUM || !(isfinite(length->v.num) && trunc(length->v.num) == length->v.num) ||
|
|
611
|
+
length->v.num < 0) {
|
|
612
|
+
/* Node renders the bare ">= 0" form here (checkPosition's cousin). */
|
|
613
|
+
if (length->kind == SCR_DYN_NUM) {
|
|
614
|
+
char recv[48], msg[160];
|
|
615
|
+
scr_num_received(length->v.num, recv);
|
|
616
|
+
const char *shape = (isfinite(length->v.num) && trunc(length->v.num) == length->v.num)
|
|
617
|
+
? "It must be >= 0."
|
|
618
|
+
: "It must be an integer.";
|
|
619
|
+
int len = snprintf(msg, sizeof msg,
|
|
620
|
+
"The value of \"length\" is out of range. %s Received %s", shape, recv);
|
|
621
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
scr_dyn_arg_type_fail("length", "of type number", length);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (length->v.num > buflen - off) {
|
|
628
|
+
char recv[48], msg[160];
|
|
629
|
+
scr_num_received(length->v.num, recv);
|
|
630
|
+
int len = snprintf(msg, sizeof msg,
|
|
631
|
+
"The value of \"length\" is out of range. It must be <= %.0f. Received %s",
|
|
632
|
+
buflen - off, recv);
|
|
633
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (!scr_fs_dyn_absent(position)) {
|
|
638
|
+
if (position->kind != SCR_DYN_NUM) {
|
|
639
|
+
scr_dyn_arg_type_fail("position", "of type bigint or integer", position);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (!scr_fs_int_range_chk(position, "position", -1, 9007199254740991.0,
|
|
643
|
+
">= -1 && <= 9007199254740991")) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
scr_throw_lowering_fence(fence);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/* createReadStream/createWriteStream(path, options?): getOptions'
|
|
651
|
+
* assertEncoding, the fd member's FileHandle-or-integer contract when
|
|
652
|
+
* present, the path contract otherwise; the stream machinery fences. */
|
|
653
|
+
void scr_fs_stream_opts_chk(const ScrDyn *path, const ScrDyn *opts, const ScrStr *fence) {
|
|
654
|
+
if (!scr_fs_encoding_chk(opts)) return;
|
|
655
|
+
const ScrDyn *fd = opts->kind == SCR_DYN_OBJ ? scr_dyn_obj_get(opts, "fd", 2) : NULL;
|
|
656
|
+
if (fd != NULL && !scr_fs_dyn_absent(fd)) {
|
|
657
|
+
if (fd->kind != SCR_DYN_NUM) {
|
|
658
|
+
scr_dyn_prop_type_fail("options.fd", "of type number or an instance of FileHandle", fd);
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (!scr_fs_int_range_chk(fd, "fd", 0, 2147483647.0, ">= 0 && <= 2147483647")) return;
|
|
662
|
+
} else if (!scr_fs_path_chk(path, "path")) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
scr_throw_lowering_fence(fence);
|
|
666
|
+
}
|
package/src/scr_dc.c
CHANGED
|
@@ -132,6 +132,12 @@ static void dc_throw_bad_fn_arg(const ScrDyn *cb, const char *arg_name) {
|
|
|
132
132
|
(int)(cb->v.str->len < 28 ? cb->v.str->len : 28), cb->v.str->data);
|
|
133
133
|
received = detail;
|
|
134
134
|
break;
|
|
135
|
+
case SCR_DYN_JSVAL:
|
|
136
|
+
/* An engine callback may well BE a function — subscribing island
|
|
137
|
+
* values is unarmed (lane dyn-routing-ops); fence loudly rather
|
|
138
|
+
* than claim "Received an instance of Object". */
|
|
139
|
+
scr_dyn_isl_fence(cb, "subscribing");
|
|
140
|
+
return;
|
|
135
141
|
default: break;
|
|
136
142
|
}
|
|
137
143
|
char buf[160];
|
package/src/scr_dgram.c
CHANGED
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
|
|
65
65
|
#include <errno.h>
|
|
66
66
|
#include <fcntl.h>
|
|
67
|
+
#include <math.h>
|
|
67
68
|
#include <stdio.h>
|
|
68
69
|
#include <stdlib.h>
|
|
69
70
|
#include <string.h>
|
|
@@ -466,7 +467,18 @@ ScrDgramSocket *scr_dgram_create(bool reuse_addr) {
|
|
|
466
467
|
}
|
|
467
468
|
|
|
468
469
|
static void scr_dgram_throw(const char *msg) {
|
|
469
|
-
|
|
470
|
+
/* Node's state errors carry their ERR_SOCKET_* codes; the message IS
|
|
471
|
+
* the discriminant (each arm throws exactly one text). */
|
|
472
|
+
const char *code =
|
|
473
|
+
strcmp(msg, "Already connected") == 0 ? "ERR_SOCKET_DGRAM_IS_CONNECTED"
|
|
474
|
+
: strcmp(msg, "Not running") == 0 ? "ERR_SOCKET_DGRAM_NOT_RUNNING"
|
|
475
|
+
: strcmp(msg, "Socket is already bound") == 0 ? "ERR_SOCKET_ALREADY_BOUND"
|
|
476
|
+
: NULL;
|
|
477
|
+
if (code != NULL) {
|
|
478
|
+
scr_throw_error_msg_code(0 /* Error */, msg, strlen(msg), code);
|
|
479
|
+
} else {
|
|
480
|
+
scr_throw_error_msg(0 /* Error */, msg, strlen(msg));
|
|
481
|
+
}
|
|
470
482
|
}
|
|
471
483
|
|
|
472
484
|
/* Resolve a numeric host into a sockaddr_in; "" means any (bind's
|
|
@@ -1005,3 +1017,166 @@ void scr_dgram_install(void) {
|
|
|
1005
1017
|
atexit(scr_dgram_cleanup_atexit);
|
|
1006
1018
|
scr_loop_set_dgram(&scr_dgram_pending, &scr_dgram_dispatch, &scr_dgram_pollfd);
|
|
1007
1019
|
}
|
|
1020
|
+
|
|
1021
|
+
/* ── the send argument-validation ladder (checked-dynamic lane) ─────────
|
|
1022
|
+
* Node's Socket.prototype.send signature shuffle and validation order
|
|
1023
|
+
* over DOM arguments, byte-for-byte: the connected/unconnected split
|
|
1024
|
+
* decides whether (a1, a2) are an offset/length slice or the port/address
|
|
1025
|
+
* pair; sliceBuffer validates the buffer's type and bounds
|
|
1026
|
+
* (ERR_BUFFER_OUT_OF_BOUNDS); list payloads validate per element with the
|
|
1027
|
+
* LIST as the Received tail; unconnected sends validate the port
|
|
1028
|
+
* (ERR_SOCKET_BAD_PORT, > 0 and < 65536 with the specific-type tail and
|
|
1029
|
+
* Node's trailing period) and the address's string contract; a send with
|
|
1030
|
+
* a port or address on a connected socket answers ERR_SOCKET_DGRAM_IS_
|
|
1031
|
+
* CONNECTED. A fully-validated unconnected single-payload send RUNS —
|
|
1032
|
+
* the callback form and the connected sends keep the compiler-rendered
|
|
1033
|
+
* fence. All dyn arguments borrowed. */
|
|
1034
|
+
|
|
1035
|
+
static bool scr_dgram_dyn_truthy(const ScrDyn *v) {
|
|
1036
|
+
switch (v->kind) {
|
|
1037
|
+
case SCR_DYN_UNDEF:
|
|
1038
|
+
case SCR_DYN_NULL: return false;
|
|
1039
|
+
case SCR_DYN_BOOL: return v->v.b;
|
|
1040
|
+
case SCR_DYN_NUM: return v->v.num == v->v.num && v->v.num != 0;
|
|
1041
|
+
case SCR_DYN_STR: return v->v.str->len > 0;
|
|
1042
|
+
default: return true;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
static uint32_t scr_dgram_to_u32(const ScrDyn *v) {
|
|
1047
|
+
if (v->kind != SCR_DYN_NUM) return 0; /* >>> 0 over the ladder's shapes */
|
|
1048
|
+
double t = v->v.num;
|
|
1049
|
+
if (t != t || isinf(t)) return 0;
|
|
1050
|
+
t = trunc(t);
|
|
1051
|
+
t = fmod(t, 4294967296.0);
|
|
1052
|
+
if (t < 0) t += 4294967296.0;
|
|
1053
|
+
return (uint32_t)t;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
static const char SCR_DGRAM_BUF_EXPECTED[] =
|
|
1057
|
+
"of type string or an instance of Buffer, TypedArray, or DataView";
|
|
1058
|
+
|
|
1059
|
+
void scr_dgram_send_chk(ScrDgramSocket *s, const ScrDyn *buffer, const ScrDyn *a1,
|
|
1060
|
+
const ScrDyn *a2, const ScrDyn *a3, const ScrDyn *a4,
|
|
1061
|
+
const ScrStr *fence) {
|
|
1062
|
+
const ScrDyn *offset = a1, *length = a2, *port = a3, *address = a4;
|
|
1063
|
+
const ScrDyn *callback = scr_dyn_undefined();
|
|
1064
|
+
bool connected = s->connected;
|
|
1065
|
+
bool sliced = false;
|
|
1066
|
+
if (!connected) {
|
|
1067
|
+
if (scr_dgram_dyn_truthy(address) ||
|
|
1068
|
+
(scr_dgram_dyn_truthy(port) && port->kind != SCR_DYN_FUNC)) {
|
|
1069
|
+
sliced = true;
|
|
1070
|
+
} else {
|
|
1071
|
+
callback = port;
|
|
1072
|
+
port = offset;
|
|
1073
|
+
address = length;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
if (length->kind == SCR_DYN_NUM) {
|
|
1077
|
+
sliced = true;
|
|
1078
|
+
if (port->kind == SCR_DYN_FUNC) callback = port;
|
|
1079
|
+
} else {
|
|
1080
|
+
callback = offset;
|
|
1081
|
+
port = a3;
|
|
1082
|
+
address = a4;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
size_t slice_off = 0, slice_len = 0;
|
|
1086
|
+
if (sliced) {
|
|
1087
|
+
double bytelen;
|
|
1088
|
+
if (buffer->kind == SCR_DYN_STR) bytelen = (double)buffer->v.str->len;
|
|
1089
|
+
else if (buffer->kind == SCR_DYN_BYTES) bytelen = scr_bytes_byte_len(buffer->v.bytes);
|
|
1090
|
+
else {
|
|
1091
|
+
scr_dyn_arg_type_fail("buffer", SCR_DGRAM_BUF_EXPECTED, buffer);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
uint32_t off = scr_dgram_to_u32(offset);
|
|
1095
|
+
uint32_t len = scr_dgram_to_u32(length);
|
|
1096
|
+
if ((double)off > bytelen) {
|
|
1097
|
+
static const char msg[] = "\"offset\" is outside of buffer bounds";
|
|
1098
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if ((double)off + (double)len > bytelen) {
|
|
1102
|
+
static const char msg[] = "\"length\" is outside of buffer bounds";
|
|
1103
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, sizeof msg - 1, "ERR_BUFFER_OUT_OF_BOUNDS");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
slice_off = off;
|
|
1107
|
+
slice_len = len;
|
|
1108
|
+
} else if (buffer->kind == SCR_DYN_ARR) {
|
|
1109
|
+
for (size_t i = 0; i < buffer->v.arr.len; i++) {
|
|
1110
|
+
const ScrDyn *e = buffer->v.arr.items[i];
|
|
1111
|
+
if (e->kind != SCR_DYN_STR && e->kind != SCR_DYN_BYTES) {
|
|
1112
|
+
scr_dyn_arg_type_fail("buffer list arguments", SCR_DGRAM_BUF_EXPECTED, buffer);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
} else if (buffer->kind != SCR_DYN_STR && buffer->kind != SCR_DYN_BYTES) {
|
|
1117
|
+
scr_dyn_arg_type_fail("buffer", SCR_DGRAM_BUF_EXPECTED, buffer);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (connected) {
|
|
1121
|
+
if (scr_dgram_dyn_truthy(port) || scr_dgram_dyn_truthy(address)) {
|
|
1122
|
+
scr_dgram_throw("Already connected");
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
scr_throw_lowering_fence(fence); /* connected sends have no lowering yet */
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
/* validatePort(port, 'Port', false): integers (or numeric strings)
|
|
1129
|
+
* strictly between 0 and 65536; everything else renders the specific
|
|
1130
|
+
* type with Node's trailing period. */
|
|
1131
|
+
double portnum = -1;
|
|
1132
|
+
{
|
|
1133
|
+
bool ok = false;
|
|
1134
|
+
if (port->kind == SCR_DYN_NUM && trunc(port->v.num) == port->v.num &&
|
|
1135
|
+
port->v.num > 0 && port->v.num < 65536) {
|
|
1136
|
+
ok = true;
|
|
1137
|
+
portnum = port->v.num;
|
|
1138
|
+
} else if (port->kind == SCR_DYN_STR && port->v.str->len > 0) {
|
|
1139
|
+
ScrStr *ps = scr_str_retain(port->v.str);
|
|
1140
|
+
double n = scr_string_to_number(ps);
|
|
1141
|
+
scr_str_release(ps);
|
|
1142
|
+
if (n == n && trunc(n) == n && n > 0 && n < 65536) {
|
|
1143
|
+
ok = true;
|
|
1144
|
+
portnum = n;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (!ok) {
|
|
1148
|
+
char detail[64], msg[160];
|
|
1149
|
+
const char *d = scr_dyn_specific_type(port, detail, sizeof detail);
|
|
1150
|
+
int len = snprintf(msg, sizeof msg, "Port should be > 0 and < 65536. Received %s.", d);
|
|
1151
|
+
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT");
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
if (address->kind == SCR_DYN_FUNC) {
|
|
1156
|
+
callback = address;
|
|
1157
|
+
address = scr_dyn_undefined();
|
|
1158
|
+
} else if (address->kind != SCR_DYN_UNDEF && address->kind != SCR_DYN_NULL &&
|
|
1159
|
+
address->kind != SCR_DYN_STR) {
|
|
1160
|
+
/* Node's gate is null/undefined-only: a falsy 0 still throws. */
|
|
1161
|
+
scr_dyn_arg_type_fail("address", "of type string", address);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (callback->kind == SCR_DYN_FUNC || buffer->kind == SCR_DYN_ARR) {
|
|
1165
|
+
/* completion callbacks and list concatenation have no lowering yet —
|
|
1166
|
+
* refuse loudly after the full validation ladder, never drop */
|
|
1167
|
+
scr_throw_lowering_fence(fence);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const char *data = buffer->kind == SCR_DYN_STR ? buffer->v.str->data
|
|
1171
|
+
: (const char *)buffer->v.bytes->data;
|
|
1172
|
+
size_t datalen = buffer->kind == SCR_DYN_STR ? buffer->v.str->len
|
|
1173
|
+
: (size_t)scr_bytes_byte_len(buffer->v.bytes);
|
|
1174
|
+
if (sliced) {
|
|
1175
|
+
data += slice_off;
|
|
1176
|
+
datalen = slice_len;
|
|
1177
|
+
}
|
|
1178
|
+
ScrStr *host = address->kind == SCR_DYN_STR ? scr_str_retain(address->v.str)
|
|
1179
|
+
: scr_str_new("127.0.0.1", 9);
|
|
1180
|
+
scr_dgram_send_raw(s, data, datalen, portnum, host);
|
|
1181
|
+
scr_str_release(host);
|
|
1182
|
+
}
|