@blamejs/core 0.14.16 → 0.14.18
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/CHANGELOG.md +4 -0
- package/README.md +2 -2
- package/lib/agent-orchestrator.js +10 -4
- package/lib/ai-prompt.js +1 -1
- package/lib/app-shutdown.js +28 -0
- package/lib/archive-read.js +215 -16
- package/lib/breach-deadline.js +166 -1
- package/lib/cloud-events.js +3 -1
- package/lib/codepoint-class.js +21 -0
- package/lib/db-schema.js +120 -3
- package/lib/db.js +10 -3
- package/lib/error-page.js +93 -9
- package/lib/external-db.js +164 -13
- package/lib/guard-email.js +36 -3
- package/lib/http-client.js +37 -7
- package/lib/mail-send-deliver.js +15 -5
- package/lib/mail-sieve.js +2 -1
- package/lib/middleware/ai-act-disclosure.js +88 -19
- package/lib/middleware/api-encrypt.js +58 -11
- package/lib/middleware/asyncapi-serve.js +56 -4
- package/lib/middleware/attach-user.js +45 -10
- package/lib/middleware/body-parser.js +70 -14
- package/lib/middleware/csp-report.js +30 -2
- package/lib/middleware/deny-response.js +29 -9
- package/lib/middleware/openapi-serve.js +56 -4
- package/lib/middleware/scim-server.js +7 -4
- package/lib/problem-details.js +15 -3
- package/lib/queue-local.js +148 -38
- package/lib/queue.js +41 -11
- package/lib/render.js +21 -3
- package/lib/router.js +13 -6
- package/lib/safe-buffer.js +55 -0
- package/lib/sse.js +7 -5
- package/lib/static.js +46 -17
- package/lib/uri-template.js +3 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/render.js
CHANGED
|
@@ -82,17 +82,35 @@ var DEFAULT_DYNAMIC_CACHE_CONTROL = "private, no-cache, must-revalidate";
|
|
|
82
82
|
* without losing Content-Type. Returns `undefined` — the response
|
|
83
83
|
* is fully written by the time the call returns.
|
|
84
84
|
*
|
|
85
|
+
* `opts.replacer` is forwarded to `JSON.stringify` (ECMA-262 §25.5.2,
|
|
86
|
+
* the second argument) so handlers can serialize values that have no
|
|
87
|
+
* native JSON form — `BigInt` (which otherwise throws), `Date` in a
|
|
88
|
+
* custom shape, `Map` / `Set`, or a redaction filter over secret-
|
|
89
|
+
* shaped keys — without pre-walking the body. Accepts the same
|
|
90
|
+
* function or property-name array `JSON.stringify` does; a non-
|
|
91
|
+
* function / non-array value is a config typo and throws.
|
|
92
|
+
*
|
|
85
93
|
* @opts
|
|
86
|
-
* status:
|
|
87
|
-
* headers:
|
|
94
|
+
* status: 200, // numeric HTTP status (200/201/202/4xx/5xx)
|
|
95
|
+
* headers: {}, // merged over defaults; later wins
|
|
96
|
+
* replacer: function|string[], // JSON.stringify replacer (BigInt/Date/redaction)
|
|
88
97
|
*
|
|
89
98
|
* @example
|
|
90
99
|
* b.render.json(res, { ok: true, id: 42 }, { status: 201 });
|
|
91
100
|
* // → response: 201, application/json, body `{"ok":true,"id":42}`
|
|
101
|
+
*
|
|
102
|
+
* b.render.json(res, { total: 9007199254740993n }, {
|
|
103
|
+
* replacer: function (k, v) { return typeof v === "bigint" ? v.toString() : v; },
|
|
104
|
+
* });
|
|
105
|
+
* // → body `{"total":"9007199254740993"}`
|
|
92
106
|
*/
|
|
93
107
|
function json(res, body, opts) {
|
|
94
108
|
opts = opts || {};
|
|
95
|
-
|
|
109
|
+
if (opts.replacer !== undefined && opts.replacer !== null &&
|
|
110
|
+
typeof opts.replacer !== "function" && !Array.isArray(opts.replacer)) {
|
|
111
|
+
throw new TypeError("render.json: opts.replacer must be a function or an array of keys");
|
|
112
|
+
}
|
|
113
|
+
var encoded = JSON.stringify(body, opts.replacer);
|
|
96
114
|
var headers = _mergedHeaders({
|
|
97
115
|
"Content-Type": "application/json; charset=utf-8",
|
|
98
116
|
"Content-Length": Buffer.byteLength(encoded, "utf8"),
|
package/lib/router.js
CHANGED
|
@@ -112,13 +112,20 @@ function _validateRouteSpec(spec, method, pattern) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function _writeValidationError(res, where, errors) {
|
|
115
|
+
function _writeValidationError(req, res, where, errors) {
|
|
116
116
|
if (res.writableEnded || res.headersSent) return;
|
|
117
|
-
var
|
|
117
|
+
var payload = {
|
|
118
118
|
error: "validation",
|
|
119
119
|
where: where,
|
|
120
120
|
issues: errors,
|
|
121
|
-
}
|
|
121
|
+
};
|
|
122
|
+
var body = JSON.stringify(payload);
|
|
123
|
+
// Seal the body when an encrypted session is active; pre-session paths
|
|
124
|
+
// (Bearer auth, handshake reject, replay refusal) lack the encoder and
|
|
125
|
+
// stay plaintext. An encryption failure falls back to the plaintext body.
|
|
126
|
+
if (req && typeof req.apiEncryptEncode === "function") {
|
|
127
|
+
try { body = JSON.stringify(req.apiEncryptEncode(payload)); } catch (_e) { /* plaintext body kept */ }
|
|
128
|
+
}
|
|
122
129
|
res.writeHead(HTTP_STATUS.BAD_REQUEST, {
|
|
123
130
|
"Content-Type": "application/json; charset=utf-8",
|
|
124
131
|
"Content-Length": Buffer.byteLength(body),
|
|
@@ -131,17 +138,17 @@ function _makeSchemaValidator(spec) {
|
|
|
131
138
|
return function schemaValidator(req, res, next) {
|
|
132
139
|
if (spec.params && req.params !== undefined) {
|
|
133
140
|
var pp = spec.params.safeParse(req.params);
|
|
134
|
-
if (!pp.ok) return _writeValidationError(res, "params", pp.errors);
|
|
141
|
+
if (!pp.ok) return _writeValidationError(req, res, "params", pp.errors);
|
|
135
142
|
req.params = pp.value;
|
|
136
143
|
}
|
|
137
144
|
if (spec.query && req.query !== undefined) {
|
|
138
145
|
var qq = spec.query.safeParse(req.query);
|
|
139
|
-
if (!qq.ok) return _writeValidationError(res, "query", qq.errors);
|
|
146
|
+
if (!qq.ok) return _writeValidationError(req, res, "query", qq.errors);
|
|
140
147
|
req.query = qq.value;
|
|
141
148
|
}
|
|
142
149
|
if (spec.body && req.body !== undefined) {
|
|
143
150
|
var bb = spec.body.safeParse(req.body);
|
|
144
|
-
if (!bb.ok) return _writeValidationError(res, "body", bb.errors);
|
|
151
|
+
if (!bb.ok) return _writeValidationError(req, res, "body", bb.errors);
|
|
145
152
|
req.body = bb.value;
|
|
146
153
|
}
|
|
147
154
|
next();
|
package/lib/safe-buffer.js
CHANGED
|
@@ -321,6 +321,60 @@ function boundedChunkCollector(opts) {
|
|
|
321
321
|
};
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/**
|
|
325
|
+
* @primitive b.safeBuffer.collectStream
|
|
326
|
+
* @signature b.safeBuffer.collectStream(stream, opts)
|
|
327
|
+
* @since 0.14.18
|
|
328
|
+
* @related b.safeBuffer.boundedChunkCollector, b.safeBuffer.toBuffer
|
|
329
|
+
*
|
|
330
|
+
* Read a Node Readable (an `http.IncomingMessage` request body, a file
|
|
331
|
+
* stream, an upstream response) fully into one Buffer with the byte cap
|
|
332
|
+
* enforced at every chunk — the streaming sibling of
|
|
333
|
+
* `boundedChunkCollector`. `boundedChunkCollector` is a push-based
|
|
334
|
+
* collector object; `collectStream` is the pump around it, so callers
|
|
335
|
+
* compose the stream case instead of reaching for a `(stream, opts)`
|
|
336
|
+
* overload that does not exist.
|
|
337
|
+
*
|
|
338
|
+
* Resolves with the concatenated Buffer when the stream ends. Rejects
|
|
339
|
+
* (and destroys the stream) the moment a chunk would overflow
|
|
340
|
+
* `maxBytes`, so a hostile sender cannot force unbounded buffering. A
|
|
341
|
+
* bad `maxBytes` (missing / non-finite / `Infinity`) rejects rather than
|
|
342
|
+
* throwing synchronously.
|
|
343
|
+
*
|
|
344
|
+
* @opts
|
|
345
|
+
* maxBytes: number, // REQUIRED positive finite int; total byte cap
|
|
346
|
+
* errorClass: Function, // caller Error subclass for the too-large reject
|
|
347
|
+
* sizeCode: string, // default "buffer/too-large"
|
|
348
|
+
* sizeMessage: string, // override the too-large message
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* var body = await b.safeBuffer.collectStream(req, { maxBytes: 65536 });
|
|
352
|
+
* var json = b.safeJson.parse(body.toString("utf8"));
|
|
353
|
+
* // → the parsed request body, never more than 64 KiB buffered
|
|
354
|
+
*/
|
|
355
|
+
function collectStream(stream, opts) {
|
|
356
|
+
return new Promise(function (resolve, reject) {
|
|
357
|
+
var collector;
|
|
358
|
+
try { collector = boundedChunkCollector(opts || {}); }
|
|
359
|
+
catch (e) { reject(e); return; }
|
|
360
|
+
var done = false;
|
|
361
|
+
function fail(e) {
|
|
362
|
+
if (done) return;
|
|
363
|
+
done = true;
|
|
364
|
+
try { if (stream && typeof stream.destroy === "function") stream.destroy(); }
|
|
365
|
+
catch (_e) { /* socket already closed */ }
|
|
366
|
+
reject(e);
|
|
367
|
+
}
|
|
368
|
+
stream.on("data", function (chunk) {
|
|
369
|
+
if (done) return;
|
|
370
|
+
try { collector.push(chunk); }
|
|
371
|
+
catch (e) { fail(e); }
|
|
372
|
+
});
|
|
373
|
+
stream.on("end", function () { if (!done) { done = true; resolve(collector.result()); } });
|
|
374
|
+
stream.on("error", fail);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
324
378
|
/**
|
|
325
379
|
* @primitive b.safeBuffer.secureZero
|
|
326
380
|
* @signature b.safeBuffer.secureZero(buf)
|
|
@@ -552,6 +606,7 @@ module.exports = {
|
|
|
552
606
|
normalizeText: normalizeText,
|
|
553
607
|
toBuffer: toBuffer,
|
|
554
608
|
boundedChunkCollector: boundedChunkCollector,
|
|
609
|
+
collectStream: collectStream,
|
|
555
610
|
secureZero: secureZero,
|
|
556
611
|
isHex: isHex,
|
|
557
612
|
hasCrlf: hasCrlf,
|
package/lib/sse.js
CHANGED
|
@@ -287,7 +287,7 @@ function create(req, res, opts) {
|
|
|
287
287
|
_writeRaw(":" + text + "\n\n");
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
function close() {
|
|
290
|
+
function close(cause) {
|
|
291
291
|
if (closed) return;
|
|
292
292
|
closed = true;
|
|
293
293
|
if (heartbeatTimer) {
|
|
@@ -296,10 +296,12 @@ function create(req, res, opts) {
|
|
|
296
296
|
}
|
|
297
297
|
try { res.end(); } catch (_e) { /* already destroyed */ }
|
|
298
298
|
if (auditOn) {
|
|
299
|
+
var closeMeta = { lastEventId: lastEventId };
|
|
300
|
+
if (cause) closeMeta.reason = cause.reason || "fault";
|
|
299
301
|
audit.safeEmit({
|
|
300
302
|
action: "sse.channel_closed",
|
|
301
|
-
outcome: "success",
|
|
302
|
-
metadata:
|
|
303
|
+
outcome: cause ? "failure" : "success",
|
|
304
|
+
metadata: closeMeta,
|
|
303
305
|
});
|
|
304
306
|
}
|
|
305
307
|
}
|
|
@@ -308,7 +310,7 @@ function create(req, res, opts) {
|
|
|
308
310
|
// the heartbeat timer.
|
|
309
311
|
if (typeof res.on === "function") {
|
|
310
312
|
res.on("close", close);
|
|
311
|
-
res.on("error", function (_e) { close(); });
|
|
313
|
+
res.on("error", function (_e) { close({ reason: "stream-error" }); });
|
|
312
314
|
res.on("finish", function () { closed = true; if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } });
|
|
313
315
|
}
|
|
314
316
|
|
|
@@ -325,7 +327,7 @@ function create(req, res, opts) {
|
|
|
325
327
|
heartbeatTimer = setInterval(function () {
|
|
326
328
|
if (closed) return;
|
|
327
329
|
try { _writeRaw(":keepalive\n\n"); }
|
|
328
|
-
catch (_e) { close(); }
|
|
330
|
+
catch (_e) { close({ reason: "heartbeat-write-failed" }); }
|
|
329
331
|
}, heartbeatMs).unref();
|
|
330
332
|
}
|
|
331
333
|
|
package/lib/static.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* quotas (cluster-shared via b.cache), Range support (RFC 7233 single-range),
|
|
7
7
|
* the full conditional-request set (If-None-Match / If-Match /
|
|
8
8
|
* If-Modified-Since / If-Unmodified-Since), MIME allowlist with magic-byte
|
|
9
|
-
* verification (composes b.fileType), per-request operator
|
|
9
|
+
* verification (composes b.fileType), per-request operator hooks (onServe
|
|
10
|
+
* on the success path, onError on every refusal path), idle-stream
|
|
10
11
|
* timeout, cancellation propagation, force-revoke, and compliance-retention
|
|
11
12
|
* gating.
|
|
12
13
|
*
|
|
@@ -16,6 +17,12 @@
|
|
|
16
17
|
* var mw = serve.middleware; // (req, res, next)
|
|
17
18
|
* await b.staticServe.integrity(absFilePath); // SRI helper (SHA-384)
|
|
18
19
|
*
|
|
20
|
+
* // onError mirrors onServe for the refusal paths (403 / 404 / 415 /
|
|
21
|
+
* // 412 / 416 / 429 / 451 / 500): it receives
|
|
22
|
+
* // { req, res, urlPath, absPath, status, code, actor } AFTER the
|
|
23
|
+
* // refusal response is written. Observability-only — a throw is
|
|
24
|
+
* // swallowed so a broken hook can't tear down the socket.
|
|
25
|
+
*
|
|
19
26
|
* Backwards compatible: every existing opt (root, mountPath,
|
|
20
27
|
* hashedPathPattern, indexFile, defaultMaxAge, contentTypes) keeps its
|
|
21
28
|
* original meaning. New opts (permissions, cache, audit, observability,
|
|
@@ -450,6 +457,7 @@ function _validateCreateOpts(opts) {
|
|
|
450
457
|
validateOpts.auditShape(opts.audit, "staticServe.create", StaticServeError);
|
|
451
458
|
validateOpts.observabilityShape(opts.observability, "staticServe.create", StaticServeError);
|
|
452
459
|
validateOpts.optionalFunction(opts.onServe, "staticServe.create: onServe", StaticServeError);
|
|
460
|
+
validateOpts.optionalFunction(opts.onError, "staticServe.create: onError", StaticServeError);
|
|
453
461
|
// contentSafety — extension-keyed gate map. Default behaviour: when
|
|
454
462
|
// undefined, the framework wires b.guardAll.byExtension({ profile:
|
|
455
463
|
// "strict" }) automatically so every shipped guard is ON by default.
|
|
@@ -604,7 +612,7 @@ function create(opts) {
|
|
|
604
612
|
"root", "mountPath", "hashedPathPattern",
|
|
605
613
|
"indexFile", "defaultMaxAge", "contentTypes",
|
|
606
614
|
"permissions", "cache", "fileType", "retention", "revokeStore",
|
|
607
|
-
"allowedFileTypes", "audit", "observability", "onServe",
|
|
615
|
+
"allowedFileTypes", "audit", "observability", "onServe", "onError",
|
|
608
616
|
"acceptRanges", "auditSuccess", "auditFailures",
|
|
609
617
|
"maxBytesPerActorPerWindowMs", "maxBytesAllActorsPerWindowMs",
|
|
610
618
|
"bandwidthWindowMs", "maxConcurrentDownloadsPerActor", "maxIdleMs",
|
|
@@ -657,6 +665,7 @@ function create(opts) {
|
|
|
657
665
|
contentSafety = opts.contentSafety;
|
|
658
666
|
}
|
|
659
667
|
var onServe = opts.onServe || null;
|
|
668
|
+
var onError = opts.onError || null;
|
|
660
669
|
var audit = opts.audit || null;
|
|
661
670
|
var auditSuccess = cfg.auditSuccess;
|
|
662
671
|
var auditFailures = cfg.auditFailures;
|
|
@@ -756,6 +765,26 @@ function create(opts) {
|
|
|
756
765
|
var actorCtx = requestHelpers.extractActorContext(req);
|
|
757
766
|
var actorKey = _actorKeyFromContext(actorCtx);
|
|
758
767
|
|
|
768
|
+
// Request-scoped error writer. Wraps the module-level _writeError so
|
|
769
|
+
// every refusal path (403 / 404 / 415 / 412 / 416 / 429 / 451 / 500)
|
|
770
|
+
// also invokes the operator's onError hook — the success-path mirror
|
|
771
|
+
// of onServe. The signature matches _writeError exactly; the `code`
|
|
772
|
+
// argument routes through to the hook so operators can branch on the
|
|
773
|
+
// refusal reason. The hook is observability-only: it runs AFTER the
|
|
774
|
+
// response is written and a throw is swallowed so a broken sink can't
|
|
775
|
+
// turn a 4xx into a torn-down socket.
|
|
776
|
+
function writeErr(r, status, code, message, headers) {
|
|
777
|
+
_writeError(r, status, code, message, headers);
|
|
778
|
+
if (onError) {
|
|
779
|
+
try {
|
|
780
|
+
onError({
|
|
781
|
+
req: req, res: r, urlPath: urlPath, absPath: absPath,
|
|
782
|
+
status: status, code: code, actor: actorCtx,
|
|
783
|
+
});
|
|
784
|
+
} catch (_he) { /* hook best-effort */ }
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
759
788
|
// Permission gate (403)
|
|
760
789
|
var perm = await _checkPermission(req);
|
|
761
790
|
if (!perm.ok) {
|
|
@@ -766,7 +795,7 @@ function create(opts) {
|
|
|
766
795
|
outcome: "failure", reason: "permission_denied", resource: urlPath,
|
|
767
796
|
}, actorCtx));
|
|
768
797
|
}
|
|
769
|
-
return
|
|
798
|
+
return writeErr(res, HTTP.FORBIDDEN, "permission_denied",
|
|
770
799
|
"Forbidden");
|
|
771
800
|
}
|
|
772
801
|
|
|
@@ -788,7 +817,7 @@ function create(opts) {
|
|
|
788
817
|
outcome: "failure", reason: "revoked", resource: urlPath,
|
|
789
818
|
}, actorCtx));
|
|
790
819
|
}
|
|
791
|
-
return
|
|
820
|
+
return writeErr(res, HTTP.NOT_FOUND, "not_found", "Not Found");
|
|
792
821
|
}
|
|
793
822
|
|
|
794
823
|
// Compliance retention (451)
|
|
@@ -800,7 +829,7 @@ function create(opts) {
|
|
|
800
829
|
outcome: "failure", reason: "retention_blocked", resource: urlPath,
|
|
801
830
|
}, actorCtx));
|
|
802
831
|
}
|
|
803
|
-
return
|
|
832
|
+
return writeErr(res, HTTP.UNAVAILABLE_FOR_LEGAL_REASONS,
|
|
804
833
|
"retention_blocked", "Unavailable For Legal Reasons");
|
|
805
834
|
}
|
|
806
835
|
|
|
@@ -820,7 +849,7 @@ function create(opts) {
|
|
|
820
849
|
detectedMime: mimeCheck.detected || null,
|
|
821
850
|
}, actorCtx));
|
|
822
851
|
}
|
|
823
|
-
return
|
|
852
|
+
return writeErr(res, HTTP.UNSUPPORTED_MEDIA_TYPE,
|
|
824
853
|
"mime_rejected", "Unsupported Media Type");
|
|
825
854
|
}
|
|
826
855
|
}
|
|
@@ -861,7 +890,7 @@ function create(opts) {
|
|
|
861
890
|
catch (_e) {
|
|
862
891
|
stats.failures += 1;
|
|
863
892
|
if (gateHandle) { try { await gateHandle.close(); } catch (_ce) { /* close best-effort */ } }
|
|
864
|
-
return
|
|
893
|
+
return writeErr(res, HTTP.INTERNAL_SERVER_ERROR,
|
|
865
894
|
"read_failed", "Internal Server Error");
|
|
866
895
|
}
|
|
867
896
|
try { await gateHandle.close(); } catch (_ce) { /* close best-effort */ }
|
|
@@ -885,7 +914,7 @@ function create(opts) {
|
|
|
885
914
|
error: gateErr && gateErr.message,
|
|
886
915
|
}, actorCtx));
|
|
887
916
|
}
|
|
888
|
-
return
|
|
917
|
+
return writeErr(res, HTTP.INTERNAL_SERVER_ERROR,
|
|
889
918
|
"content_safety_threw", "Internal Server Error");
|
|
890
919
|
}
|
|
891
920
|
if (!gateDecision.ok || gateDecision.action === "refuse") {
|
|
@@ -898,7 +927,7 @@ function create(opts) {
|
|
|
898
927
|
issues: gateContract.summarizeIssues(gateDecision.issues),
|
|
899
928
|
}, actorCtx));
|
|
900
929
|
}
|
|
901
|
-
return
|
|
930
|
+
return writeErr(res, HTTP.UNSUPPORTED_MEDIA_TYPE,
|
|
902
931
|
"content_safety_refused", "Unsupported Media Type");
|
|
903
932
|
}
|
|
904
933
|
if (gateDecision.action === "sanitize" && gateDecision.sanitized) {
|
|
@@ -929,7 +958,7 @@ function create(opts) {
|
|
|
929
958
|
if (ifMatch && ifMatch !== "*" && ifMatch !== meta.etag) {
|
|
930
959
|
stats.failures += 1;
|
|
931
960
|
_emitObs("staticServe.precondition_failed", 1, { route: urlPath, header: "if-match" });
|
|
932
|
-
return
|
|
961
|
+
return writeErr(res, HTTP.PRECONDITION_FAILED || 412,
|
|
933
962
|
"precondition_failed", "Precondition Failed");
|
|
934
963
|
}
|
|
935
964
|
|
|
@@ -956,7 +985,7 @@ function create(opts) {
|
|
|
956
985
|
if (isFinite(ius) && Math.floor(meta.mtimeMs / C.TIME.seconds(1)) > Math.floor(ius / C.TIME.seconds(1))) {
|
|
957
986
|
stats.failures += 1;
|
|
958
987
|
_emitObs("staticServe.precondition_failed", 1, { route: urlPath, header: "if-unmodified-since" });
|
|
959
|
-
return
|
|
988
|
+
return writeErr(res, HTTP.PRECONDITION_FAILED,
|
|
960
989
|
"precondition_failed", "Precondition Failed");
|
|
961
990
|
}
|
|
962
991
|
}
|
|
@@ -970,19 +999,19 @@ function create(opts) {
|
|
|
970
999
|
if (range && (range.malformed || range.multi)) {
|
|
971
1000
|
stats.failures += 1;
|
|
972
1001
|
_emitObs("staticServe.range_invalid", 1, { route: urlPath });
|
|
973
|
-
return
|
|
1002
|
+
return writeErr(res, HTTP.RANGE_NOT_SATISFIABLE, "range_not_satisfiable",
|
|
974
1003
|
"Range Not Satisfiable", { "Content-Range": "bytes */" + meta.size });
|
|
975
1004
|
}
|
|
976
1005
|
if (range && range.unsatisfiable) {
|
|
977
1006
|
stats.failures += 1;
|
|
978
1007
|
_emitObs("staticServe.range_invalid", 1, { route: urlPath });
|
|
979
|
-
return
|
|
1008
|
+
return writeErr(res, HTTP.RANGE_NOT_SATISFIABLE, "range_not_satisfiable",
|
|
980
1009
|
"Range Not Satisfiable", { "Content-Range": "bytes */" + meta.size });
|
|
981
1010
|
}
|
|
982
1011
|
if (range && cfg.maxRangeBytes !== Infinity && range.length > cfg.maxRangeBytes) {
|
|
983
1012
|
stats.failures += 1;
|
|
984
1013
|
_emitObs("staticServe.range_too_large", 1, { route: urlPath });
|
|
985
|
-
return
|
|
1014
|
+
return writeErr(res, HTTP.RANGE_NOT_SATISFIABLE, "range_too_large",
|
|
986
1015
|
"Range Not Satisfiable", { "Content-Range": "bytes */" + meta.size });
|
|
987
1016
|
}
|
|
988
1017
|
if (range) {
|
|
@@ -1005,7 +1034,7 @@ function create(opts) {
|
|
|
1005
1034
|
current: concCheck.current, cap: concCheck.cap,
|
|
1006
1035
|
}, actorCtx));
|
|
1007
1036
|
}
|
|
1008
|
-
return
|
|
1037
|
+
return writeErr(res, HTTP.TOO_MANY_REQUESTS,
|
|
1009
1038
|
"concurrency_cap", "Too Many Requests",
|
|
1010
1039
|
{ "Retry-After": "5" });
|
|
1011
1040
|
}
|
|
@@ -1021,7 +1050,7 @@ function create(opts) {
|
|
|
1021
1050
|
scope: bwCheck.scope, used: bwCheck.used, cap: bwCheck.cap,
|
|
1022
1051
|
}, actorCtx));
|
|
1023
1052
|
}
|
|
1024
|
-
return
|
|
1053
|
+
return writeErr(res, HTTP.TOO_MANY_REQUESTS,
|
|
1025
1054
|
"bandwidth_quota", "Too Many Requests",
|
|
1026
1055
|
{ "Retry-After": String(bwCheck.retryAfter) });
|
|
1027
1056
|
}
|
|
@@ -1077,7 +1106,7 @@ function create(opts) {
|
|
|
1077
1106
|
error: e && e.message,
|
|
1078
1107
|
}, actorCtx));
|
|
1079
1108
|
}
|
|
1080
|
-
return
|
|
1109
|
+
return writeErr(res, HTTP.INTERNAL_SERVER_ERROR, "onServe_threw",
|
|
1081
1110
|
"Internal Server Error");
|
|
1082
1111
|
}
|
|
1083
1112
|
}
|
package/lib/uri-template.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
var { defineClass } = require("./framework-error");
|
|
32
|
+
var codepointClass = require("./codepoint-class");
|
|
32
33
|
|
|
33
34
|
var UriTemplateError = defineClass("UriTemplateError", { alwaysPermanent: true });
|
|
34
35
|
|
|
@@ -59,7 +60,8 @@ function _pctEncode(str, allowReserved) {
|
|
|
59
60
|
var ch = str.charAt(i);
|
|
60
61
|
// Preserve existing percent-encoded triplets when the reserved set is
|
|
61
62
|
// allowed (operators "+" and "#").
|
|
62
|
-
|
|
63
|
+
// allow:regex-no-length-cap — the substr is a fixed 2-char window
|
|
64
|
+
if (allowReserved && ch === "%" && codepointClass.HEX_PAIR_RE.test(str.substr(i + 1, 2))) {
|
|
63
65
|
out += str.substr(i, 3); i += 2; continue;
|
|
64
66
|
}
|
|
65
67
|
if (UNRESERVED.test(ch) || (allowReserved && RESERVED.test(ch))) { out += ch; continue; }
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:b96f0679-8967-43f9-b412-aa598ea52508",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-02T15:25:35.604Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.14.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.14.18",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.14.
|
|
25
|
+
"version": "0.14.18",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.14.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.14.18",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.14.
|
|
57
|
+
"ref": "@blamejs/core@0.14.18",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|