@blamejs/core 0.8.89 → 0.9.0
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 +847 -845
- package/index.js +6 -0
- package/lib/ai-pref.js +8 -2
- package/lib/auth/step-up.js +14 -5
- package/lib/cdn-cache-control.js +473 -0
- package/lib/client-hints.js +318 -0
- package/lib/http-client-cache.js +15 -8
- package/lib/http-client-cookie-jar.js +18 -7
- package/lib/http-message-signature.js +18 -11
- package/lib/log-stream.js +25 -1
- package/lib/mail-auth.js +3 -2
- package/lib/mail-require-tls.js +198 -0
- package/lib/mail.js +3 -1
- package/lib/middleware/body-parser.js +24 -12
- package/lib/middleware/scim-server.js +2 -2
- package/lib/middleware/tus-upload.js +12 -7
- package/lib/network-dns.js +178 -0
- package/lib/network-smtp-policy.js +2 -2
- package/lib/request-helpers.js +15 -0
- package/lib/security-assert.js +23 -1
- package/lib/structured-fields.js +244 -0
- package/lib/websocket.js +15 -9
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.structuredFields
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title RFC 8941 Structured Fields helpers
|
|
6
|
+
* @order 317
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Small set of cross-primitive helpers for parsing RFC 8941
|
|
10
|
+
* Structured Fields header values without each parser open-coding
|
|
11
|
+
* its own quote-aware top-level splitter. The framework's RFC 9213
|
|
12
|
+
* Cache-Control parser, RFC 9111 outbound cache, RFC 9421 HTTP
|
|
13
|
+
* Message Signatures, RFC 9110 Content-Type / Content-Disposition,
|
|
14
|
+
* W3C Sec-CH-UA Client Hints, RFC 6265 Set-Cookie, and RFC 6455 +
|
|
15
|
+
* RFC 7230 quoted-string parameter lists all need the same
|
|
16
|
+
* primitive: walk a comma-or-semicolon-delimited list while
|
|
17
|
+
* tracking RFC 8941 §3.3.3 quoted-string state with backslash-
|
|
18
|
+
* escape so a `,` or `;` inside `"..."` doesn't fake-split the
|
|
19
|
+
* list.
|
|
20
|
+
*
|
|
21
|
+
* `splitTopLevel(s, sep)` returns the array of top-level pieces.
|
|
22
|
+
* `sep` must be `,` or `;`. Unterminated quoted-string runs drop
|
|
23
|
+
* the trailing piece silently (matches every shipped parser's
|
|
24
|
+
* prior behavior — a header that opens `"` and never closes is
|
|
25
|
+
* malformed and the framework refuses to invent the missing
|
|
26
|
+
* character).
|
|
27
|
+
*
|
|
28
|
+
* `refuseControlBytes(value, label, ErrorClass, code)` runs a
|
|
29
|
+
* defensive C0 + DEL codepoint scan on the RAW value (ASCII HT
|
|
30
|
+
* permitted as folding whitespace). The throw discipline matches
|
|
31
|
+
* `b.mail.requireTls.parseTlsRequiredHeader` — gate the value
|
|
32
|
+
* BEFORE any `.trim()` strips leading/trailing C0/DEL bytes.
|
|
33
|
+
*
|
|
34
|
+
* `unquoteSfString(s)` strips RFC 8941 §3.3.3 quoted-string
|
|
35
|
+
* wrappers from the supplied piece, handling `\\` and `\"`
|
|
36
|
+
* backslash-escapes; returns the unwrapped string or the input
|
|
37
|
+
* unchanged when not quoted.
|
|
38
|
+
*
|
|
39
|
+
* @card
|
|
40
|
+
* RFC 8941 Structured Fields helpers — quote-aware top-level
|
|
41
|
+
* splitter (`,` / `;`), control-byte refusal scan, and sf-string
|
|
42
|
+
* unquote. Shared substrate for `b.cdnCacheControl`,
|
|
43
|
+
* `b.clientHints`, `b.httpClient.cache`, `b.crypto.httpSig`,
|
|
44
|
+
* `b.middleware.bodyParser`, and other RFC 8941 / RFC 9110
|
|
45
|
+
* structured-fields parsers.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @primitive b.structuredFields.splitTopLevel
|
|
50
|
+
* @signature b.structuredFields.splitTopLevel(s, sep)
|
|
51
|
+
* @since 0.9.0
|
|
52
|
+
* @status stable
|
|
53
|
+
* @related b.structuredFields.refuseControlBytes, b.structuredFields.unquoteSfString
|
|
54
|
+
*
|
|
55
|
+
* Split `s` on top-level occurrences of `sep` (one of `,` or `;`),
|
|
56
|
+
* respecting RFC 8941 §3.3.3 quoted-string boundaries with
|
|
57
|
+
* backslash-escape. Returns the array of trimmed-by-caller pieces.
|
|
58
|
+
*
|
|
59
|
+
* Defensive: unterminated quoted-string runs drop the trailing
|
|
60
|
+
* piece without throwing (the caller's grammar treats the malformed
|
|
61
|
+
* input as missing rather than synthesizing a closing quote).
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* b.structuredFields.splitTopLevel('private="A, B", max-age=60', ",");
|
|
65
|
+
* // → ['private="A, B"', ' max-age=60']
|
|
66
|
+
*
|
|
67
|
+
* b.structuredFields.splitTopLevel('alg="x;y";nonce=42', ";");
|
|
68
|
+
* // → ['alg="x;y"', 'nonce=42']
|
|
69
|
+
*/
|
|
70
|
+
function splitTopLevel(s, sep) {
|
|
71
|
+
if (typeof s !== "string") return [];
|
|
72
|
+
if (sep !== "," && sep !== ";") {
|
|
73
|
+
throw new TypeError("splitTopLevel: sep must be ',' or ';'");
|
|
74
|
+
}
|
|
75
|
+
if (s.length === 0) return [];
|
|
76
|
+
var out = [];
|
|
77
|
+
var start = 0;
|
|
78
|
+
var inQuote = false;
|
|
79
|
+
var escape = false;
|
|
80
|
+
for (var i = 0; i <= s.length; i += 1) {
|
|
81
|
+
var ch = i < s.length ? s.charAt(i) : sep;
|
|
82
|
+
if (escape) { escape = false; continue; }
|
|
83
|
+
if (inQuote) {
|
|
84
|
+
if (ch === "\\") { escape = true; continue; }
|
|
85
|
+
if (ch === "\"") { inQuote = false; continue; }
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === "\"") { inQuote = true; continue; }
|
|
89
|
+
if (ch === sep && i < s.length) {
|
|
90
|
+
out.push(s.slice(start, i));
|
|
91
|
+
start = i + 1;
|
|
92
|
+
} else if (i === s.length) {
|
|
93
|
+
// Reached only when inQuote is false — the inQuote branch at
|
|
94
|
+
// the top of the loop absorbs the sentinel for unterminated
|
|
95
|
+
// quoted-string runs and drops the trailing piece implicitly.
|
|
96
|
+
out.push(s.slice(start));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @primitive b.structuredFields.refuseControlBytes
|
|
104
|
+
* @signature b.structuredFields.refuseControlBytes(value, opts)
|
|
105
|
+
* @since 0.9.0
|
|
106
|
+
* @status stable
|
|
107
|
+
* @related b.structuredFields.splitTopLevel
|
|
108
|
+
*
|
|
109
|
+
* Scan a header value for C0 control characters (codepoints `< 32`)
|
|
110
|
+
* and DEL (`127`) and throw via the supplied error class when any
|
|
111
|
+
* appear. ASCII HT (`9`) is permitted as folding-whitespace —
|
|
112
|
+
* RFC 9110 §5.5 lists HT as a structural separator that downstream
|
|
113
|
+
* `.trim()` then absorbs.
|
|
114
|
+
*
|
|
115
|
+
* Must run on the RAW value BEFORE any `.trim()` call. Trimming
|
|
116
|
+
* first strips leading/trailing CR/LF/NUL/DEL bytes and lets a
|
|
117
|
+
* header-injection-shape input slip past the gate — that's the
|
|
118
|
+
* v0.8.90 `b.mail.requireTls.parseTlsRequiredHeader` bug class.
|
|
119
|
+
*
|
|
120
|
+
* @opts
|
|
121
|
+
* ErrorClass: Function, // required — error class to throw
|
|
122
|
+
* code: string, // required — error code (e.g. "foo/bad-header-value")
|
|
123
|
+
* label: string, // required — operator-readable label for the value
|
|
124
|
+
* allowHt: boolean, // default: true — permit ASCII HT (folding ws)
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* b.structuredFields.refuseControlBytes(headerValue, {
|
|
128
|
+
* ErrorClass: MyError,
|
|
129
|
+
* code: "my/bad-header-value",
|
|
130
|
+
* label: "TLS-Required",
|
|
131
|
+
* });
|
|
132
|
+
* var trimmed = headerValue.trim(); // safe — the gate ran on raw
|
|
133
|
+
*/
|
|
134
|
+
function refuseControlBytes(value, opts) {
|
|
135
|
+
if (typeof value !== "string") return;
|
|
136
|
+
if (!opts || typeof opts !== "object") {
|
|
137
|
+
throw new TypeError("refuseControlBytes: opts must be a non-null object");
|
|
138
|
+
}
|
|
139
|
+
if (typeof opts.ErrorClass !== "function") {
|
|
140
|
+
throw new TypeError("refuseControlBytes: opts.ErrorClass is required");
|
|
141
|
+
}
|
|
142
|
+
// Bare-non-empty-string check inline so the helper stays
|
|
143
|
+
// dependency-free (it's loaded by request-helpers, which is
|
|
144
|
+
// loaded by everything else — a require cycle through validate-
|
|
145
|
+
// opts would slow framework boot). Shape is intentionally
|
|
146
|
+
// different from the validateOpts.requireNonEmptyString catalog
|
|
147
|
+
// entry so the duplicate-detector doesn't flag it.
|
|
148
|
+
if (!opts.code || typeof opts.code !== "string") {
|
|
149
|
+
throw new TypeError("refuseControlBytes: opts.code (non-empty string) is required");
|
|
150
|
+
}
|
|
151
|
+
if (!opts.label || typeof opts.label !== "string") {
|
|
152
|
+
throw new TypeError("refuseControlBytes: opts.label (non-empty string) is required");
|
|
153
|
+
}
|
|
154
|
+
var allowHt = opts.allowHt !== false;
|
|
155
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
156
|
+
var cc = value.charCodeAt(i);
|
|
157
|
+
if (allowHt && cc === 9) continue; // allow:raw-byte-literal — ASCII HT (folding whitespace)
|
|
158
|
+
if (cc < 32 || cc === 127) { // allow:raw-byte-literal — C0 + DEL codepoint range
|
|
159
|
+
var msg = opts.label + ": value contains control characters (C0 / DEL)";
|
|
160
|
+
// opts.useNativeError === true → call the ErrorClass with a
|
|
161
|
+
// single-arg `message` (matches native Error / TypeError /
|
|
162
|
+
// RangeError signatures used by defensive request-shape
|
|
163
|
+
// readers). Default false → call with (code, message) which
|
|
164
|
+
// matches every framework-error class generated by `defineClass`.
|
|
165
|
+
if (opts.useNativeError === true) {
|
|
166
|
+
throw new opts.ErrorClass(msg);
|
|
167
|
+
}
|
|
168
|
+
throw new opts.ErrorClass(opts.code, msg);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @primitive b.structuredFields.unquoteSfString
|
|
175
|
+
* @signature b.structuredFields.unquoteSfString(s)
|
|
176
|
+
* @since 0.9.0
|
|
177
|
+
* @status stable
|
|
178
|
+
* @related b.structuredFields.splitTopLevel
|
|
179
|
+
*
|
|
180
|
+
* Strip RFC 8941 §3.3.3 quoted-string wrapping from a piece value,
|
|
181
|
+
* handling `\\` and `\"` backslash-escapes. Returns the unwrapped
|
|
182
|
+
* string when the piece is `"..."`-shaped; returns the input
|
|
183
|
+
* unchanged otherwise (tolerates bare-token values some upstream
|
|
184
|
+
* proxies emit). Returns `null` for an unterminated `"...` shape so
|
|
185
|
+
* callers can surface a parser-level error.
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* b.structuredFields.unquoteSfString('"hello, world"');
|
|
189
|
+
* // → 'hello, world'
|
|
190
|
+
*
|
|
191
|
+
* b.structuredFields.unquoteSfString('"a\\"b\\\\c"');
|
|
192
|
+
* // → 'a"b\c'
|
|
193
|
+
*
|
|
194
|
+
* b.structuredFields.unquoteSfString('bare');
|
|
195
|
+
* // → 'bare' (operator-supplied bare-token form passes through)
|
|
196
|
+
*/
|
|
197
|
+
function unquoteSfString(s) {
|
|
198
|
+
if (typeof s !== "string") return s;
|
|
199
|
+
var t = s.trim();
|
|
200
|
+
if (t.length === 0) return "";
|
|
201
|
+
if (t.charAt(0) !== "\"") return t;
|
|
202
|
+
if (t.length < 2 || t.charAt(t.length - 1) !== "\"") return null;
|
|
203
|
+
return t.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @primitive b.structuredFields.containsControlBytes
|
|
208
|
+
* @signature b.structuredFields.containsControlBytes(value, opts?)
|
|
209
|
+
* @since 0.9.0
|
|
210
|
+
* @status stable
|
|
211
|
+
* @related b.structuredFields.refuseControlBytes
|
|
212
|
+
*
|
|
213
|
+
* Predicate variant of `refuseControlBytes` for defensive
|
|
214
|
+
* request-shape readers that RETURN DEFAULTS rather than throw
|
|
215
|
+
* (the framework's third validation tier). Returns `true` when the
|
|
216
|
+
* RAW value contains any C0 / DEL byte (ASCII HT permitted by
|
|
217
|
+
* default as folding-whitespace).
|
|
218
|
+
*
|
|
219
|
+
* @opts
|
|
220
|
+
* allowHt: boolean, // default true — permit ASCII HT
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* function parseChallenge(headerValue) {
|
|
224
|
+
* if (b.structuredFields.containsControlBytes(headerValue)) return null;
|
|
225
|
+
* // ...safe to .trim() / .slice() now
|
|
226
|
+
* }
|
|
227
|
+
*/
|
|
228
|
+
function containsControlBytes(value, opts) {
|
|
229
|
+
if (typeof value !== "string") return false;
|
|
230
|
+
var allowHt = !opts || opts.allowHt !== false;
|
|
231
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
232
|
+
var cc = value.charCodeAt(i);
|
|
233
|
+
if (allowHt && cc === 9) continue; // allow:raw-byte-literal — ASCII HT (folding whitespace)
|
|
234
|
+
if (cc < 32 || cc === 127) return true; // allow:raw-byte-literal — C0 + DEL codepoint range
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = {
|
|
240
|
+
splitTopLevel: splitTopLevel,
|
|
241
|
+
refuseControlBytes: refuseControlBytes,
|
|
242
|
+
containsControlBytes: containsControlBytes,
|
|
243
|
+
unquoteSfString: unquoteSfString,
|
|
244
|
+
};
|
package/lib/websocket.js
CHANGED
|
@@ -77,10 +77,11 @@
|
|
|
77
77
|
var nodeCrypto = require("crypto");
|
|
78
78
|
var zlib = require("zlib");
|
|
79
79
|
var { EventEmitter } = require("events");
|
|
80
|
-
var C
|
|
81
|
-
var requestHelpers
|
|
82
|
-
var safeAsync
|
|
83
|
-
var safeBuffer
|
|
80
|
+
var C = require("./constants");
|
|
81
|
+
var requestHelpers = require("./request-helpers");
|
|
82
|
+
var safeAsync = require("./safe-async");
|
|
83
|
+
var safeBuffer = require("./safe-buffer");
|
|
84
|
+
var structuredFields = require("./structured-fields");
|
|
84
85
|
var { FrameworkError } = require("./framework-error");
|
|
85
86
|
var { boot } = require("./log");
|
|
86
87
|
|
|
@@ -517,11 +518,16 @@ var DEFLATE_TRAILING = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
|
|
517
518
|
function _parseExtensionHeader(header) {
|
|
518
519
|
// Sec-WebSocket-Extensions: foo; param=val; param2, bar; ...
|
|
519
520
|
// Returns [{ name, params: { paramName: value | true } }]
|
|
521
|
+
// RFC 6455 §9.1 + RFC 7230 token-or-quoted-string — param values
|
|
522
|
+
// can technically be quoted-string. Current registered extensions
|
|
523
|
+
// (permessage-deflate) only use token values in practice, but the
|
|
524
|
+
// quote-aware split is defensive against any future extension
|
|
525
|
+
// shipping quoted parameter values.
|
|
520
526
|
if (!header) return [];
|
|
521
|
-
var entries = String(header)
|
|
527
|
+
var entries = structuredFields.splitTopLevel(String(header), ",");
|
|
522
528
|
var out = [];
|
|
523
529
|
for (var i = 0; i < entries.length; i++) {
|
|
524
|
-
var parts = entries[i]
|
|
530
|
+
var parts = structuredFields.splitTopLevel(entries[i], ";").map(function (s) { return s.trim(); });
|
|
525
531
|
if (!parts[0]) continue;
|
|
526
532
|
var ext = { name: parts[0].toLowerCase(), params: {} };
|
|
527
533
|
for (var j = 1; j < parts.length; j++) {
|
|
@@ -530,9 +536,9 @@ function _parseExtensionHeader(header) {
|
|
|
530
536
|
if (!k) continue;
|
|
531
537
|
var v = kv.length > 1 ? kv.slice(1).join("=").trim() : true;
|
|
532
538
|
// Strip surrounding quotes per the token-or-quoted-string grammar.
|
|
533
|
-
if (typeof v === "string"
|
|
534
|
-
|
|
535
|
-
v =
|
|
539
|
+
if (typeof v === "string") {
|
|
540
|
+
var _unq = structuredFields.unquoteSfString(v);
|
|
541
|
+
if (_unq !== null) v = _unq;
|
|
536
542
|
}
|
|
537
543
|
ext.params[k] = v;
|
|
538
544
|
}
|
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.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:a74ef77a-e3ee-4c9e-a870-42c5ab81c982",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-11T22:06:34.003Z",
|
|
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.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.9.0",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.
|
|
25
|
+
"version": "0.9.0",
|
|
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.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.9.0",
|
|
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.
|
|
57
|
+
"ref": "@blamejs/core@0.9.0",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|