@blamejs/core 0.18.50 → 0.18.53
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 +121 -0
- package/NOTICE +1 -1
- package/README.md +1 -1
- package/lib/ai-input.js +25 -3
- package/lib/db-query.js +32 -4
- package/lib/gate-contract.js +11 -2
- package/lib/guard-filename.js +32 -6
- package/lib/guard-jwt.js +2 -2
- package/lib/guard-yaml.js +157 -21
- package/lib/json-schema.js +130 -13
- package/lib/mail-bimi.js +279 -6
- package/lib/markup-tokenizer.js +88 -0
- package/lib/parsers/safe-yaml.js +24 -3
- package/lib/vendor/MANIFEST.json +12 -12
- package/lib/vendor/blamejs-pki.cjs +835 -41
- package/lib/yaml-lex.js +533 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/yaml-lex.js
ADDED
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
//
|
|
5
|
+
// Where in a YAML document a character actually SITS.
|
|
6
|
+
//
|
|
7
|
+
// Two modules were answering that question separately and getting different
|
|
8
|
+
// wrong answers. `guard-yaml` decided a `!` opened a tag if it followed
|
|
9
|
+
// whitespace, so it reported one inside a quoted scalar, inside a block-scalar
|
|
10
|
+
// body, and inside a comment. `parsers/safe-yaml` masked quoted scalars but
|
|
11
|
+
// passed comment text through verbatim and had no block-scalar handling at all,
|
|
12
|
+
// despite its own note promising both. Between them every ordinary document
|
|
13
|
+
// carrying an exclamation mark in prose was refused, by one module or the other.
|
|
14
|
+
//
|
|
15
|
+
// The question is not "what precedes this character" — it is "what region is
|
|
16
|
+
// this character in", and no amount of looking at the previous byte answers it.
|
|
17
|
+
// Region is a property of everything before, so it takes a scan. This module is
|
|
18
|
+
// that scan, written once, so the two callers cannot drift apart again.
|
|
19
|
+
//
|
|
20
|
+
// `maskNonStructural` returns a string the SAME LENGTH as its input, with every
|
|
21
|
+
// non-structural region replaced by spaces and newlines preserved. Callers keep
|
|
22
|
+
// their existing sigil searches and run them against the mask instead of the
|
|
23
|
+
// source: an index into one is an index into the other, so reported locations
|
|
24
|
+
// and line numbers stay true.
|
|
25
|
+
//
|
|
26
|
+
// What survives the mask, because it is what the callers are looking for:
|
|
27
|
+
// structural punctuation, node properties (`!tag`, `&anchor`, `*alias`),
|
|
28
|
+
// directive lines, and document markers. What is masked is scalar content in
|
|
29
|
+
// every form it takes: quoted, plain, and block.
|
|
30
|
+
//
|
|
31
|
+
// The mask is deliberately readier to KEEP than to hide. Masking something
|
|
32
|
+
// structural is the dangerous direction: it does not refuse a good document, it
|
|
33
|
+
// hides a real tag inside a bad one and hands the callers something they then
|
|
34
|
+
// call clean. Every case below that looked like a tidy simplification and was
|
|
35
|
+
// not — blanking the rest of a line, measuring a block body from the wrong
|
|
36
|
+
// column — failed in exactly that direction.
|
|
37
|
+
|
|
38
|
+
// No line-splitting helper is used here on purpose: `codepointClass.splitLines`
|
|
39
|
+
// strips the carriage return of a CRLF pair, and this function's whole contract
|
|
40
|
+
// is that its output is the same length as its input. See the split below.
|
|
41
|
+
|
|
42
|
+
// Deliberately not a regex, in either sense. The guard and safe families forbid
|
|
43
|
+
// them, and a scanner is what this file exists to be.
|
|
44
|
+
function _isSpace(code) { return code === 0x20 || code === 0x09; }
|
|
45
|
+
|
|
46
|
+
function _isPlainSpace(ch) { return ch === " " || ch === "\t"; }
|
|
47
|
+
|
|
48
|
+
// A node property is a tag, an anchor, or an alias. They chain, so `!tag &a`
|
|
49
|
+
// and `&a !tag` are both a single node's properties and the scalar begins after
|
|
50
|
+
// the last of them.
|
|
51
|
+
function _isPropertySigil(ch) { return ch === "!" || ch === "&" || ch === "*"; }
|
|
52
|
+
|
|
53
|
+
// A block-scalar header is `|` or `>`, optionally followed by the chomping and
|
|
54
|
+
// indentation indicators in EITHER order (`|2-` and `|-2` are equally valid),
|
|
55
|
+
// and then nothing but whitespace or a comment. Misreading one scans a shell
|
|
56
|
+
// script as if it were YAML.
|
|
57
|
+
// Returns `{ end, declared }` or null. `declared` is the indentation indicator's
|
|
58
|
+
// digit when the header carries one, or 0. That digit is not decoration: it
|
|
59
|
+
// DECLARES the body's indentation relative to the parent node, so a body whose
|
|
60
|
+
// first line happens to be indented further does not move it. Detecting from
|
|
61
|
+
// the first line in that case ends the block early and hands the rest of a
|
|
62
|
+
// scalar to the structural scan.
|
|
63
|
+
function _blockHeaderEnd(line, at) {
|
|
64
|
+
var ch = line.charAt(at);
|
|
65
|
+
if (ch !== "|" && ch !== ">") return null;
|
|
66
|
+
var i = at + 1;
|
|
67
|
+
var declared = 0, sawChomp = false;
|
|
68
|
+
while (i < line.length) {
|
|
69
|
+
var c = line.charAt(i);
|
|
70
|
+
if (c >= "1" && c <= "9" && !declared) { declared = Number(c); i += 1; continue; }
|
|
71
|
+
if ((c === "-" || c === "+") && !sawChomp) { sawChomp = true; i += 1; continue; }
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
// Only a header if the rest of the line is blank or a comment; `x: |foo` is
|
|
75
|
+
// an ordinary plain scalar that happens to start with a bar.
|
|
76
|
+
var j = i;
|
|
77
|
+
while (j < line.length && _isPlainSpace(line.charAt(j))) j += 1;
|
|
78
|
+
if (j < line.length && line.charAt(j) !== "#") return null;
|
|
79
|
+
return { end: i, declared: declared };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The document markers and directive lines a caller still needs to see. A
|
|
83
|
+
// directive is only a directive at column zero, and `---` / `...` only at the
|
|
84
|
+
// start of a line.
|
|
85
|
+
function _isVerbatimLine(line, indent) {
|
|
86
|
+
if (line.charAt(0) === "%") return true;
|
|
87
|
+
// Column ZERO only. YAML puts document markers there and nowhere else, so an
|
|
88
|
+
// indented `---` is scalar content — `- hello` / ` ---` / ` !world` is one
|
|
89
|
+
// plain scalar. Matching it wherever it appeared ended the document, threw
|
|
90
|
+
// away the scalar state, and left the continuation to be read as structure.
|
|
91
|
+
if (indent !== 0) return false;
|
|
92
|
+
if (line.indexOf("---") === 0 &&
|
|
93
|
+
(line.length === 3 || _isPlainSpace(line.charAt(3)))) return true;
|
|
94
|
+
if (line.indexOf("...") === 0 &&
|
|
95
|
+
(line.length === 3 || _isPlainSpace(line.charAt(3)))) return true;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function maskNonStructural(text) {
|
|
100
|
+
var src = String(text == null ? "" : text);
|
|
101
|
+
// Split on the newline ALONE, keeping any `\r` with the line it terminates.
|
|
102
|
+
// `codepointClass.splitLines` strips it, and rejoining those with "\n" drops
|
|
103
|
+
// one character per CRLF line — which would shift every index after the first
|
|
104
|
+
// such line and silently break the alignment this whole function exists to
|
|
105
|
+
// provide. A mask that is not the same length as its source is worse than no
|
|
106
|
+
// mask, because the locations it reports are confidently wrong.
|
|
107
|
+
var lines = src.split("\n");
|
|
108
|
+
var out = [];
|
|
109
|
+
// A block scalar's body indentation is DETECTED from its first content line,
|
|
110
|
+
// which is what YAML specifies and the only thing that works. Two fixed
|
|
111
|
+
// columns were tried and each failed the other's case: measuring from the
|
|
112
|
+
// key's column masks nothing when extra spacing after a dash puts the body in
|
|
113
|
+
// that same column, and measuring from a canonical column swallows a sibling
|
|
114
|
+
// key written between the two. With extra spacing the body can sit anywhere
|
|
115
|
+
// past the dash, so only the body itself says where it starts.
|
|
116
|
+
//
|
|
117
|
+
// `blockOwner` is the column the body must beat for the block to have one at
|
|
118
|
+
// all; `blockBody` is the detected indentation, -1 until the first content
|
|
119
|
+
// line sets it. Both -1 when no block is open.
|
|
120
|
+
var blockOwner = -1;
|
|
121
|
+
var blockBody = -1;
|
|
122
|
+
// A quoted scalar may span lines, so the quote that opened one carries across
|
|
123
|
+
// until it closes. Scanning each line independently would read the
|
|
124
|
+
// CONTINUATION as structure, which puts back the exact false positive this
|
|
125
|
+
// module exists to remove: `x: "hello` / ` !world"` would name a tag on the
|
|
126
|
+
// second line.
|
|
127
|
+
var openQuote = null;
|
|
128
|
+
// A flow collection may also span lines, so its depth carries across too. It
|
|
129
|
+
// decides where a plain scalar ends, and resetting it per line would end one
|
|
130
|
+
// at the wrong place inside a multi-line `[ ... ]`.
|
|
131
|
+
var flowDepth = 0;
|
|
132
|
+
// A PLAIN scalar spans lines as well — that is how a long description gets
|
|
133
|
+
// written without quotes — and its continuation lines are indented further
|
|
134
|
+
// than the node that opened it. Reading one as a fresh line puts the false
|
|
135
|
+
// positive back a fourth way: in
|
|
136
|
+
//
|
|
137
|
+
// x: hello
|
|
138
|
+
// !world
|
|
139
|
+
//
|
|
140
|
+
// the value is `hello !world` and the bang introduces nothing.
|
|
141
|
+
//
|
|
142
|
+
// -1 when no plain scalar is open. Otherwise the indent of the node that
|
|
143
|
+
// opened it, which a continuation must beat. This is set ONLY when a line
|
|
144
|
+
// actually read plain-scalar content, so `x:` with its value underneath does
|
|
145
|
+
// not arm it and a nested mapping is still read as structure.
|
|
146
|
+
var plainOpen = -1;
|
|
147
|
+
// ...and whether that scalar is a sequence item's own, which decides whether
|
|
148
|
+
// a continuation at the SAME column counts.
|
|
149
|
+
var plainFromDash = false;
|
|
150
|
+
|
|
151
|
+
for (var li = 0; li < lines.length; li += 1) {
|
|
152
|
+
var raw = lines[li];
|
|
153
|
+
// The carriage return of a CRLF pair is a line TERMINATOR, not content, so
|
|
154
|
+
// it is held aside and put back verbatim. Letting it into the scan would
|
|
155
|
+
// have it read as the first character of a plain scalar and masked to a
|
|
156
|
+
// space, which changes the bytes of a document the mask is meant to mirror.
|
|
157
|
+
var cr = raw.length && raw.charAt(raw.length - 1) === "\r";
|
|
158
|
+
var line = cr ? raw.slice(0, raw.length - 1) : raw;
|
|
159
|
+
var indent = 0;
|
|
160
|
+
while (indent < line.length && _isSpace(line.charCodeAt(indent))) indent += 1;
|
|
161
|
+
var blank = indent === line.length;
|
|
162
|
+
|
|
163
|
+
// A quoted scalar carried over from an earlier line owns this one until it
|
|
164
|
+
// closes, and nothing before that point is structure — not a `---` that
|
|
165
|
+
// looks like a document marker, and not a `%` in column zero that looks
|
|
166
|
+
// like a directive. So this is answered before either of those.
|
|
167
|
+
//
|
|
168
|
+
// What follows the closing quote on that line IS structure again, and the
|
|
169
|
+
// scan resumes there rather than blanking it. Blanking hid a real tag: in
|
|
170
|
+
// `x: ["first` / ` second", !tag value]` the comma and the tag after the
|
|
171
|
+
// scalar belong to the collection, and masking them takes a tagged document
|
|
172
|
+
// and hands both screens something they call clean.
|
|
173
|
+
var resumeAt = -1;
|
|
174
|
+
var resumePrefix = "";
|
|
175
|
+
if (openQuote !== null) {
|
|
176
|
+
var cont = _maskQuotedBody(line, 0, openQuote);
|
|
177
|
+
if (!cont.closed) {
|
|
178
|
+
out.push(cont.masked + (cr ? "\r" : ""));
|
|
179
|
+
continue; // the whole line is body
|
|
180
|
+
}
|
|
181
|
+
openQuote = null;
|
|
182
|
+
resumeAt = cont.end;
|
|
183
|
+
resumePrefix = cont.masked;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (resumeAt === -1) {
|
|
187
|
+
if (blockOwner >= 0) {
|
|
188
|
+
if (blank) { out.push(_blanked(line) + (cr ? "\r" : "")); continue; }
|
|
189
|
+
if (blockBody === -1) {
|
|
190
|
+
// The first content line sets the body's indentation — provided it is
|
|
191
|
+
// past the owner at all. If it is not, the block has an empty body and
|
|
192
|
+
// this line is structure.
|
|
193
|
+
if (indent > blockOwner) {
|
|
194
|
+
blockBody = indent;
|
|
195
|
+
out.push(_blanked(line) + (cr ? "\r" : ""));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
blockOwner = -1;
|
|
199
|
+
} else if (indent >= blockBody) {
|
|
200
|
+
out.push(_blanked(line) + (cr ? "\r" : ""));
|
|
201
|
+
continue;
|
|
202
|
+
} else {
|
|
203
|
+
blockOwner = -1;
|
|
204
|
+
blockBody = -1; // the body ended here
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (blank) { out.push(raw); continue; }
|
|
208
|
+
// A directive or a document marker is structure the callers still need to
|
|
209
|
+
// see, but only the marker itself is. A comment may follow one on the same
|
|
210
|
+
// line, and its text is no more YAML there than anywhere else — passing
|
|
211
|
+
// the whole line through left `--- # note !bang` naming a tag, which is
|
|
212
|
+
// the very class this module removes, surviving in the one branch that
|
|
213
|
+
// skipped the scan.
|
|
214
|
+
if (_isVerbatimLine(line, indent)) {
|
|
215
|
+
out.push(_maskTrailingComment(line) + (cr ? "\r" : ""));
|
|
216
|
+
// A document marker ends the document, so EVERY piece of carried state
|
|
217
|
+
// ends with it. Resetting only the flow depth left a scalar open across
|
|
218
|
+
// the boundary, and with the sequence-item rule that meant
|
|
219
|
+
// `- hello` / `---` / ` !tag v` read the new document's first line as
|
|
220
|
+
// a continuation of the old document's last item — masking a real tag.
|
|
221
|
+
// A marker is the one place where "carry this across lines" is always
|
|
222
|
+
// wrong, so the reset is total rather than itemised.
|
|
223
|
+
flowDepth = 0;
|
|
224
|
+
plainOpen = -1;
|
|
225
|
+
plainFromDash = false;
|
|
226
|
+
blockOwner = -1;
|
|
227
|
+
blockBody = -1;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Resuming after a closed continuation starts where the quote ended, with
|
|
233
|
+
// that scalar already behind us; otherwise the line's own content begins.
|
|
234
|
+
var masked = resumeAt === -1 ? line.slice(0, indent) : resumePrefix;
|
|
235
|
+
var i = resumeAt === -1 ? indent : resumeAt;
|
|
236
|
+
// A node may begin here: at the start of the line's content, and again
|
|
237
|
+
// after every structural token that introduces one.
|
|
238
|
+
//
|
|
239
|
+
// Two things mean it does NOT begin here. A resumed line has just finished
|
|
240
|
+
// reading a quoted scalar. And a line continuing a PLAIN scalar opened
|
|
241
|
+
// earlier is more of that scalar, so its first character is content:
|
|
242
|
+
//
|
|
243
|
+
// x: hello
|
|
244
|
+
// !world
|
|
245
|
+
//
|
|
246
|
+
// The continuation is scanned rather than blanked, because inside a flow
|
|
247
|
+
// collection it may still end at a `,` or a `]` that belongs to the
|
|
248
|
+
// collection — `x: [hello` / ` !world]` closes a sequence on its second
|
|
249
|
+
// line. Closing the node position is enough to make the leading sigil fall
|
|
250
|
+
// into the plain-scalar branch, and it costs no special case.
|
|
251
|
+
// A scalar that IS a sequence item has its continuation aligned with the
|
|
252
|
+
// item's content column rather than deeper than it — `- hello` then
|
|
253
|
+
// ` !world` is the scalar `hello !world`, and both sit at column 2. So
|
|
254
|
+
// equality continues that one, while a mapping entry's value still needs a
|
|
255
|
+
// strictly deeper line, where equality would be a sibling key.
|
|
256
|
+
var continuesPlain = resumeAt === -1 && plainOpen >= 0 && !blank &&
|
|
257
|
+
(plainFromDash ? indent >= plainOpen : indent > plainOpen);
|
|
258
|
+
var atNodeStart = resumeAt === -1 && !continuesPlain;
|
|
259
|
+
// Where the NODE on this line begins, which is the line's indent until a
|
|
260
|
+
// sequence dash moves it along. A block scalar's body is measured from
|
|
261
|
+
// here, not from the leading whitespace.
|
|
262
|
+
var nodeIndent = indent;
|
|
263
|
+
// The column of the innermost sequence dash read on this line, or -1. A
|
|
264
|
+
// block scalar standing where that dash's own node goes belongs to the
|
|
265
|
+
// ITEM, so this is what bounds its body.
|
|
266
|
+
var dashIndent = -1;
|
|
267
|
+
// Was the token just read a JSON-LIKE key? YAML's JSON compatibility lets
|
|
268
|
+
// one take its value colon with no space after it, and there are two kinds:
|
|
269
|
+
// a quoted scalar, and a flow collection. Both end this flag set.
|
|
270
|
+
var prevWasJsonKey = false;
|
|
271
|
+
// Did the line finish inside a plain scalar? Set by the plain-scalar branch
|
|
272
|
+
// when it runs to the end of the line, and false the moment anything else
|
|
273
|
+
// is read after it.
|
|
274
|
+
var sawPlainToEol = false;
|
|
275
|
+
// The column the last plain scalar on this line started in.
|
|
276
|
+
var plainStartCol = -1;
|
|
277
|
+
|
|
278
|
+
while (i < line.length) {
|
|
279
|
+
var ch = line.charAt(i);
|
|
280
|
+
// Cleared here and re-set only by the quoted-scalar branch, so no branch
|
|
281
|
+
// can leave it true by forgetting to. The colon rule reads the captured
|
|
282
|
+
// copy rather than the live flag.
|
|
283
|
+
var jsonKeyBefore = prevWasJsonKey;
|
|
284
|
+
prevWasJsonKey = false;
|
|
285
|
+
|
|
286
|
+
// A comment opens on `#` at the start of the content or after whitespace,
|
|
287
|
+
// and runs to the end of the line. Its text is not YAML, whatever it
|
|
288
|
+
// says: `x: 1 # note !bang` names no tag.
|
|
289
|
+
if (ch === "#" && (i === indent || _isPlainSpace(line.charAt(i - 1)))) {
|
|
290
|
+
masked += _blanked(line.slice(i));
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Whitespace carries the quoted-key flag rather than clearing it. YAML
|
|
295
|
+
// allows separation between a JSON-style key and its adjacent value, so
|
|
296
|
+
// `{"a" :!!python/object x}` is as valid as `{"a":!!python/object x}` —
|
|
297
|
+
// and clearing here left the second form closed and the first one open,
|
|
298
|
+
// which is a deserialization tag hidden behind one space.
|
|
299
|
+
if (_isPlainSpace(ch)) {
|
|
300
|
+
masked += ch; i += 1; prevWasJsonKey = jsonKeyBefore; continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Structural punctuation stays visible, and each of these opens a node
|
|
304
|
+
// position after it.
|
|
305
|
+
if (ch === "{" || ch === "[") {
|
|
306
|
+
masked += ch; i += 1; flowDepth += 1; atNodeStart = true; continue;
|
|
307
|
+
}
|
|
308
|
+
if (ch === "}" || ch === "]") {
|
|
309
|
+
masked += ch; i += 1; if (flowDepth > 0) flowDepth -= 1;
|
|
310
|
+
atNodeStart = false;
|
|
311
|
+
// A closing delimiter ends a JSON-LIKE key just as a closing quote
|
|
312
|
+
// does, and YAML lets that kind of key take its colon with no space
|
|
313
|
+
// after it. `{{a: b}:!!python/object x}` is valid, and reading the `:`
|
|
314
|
+
// as ordinary text left the tag masked — the same bypass as the quoted
|
|
315
|
+
// key, through the other production for the same rule.
|
|
316
|
+
prevWasJsonKey = true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (ch === ",") { masked += ch; i += 1; atNodeStart = true; continue; }
|
|
320
|
+
if (ch === "-" && (i + 1 >= line.length || _isPlainSpace(line.charAt(i + 1)))) {
|
|
321
|
+
masked += ch; i += 1; atNodeStart = true;
|
|
322
|
+
// A sequence entry written inline starts a node PAST the dash, and any
|
|
323
|
+
// block scalar it opens is measured from there rather than from the
|
|
324
|
+
// line's leading whitespace. Getting this wrong is not a false refusal
|
|
325
|
+
// but a false ACCEPT: in
|
|
326
|
+
//
|
|
327
|
+
// - key: |
|
|
328
|
+
// body
|
|
329
|
+
// evil: !tag x
|
|
330
|
+
//
|
|
331
|
+
// `evil` is a sibling of `key`, and measuring the body against the
|
|
332
|
+
// dash's indent of zero swallows it — masking a real tag and handing
|
|
333
|
+
// both screens a document they then call clean.
|
|
334
|
+
var afterDash = i;
|
|
335
|
+
while (afterDash < line.length && _isPlainSpace(line.charAt(afterDash))) afterDash += 1;
|
|
336
|
+
if (afterDash < line.length) nodeIndent = afterDash;
|
|
337
|
+
// ...unless the item's node IS a block scalar, with no mapping in
|
|
338
|
+
// between: `- |` then ` !hello`. There the body starts at the same
|
|
339
|
+
// column the `|` sits in, so measuring from that column masks nothing
|
|
340
|
+
// and the scalar's first line reads as a tag. The owner of the block is
|
|
341
|
+
// the sequence ITEM, so the dash's column is what bounds it.
|
|
342
|
+
dashIndent = i - 1;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (ch === "?" && (i + 1 >= line.length || _isPlainSpace(line.charAt(i + 1)))) {
|
|
346
|
+
masked += ch; i += 1; atNodeStart = true; continue;
|
|
347
|
+
}
|
|
348
|
+
// A colon separates a key from its value when whitespace follows, when a
|
|
349
|
+
// flow delimiter does — and, inside a flow collection, when the key was
|
|
350
|
+
// QUOTED. That last form is YAML's JSON compatibility: `{"a":value}` is
|
|
351
|
+
// valid and needs no space, so requiring one left the colon unread, the
|
|
352
|
+
// node position closed, and the value masked as though it were more of
|
|
353
|
+
// the key's scalar. `{"a":!!python/object x}` therefore reached both
|
|
354
|
+
// screens with its tag hidden — a deserialization tag, which is the
|
|
355
|
+
// single most dangerous thing this scan exists to surface.
|
|
356
|
+
if (ch === ":" &&
|
|
357
|
+
(i + 1 >= line.length || _isPlainSpace(line.charAt(i + 1)) ||
|
|
358
|
+
(flowDepth > 0 && ",}]".indexOf(line.charAt(i + 1)) !== -1) ||
|
|
359
|
+
(flowDepth > 0 && jsonKeyBefore))) {
|
|
360
|
+
masked += ch; i += 1; atNodeStart = true; continue;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// A node's properties survive: they are exactly what the callers scan for,
|
|
364
|
+
// and they are the ONLY place a `!`, `&` or `*` means what it looks like.
|
|
365
|
+
if (atNodeStart && _isPropertySigil(ch)) {
|
|
366
|
+
var pEnd = i + 1;
|
|
367
|
+
if (ch === "!" && line.charAt(pEnd) === "!") pEnd += 1;
|
|
368
|
+
while (pEnd < line.length && !_isPlainSpace(line.charAt(pEnd)) &&
|
|
369
|
+
(flowDepth === 0 || ",}]".indexOf(line.charAt(pEnd)) === -1)) pEnd += 1;
|
|
370
|
+
masked += line.slice(i, pEnd);
|
|
371
|
+
i = pEnd;
|
|
372
|
+
continue; // properties chain
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// A quoted scalar: the quotes stay, the body goes. The body is content by
|
|
376
|
+
// construction, so nothing in it is ever structure.
|
|
377
|
+
if (ch === '"' || ch === "'") {
|
|
378
|
+
var qr = _maskQuotedBody(line, i + 1, ch);
|
|
379
|
+
masked += ch + qr.masked;
|
|
380
|
+
i = qr.end;
|
|
381
|
+
// Not closed on this line means the scalar CONTINUES, which YAML allows
|
|
382
|
+
// and which the line-at-a-time reading would otherwise lose. The quote
|
|
383
|
+
// is remembered so the next line is read as its body rather than as
|
|
384
|
+
// structure.
|
|
385
|
+
if (!qr.closed) { openQuote = ch; break; }
|
|
386
|
+
atNodeStart = false;
|
|
387
|
+
// Remembered for the colon rule above: a quoted scalar is one of the
|
|
388
|
+
// two JSON-like key forms that may take its colon with no space between.
|
|
389
|
+
prevWasJsonKey = true;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// A block-scalar header ends the line's structure; the body is masked by
|
|
394
|
+
// the outer loop.
|
|
395
|
+
var bHead = _blockHeaderEnd(line, i);
|
|
396
|
+
if (bHead) {
|
|
397
|
+
masked += line.slice(i, bHead.end);
|
|
398
|
+
// The body must beat the DASH when there is one, and the line's node
|
|
399
|
+
// otherwise. That is only a floor: where the body actually starts is
|
|
400
|
+
// detected from its first content line, because with extra spacing
|
|
401
|
+
// after a dash it can sit anywhere past that floor.
|
|
402
|
+
// The owner is the node the block hangs off: the ITEM when the header
|
|
403
|
+
// stands where the item's own node goes (`- |`), and the MAPPING ENTRY
|
|
404
|
+
// when it follows a key (`- key: |`). Using the dash for the second
|
|
405
|
+
// case made a sibling written straight after an empty block look like
|
|
406
|
+
// its first body line — `- key: |` / ` evil: !tag x` masked the tag.
|
|
407
|
+
//
|
|
408
|
+
// It also settles the padded case the honest way. `- key: |` puts the
|
|
409
|
+
// mapping at column 4, so a body at column 4 is not content by YAML's
|
|
410
|
+
// own indentation rule; that document is malformed, and reading its
|
|
411
|
+
// next line as structure surfaces whatever it says rather than hiding
|
|
412
|
+
// it. Where a shape is ambiguous, the reading that keeps a sibling
|
|
413
|
+
// VISIBLE is the one to take — the other hides real tags.
|
|
414
|
+
blockOwner = (dashIndent >= 0 && i === nodeIndent) ? dashIndent : nodeIndent;
|
|
415
|
+
// ...unless the header DECLARED it. `|2` fixes the body's indentation
|
|
416
|
+
// relative to the parent, so a first line indented further does not
|
|
417
|
+
// move it, and a later line back at the declared column is still body.
|
|
418
|
+
// Detecting in that case ends the block early and hands the rest of the
|
|
419
|
+
// scalar to the structural scan.
|
|
420
|
+
//
|
|
421
|
+
// Counted from that same owner: for `- key: |2` the indicator is
|
|
422
|
+
// relative to the mapping entry, not the dash, and counting from the
|
|
423
|
+
// dash puts the body two columns too far left and swallows the entry's
|
|
424
|
+
// siblings.
|
|
425
|
+
blockBody = bHead.declared ? blockOwner + bHead.declared : -1;
|
|
426
|
+
i = bHead.end;
|
|
427
|
+
atNodeStart = false;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Anything else begins a PLAIN scalar, and everything to the end of it is
|
|
432
|
+
// content. This is the case the previous implementations had no notion of:
|
|
433
|
+
// in `x: hello !world` the `!world` sits inside a scalar that started at
|
|
434
|
+
// `hello`, so it names no tag, and only knowing a scalar had already begun
|
|
435
|
+
// can tell you that.
|
|
436
|
+
var s = i;
|
|
437
|
+
while (s < line.length) {
|
|
438
|
+
var c3 = line.charAt(s);
|
|
439
|
+
if (c3 === "#" && _isPlainSpace(line.charAt(s - 1))) break;
|
|
440
|
+
if (c3 === ":" &&
|
|
441
|
+
(s + 1 >= line.length || _isPlainSpace(line.charAt(s + 1)) ||
|
|
442
|
+
(flowDepth > 0 && ",}]".indexOf(line.charAt(s + 1)) !== -1))) break;
|
|
443
|
+
if (flowDepth > 0 && ",}][{".indexOf(c3) !== -1) break;
|
|
444
|
+
s += 1;
|
|
445
|
+
}
|
|
446
|
+
masked += _blanked(line.slice(i, s));
|
|
447
|
+
// Where this scalar BEGAN. A dash earlier on the line is not enough to
|
|
448
|
+
// make the scalar the item's own: in `- key: hello` the scalar belongs to
|
|
449
|
+
// `key`, and treating the next line at the mapping's column as its
|
|
450
|
+
// continuation masked the tag on a sibling key.
|
|
451
|
+
plainStartCol = i;
|
|
452
|
+
i = s;
|
|
453
|
+
atNodeStart = false;
|
|
454
|
+
// A plain scalar reaching the end of the line may continue on the next
|
|
455
|
+
// one. Only the LAST thing read on a line can, so this is recorded here
|
|
456
|
+
// and cleared by anything that follows it.
|
|
457
|
+
sawPlainToEol = s >= line.length;
|
|
458
|
+
}
|
|
459
|
+
// Armed only when the line ended inside a plain scalar, and measured
|
|
460
|
+
// against the node that opened it rather than the line's own indent. `x:`
|
|
461
|
+
// with nothing after it never arms this, so the mapping written underneath
|
|
462
|
+
// it is still read as structure rather than swallowed as text.
|
|
463
|
+
// Armed inside a flow collection too: a plain scalar spans lines there just
|
|
464
|
+
// as it does outside one, and requiring depth zero left `x: [hello` /
|
|
465
|
+
// ` !world]` reading its second line at a fresh node start.
|
|
466
|
+
if (sawPlainToEol && openQuote === null && blockOwner < 0) {
|
|
467
|
+
if (!continuesPlain) {
|
|
468
|
+
plainOpen = nodeIndent;
|
|
469
|
+
// Whether the scalar hangs directly off a sequence dash decides how its
|
|
470
|
+
// continuation is measured, so it is remembered with the column.
|
|
471
|
+
// Only when the scalar IS the item's node: a dash on the line, and the
|
|
472
|
+
// scalar starting exactly where that item's node begins.
|
|
473
|
+
plainFromDash = dashIndent >= 0 && plainStartCol === nodeIndent;
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
plainOpen = -1;
|
|
477
|
+
plainFromDash = false;
|
|
478
|
+
}
|
|
479
|
+
out.push(masked + (cr ? "\r" : ""));
|
|
480
|
+
}
|
|
481
|
+
return out.join("\n");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// A line kept for its structure, with any comment on it masked. Used for the
|
|
485
|
+
// directive and document-marker lines, which are passed through whole because
|
|
486
|
+
// what makes them structural is their shape rather than a scan of their parts.
|
|
487
|
+
// The comment is still content and still has to go.
|
|
488
|
+
function _maskTrailingComment(line) {
|
|
489
|
+
for (var i = 0; i < line.length; i += 1) {
|
|
490
|
+
if (line.charAt(i) !== "#") continue;
|
|
491
|
+
if (i !== 0 && !_isPlainSpace(line.charAt(i - 1))) continue;
|
|
492
|
+
return line.slice(0, i) + _blanked(line.slice(i));
|
|
493
|
+
}
|
|
494
|
+
return line;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// The body of a quoted scalar from `at`, masked, stopping at the closing quote.
|
|
498
|
+
// Returns where the scan ended (past the quote when it closed) and whether it
|
|
499
|
+
// did close, which is what tells the caller the scalar runs onto the next line.
|
|
500
|
+
//
|
|
501
|
+
// Both the escape forms are honoured because both hide a quote that would
|
|
502
|
+
// otherwise look like the end: `\"` inside a double-quoted scalar and `''`
|
|
503
|
+
// inside a single-quoted one. Reading either as a terminator ends the mask
|
|
504
|
+
// early and hands the rest of the scalar back to the structural scan as though
|
|
505
|
+
// it were YAML.
|
|
506
|
+
function _maskQuotedBody(line, at, quote) {
|
|
507
|
+
var body = "";
|
|
508
|
+
var k = at;
|
|
509
|
+
while (k < line.length) {
|
|
510
|
+
var c = line.charAt(k);
|
|
511
|
+
if (quote === '"' && c === "\\" && k + 1 < line.length) { body += " "; k += 2; continue; }
|
|
512
|
+
if (c === quote) {
|
|
513
|
+
if (quote === "'" && line.charAt(k + 1) === "'") { body += " "; k += 2; continue; }
|
|
514
|
+
return { masked: body + quote, end: k + 1, closed: true };
|
|
515
|
+
}
|
|
516
|
+
body += c === "\t" ? "\t" : " ";
|
|
517
|
+
k += 1;
|
|
518
|
+
}
|
|
519
|
+
return { masked: body, end: k, closed: false };
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Same length, spaces throughout. Newlines cannot appear here — the caller
|
|
523
|
+
// splits on them first — but a tab is preserved so column arithmetic that
|
|
524
|
+
// counts it as one character still agrees with the source.
|
|
525
|
+
function _blanked(s) {
|
|
526
|
+
var o = "";
|
|
527
|
+
for (var i = 0; i < s.length; i += 1) o += s.charAt(i) === "\t" ? "\t" : " ";
|
|
528
|
+
return o;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
module.exports = {
|
|
532
|
+
maskNonStructural: maskNonStructural,
|
|
533
|
+
};
|
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:f52f2ee6-253b-4bf4-9915-22d453a4407a",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-24T12:08:00.505Z",
|
|
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.18.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.18.53",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.18.
|
|
25
|
+
"version": "0.18.53",
|
|
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.18.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.18.53",
|
|
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.18.
|
|
57
|
+
"ref": "@blamejs/core@0.18.53",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|