poe-code 14.0.73 → 14.0.74
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/dist/index.js +1855 -1153
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/package.json +1 -1
- package/packages/safe-js/dist/chunks/{chunk-I2Z37MKZ.js → chunk-2J6W3YGB.js} +2 -2
- package/packages/safe-js/dist/chunks/{chunk-XKCTB5FR.js → chunk-YEZOXLY4.js} +3203 -2543
- package/packages/safe-js/dist/chunks/chunk-YEZOXLY4.js.map +7 -0
- package/packages/safe-js/dist/cli.js +2 -2
- package/packages/safe-js/dist/core.js +1 -1
- package/packages/safe-js/dist/graph-depth.js +3 -0
- package/packages/safe-js/dist/index.js +2 -2
- package/packages/safe-js/dist/interp/cancel.js +3 -0
- package/packages/safe-js/dist/interp/globals/misc.js +2 -1
- package/packages/safe-js/dist/interp/globals/object.js +3 -0
- package/packages/safe-js/dist/interp/globals/regex.js +88 -7
- package/packages/safe-js/dist/interp/interpreter.js +14 -7
- package/packages/safe-js/dist/interp/iteration.js +6 -1
- package/packages/safe-js/dist/interp/methods/regex.d.ts +7 -3
- package/packages/safe-js/dist/interp/methods/regex.js +127 -21
- package/packages/safe-js/dist/interp/methods/regexp-iterator.d.ts +8 -0
- package/packages/safe-js/dist/interp/methods/regexp-iterator.js +49 -0
- package/packages/safe-js/dist/interp/methods/string.d.ts +2 -0
- package/packages/safe-js/dist/interp/methods/string.js +412 -118
- package/packages/safe-js/dist/interp/object-model.d.ts +2 -0
- package/packages/safe-js/dist/interp/object-model.js +39 -9
- package/packages/safe-js/dist/interp/regexp-iterator.d.ts +14 -0
- package/packages/safe-js/dist/interp/regexp-iterator.js +18 -0
- package/packages/safe-js/dist/interp/string-coercion.js +11 -7
- package/packages/safe-js/dist/interp/values.d.ts +2 -1
- package/packages/safe-js/dist/interp/values.js +35 -0
- package/packages/safe-js/dist/snapshot/dump-format.js +26 -2
- package/packages/safe-js/dist/snapshot/replay-data.d.ts +9 -1
- package/packages/safe-js/dist/snapshot/replay-data.js +31 -0
- package/packages/safe-js/dist/snapshot/replay-inputs.js +17 -0
- package/packages/safe-js/dist/snapshot/restore.js +17 -1
- package/packages/safe-js/dist/snapshot/serialize.d.ts +10 -2
- package/packages/safe-js/dist/snapshot/serialize.js +26 -10
- package/packages/safe-js/dist/snapshot/validation.js +14 -2
- package/packages/safe-js/dist/chunks/chunk-XKCTB5FR.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-I2Z37MKZ.js.map → chunk-2J6W3YGB.js.map} +0 -0
|
@@ -4,10 +4,13 @@ import { CompileScope } from "../regex/compile-guard.js";
|
|
|
4
4
|
import { normalizeLastIndex } from "../regex/engine.js";
|
|
5
5
|
import { sandboxNumber, sandboxString } from "../string-coercion.js";
|
|
6
6
|
import { retainValues } from "../resources.js";
|
|
7
|
-
import { getSandboxDataProperty, getSandboxPropertyDescriptor } from "../object-model.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
7
|
+
import { getSandboxDataProperty, getSandboxPropertyDescriptor, hasRegexPropertyOverride } from "../object-model.js";
|
|
8
|
+
import { readPropertyDescriptor } from "../accessors.js";
|
|
9
|
+
import { createSandboxBox } from "../boxed.js";
|
|
10
|
+
import { setSandboxProperty } from "../interpreter.js";
|
|
11
|
+
import { createSandboxClosure, createSandboxRegex, deepCopyFromSandbox, isSandboxClosure, isSandboxRegex, measureSandboxData } from "../values.js";
|
|
12
|
+
import { executeRegex, getRegexMember, regexExec, regexFlagProperties, regexSearch, toMatchArray } from "./regex.js";
|
|
13
|
+
import { restoreSandboxRegExpIterator } from "../regexp-iterator.js";
|
|
11
14
|
export const stringMethodNames = new Set([
|
|
12
15
|
"at",
|
|
13
16
|
"charAt",
|
|
@@ -87,9 +90,19 @@ export function validateStringMethodArguments(_methodName, _args) { }
|
|
|
87
90
|
export function callStringMethod(value, methodName, args, budget, callClosure = async (closure, closureArgs) => await closure.call(closureArgs), parent, context) {
|
|
88
91
|
if (value === null || value === undefined)
|
|
89
92
|
throw new TypeError(`String#${methodName} requires a non-null receiver.`);
|
|
90
|
-
const fallback = () =>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
const fallback = (coercePattern = false) => {
|
|
94
|
+
const apply = (string) => {
|
|
95
|
+
if (coercePattern && (methodName === "match" || methodName === "matchAll" || methodName === "search"))
|
|
96
|
+
return callStringPattern(string, methodName, args[0], budget, parent, context);
|
|
97
|
+
const useRegex = isSandboxRegex(args[0]) && !coercePattern;
|
|
98
|
+
if (methodName === "replace" || methodName === "replaceAll")
|
|
99
|
+
return callReplaceLikeMethod(string, methodName, args, budget, callClosure, useRegex, context);
|
|
100
|
+
if (methodName === "split")
|
|
101
|
+
return callSplit(string, args, budget, useRegex, parent, context);
|
|
102
|
+
return callStringMethodBody(string, methodName, args, budget, parent, context);
|
|
103
|
+
};
|
|
104
|
+
return typeof value === "string" ? apply(value) : Promise.resolve(sandboxString(value, budget, context)).then(apply);
|
|
105
|
+
};
|
|
93
106
|
const symbol = methodName === "match" ? Symbol.match
|
|
94
107
|
: methodName === "search" ? Symbol.search
|
|
95
108
|
: methodName === "matchAll" ? Symbol.matchAll
|
|
@@ -100,18 +113,19 @@ export function callStringMethod(value, methodName, args, budget, callClosure =
|
|
|
100
113
|
throw new TypeError(`String#${methodName} does not accept unbranded host RegExp values.`);
|
|
101
114
|
if (symbol === undefined || pattern === null || pattern === undefined)
|
|
102
115
|
return fallback();
|
|
103
|
-
const applyHook = (hook) => {
|
|
104
|
-
if (hook === null || hook === undefined)
|
|
105
|
-
return fallback();
|
|
106
|
-
if (!isSandboxClosure(hook))
|
|
107
|
-
throw new TypeError(`String#${methodName} symbol hook must be callable.`);
|
|
108
|
-
return invokeBuiltinClosure(hook, symbol === Symbol.split || symbol === Symbol.replace ? [value, args[1]] : [value], budget, context, pattern);
|
|
109
|
-
};
|
|
110
116
|
const readProperty = (key) => context?.getProperty !== undefined
|
|
111
117
|
? context.getProperty(pattern, key)
|
|
112
118
|
: key === "flags" && isSandboxRegex(pattern) && getSandboxPropertyDescriptor(pattern, key, budget) === undefined
|
|
113
|
-
? pattern
|
|
119
|
+
? getRegexMember(pattern, key, budget, context) : getSandboxDataProperty(pattern, key, budget);
|
|
114
120
|
const dispatch = () => {
|
|
121
|
+
const overriddenRegex = isSandboxRegex(pattern) && getSandboxPropertyDescriptor(pattern, symbol, budget) !== undefined;
|
|
122
|
+
const applyHook = (hook) => {
|
|
123
|
+
if (hook === null || hook === undefined)
|
|
124
|
+
return fallback(overriddenRegex);
|
|
125
|
+
if (!isSandboxClosure(hook))
|
|
126
|
+
throw new TypeError(`String#${methodName} symbol hook must be callable.`);
|
|
127
|
+
return invokeBuiltinClosure(hook, symbol === Symbol.split || symbol === Symbol.replace ? [value, args[1]] : [value], budget, context, pattern);
|
|
128
|
+
};
|
|
115
129
|
const hook = readProperty(symbol);
|
|
116
130
|
return hook instanceof Promise ? hook.then(applyHook) : applyHook(hook);
|
|
117
131
|
};
|
|
@@ -137,7 +151,7 @@ export function callStringMethod(value, methodName, args, budget, callClosure =
|
|
|
137
151
|
}
|
|
138
152
|
return dispatch();
|
|
139
153
|
}
|
|
140
|
-
function callStringMethodBody(value, methodName, args, budget,
|
|
154
|
+
function callStringMethodBody(value, methodName, args, budget, parent, context) {
|
|
141
155
|
if (methodName === "concat" && args.some(argument => argument !== null && typeof argument === "object")) {
|
|
142
156
|
return callConcat(value, args, budget, context);
|
|
143
157
|
}
|
|
@@ -178,13 +192,16 @@ function callStringMethodBody(value, methodName, args, budget, callClosure = asy
|
|
|
178
192
|
}
|
|
179
193
|
return budget.allocateString(result + value.slice(spanStart));
|
|
180
194
|
}
|
|
181
|
-
if (methodName === "replace" || methodName === "replaceAll") {
|
|
182
|
-
return callReplaceLikeMethod(value, methodName, args, budget, callClosure, context);
|
|
183
|
-
}
|
|
184
195
|
const regex = args[0];
|
|
185
|
-
if (
|
|
186
|
-
(
|
|
187
|
-
|
|
196
|
+
if (methodName === "search" && isSandboxRegex(regex) &&
|
|
197
|
+
hasRegexPropertyOverride(regex, ["exec"], budget))
|
|
198
|
+
return regexSearch(regex, value, budget, context);
|
|
199
|
+
if (methodName === "match" && isSandboxRegex(regex) &&
|
|
200
|
+
hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
|
|
201
|
+
return regexMatch(value, regex, budget, context);
|
|
202
|
+
if (isSandboxRegex(regex) && (methodName === "matchAll" ||
|
|
203
|
+
(methodName === "match" && !regex.flags.includes("g") &&
|
|
204
|
+
regex.lastIndex !== null && typeof regex.lastIndex === "object"))) {
|
|
188
205
|
return callStringRegexCursor(value, methodName, regex, budget, parent, context);
|
|
189
206
|
}
|
|
190
207
|
if ((methodName === "match" || methodName === "matchAll" || methodName === "search") && !isSandboxRegex(args[0])) {
|
|
@@ -193,9 +210,6 @@ function callStringMethodBody(value, methodName, args, budget, callClosure = asy
|
|
|
193
210
|
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
194
211
|
const compilation = new CompileScope(operation.owner);
|
|
195
212
|
try {
|
|
196
|
-
if (methodName === "split") {
|
|
197
|
-
return callSplit(value, args, budget, compilation);
|
|
198
|
-
}
|
|
199
213
|
if (methodName === "match" || methodName === "matchAll" || methodName === "search") {
|
|
200
214
|
return callMatchLikeMethod(value, methodName, args, compilation);
|
|
201
215
|
}
|
|
@@ -311,20 +325,20 @@ async function callConcat(value, args, budget, context) {
|
|
|
311
325
|
release();
|
|
312
326
|
}
|
|
313
327
|
}
|
|
314
|
-
function callReplaceLikeMethod(value, methodName, args, budget, callClosure, context) {
|
|
328
|
+
function callReplaceLikeMethod(value, methodName, args, budget, callClosure, useRegex, context) {
|
|
315
329
|
const search = args[0];
|
|
316
330
|
const replacement = args[1];
|
|
317
|
-
const
|
|
318
|
-
if ((!
|
|
331
|
+
const regexSearch = useRegex && isSandboxRegex(search);
|
|
332
|
+
if ((!regexSearch && typeof search !== "string") ||
|
|
319
333
|
(typeof replacement !== "string" && !isSandboxClosure(replacement))) {
|
|
320
334
|
return (async () => {
|
|
321
335
|
let normalizedSearch;
|
|
322
336
|
let normalizedReplacement;
|
|
323
337
|
const release = retainValues(budget, () => [normalizedSearch, normalizedReplacement]);
|
|
324
338
|
try {
|
|
325
|
-
normalizedSearch =
|
|
339
|
+
normalizedSearch = regexSearch ? search : await sandboxString(search, budget, context);
|
|
326
340
|
normalizedReplacement = isSandboxClosure(replacement) ? replacement : await sandboxString(replacement, budget, context);
|
|
327
|
-
return await callReplaceLikeMethod(value, methodName, [normalizedSearch, normalizedReplacement], budget, callClosure, context);
|
|
341
|
+
return await callReplaceLikeMethod(value, methodName, [normalizedSearch, normalizedReplacement], budget, callClosure, useRegex, context);
|
|
328
342
|
}
|
|
329
343
|
finally {
|
|
330
344
|
release();
|
|
@@ -339,25 +353,27 @@ function callReplaceLikeMethod(value, methodName, args, budget, callClosure, con
|
|
|
339
353
|
? value.replace(search, replacement)
|
|
340
354
|
: value.replaceAll(search, replacement));
|
|
341
355
|
}
|
|
342
|
-
return replaceWithClosure(value, search, replacement, methodName === "replaceAll", budget, callClosure);
|
|
356
|
+
return replaceWithClosure(value, search, replacement, methodName === "replaceAll", budget, callClosure, context);
|
|
343
357
|
}
|
|
344
358
|
async function replaceRegex(value, regex, replacement, budget, callClosure, context) {
|
|
359
|
+
if (hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget))
|
|
360
|
+
return regexReplace(value, regex, replacement, budget, callClosure, context);
|
|
345
361
|
if (regex.flags.includes("g"))
|
|
346
362
|
regex.lastIndex = 0;
|
|
347
363
|
const cursor = regex.lastIndex;
|
|
364
|
+
let result = "";
|
|
348
365
|
const retained = {};
|
|
349
|
-
budget.setRetainedValues(retained, () => [value, regex, cursor, replacement]);
|
|
366
|
+
budget.setRetainedValues(retained, () => [value, regex, cursor, replacement, result]);
|
|
350
367
|
try {
|
|
351
368
|
const lastIndex = await sandboxNumber(cursor, budget, context);
|
|
352
369
|
const matches = collectRegexMatches(regex, value, regex.flags.includes("g"), undefined, lastIndex);
|
|
353
|
-
let result = "";
|
|
354
370
|
let copiedThrough = 0;
|
|
355
371
|
for (const match of matches) {
|
|
356
372
|
result += value.slice(copiedThrough, match.index);
|
|
357
373
|
result +=
|
|
358
374
|
typeof replacement === "string"
|
|
359
|
-
? expandReplacement(replacement, match.text, match.captures, value, match.index)
|
|
360
|
-
:
|
|
375
|
+
? await expandReplacement(replacement, match.text, match.captures, value, match.index, budget, context)
|
|
376
|
+
: await sandboxString(await callClosure(replacement, [match.text, ...match.captures, match.index, value]), budget, context);
|
|
361
377
|
copiedThrough = match.index + match.text.length;
|
|
362
378
|
}
|
|
363
379
|
result += value.slice(copiedThrough);
|
|
@@ -367,62 +383,201 @@ async function replaceRegex(value, regex, replacement, budget, callClosure, cont
|
|
|
367
383
|
budget.setRetainedValues(retained, undefined);
|
|
368
384
|
}
|
|
369
385
|
}
|
|
370
|
-
function
|
|
386
|
+
function readReplacementProperty(target, key, budget, context) {
|
|
387
|
+
if (context?.getProperty !== undefined)
|
|
388
|
+
return context.getProperty(target, key);
|
|
389
|
+
const descriptor = getSandboxPropertyDescriptor(target, key, budget);
|
|
390
|
+
if (descriptor !== undefined)
|
|
391
|
+
return readPropertyDescriptor(descriptor, target, context);
|
|
392
|
+
return isSandboxRegex(target) ? getRegexMember(target, key, budget, context) : undefined;
|
|
393
|
+
}
|
|
394
|
+
export async function regexReplace(input, regex, replacementInput, budget, callClosure, context) {
|
|
395
|
+
if (regex === null || typeof regex !== "object")
|
|
396
|
+
throw new TypeError("RegExp replace requires an object receiver.");
|
|
397
|
+
let value;
|
|
398
|
+
let replacement;
|
|
399
|
+
const matches = [];
|
|
400
|
+
let current;
|
|
401
|
+
let field;
|
|
402
|
+
let groups;
|
|
403
|
+
let matched;
|
|
404
|
+
let captures = [];
|
|
371
405
|
let result = "";
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
else if (token === "&") {
|
|
383
|
-
result += match;
|
|
406
|
+
let flags;
|
|
407
|
+
const release = retainValues(budget, () => [input, value, regex, replacementInput, replacement, matches, current, field, groups, matched, captures, result, flags]);
|
|
408
|
+
try {
|
|
409
|
+
value = await sandboxString(input, budget, context);
|
|
410
|
+
input = undefined;
|
|
411
|
+
replacement = isSandboxClosure(replacementInput) ? replacementInput : await sandboxString(replacementInput, budget, context);
|
|
412
|
+
replacementInput = undefined;
|
|
413
|
+
if (isSandboxRegex(regex) && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
|
|
414
|
+
release();
|
|
415
|
+
return await replaceRegex(value, regex, replacement, budget, callClosure, context);
|
|
384
416
|
}
|
|
385
|
-
|
|
386
|
-
|
|
417
|
+
field = await readReplacementProperty(regex, "flags", budget, context);
|
|
418
|
+
flags = await sandboxString(field, budget, context);
|
|
419
|
+
field = undefined;
|
|
420
|
+
const global = flags.includes("g");
|
|
421
|
+
const fullUnicode = flags.includes("u") || flags.includes("v");
|
|
422
|
+
flags = undefined;
|
|
423
|
+
if (global)
|
|
424
|
+
await setSandboxProperty(regex, "lastIndex", 0, budget, true, context);
|
|
425
|
+
while (true) {
|
|
426
|
+
budget.visitNode();
|
|
427
|
+
current = await regexExec(regex, value, budget, context);
|
|
428
|
+
if (current === null)
|
|
429
|
+
break;
|
|
430
|
+
budget.allocateArrayLength(matches.length + 1);
|
|
431
|
+
matches.push(current);
|
|
432
|
+
if (!global)
|
|
433
|
+
break;
|
|
434
|
+
field = await readReplacementProperty(current, "0", budget, context);
|
|
435
|
+
matched = await sandboxString(field, budget, context);
|
|
436
|
+
field = undefined;
|
|
437
|
+
if (matched.length === 0) {
|
|
438
|
+
field = await readReplacementProperty(regex, "lastIndex", budget, context);
|
|
439
|
+
const index = normalizeLastIndex(await sandboxNumber(field, budget, context));
|
|
440
|
+
const point = fullUnicode ? value.codePointAt(index) : undefined;
|
|
441
|
+
await setSandboxProperty(regex, "lastIndex", index + (point !== undefined && point > 0xffff ? 2 : 1), budget, true, context);
|
|
442
|
+
field = undefined;
|
|
443
|
+
}
|
|
444
|
+
matched = undefined;
|
|
445
|
+
current = undefined;
|
|
387
446
|
}
|
|
388
|
-
|
|
389
|
-
|
|
447
|
+
let copiedThrough = 0;
|
|
448
|
+
for (current of matches) {
|
|
449
|
+
field = await readReplacementProperty(current, "length", budget, context);
|
|
450
|
+
const count = Math.max(normalizeLastIndex(await sandboxNumber(field, budget, context)) - 1, 0);
|
|
451
|
+
field = await readReplacementProperty(current, "0", budget, context);
|
|
452
|
+
matched = await sandboxString(field, budget, context);
|
|
453
|
+
field = await readReplacementProperty(current, "index", budget, context);
|
|
454
|
+
const number = await sandboxNumber(field, budget, context);
|
|
455
|
+
const position = Math.min(Math.max(Number.isNaN(number) ? 0 : Math.trunc(number), 0), value.length);
|
|
456
|
+
field = undefined;
|
|
457
|
+
captures = [];
|
|
458
|
+
budget.allocateArrayLength(count);
|
|
459
|
+
for (let index = 1; index <= count; index++) {
|
|
460
|
+
budget.visitNode();
|
|
461
|
+
field = await readReplacementProperty(current, String(index), budget, context);
|
|
462
|
+
captures.push(field === undefined ? undefined : await sandboxString(field, budget, context));
|
|
463
|
+
field = undefined;
|
|
464
|
+
}
|
|
465
|
+
groups = await readReplacementProperty(current, "groups", budget, context);
|
|
466
|
+
let text;
|
|
467
|
+
if (typeof replacement === "string") {
|
|
468
|
+
if (groups === null)
|
|
469
|
+
throw new TypeError("Replacement groups cannot be null.");
|
|
470
|
+
if (groups !== undefined && typeof groups !== "object") {
|
|
471
|
+
groups = createSandboxBox(groups);
|
|
472
|
+
budget.chargeDataUsage(measureSandboxData([groups]));
|
|
473
|
+
}
|
|
474
|
+
text = await expandReplacement(replacement, matched, captures, value, position, budget, context, groups);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const args = [matched, ...captures, position, value];
|
|
478
|
+
if (groups !== undefined)
|
|
479
|
+
args.push(groups);
|
|
480
|
+
budget.allocateArrayLength(args.length);
|
|
481
|
+
field = await callClosure(replacement, args);
|
|
482
|
+
text = await sandboxString(field, budget, context);
|
|
483
|
+
field = undefined;
|
|
484
|
+
}
|
|
485
|
+
if (position >= copiedThrough) {
|
|
486
|
+
result = budget.allocateString(result + value.slice(copiedThrough, position) + text);
|
|
487
|
+
copiedThrough = position + matched.length;
|
|
488
|
+
}
|
|
489
|
+
matched = undefined;
|
|
490
|
+
captures = [];
|
|
491
|
+
groups = undefined;
|
|
390
492
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
493
|
+
return budget.allocateString(result + value.slice(copiedThrough));
|
|
494
|
+
}
|
|
495
|
+
finally {
|
|
496
|
+
release();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function expandReplacement(replacement, match, captures, input, matchIndex, budget, context, groups) {
|
|
500
|
+
let result = "";
|
|
501
|
+
const release = retainValues(budget, () => [replacement, match, captures, input, groups, result]);
|
|
502
|
+
try {
|
|
503
|
+
for (let index = 0; index < replacement.length; index += 1) {
|
|
504
|
+
budget.visitNode();
|
|
505
|
+
budget.allocateString(result);
|
|
506
|
+
const character = replacement.charAt(index);
|
|
507
|
+
if (character !== "$") {
|
|
508
|
+
result += character;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const token = replacement.charAt(index + 1);
|
|
512
|
+
if (token === "$") {
|
|
513
|
+
result += "$";
|
|
514
|
+
}
|
|
515
|
+
else if (token === "&") {
|
|
516
|
+
result += match;
|
|
517
|
+
}
|
|
518
|
+
else if (token === "`") {
|
|
519
|
+
result += input.slice(0, matchIndex);
|
|
520
|
+
}
|
|
521
|
+
else if (token === "'") {
|
|
522
|
+
result += input.slice(matchIndex + match.length);
|
|
523
|
+
}
|
|
524
|
+
else if (token === "<" && groups !== undefined) {
|
|
525
|
+
const end = replacement.indexOf(">", index + 2);
|
|
526
|
+
if (end === -1) {
|
|
527
|
+
result += "$";
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const capture = await readReplacementProperty(groups, replacement.slice(index + 2, end), budget, context);
|
|
531
|
+
if (capture !== undefined)
|
|
532
|
+
result += await sandboxString(capture, budget, context);
|
|
533
|
+
index = end;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
else if (token >= "0" && token <= "9") {
|
|
537
|
+
let captureIndex = Number(token);
|
|
538
|
+
const nextDigit = replacement.charAt(index + 2);
|
|
539
|
+
if (nextDigit >= "0" && nextDigit <= "9") {
|
|
540
|
+
const twoDigitIndex = Number(token + nextDigit);
|
|
541
|
+
if (twoDigitIndex > 0 && twoDigitIndex <= captures.length) {
|
|
542
|
+
captureIndex = twoDigitIndex;
|
|
543
|
+
index += 1;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (captureIndex === 0 || captureIndex > captures.length) {
|
|
547
|
+
result += "$";
|
|
548
|
+
continue;
|
|
399
549
|
}
|
|
550
|
+
result += captures[captureIndex - 1] ?? "";
|
|
400
551
|
}
|
|
401
|
-
|
|
552
|
+
else {
|
|
402
553
|
result += "$";
|
|
403
554
|
continue;
|
|
404
555
|
}
|
|
405
|
-
|
|
556
|
+
index += 1;
|
|
406
557
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
index += 1;
|
|
558
|
+
return budget.allocateString(result);
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
release();
|
|
412
562
|
}
|
|
413
|
-
return result;
|
|
414
563
|
}
|
|
415
|
-
async function replaceWithClosure(value, searchValue, replacer, replaceAll, budget, callClosure) {
|
|
564
|
+
async function replaceWithClosure(value, searchValue, replacer, replaceAll, budget, callClosure, context) {
|
|
416
565
|
const offsets = findReplacementOffsets(value, searchValue, replaceAll);
|
|
417
566
|
let result = "";
|
|
418
567
|
let copiedThrough = 0;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
568
|
+
const release = retainValues(budget, () => [value, searchValue, replacer, result]);
|
|
569
|
+
try {
|
|
570
|
+
for (const offset of offsets) {
|
|
571
|
+
result += value.slice(copiedThrough, offset);
|
|
572
|
+
result += await sandboxString(await callClosure(replacer, [searchValue, offset, value]), budget, context);
|
|
573
|
+
copiedThrough = offset + searchValue.length;
|
|
574
|
+
}
|
|
575
|
+
result += value.slice(copiedThrough);
|
|
576
|
+
return budget.allocateString(result);
|
|
577
|
+
}
|
|
578
|
+
finally {
|
|
579
|
+
release();
|
|
423
580
|
}
|
|
424
|
-
result += value.slice(copiedThrough);
|
|
425
|
-
return budget.allocateString(result);
|
|
426
581
|
}
|
|
427
582
|
function findReplacementOffsets(value, searchValue, replaceAll) {
|
|
428
583
|
const firstOffset = value.indexOf(searchValue);
|
|
@@ -443,40 +598,76 @@ function findReplacementOffsets(value, searchValue, replaceAll) {
|
|
|
443
598
|
}
|
|
444
599
|
return offsets;
|
|
445
600
|
}
|
|
446
|
-
function callSplit(value, args, budget,
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
result.
|
|
464
|
-
|
|
465
|
-
|
|
601
|
+
function callSplit(value, args, budget, useRegex, parent, context) {
|
|
602
|
+
const pattern = args[0];
|
|
603
|
+
let separator;
|
|
604
|
+
const release = retainValues(budget, () => [value, ...args, separator]);
|
|
605
|
+
const withLimit = (number) => {
|
|
606
|
+
const limit = number >>> 0;
|
|
607
|
+
const converted = useRegex || pattern === undefined ? pattern : sandboxString(pattern, budget, context);
|
|
608
|
+
const split = (converted) => {
|
|
609
|
+
separator = converted;
|
|
610
|
+
return splitNormalized(value, converted, limit, budget, parent);
|
|
611
|
+
};
|
|
612
|
+
return converted instanceof Promise ? converted.then(split) : split(converted);
|
|
613
|
+
};
|
|
614
|
+
try {
|
|
615
|
+
const limit = args[1] === undefined ? 2 ** 32 - 1 : sandboxNumber(args[1], budget, context);
|
|
616
|
+
const result = limit instanceof Promise ? limit.then(withLimit) : withLimit(limit);
|
|
617
|
+
if (result instanceof Promise)
|
|
618
|
+
return result.finally(release);
|
|
619
|
+
release();
|
|
620
|
+
return result;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
release();
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function splitNormalized(value, separator, limit, budget, parent) {
|
|
628
|
+
if (limit === 0)
|
|
629
|
+
return [];
|
|
630
|
+
if (isSandboxRegex(separator)) {
|
|
631
|
+
const regex = separator;
|
|
632
|
+
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
633
|
+
const compilation = new CompileScope(operation.owner);
|
|
634
|
+
try {
|
|
635
|
+
const splitter = createSandboxRegex(regex.source, regex.flags.includes("g") ? regex.flags : `${regex.flags}g`, 0, compilation);
|
|
636
|
+
const result = [];
|
|
637
|
+
let copiedThrough = 0;
|
|
638
|
+
let endedWithZeroWidthMatch = false;
|
|
639
|
+
while (result.length < limit) {
|
|
640
|
+
const match = executeRegex(splitter, value, Number(splitter.lastIndex));
|
|
641
|
+
if (match === null)
|
|
466
642
|
break;
|
|
467
|
-
|
|
643
|
+
if (match.text.length === 0)
|
|
644
|
+
splitter.lastIndex = match.index + 1;
|
|
645
|
+
endedWithZeroWidthMatch = match.text.length === 0 && match.index === value.length;
|
|
646
|
+
if (match.text.length === 0 &&
|
|
647
|
+
(match.index === copiedThrough || match.index === value.length))
|
|
648
|
+
continue;
|
|
649
|
+
budget.allocateArrayLength(result.length + 1);
|
|
650
|
+
result.push(budget.allocateString(value.slice(copiedThrough, match.index)));
|
|
651
|
+
for (const capture of match.captures) {
|
|
652
|
+
if (result.length >= limit)
|
|
653
|
+
break;
|
|
654
|
+
budget.allocateArrayLength(result.length + 1);
|
|
655
|
+
result.push(capture === undefined ? undefined : budget.allocateString(capture));
|
|
656
|
+
}
|
|
657
|
+
copiedThrough = match.index + match.text.length;
|
|
468
658
|
}
|
|
469
|
-
|
|
659
|
+
if (result.length < limit && (value.length > 0 || !endedWithZeroWidthMatch)) {
|
|
660
|
+
budget.allocateArrayLength(result.length + 1);
|
|
661
|
+
result.push(budget.allocateString(value.slice(copiedThrough)));
|
|
662
|
+
}
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
finally {
|
|
666
|
+
compilation.dispose();
|
|
667
|
+
operation.release();
|
|
470
668
|
}
|
|
471
|
-
if (result.length < limit && (value.length > 0 || !endedWithZeroWidthMatch))
|
|
472
|
-
result.push(budget.allocateString(value.slice(copiedThrough)));
|
|
473
|
-
budget.allocateArrayLength(result.length);
|
|
474
|
-
return result;
|
|
475
669
|
}
|
|
476
|
-
|
|
477
|
-
throw new TypeError(SPLIT_STRING_MESSAGE);
|
|
478
|
-
const limit = asNumberOrUndefined(args[1]);
|
|
479
|
-
const result = splitString(value, args[0], limit).map((part) => budget.allocateString(part));
|
|
670
|
+
const result = splitString(value, separator, limit).map((part) => budget.allocateString(part));
|
|
480
671
|
budget.allocateArrayLength(result.length);
|
|
481
672
|
return result;
|
|
482
673
|
}
|
|
@@ -496,20 +687,125 @@ async function callStringPattern(value, methodName, pattern, budget, parent, con
|
|
|
496
687
|
operation.release();
|
|
497
688
|
}
|
|
498
689
|
}
|
|
499
|
-
async function
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
690
|
+
export async function regexMatch(input, regex, budget, context) {
|
|
691
|
+
if (regex === null || typeof regex !== "object")
|
|
692
|
+
throw new TypeError("RegExp match requires an object receiver.");
|
|
693
|
+
let value;
|
|
694
|
+
const matches = [];
|
|
695
|
+
let result;
|
|
696
|
+
let matched;
|
|
697
|
+
let cursor;
|
|
698
|
+
let flagsValue;
|
|
699
|
+
let flags;
|
|
700
|
+
const release = retainValues(budget, () => [input, value, regex, matches, result, matched, cursor, flagsValue, flags]);
|
|
701
|
+
const read = (target, key) => {
|
|
702
|
+
if (context?.getProperty !== undefined)
|
|
703
|
+
return context.getProperty(target, key);
|
|
704
|
+
const descriptor = getSandboxPropertyDescriptor(target, key, budget);
|
|
705
|
+
return descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, target, context);
|
|
706
|
+
};
|
|
505
707
|
try {
|
|
506
|
-
|
|
507
|
-
|
|
708
|
+
value = await sandboxString(input, budget, context);
|
|
709
|
+
input = undefined;
|
|
710
|
+
if (isSandboxRegex(regex) && !hasRegexPropertyOverride(regex, ["exec", "flags", ...Object.keys(regexFlagProperties)], budget)) {
|
|
711
|
+
release();
|
|
712
|
+
return await callStringMethodBody(value, "match", [regex], budget, context?.compilation, context);
|
|
713
|
+
}
|
|
714
|
+
flagsValue = await read(regex, "flags");
|
|
715
|
+
flags = await sandboxString(flagsValue, budget, context);
|
|
716
|
+
flagsValue = undefined;
|
|
717
|
+
if (!flags.includes("g"))
|
|
718
|
+
return await regexExec(regex, value, budget, context);
|
|
719
|
+
const fullUnicode = flags.includes("u") || flags.includes("v");
|
|
720
|
+
flags = undefined;
|
|
721
|
+
await setSandboxProperty(regex, "lastIndex", 0, budget, true, context);
|
|
722
|
+
while (true) {
|
|
723
|
+
budget.visitNode();
|
|
724
|
+
result = await regexExec(regex, value, budget, context);
|
|
725
|
+
if (result === null)
|
|
726
|
+
return matches.length === 0 ? null : matches;
|
|
727
|
+
const descriptor = context?.getProperty === undefined
|
|
728
|
+
? getSandboxPropertyDescriptor(result, "0", budget) : undefined;
|
|
729
|
+
matched = await (context?.getProperty !== undefined ? context.getProperty(result, "0")
|
|
730
|
+
: descriptor === undefined ? undefined : readPropertyDescriptor(descriptor, result, context));
|
|
731
|
+
const text = await sandboxString(matched, budget, context);
|
|
732
|
+
matched = undefined;
|
|
733
|
+
budget.allocateArrayLength(matches.length + 1);
|
|
734
|
+
matches.push(text);
|
|
735
|
+
if (text.length === 0) {
|
|
736
|
+
cursor = await read(regex, "lastIndex");
|
|
737
|
+
const index = normalizeLastIndex(await sandboxNumber(cursor, budget, context));
|
|
738
|
+
const point = fullUnicode ? value.codePointAt(index) : undefined;
|
|
739
|
+
await setSandboxProperty(regex, "lastIndex", index + (point !== undefined && point > 0xffff ? 2 : 1), budget, true, context);
|
|
740
|
+
cursor = undefined;
|
|
741
|
+
}
|
|
742
|
+
result = undefined;
|
|
743
|
+
}
|
|
508
744
|
}
|
|
509
745
|
finally {
|
|
746
|
+
release();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function callStringRegexCursor(value, methodName, regex, budget, parent, context) {
|
|
750
|
+
const operation = budget.acquireCompileOwner(false, parent?.owner);
|
|
751
|
+
const compilation = new CompileScope(operation.owner);
|
|
752
|
+
let cursor;
|
|
753
|
+
let flagsValue;
|
|
754
|
+
let flags;
|
|
755
|
+
let matcher;
|
|
756
|
+
const retained = {};
|
|
757
|
+
budget.setRetainedValues(retained, () => [value, regex, cursor, flagsValue, flags, matcher]);
|
|
758
|
+
const release = () => {
|
|
510
759
|
budget.setRetainedValues(retained, undefined);
|
|
511
760
|
compilation.dispose();
|
|
512
761
|
operation.release();
|
|
762
|
+
};
|
|
763
|
+
const readCursor = () => {
|
|
764
|
+
cursor = regex.lastIndex;
|
|
765
|
+
const finish = (lastIndex) => {
|
|
766
|
+
if (matcher !== undefined) {
|
|
767
|
+
matcher.lastIndex = normalizeLastIndex(lastIndex);
|
|
768
|
+
return restoreSandboxRegExpIterator({ matcher, input: value, exhausted: false });
|
|
769
|
+
}
|
|
770
|
+
return callMatchLikeMethod(value, methodName, [regex], compilation, lastIndex);
|
|
771
|
+
};
|
|
772
|
+
const lastIndex = sandboxNumber(cursor, budget, context);
|
|
773
|
+
return lastIndex instanceof Promise ? lastIndex.then(finish) : finish(lastIndex);
|
|
774
|
+
};
|
|
775
|
+
const clone = (text) => {
|
|
776
|
+
flags = text;
|
|
777
|
+
flagsValue = undefined;
|
|
778
|
+
matcher = createSandboxRegex(regex.source, flags, 0, compilation);
|
|
779
|
+
flags = undefined;
|
|
780
|
+
return readCursor();
|
|
781
|
+
};
|
|
782
|
+
const convertFlags = (value) => {
|
|
783
|
+
flagsValue = value;
|
|
784
|
+
const text = sandboxString(value, budget, context);
|
|
785
|
+
return text instanceof Promise ? text.then(clone) : clone(text);
|
|
786
|
+
};
|
|
787
|
+
try {
|
|
788
|
+
let result;
|
|
789
|
+
if (methodName === "matchAll") {
|
|
790
|
+
const descriptor = context?.getProperty === undefined
|
|
791
|
+
? getSandboxPropertyDescriptor(regex, "flags", budget) : undefined;
|
|
792
|
+
const value = context?.getProperty !== undefined
|
|
793
|
+
? context.getProperty(regex, "flags")
|
|
794
|
+
: descriptor !== undefined
|
|
795
|
+
? readPropertyDescriptor(descriptor, regex, context)
|
|
796
|
+
: getRegexMember(regex, "flags", budget, context);
|
|
797
|
+
result = value instanceof Promise ? value.then(convertFlags) : convertFlags(value);
|
|
798
|
+
}
|
|
799
|
+
else
|
|
800
|
+
result = readCursor();
|
|
801
|
+
if (result instanceof Promise)
|
|
802
|
+
return result.finally(release);
|
|
803
|
+
release();
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
release();
|
|
808
|
+
throw error;
|
|
513
809
|
}
|
|
514
810
|
}
|
|
515
811
|
function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
|
|
@@ -532,12 +828,10 @@ function callMatchLikeMethod(value, methodName, args, compilation, lastIndex) {
|
|
|
532
828
|
const matcher = methodName === "matchAll"
|
|
533
829
|
? createSandboxRegex(regex.source, regex.flags, normalizeLastIndex(lastIndex ?? Number(regex.lastIndex)), compilation)
|
|
534
830
|
: regex;
|
|
831
|
+
if (methodName === "matchAll")
|
|
832
|
+
return restoreSandboxRegExpIterator({ matcher, input: value, exhausted: false });
|
|
535
833
|
const matches = collectRegexMatches(matcher, value, matcher.flags.includes("g"), compilation.owner?.budget, Number(matcher.lastIndex));
|
|
536
|
-
|
|
537
|
-
return null;
|
|
538
|
-
return methodName === "match"
|
|
539
|
-
? matches.map((match) => match.text)
|
|
540
|
-
: matches.map((match) => toMatchArray(match, value));
|
|
834
|
+
return matches.length === 0 ? null : matches.map((match) => match.text);
|
|
541
835
|
}
|
|
542
836
|
function collectRegexMatches(regex, value, all, budget, lastIndex = 0) {
|
|
543
837
|
const matches = [];
|