poe-code 14.0.28 → 14.0.30
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/cli/program.js +3 -3
- package/dist/cli/program.js.map +1 -1
- package/dist/index.js +10 -547
- package/dist/index.js.map +4 -4
- package/dist/metafile.json +1 -1
- package/dist/package-metadata.d.ts +1 -0
- package/dist/package-metadata.js +5 -0
- package/dist/package-metadata.js.map +1 -0
- package/package.json +1 -1
- package/packages/safe-bash/dist/browser.js +584 -200
- package/packages/safe-bash/dist/browser.js.map +4 -4
- package/packages/safe-bash/dist/commands/filesystem.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/filesystem.js +49 -28
- package/packages/safe-bash/dist/commands/filesystem.js.map +1 -1
- package/packages/safe-bash/dist/commands/regex-execution/bounded-provider.d.ts +1 -1
- package/packages/safe-bash/dist/commands/regex-execution/bounded-provider.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/regex-execution/bounded-provider.js +135 -3
- package/packages/safe-bash/dist/commands/regex-execution/bounded-provider.js.map +1 -1
- package/packages/safe-bash/dist/commands/structured/limits.js +3 -1
- package/packages/safe-bash/dist/commands/structured/limits.js.map +1 -1
- package/packages/safe-bash/dist/commands/structured/split.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/structured/split.js +7 -2
- package/packages/safe-bash/dist/commands/structured/split.js.map +1 -1
- package/packages/safe-bash/dist/commands/structured/values.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/structured/values.js +4 -2
- package/packages/safe-bash/dist/commands/structured/values.js.map +1 -1
- package/packages/safe-bash/dist/commands/text.d.ts.map +1 -1
- package/packages/safe-bash/dist/commands/text.js +194 -52
- package/packages/safe-bash/dist/commands/text.js.map +1 -1
- package/packages/safe-bash/dist/shell/arithmetic-parameters.d.ts +2 -0
- package/packages/safe-bash/dist/shell/arithmetic-parameters.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/arithmetic-parameters.js +1 -1
- package/packages/safe-bash/dist/shell/arithmetic-parameters.js.map +1 -1
- package/packages/safe-bash/dist/shell/arithmetic.d.ts +4 -3
- package/packages/safe-bash/dist/shell/arithmetic.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/arithmetic.js +19 -7
- package/packages/safe-bash/dist/shell/arithmetic.js.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/syntax.d.ts +6 -5
- package/packages/safe-bash/dist/shell/arrays/syntax.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/arrays/syntax.js +22 -12
- package/packages/safe-bash/dist/shell/arrays/syntax.js.map +1 -1
- package/packages/safe-bash/dist/shell/index.d.ts +1 -1
- package/packages/safe-bash/dist/shell/index.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/parse-budget.d.ts +10 -0
- package/packages/safe-bash/dist/shell/parse-budget.d.ts.map +1 -0
- package/packages/safe-bash/dist/shell/parse-budget.js +29 -0
- package/packages/safe-bash/dist/shell/parse-budget.js.map +1 -0
- package/packages/safe-bash/dist/shell/parser.d.ts +6 -4
- package/packages/safe-bash/dist/shell/parser.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/parser.js +80 -29
- package/packages/safe-bash/dist/shell/parser.js.map +1 -1
- package/packages/safe-bash/dist/shell/runtime.d.ts +3 -0
- package/packages/safe-bash/dist/shell/runtime.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/runtime.js +136 -78
- package/packages/safe-bash/dist/shell/runtime.js.map +1 -1
- package/packages/safe-bash/dist/shell/shell.js +2 -2
- package/packages/safe-bash/dist/shell/shell.js.map +1 -1
- package/packages/safe-bash/dist/shell/types.d.ts +4 -0
- package/packages/safe-bash/dist/shell/types.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/types.js.map +1 -1
- package/packages/safe-bash/dist/shell/worker-limits.d.ts.map +1 -1
- package/packages/safe-bash/dist/shell/worker-limits.js +1 -0
- package/packages/safe-bash/dist/shell/worker-limits.js.map +1 -1
- package/packages/safe-fs/dist/fs/webdav/xml.d.ts +3 -0
- package/packages/safe-js/dist/browser/chunks/{chunk-FX3SOWQN.js → chunk-BB4KTS26.js} +19 -9
- package/packages/safe-js/dist/browser/chunks/{chunk-FX3SOWQN.js.map → chunk-BB4KTS26.js.map} +3 -3
- package/packages/safe-js/dist/browser/safe-fs-core.js +1 -1
- package/packages/safe-js/dist/browser/safe-fs.js +1 -1
- package/packages/safe-js/dist/chunks/{chunk-EGTPUJGO.js → chunk-FOWKJRPH.js} +3 -3
- package/packages/safe-js/dist/chunks/{chunk-X2BJ2VDZ.js → chunk-LRWPYBOU.js} +19 -9
- package/packages/safe-js/dist/chunks/chunk-LRWPYBOU.js.map +7 -0
- package/packages/safe-js/dist/chunks/{chunk-47R6V3XW.js → chunk-OXQ2XP74.js} +2 -2
- package/packages/safe-js/dist/cli.js +3 -3
- package/packages/safe-js/dist/index.js +3 -3
- package/packages/safe-js/dist/safe-fs-core.js +1 -1
- package/packages/safe-js/dist/safe-fs-node.js +2 -2
- package/packages/safe-js/dist/safe-fs.js +2 -2
- package/packages/tiny-http-mcp-server/dist/http-transport.js +1 -1
- package/packages/tiny-http-mcp-server/dist/parse-body.js +12 -5
- package/packages/safe-js/dist/chunks/chunk-X2BJ2VDZ.js.map +0 -7
- /package/packages/safe-js/dist/chunks/{chunk-EGTPUJGO.js.map → chunk-FOWKJRPH.js.map} +0 -0
- /package/packages/safe-js/dist/chunks/{chunk-47R6V3XW.js.map → chunk-OXQ2XP74.js.map} +0 -0
|
@@ -3432,6 +3432,7 @@ function createDirectoryReader(maxEntries = 1e4) {
|
|
|
3432
3432
|
}
|
|
3433
3433
|
|
|
3434
3434
|
// packages/safe-bash/src/commands/filesystem.ts
|
|
3435
|
+
var MAX_RECURSIVE_DIRECTORY_DEPTH = 1024;
|
|
3435
3436
|
async function preflightOperands(context, operands, check) {
|
|
3436
3437
|
for (const operand of operands) {
|
|
3437
3438
|
try {
|
|
@@ -3514,11 +3515,19 @@ async function copy(context, source, target, flags, readDirectory, top = true, a
|
|
|
3514
3515
|
if (isPathWithin(physicalSource, physicalTarget)) throw new FsError("EINVAL", { path: target, message: "cannot copy a directory into itself" });
|
|
3515
3516
|
if (ancestors.has(physicalSource)) throw new FsError("ELOOP", { path: source });
|
|
3516
3517
|
if (targetStat && targetStat.type !== "directory") throw new FsError("ENOTDIR", { path: target });
|
|
3518
|
+
context.signal.throwIfAborted();
|
|
3519
|
+
if (ancestors.size > MAX_RECURSIVE_DIRECTORY_DEPTH) {
|
|
3520
|
+
throw new FsError("ELOOP", { path: source, message: `cp directory depth limit exceeded (${MAX_RECURSIVE_DIRECTORY_DEPTH})` });
|
|
3521
|
+
}
|
|
3517
3522
|
await admitFilesystemModes(context, "cp", ["recursive"], [target]);
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3523
|
+
ancestors.add(physicalSource);
|
|
3524
|
+
try {
|
|
3525
|
+
if (!targetStat && !preflight) await context.fs.mkdir(target, { mode: sourceStat.mode & 511, signal: context.signal });
|
|
3526
|
+
for (const entry of await readDirectory(context, source, true)) {
|
|
3527
|
+
await copy(context, joinPath(source, entry.name), joinPath(target, entry.name), flags, readDirectory, false, ancestors, preflight);
|
|
3528
|
+
}
|
|
3529
|
+
} finally {
|
|
3530
|
+
ancestors.delete(physicalSource);
|
|
3522
3531
|
}
|
|
3523
3532
|
} else if (preserveLink) {
|
|
3524
3533
|
await admitFilesystemModes(context, "cp", ["symlink"], [target]);
|
|
@@ -3856,25 +3865,33 @@ function filesystemCommands(maxDirectoryEntries) {
|
|
|
3856
3865
|
await admitFilesystemModes(context, "ls", ["directory"], [path]);
|
|
3857
3866
|
const physical = await context.fs.realpath(path, { signal: context.signal });
|
|
3858
3867
|
if (ancestors.has(physical)) throw new FsError("ELOOP", { path });
|
|
3859
|
-
|
|
3860
|
-
if (
|
|
3861
|
-
|
|
3862
|
-
`);
|
|
3863
|
-
headerWritten = true;
|
|
3864
|
-
}
|
|
3865
|
-
const entries = await readDirectory(context, path, true);
|
|
3866
|
-
const names = entries.map((entry) => entry.name).filter((name2) => parsed.flags.has("a") || parsed.flags.has("A") || !name2.startsWith("."));
|
|
3867
|
-
if (parsed.flags.has("a")) for (const name2 of [".", ".."]) {
|
|
3868
|
-
const index = names.findIndex((entry) => entry > name2);
|
|
3869
|
-
names.splice(index < 0 ? names.length : index, 0, name2);
|
|
3868
|
+
context.signal.throwIfAborted();
|
|
3869
|
+
if (ancestors.size > MAX_RECURSIVE_DIRECTORY_DEPTH) {
|
|
3870
|
+
throw new FsError("ELOOP", { path, message: `ls directory depth limit exceeded (${MAX_RECURSIVE_DIRECTORY_DEPTH})` });
|
|
3870
3871
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3872
|
+
ancestors.add(physical);
|
|
3873
|
+
try {
|
|
3874
|
+
if (header) {
|
|
3875
|
+
await output(context, `${headerWritten ? "\n" : ""}${display}:
|
|
3876
|
+
`);
|
|
3877
|
+
headerWritten = true;
|
|
3878
|
+
}
|
|
3879
|
+
const entries = await readDirectory(context, path, true);
|
|
3880
|
+
const names = entries.map((entry) => entry.name).filter((name2) => parsed.flags.has("a") || parsed.flags.has("A") || !name2.startsWith("."));
|
|
3881
|
+
if (parsed.flags.has("a")) for (const name2 of [".", ".."]) {
|
|
3882
|
+
const index = names.findIndex((entry) => entry > name2);
|
|
3883
|
+
names.splice(index < 0 ? names.length : index, 0, name2);
|
|
3884
|
+
}
|
|
3885
|
+
if (parsed.flags.has("r")) names.reverse();
|
|
3886
|
+
for (const name2 of names) await render(joinPath(path, name2), name2);
|
|
3887
|
+
if (parsed.flags.has("R")) for (const name2 of names) {
|
|
3888
|
+
if (name2 === "." || name2 === "..") continue;
|
|
3889
|
+
const child = joinPath(path, name2);
|
|
3890
|
+
const childStat = await context.fs[parsed.flags.has("L") ? "stat" : "lstat"](child, { signal: context.signal });
|
|
3891
|
+
if (childStat.type === "directory") await list(child, `${display.replace(/\/$/u, "")}/${name2}`, true, ancestors);
|
|
3892
|
+
}
|
|
3893
|
+
} finally {
|
|
3894
|
+
ancestors.delete(physical);
|
|
3878
3895
|
}
|
|
3879
3896
|
};
|
|
3880
3897
|
return eachOperand(context, operands, (operand) => list(pathOf(context, operand), operand, operands.length > 1 || parsed.flags.has("R")));
|
|
@@ -4656,6 +4673,103 @@ async function sortRecords(records, compare, work) {
|
|
|
4656
4673
|
}
|
|
4657
4674
|
return source;
|
|
4658
4675
|
}
|
|
4676
|
+
async function cutRanges(list, work) {
|
|
4677
|
+
const ranges = [];
|
|
4678
|
+
let tokenStart = 0;
|
|
4679
|
+
let dash = -1;
|
|
4680
|
+
let start = 0;
|
|
4681
|
+
let end = 0;
|
|
4682
|
+
let invalid = false;
|
|
4683
|
+
for (let index = 0; index <= list.length; index++) {
|
|
4684
|
+
const checkpoint = work.charge();
|
|
4685
|
+
if (checkpoint) await checkpoint;
|
|
4686
|
+
const character = list[index];
|
|
4687
|
+
if (character === "," || character === " " || index === list.length) {
|
|
4688
|
+
if (index === tokenStart) {
|
|
4689
|
+
if (index === 0 || index === list.length) throw new UsageError("invalid range ''");
|
|
4690
|
+
tokenStart = index + 1;
|
|
4691
|
+
continue;
|
|
4692
|
+
}
|
|
4693
|
+
if (invalid || dash === tokenStart && dash === index - 1) throw new UsageError(`invalid range '${list.slice(tokenStart, index)}'`);
|
|
4694
|
+
if (dash === tokenStart) start = 1;
|
|
4695
|
+
if (dash < 0) end = start;
|
|
4696
|
+
const openEnd = dash >= 0 && dash === index - 1;
|
|
4697
|
+
if (openEnd) end = Infinity;
|
|
4698
|
+
if (!Number.isSafeInteger(start) || start < 1) throw new UsageError(`invalid number '${list.slice(tokenStart, dash < 0 ? index : dash)}'`);
|
|
4699
|
+
if (!openEnd && (!Number.isSafeInteger(end) || end < 1)) throw new UsageError(`invalid number '${list.slice(dash < 0 ? tokenStart : dash + 1, index)}'`);
|
|
4700
|
+
if (end < start) throw new UsageError(`decreasing range '${list.slice(tokenStart, index)}'`);
|
|
4701
|
+
ranges.push({ start, end });
|
|
4702
|
+
tokenStart = index + 1;
|
|
4703
|
+
dash = -1;
|
|
4704
|
+
start = 0;
|
|
4705
|
+
end = 0;
|
|
4706
|
+
invalid = false;
|
|
4707
|
+
} else if (character === "-" && dash < 0) {
|
|
4708
|
+
dash = index;
|
|
4709
|
+
} else {
|
|
4710
|
+
const digit2 = list.charCodeAt(index) - 48;
|
|
4711
|
+
if (digit2 < 0 || digit2 > 9) invalid = true;
|
|
4712
|
+
else if (dash < 0) start = start * 10 + digit2;
|
|
4713
|
+
else end = end * 10 + digit2;
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
const ordered = await sortRecords(ranges, async (left, right) => left.start - right.start, work);
|
|
4717
|
+
const normalized = [];
|
|
4718
|
+
for (const range of ordered) {
|
|
4719
|
+
const checkpoint = work.charge();
|
|
4720
|
+
if (checkpoint) await checkpoint;
|
|
4721
|
+
const previous = normalized.at(-1);
|
|
4722
|
+
if (previous && range.start <= previous.end + 1) previous.end = Math.max(previous.end, range.end);
|
|
4723
|
+
else normalized.push(range);
|
|
4724
|
+
}
|
|
4725
|
+
return normalized;
|
|
4726
|
+
}
|
|
4727
|
+
var CutOutput = class {
|
|
4728
|
+
constructor(context, work) {
|
|
4729
|
+
this.context = context;
|
|
4730
|
+
this.work = work;
|
|
4731
|
+
}
|
|
4732
|
+
context;
|
|
4733
|
+
work;
|
|
4734
|
+
#buffer = new Uint8Array(64 * 1024);
|
|
4735
|
+
#used = 0;
|
|
4736
|
+
async write(bytes2) {
|
|
4737
|
+
for (let offset = 0; offset < bytes2.length; ) {
|
|
4738
|
+
const length = Math.min(4096, bytes2.length - offset, this.#buffer.length - this.#used);
|
|
4739
|
+
const checkpoint = this.work.charge(length);
|
|
4740
|
+
if (checkpoint) await checkpoint;
|
|
4741
|
+
this.#buffer.set(bytes2.subarray(offset, offset + length), this.#used);
|
|
4742
|
+
this.#used += length;
|
|
4743
|
+
offset += length;
|
|
4744
|
+
if (this.#used === this.#buffer.length) await this.flush();
|
|
4745
|
+
}
|
|
4746
|
+
}
|
|
4747
|
+
async text(text) {
|
|
4748
|
+
for (let offset = 0; offset < text.length; ) {
|
|
4749
|
+
let end = Math.min(offset + 4096, text.length);
|
|
4750
|
+
const last = text.charCodeAt(end - 1);
|
|
4751
|
+
if (end < text.length && last >= 55296 && last <= 56319) end--;
|
|
4752
|
+
await this.write(encoder2.encode(text.slice(offset, end)));
|
|
4753
|
+
offset = end;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
async flush() {
|
|
4757
|
+
if (!this.#used) return;
|
|
4758
|
+
const bytes2 = this.#buffer.slice(0, this.#used);
|
|
4759
|
+
this.#used = 0;
|
|
4760
|
+
await output(this.context, bytes2);
|
|
4761
|
+
}
|
|
4762
|
+
};
|
|
4763
|
+
async function cutFieldBoundary(record4, separator, start, work) {
|
|
4764
|
+
for (let offset = start; offset < record4.length; offset += 4096) {
|
|
4765
|
+
const window = record4.subarray(offset, Math.min(record4.length, offset + 4096 + separator.length - 1));
|
|
4766
|
+
const found = window.indexOf(separator);
|
|
4767
|
+
const checkpoint = work.charge(found < 0 ? Math.min(4096, window.length) : found + separator.length);
|
|
4768
|
+
if (checkpoint) await checkpoint;
|
|
4769
|
+
if (found >= 0) return offset + found;
|
|
4770
|
+
}
|
|
4771
|
+
return -1;
|
|
4772
|
+
}
|
|
4659
4773
|
async function compareSortBytes(left, right, work) {
|
|
4660
4774
|
const length = Math.min(left.length, right.length);
|
|
4661
4775
|
for (let offset = 0; offset < length; offset += 1024) {
|
|
@@ -5015,85 +5129,104 @@ function textCommands() {
|
|
|
5015
5129
|
if (modes.length !== 1) throw new UsageError("exactly one byte, character, or field list is required");
|
|
5016
5130
|
const mode = modes[0];
|
|
5017
5131
|
if (mode !== "f" && (parsed.flags.has("d") || parsed.flags.has("s"))) throw new UsageError("delimiter options require field mode");
|
|
5018
|
-
const
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
const start = match[3] === void 0 ? integer(match[1], 1) : 1;
|
|
5022
|
-
const end = match[3] !== void 0 ? integer(match[3], 1) : match[2] === void 0 ? start : match[2] === "" ? Infinity : integer(match[2], 1);
|
|
5023
|
-
if (end < start) throw new UsageError(`decreasing range '${part}'`);
|
|
5024
|
-
return { start, end };
|
|
5025
|
-
});
|
|
5026
|
-
const selected = (position) => ranges.some((range) => position >= range.start && position <= range.end) !== parsed.flags.has("C");
|
|
5132
|
+
const work = new SortWork(context.signal);
|
|
5133
|
+
const ranges = await cutRanges(value(parsed, mode), work);
|
|
5134
|
+
const complement = parsed.flags.has("C");
|
|
5027
5135
|
const delimiter = value(parsed, "d") ?? " ";
|
|
5028
|
-
if (
|
|
5136
|
+
if (delimiter.length !== (delimiter.codePointAt(0) > 65535 ? 2 : 1)) throw new UsageError("delimiter must be a single character");
|
|
5029
5137
|
const outputDelimiter = value(parsed, "o");
|
|
5030
5138
|
const recordDelimiter = parsed.flags.has("z") ? 0 : 10;
|
|
5139
|
+
const separator = import_buffer.Buffer.from(delimiter);
|
|
5140
|
+
const writer = new CutOutput(context, work);
|
|
5031
5141
|
let exitCode = 0;
|
|
5032
5142
|
for (const name2 of parsed.operands.length ? parsed.operands : ["-"]) {
|
|
5033
5143
|
try {
|
|
5034
5144
|
for await (const line of lines(input(context, name2), recordDelimiter)) {
|
|
5035
5145
|
context.signal.throwIfAborted();
|
|
5036
|
-
let
|
|
5146
|
+
let cursor = 0;
|
|
5147
|
+
const selected = (position) => {
|
|
5148
|
+
while (cursor < ranges.length && position > ranges[cursor].end) cursor++;
|
|
5149
|
+
return (cursor < ranges.length && position >= ranges[cursor].start) !== complement;
|
|
5150
|
+
};
|
|
5037
5151
|
if (mode === "f") {
|
|
5038
5152
|
const record4 = import_buffer.Buffer.from(line.bytes.buffer, line.bytes.byteOffset, line.bytes.byteLength);
|
|
5039
|
-
|
|
5040
|
-
let boundary = record4.indexOf(separator);
|
|
5153
|
+
let boundary = await cutFieldBoundary(record4, separator, 0, work);
|
|
5041
5154
|
if (boundary < 0) {
|
|
5042
5155
|
if (parsed.flags.has("s")) continue;
|
|
5043
|
-
|
|
5156
|
+
await writer.write(line.bytes);
|
|
5044
5157
|
} else {
|
|
5045
|
-
const pieces = [];
|
|
5046
|
-
const joiner = encoder2.encode(outputDelimiter ?? delimiter);
|
|
5047
5158
|
let field = 1;
|
|
5048
5159
|
let start = 0;
|
|
5049
5160
|
let emitted = false;
|
|
5050
5161
|
while (true) {
|
|
5162
|
+
const checkpoint = work.charge();
|
|
5163
|
+
if (checkpoint) await checkpoint;
|
|
5051
5164
|
if (selected(field++)) {
|
|
5052
|
-
if (emitted)
|
|
5053
|
-
|
|
5165
|
+
if (emitted) await writer.text(outputDelimiter ?? delimiter);
|
|
5166
|
+
await writer.write(record4.subarray(start, boundary < 0 ? record4.length : boundary));
|
|
5054
5167
|
emitted = true;
|
|
5055
5168
|
}
|
|
5056
5169
|
if (boundary < 0) break;
|
|
5057
5170
|
start = boundary + separator.length;
|
|
5058
|
-
boundary = record4
|
|
5171
|
+
boundary = await cutFieldBoundary(record4, separator, start, work);
|
|
5059
5172
|
}
|
|
5060
|
-
bytes2 = concatenate(pieces);
|
|
5061
5173
|
}
|
|
5062
5174
|
} else if (mode === "b") {
|
|
5063
|
-
const chunks = [];
|
|
5064
|
-
let start = -1;
|
|
5065
5175
|
let emitted = false;
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5176
|
+
let previousIncluded = false;
|
|
5177
|
+
for (let offset = 0; offset < line.bytes.length; offset += 4096) {
|
|
5178
|
+
const end = Math.min(line.bytes.length, offset + 4096);
|
|
5179
|
+
const checkpoint = work.charge(end - offset);
|
|
5180
|
+
if (checkpoint) await checkpoint;
|
|
5181
|
+
let start = -1;
|
|
5182
|
+
for (let index = offset; index < end; index++) {
|
|
5183
|
+
const included = selected(index + 1);
|
|
5184
|
+
if (included && start < 0) {
|
|
5185
|
+
if (!previousIncluded && emitted && outputDelimiter !== void 0) await writer.text(outputDelimiter);
|
|
5186
|
+
start = index;
|
|
5187
|
+
emitted = true;
|
|
5188
|
+
}
|
|
5189
|
+
if (!included && start >= 0) {
|
|
5190
|
+
await writer.write(line.bytes.subarray(start, index));
|
|
5191
|
+
start = -1;
|
|
5192
|
+
}
|
|
5193
|
+
previousIncluded = included;
|
|
5074
5194
|
}
|
|
5195
|
+
if (start >= 0) await writer.write(line.bytes.subarray(start, end));
|
|
5075
5196
|
}
|
|
5076
|
-
bytes2 = concatenate(chunks);
|
|
5077
5197
|
} else {
|
|
5078
|
-
const
|
|
5079
|
-
const pieces = [];
|
|
5198
|
+
const decoder3 = new TextDecoder();
|
|
5080
5199
|
let index = 0;
|
|
5081
|
-
let
|
|
5082
|
-
let
|
|
5083
|
-
for (
|
|
5084
|
-
const
|
|
5085
|
-
|
|
5086
|
-
if (
|
|
5087
|
-
|
|
5088
|
-
|
|
5200
|
+
let emitted = false;
|
|
5201
|
+
let previousIncluded = false;
|
|
5202
|
+
for (let offset = 0; offset < line.bytes.length; offset += 4096) {
|
|
5203
|
+
const end = Math.min(line.bytes.length, offset + 4096);
|
|
5204
|
+
const checkpoint = work.charge(end - offset);
|
|
5205
|
+
if (checkpoint) await checkpoint;
|
|
5206
|
+
const text = decoder3.decode(line.bytes.subarray(offset, end), { stream: end < line.bytes.length });
|
|
5207
|
+
let start = -1;
|
|
5208
|
+
let position = 0;
|
|
5209
|
+
for (const character of text) {
|
|
5210
|
+
const checkpoint2 = work.charge();
|
|
5211
|
+
if (checkpoint2) await checkpoint2;
|
|
5212
|
+
const included = selected(++index);
|
|
5213
|
+
if (included && start < 0) {
|
|
5214
|
+
if (!previousIncluded && emitted && outputDelimiter !== void 0) await writer.text(outputDelimiter);
|
|
5215
|
+
start = position;
|
|
5216
|
+
emitted = true;
|
|
5217
|
+
}
|
|
5218
|
+
if (!included && start >= 0) {
|
|
5219
|
+
await writer.text(text.slice(start, position));
|
|
5220
|
+
start = -1;
|
|
5221
|
+
}
|
|
5222
|
+
position += character.length;
|
|
5223
|
+
previousIncluded = included;
|
|
5089
5224
|
}
|
|
5090
|
-
|
|
5225
|
+
if (start >= 0) await writer.text(text.slice(start));
|
|
5091
5226
|
}
|
|
5092
|
-
if (start >= 0) pieces.push(text.slice(start));
|
|
5093
|
-
bytes2 = encoder2.encode(pieces.join(outputDelimiter ?? ""));
|
|
5094
5227
|
}
|
|
5095
|
-
await
|
|
5096
|
-
await
|
|
5228
|
+
await writer.write(Uint8Array.of(recordDelimiter));
|
|
5229
|
+
await writer.flush();
|
|
5097
5230
|
}
|
|
5098
5231
|
} catch (error) {
|
|
5099
5232
|
await diagnostic(context, error);
|
|
@@ -5152,6 +5285,33 @@ var ShellLimitError = class extends Error {
|
|
|
5152
5285
|
limit;
|
|
5153
5286
|
};
|
|
5154
5287
|
|
|
5288
|
+
// packages/safe-bash/src/shell/parse-budget.ts
|
|
5289
|
+
init_platform();
|
|
5290
|
+
var defaultMaxParseUnits = 262144;
|
|
5291
|
+
var ParseBudget = class {
|
|
5292
|
+
constructor(maximum = defaultMaxParseUnits, signal, onLimit) {
|
|
5293
|
+
this.signal = signal;
|
|
5294
|
+
this.onLimit = onLimit;
|
|
5295
|
+
if (!Number.isSafeInteger(maximum) || maximum < 0) throw new RangeError("maxParseUnits must be a nonnegative safe integer");
|
|
5296
|
+
this.#remaining = maximum;
|
|
5297
|
+
}
|
|
5298
|
+
signal;
|
|
5299
|
+
onLimit;
|
|
5300
|
+
#remaining;
|
|
5301
|
+
#failure;
|
|
5302
|
+
admit(units = 1) {
|
|
5303
|
+
this.signal?.throwIfAborted();
|
|
5304
|
+
if (this.#failure) throw this.#failure;
|
|
5305
|
+
if (!Number.isSafeInteger(units) || units < 0) throw new RangeError("Parse admission must be a nonnegative safe integer");
|
|
5306
|
+
if (units > this.#remaining) {
|
|
5307
|
+
const error = this.#failure = new ShellLimitError("maxParseUnits");
|
|
5308
|
+
this.onLimit?.(error);
|
|
5309
|
+
throw error;
|
|
5310
|
+
}
|
|
5311
|
+
this.#remaining -= units;
|
|
5312
|
+
}
|
|
5313
|
+
};
|
|
5314
|
+
|
|
5155
5315
|
// packages/safe-bash/src/shell/arithmetic.ts
|
|
5156
5316
|
init_platform();
|
|
5157
5317
|
var ArithmeticFailure = class extends Error {
|
|
@@ -5161,11 +5321,13 @@ var ArithmeticFailure = class extends Error {
|
|
|
5161
5321
|
}
|
|
5162
5322
|
offset;
|
|
5163
5323
|
};
|
|
5164
|
-
function prepareArithmetic(source) {
|
|
5324
|
+
function prepareArithmetic(source, budget = new ParseBudget()) {
|
|
5325
|
+
budget.admit();
|
|
5165
5326
|
try {
|
|
5166
|
-
return { source, tree: parseArithmetic(source) };
|
|
5327
|
+
return { source, tree: parseArithmetic(source, 0, budget) };
|
|
5167
5328
|
} catch (error) {
|
|
5168
5329
|
if (!(error instanceof ShellSyntaxError) || /nesting/u.test(error.reason)) throw error;
|
|
5330
|
+
budget.admit();
|
|
5169
5331
|
return { source, error };
|
|
5170
5332
|
}
|
|
5171
5333
|
}
|
|
@@ -5220,7 +5382,7 @@ function integer2(text) {
|
|
|
5220
5382
|
}
|
|
5221
5383
|
return value2;
|
|
5222
5384
|
}
|
|
5223
|
-
function parseArithmetic(source, offset = 0) {
|
|
5385
|
+
function parseArithmetic(source, offset = 0, budget = new ParseBudget()) {
|
|
5224
5386
|
const tokens2 = [];
|
|
5225
5387
|
let position = 0;
|
|
5226
5388
|
while (position < source.length) {
|
|
@@ -5230,6 +5392,7 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5230
5392
|
}
|
|
5231
5393
|
const value2 = /^(?:\d+#[\da-zA-Z@_]+|0[xX][\da-fA-F]+|\d+|[a-zA-Z_][a-zA-Z_0-9]*|<<=|>>=|\*\*|\+\+|--|&&|\|\||<<|>>|[+*/%&^|!<>=-]=|[()+*/%~!<>=&^|?:,\-])/u.exec(source.slice(position))?.[0];
|
|
5232
5394
|
if (!value2) throw new ShellSyntaxError("Unsupported arithmetic token", offset + position);
|
|
5395
|
+
budget.admit();
|
|
5233
5396
|
tokens2.push({ value: value2, offset: offset + position });
|
|
5234
5397
|
position += value2.length;
|
|
5235
5398
|
}
|
|
@@ -5246,6 +5409,7 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5246
5409
|
const token = current();
|
|
5247
5410
|
cursor++;
|
|
5248
5411
|
if (["+", "-", "!", "~", "++", "--"].includes(token)) {
|
|
5412
|
+
budget.admit();
|
|
5249
5413
|
const operand = expression(15);
|
|
5250
5414
|
if (["++", "--"].includes(token) && operand.kind !== "name") error("Arithmetic assignment requires a variable");
|
|
5251
5415
|
left = { kind: "unary", operator: token, operand, postfix: false };
|
|
@@ -5253,8 +5417,11 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5253
5417
|
left = expression();
|
|
5254
5418
|
if (current() !== ")") error("Unclosed arithmetic parenthesis");
|
|
5255
5419
|
cursor++;
|
|
5256
|
-
} else if (/^[a-zA-Z_]/u.test(token))
|
|
5257
|
-
|
|
5420
|
+
} else if (/^[a-zA-Z_]/u.test(token)) {
|
|
5421
|
+
budget.admit();
|
|
5422
|
+
left = { kind: "name", name: token };
|
|
5423
|
+
} else {
|
|
5424
|
+
budget.admit();
|
|
5258
5425
|
try {
|
|
5259
5426
|
left = { kind: "literal", value: BigInt.asIntN(64, integer2(token)) };
|
|
5260
5427
|
} catch {
|
|
@@ -5267,12 +5434,14 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5267
5434
|
if (operator === "++" || operator === "--") {
|
|
5268
5435
|
if (left.kind !== "name") error("Arithmetic assignment requires a variable");
|
|
5269
5436
|
cursor++;
|
|
5437
|
+
budget.admit();
|
|
5270
5438
|
left = { kind: "unary", operator, operand: left, postfix: true, start };
|
|
5271
5439
|
continue;
|
|
5272
5440
|
}
|
|
5273
5441
|
const priority = Object.hasOwn(precedence, operator) ? precedence[operator] : 0;
|
|
5274
5442
|
if (priority < minimum || priority === 0) break;
|
|
5275
5443
|
cursor++;
|
|
5444
|
+
budget.admit();
|
|
5276
5445
|
if (operator === "?") {
|
|
5277
5446
|
const yes = expression();
|
|
5278
5447
|
if (current() !== ":") error("Expected arithmetic colon");
|
|
@@ -5288,7 +5457,10 @@ function parseArithmetic(source, offset = 0) {
|
|
|
5288
5457
|
depth--;
|
|
5289
5458
|
return left;
|
|
5290
5459
|
};
|
|
5291
|
-
if (!tokens2.length)
|
|
5460
|
+
if (!tokens2.length) {
|
|
5461
|
+
budget.admit();
|
|
5462
|
+
return { kind: "literal", value: 0n };
|
|
5463
|
+
}
|
|
5292
5464
|
const tree = expression();
|
|
5293
5465
|
if (cursor < tokens2.length) error("Unexpected arithmetic token");
|
|
5294
5466
|
return tree;
|
|
@@ -5305,7 +5477,7 @@ function arithmeticEnd(source, start) {
|
|
|
5305
5477
|
}
|
|
5306
5478
|
throw new ShellSyntaxError("Unterminated arithmetic expression", start);
|
|
5307
5479
|
}
|
|
5308
|
-
function evaluateArithmetic(program, variables) {
|
|
5480
|
+
function evaluateArithmetic(program, variables, budget = new ParseBudget()) {
|
|
5309
5481
|
const visiting = /* @__PURE__ */ new Set();
|
|
5310
5482
|
let steps = 0;
|
|
5311
5483
|
const binary = (operator, left, right, offset) => {
|
|
@@ -5373,7 +5545,7 @@ function evaluateArithmetic(program, variables) {
|
|
|
5373
5545
|
else if (node.kind === "name") {
|
|
5374
5546
|
if (visiting.has(node.name) || visiting.size >= 64) throw new Error("Arithmetic variable recursion");
|
|
5375
5547
|
visiting.add(node.name);
|
|
5376
|
-
pending.push({ kind: "variable", name: node.name }, { kind: "evaluate", node: parseArithmetic(variables[node.name] ?? "0") });
|
|
5548
|
+
pending.push({ kind: "variable", name: node.name }, { kind: "evaluate", node: parseArithmetic(variables[node.name] ?? "0", 0, budget) });
|
|
5377
5549
|
} else if (node.kind === "conditional") {
|
|
5378
5550
|
pending.push({ kind: "conditional", node }, { kind: "evaluate", node: node.condition });
|
|
5379
5551
|
} else if (node.kind === "unary") {
|
|
@@ -5439,21 +5611,23 @@ function setQuoteMarker(part, synthetic) {
|
|
|
5439
5611
|
function isQuoteMarker(part) {
|
|
5440
5612
|
return quoteMarkers.has(part);
|
|
5441
5613
|
}
|
|
5442
|
-
function literalIndex(source, offset) {
|
|
5614
|
+
function literalIndex(source, offset, budget = new ParseBudget()) {
|
|
5443
5615
|
let decimal = source;
|
|
5444
5616
|
if (source[0] === "'" || source[0] === '"') {
|
|
5445
5617
|
if (source.length < 2 || source.at(-1) !== source[0]) throw new ShellSyntaxError("Unsupported indexed-array subscript", offset);
|
|
5446
5618
|
decimal = source.slice(1, -1);
|
|
5447
5619
|
}
|
|
5448
5620
|
if (!/^(?:0|[1-9][0-9]*)$/u.test(decimal)) throw new ShellSyntaxError("Unsupported indexed-array subscript", offset);
|
|
5621
|
+
budget.admit();
|
|
5449
5622
|
return { decimal };
|
|
5450
5623
|
}
|
|
5451
5624
|
function numericIndex(index) {
|
|
5452
5625
|
if (index.decimal.length > 10 || index.decimal.length === 10 && index.decimal > "2147483647") return void 0;
|
|
5453
5626
|
return Number(index.decimal);
|
|
5454
5627
|
}
|
|
5455
|
-
function arraySelector(source, offset) {
|
|
5456
|
-
|
|
5628
|
+
function arraySelector(source, offset, budget = new ParseBudget()) {
|
|
5629
|
+
budget.admit();
|
|
5630
|
+
return source === "@" || source === "*" ? { kind: "members", separator: source } : { kind: "element", index: literalIndex(source, offset, budget) };
|
|
5457
5631
|
}
|
|
5458
5632
|
function setArraySelector(part, selector) {
|
|
5459
5633
|
selectors.set(part, selector);
|
|
@@ -5473,7 +5647,8 @@ function setArrayAssignment(word, assignment) {
|
|
|
5473
5647
|
function getArrayAssignment(word) {
|
|
5474
5648
|
return assignments.get(word);
|
|
5475
5649
|
}
|
|
5476
|
-
function removePrefix(word, length) {
|
|
5650
|
+
function removePrefix(word, length, budget) {
|
|
5651
|
+
budget.admit();
|
|
5477
5652
|
const parts = [];
|
|
5478
5653
|
for (const part of word.parts) {
|
|
5479
5654
|
if (length === 0) parts.push(part);
|
|
@@ -5481,6 +5656,7 @@ function removePrefix(word, length) {
|
|
|
5481
5656
|
if (part.kind !== "text") throw new ShellSyntaxError("Unsupported indexed-array subscript", word.offset);
|
|
5482
5657
|
if (part.value.length <= length) length -= part.value.length;
|
|
5483
5658
|
else {
|
|
5659
|
+
budget.admit();
|
|
5484
5660
|
parts.push({ ...part, value: part.value.slice(length) });
|
|
5485
5661
|
length = 0;
|
|
5486
5662
|
}
|
|
@@ -5489,7 +5665,7 @@ function removePrefix(word, length) {
|
|
|
5489
5665
|
if (length !== 0) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
|
|
5490
5666
|
return { offset: word.offset, parts };
|
|
5491
5667
|
}
|
|
5492
|
-
function elementAssignment(word) {
|
|
5668
|
+
function elementAssignment(word, budget = new ParseBudget()) {
|
|
5493
5669
|
const source = word.spelling;
|
|
5494
5670
|
const first = word.parts[0];
|
|
5495
5671
|
if (source === void 0 || first?.kind !== "text" || first.quoted) return void 0;
|
|
@@ -5500,19 +5676,23 @@ function elementAssignment(word) {
|
|
|
5500
5676
|
if (source.includes("=")) throw new ShellSyntaxError("Invalid indexed-array assignment", word.offset);
|
|
5501
5677
|
return void 0;
|
|
5502
5678
|
}
|
|
5503
|
-
|
|
5679
|
+
budget.admit();
|
|
5680
|
+
const index = literalIndex(source.slice(name2.length + 1, end), word.offset + name2.length + 1, budget);
|
|
5504
5681
|
const append = source[end + 1] === "+";
|
|
5505
|
-
const value2 = removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3));
|
|
5682
|
+
const value2 = removePrefix(word, name2.length + index.decimal.length + (append ? 4 : 3), budget);
|
|
5506
5683
|
return { kind: "element", name: name2, index, append, value: value2 };
|
|
5507
5684
|
}
|
|
5508
|
-
function compoundHead(word) {
|
|
5685
|
+
function compoundHead(word, budget = new ParseBudget()) {
|
|
5509
5686
|
if (word.parts.length !== 1) return void 0;
|
|
5510
5687
|
const first = word.parts[0];
|
|
5511
5688
|
if (first?.kind !== "text" || first.quoted) return void 0;
|
|
5512
5689
|
const match = /^([a-zA-Z_][a-zA-Z_0-9]*)(\+?)=$/u.exec(first.value);
|
|
5513
|
-
|
|
5690
|
+
if (!match) return void 0;
|
|
5691
|
+
budget.admit();
|
|
5692
|
+
return { name: match[1], append: match[2] === "+" };
|
|
5514
5693
|
}
|
|
5515
|
-
function compoundEntry(word) {
|
|
5694
|
+
function compoundEntry(word, budget = new ParseBudget()) {
|
|
5695
|
+
budget.admit();
|
|
5516
5696
|
const source = word.spelling;
|
|
5517
5697
|
const first = word.parts[0];
|
|
5518
5698
|
if (source === void 0 || source[0] !== "[" || first?.kind !== "text" || first.quoted) return { value: word };
|
|
@@ -5521,8 +5701,8 @@ function compoundEntry(word) {
|
|
|
5521
5701
|
if (source.includes("=")) throw new ShellSyntaxError("Invalid indexed-array entry", word.offset);
|
|
5522
5702
|
return { value: word };
|
|
5523
5703
|
}
|
|
5524
|
-
const index = literalIndex(source.slice(1, end), word.offset + 1);
|
|
5525
|
-
return { index, value: removePrefix(word, index.decimal.length + 3) };
|
|
5704
|
+
const index = literalIndex(source.slice(1, end), word.offset + 1, budget);
|
|
5705
|
+
return { index, value: removePrefix(word, index.decimal.length + 3, budget) };
|
|
5526
5706
|
}
|
|
5527
5707
|
function scalarAssignmentName(word) {
|
|
5528
5708
|
const first = word.parts[0];
|
|
@@ -5842,7 +6022,8 @@ var HereDocumentSyntaxError = class extends Error {
|
|
|
5842
6022
|
}
|
|
5843
6023
|
diagnostic;
|
|
5844
6024
|
};
|
|
5845
|
-
function printedSimpleLines(lists, separators) {
|
|
6025
|
+
function printedSimpleLines(lists, separators, budget) {
|
|
6026
|
+
budget.admit();
|
|
5846
6027
|
const printedLines = /* @__PURE__ */ new Map();
|
|
5847
6028
|
let line = 1;
|
|
5848
6029
|
for (let index = 0; index < lists.length; index++) {
|
|
@@ -5860,7 +6041,8 @@ function printedSimpleLines(lists, separators) {
|
|
|
5860
6041
|
var IncompleteShellInput = class extends Error {
|
|
5861
6042
|
};
|
|
5862
6043
|
var Lexer = class {
|
|
5863
|
-
constructor(source, depth, warnings = [], lineOffset = 0, byteLocale2 = false, documentLine, partial = false) {
|
|
6044
|
+
constructor(budget, source, depth, warnings = [], lineOffset = 0, byteLocale2 = false, documentLine, partial = false) {
|
|
6045
|
+
this.budget = budget;
|
|
5864
6046
|
this.source = source;
|
|
5865
6047
|
this.depth = depth;
|
|
5866
6048
|
this.warnings = warnings;
|
|
@@ -5871,6 +6053,7 @@ var Lexer = class {
|
|
|
5871
6053
|
if (depth > 64) throw new ShellSyntaxError("Syntax nesting exceeds 64", 0);
|
|
5872
6054
|
for (let offset = source.indexOf("\n"); offset !== -1; offset = source.indexOf("\n", offset + 1)) this.newlineOffsets.push(offset);
|
|
5873
6055
|
}
|
|
6056
|
+
budget;
|
|
5874
6057
|
source;
|
|
5875
6058
|
depth;
|
|
5876
6059
|
warnings;
|
|
@@ -5900,6 +6083,7 @@ var Lexer = class {
|
|
|
5900
6083
|
throw new ShellSyntaxError(message2, this.position, 2, void 0, unclosedQuote);
|
|
5901
6084
|
}
|
|
5902
6085
|
next() {
|
|
6086
|
+
this.budget.admit();
|
|
5903
6087
|
while (this.position < this.source.length) {
|
|
5904
6088
|
const current = this.source[this.position];
|
|
5905
6089
|
if (current === " " || current === " " || this.conditional && current === "\n") {
|
|
@@ -5941,6 +6125,7 @@ var Lexer = class {
|
|
|
5941
6125
|
if (this.position <= offset) this.error("Tokenizer made no progress");
|
|
5942
6126
|
let document;
|
|
5943
6127
|
if (delimiterOperator) {
|
|
6128
|
+
this.budget.admit();
|
|
5944
6129
|
document = {
|
|
5945
6130
|
delimiter: word.parts.map((part) => part.kind === "text" ? part.value : "").join(""),
|
|
5946
6131
|
quoted: word.parts.some((part) => part.quoted),
|
|
@@ -5952,6 +6137,7 @@ var Lexer = class {
|
|
|
5952
6137
|
};
|
|
5953
6138
|
this.documents.push(document);
|
|
5954
6139
|
}
|
|
6140
|
+
this.budget.admit();
|
|
5955
6141
|
return { kind: "word", value: word.plain ?? "", offset, end: this.position, word: { ...word, spelling: this.source.slice(offset, this.position) }, ...document ? { document } : {} };
|
|
5956
6142
|
}
|
|
5957
6143
|
readDocuments() {
|
|
@@ -5986,10 +6172,12 @@ var Lexer = class {
|
|
|
5986
6172
|
const current = this.source[this.position];
|
|
5987
6173
|
if (current === "$" || current === "`") {
|
|
5988
6174
|
if (text) {
|
|
6175
|
+
this.budget.admit(2);
|
|
5989
6176
|
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
5990
6177
|
text = "";
|
|
5991
6178
|
}
|
|
5992
6179
|
const offset = this.position;
|
|
6180
|
+
this.budget.admit();
|
|
5993
6181
|
const parts = [];
|
|
5994
6182
|
try {
|
|
5995
6183
|
this.expansion(parts, true);
|
|
@@ -6013,11 +6201,15 @@ var Lexer = class {
|
|
|
6013
6201
|
this.position++;
|
|
6014
6202
|
}
|
|
6015
6203
|
if (text.length >= 1024) {
|
|
6204
|
+
this.budget.admit(2);
|
|
6016
6205
|
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
6017
6206
|
text = "";
|
|
6018
6207
|
}
|
|
6019
6208
|
}
|
|
6020
|
-
if (text)
|
|
6209
|
+
if (text) {
|
|
6210
|
+
this.budget.admit(2);
|
|
6211
|
+
yield { offset: 0, parts: [{ kind: "text", value: text, quoted: true }] };
|
|
6212
|
+
}
|
|
6021
6213
|
}
|
|
6022
6214
|
documentSubstitutionError(source, error, backtick = false) {
|
|
6023
6215
|
const line = this.documentLine + source.slice(0, error.offset).split("\n").length;
|
|
@@ -6064,6 +6256,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6064
6256
|
this.error("Unterminated delimiter expansion syntax");
|
|
6065
6257
|
}
|
|
6066
6258
|
word(terminator, enclosingQuoted = false, literal = false, arithmetic = false) {
|
|
6259
|
+
this.budget.admit();
|
|
6067
6260
|
const offset = this.position;
|
|
6068
6261
|
const reduction = this.printedNewlineReduction;
|
|
6069
6262
|
const unprinted = this.unprintedWords;
|
|
@@ -6088,6 +6281,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6088
6281
|
previous.value += value2;
|
|
6089
6282
|
if (!synthetic) setQuoteMarker(previous, false);
|
|
6090
6283
|
} else {
|
|
6284
|
+
this.budget.admit();
|
|
6091
6285
|
const part = { kind: "text", value: projection, quoted, ...typeof value2 === "string" ? {} : { byteValue: value2 } };
|
|
6092
6286
|
setQuoteMarker(part, synthetic);
|
|
6093
6287
|
parts.push(part);
|
|
@@ -6195,6 +6389,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6195
6389
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(raw);
|
|
6196
6390
|
} catch (error) {
|
|
6197
6391
|
if (!(error instanceof TypeError) || !("code" in error) || error.code !== "ERR_ENCODING_INVALID_ENCODED_DATA") throw error;
|
|
6392
|
+
this.budget.admit(2);
|
|
6198
6393
|
return shellValueFromBytes(raw);
|
|
6199
6394
|
}
|
|
6200
6395
|
}
|
|
@@ -6237,6 +6432,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6237
6432
|
this.error("Unterminated ANSI-C quote", { quote: "'", line: quoteLine });
|
|
6238
6433
|
}
|
|
6239
6434
|
expansion(parts, quoted) {
|
|
6435
|
+
this.budget.admit();
|
|
6240
6436
|
const line = this.documentLine ?? this.lineAt(this.position);
|
|
6241
6437
|
if (this.source[this.position] === "`") {
|
|
6242
6438
|
this.position++;
|
|
@@ -6248,9 +6444,10 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6248
6444
|
if (this.source[this.position] !== "`") this.error("Unterminated command substitution");
|
|
6249
6445
|
this.position++;
|
|
6250
6446
|
try {
|
|
6251
|
-
parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale), line, quoted });
|
|
6447
|
+
parts.push({ kind: "substitution", script: parseSource(source, this.depth + 1, this.warnings, line - 1, this.byteLocale, this.budget), line, quoted });
|
|
6252
6448
|
} catch (error) {
|
|
6253
6449
|
if (this.documentLine === void 0 || !(error instanceof ShellSyntaxError) || /nesting|exceeds/u.test(error.reason)) throw error;
|
|
6450
|
+
this.budget.admit();
|
|
6254
6451
|
parts.push({ kind: "failed-substitution", diagnostic: this.documentSubstitutionError(source, error, true).diagnostic, quoted });
|
|
6255
6452
|
}
|
|
6256
6453
|
return;
|
|
@@ -6261,14 +6458,15 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6261
6458
|
const start = this.position + 2;
|
|
6262
6459
|
const end = arithmeticEnd(this.source, start);
|
|
6263
6460
|
const source = this.source.slice(start, end);
|
|
6264
|
-
parts.push({ kind: "arithmetic", expression: prepareArithmetic(source), source, line, quoted });
|
|
6461
|
+
parts.push({ kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, line, quoted });
|
|
6265
6462
|
this.position = end + 2;
|
|
6266
6463
|
} else if (this.source[this.position] === "(") {
|
|
6267
6464
|
const start = this.position + 1;
|
|
6268
6465
|
let nested;
|
|
6269
6466
|
let script;
|
|
6270
6467
|
try {
|
|
6271
|
-
|
|
6468
|
+
this.budget.admit();
|
|
6469
|
+
nested = new Parser(this.budget, this.source.slice(start), this.depth + 1, this.warnings, this.lineAt(start) - 1, void 0, this.byteLocale);
|
|
6272
6470
|
script = nested.script(/* @__PURE__ */ new Set([")"]));
|
|
6273
6471
|
} catch (error) {
|
|
6274
6472
|
if (this.documentLine !== void 0 && error instanceof ShellSyntaxError && !/nesting|exceeds/u.test(error.reason)) throw this.documentSubstitutionError(this.source.slice(start), error);
|
|
@@ -6298,7 +6496,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6298
6496
|
const start = ++this.position;
|
|
6299
6497
|
const end = this.source.indexOf("]", start);
|
|
6300
6498
|
if (end < 0) this.error("Unterminated indexed-array subscript");
|
|
6301
|
-
selector = arraySelector(this.source.slice(start, end), start);
|
|
6499
|
+
selector = arraySelector(this.source.slice(start, end), start, this.budget);
|
|
6302
6500
|
this.position = end + 1;
|
|
6303
6501
|
if (this.source[this.position] !== "}") this.error("Unsupported indexed-array operator");
|
|
6304
6502
|
}
|
|
@@ -6323,6 +6521,7 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6323
6521
|
this.position++;
|
|
6324
6522
|
substringLength = this.word("}", true, false, true);
|
|
6325
6523
|
}
|
|
6524
|
+
this.budget.admit();
|
|
6326
6525
|
substring = { offset, ...substringLength ? { length: substringLength } : {}, source: this.source.slice(parameterStart, this.position + 1) };
|
|
6327
6526
|
}
|
|
6328
6527
|
if (this.source[this.position] !== "}") this.error("Unterminated or unsupported parameter expansion");
|
|
@@ -6340,17 +6539,20 @@ shell: command substitution: line ${line}: \`${sourceLine}'
|
|
|
6340
6539
|
}
|
|
6341
6540
|
};
|
|
6342
6541
|
var Parser = class {
|
|
6542
|
+
constructor(budget, source, depth, warnings = [], lineOffset = 0, position, byteLocale2 = false, partial = false) {
|
|
6543
|
+
this.budget = budget;
|
|
6544
|
+
if (position === void 0 && depth === 0 && source.includes("\0")) throw new ShellSyntaxError("NUL bytes are not valid shell source", source.indexOf("\0"));
|
|
6545
|
+
budget.admit();
|
|
6546
|
+
this.lexer = new Lexer(budget, source, depth, warnings, lineOffset, byteLocale2, void 0, partial);
|
|
6547
|
+
this.lexer.position = position ?? 0;
|
|
6548
|
+
this.current = this.lexer.next();
|
|
6549
|
+
}
|
|
6550
|
+
budget;
|
|
6343
6551
|
lexer;
|
|
6344
6552
|
current;
|
|
6345
6553
|
lookahead;
|
|
6346
6554
|
nesting = 0;
|
|
6347
6555
|
openCommands = [];
|
|
6348
|
-
constructor(source, depth, warnings = [], lineOffset = 0, position, byteLocale2 = false, partial = false) {
|
|
6349
|
-
if (position === void 0 && depth === 0 && source.includes("\0")) throw new ShellSyntaxError("NUL bytes are not valid shell source", source.indexOf("\0"));
|
|
6350
|
-
this.lexer = new Lexer(source, depth, warnings, lineOffset, byteLocale2, void 0, partial);
|
|
6351
|
-
this.lexer.position = position ?? 0;
|
|
6352
|
-
this.current = this.lexer.next();
|
|
6353
|
-
}
|
|
6354
6556
|
error(message2) {
|
|
6355
6557
|
const command2 = this.current.kind === "end" ? this.openCommands.findLast((command3) => ["{", "if", "while", "until", "for", "case"].includes(command3.name)) : void 0;
|
|
6356
6558
|
throw new ShellSyntaxError(message2, this.current.offset, 2, command2);
|
|
@@ -6378,11 +6580,13 @@ var Parser = class {
|
|
|
6378
6580
|
while (this.is("\n")) this.advance();
|
|
6379
6581
|
}
|
|
6380
6582
|
script(stops = /* @__PURE__ */ new Set(), inputUnit = false) {
|
|
6583
|
+
this.budget.admit();
|
|
6381
6584
|
const lists = [];
|
|
6382
6585
|
const separators = [];
|
|
6383
6586
|
this.newlines();
|
|
6384
6587
|
const line = this.lexer.lineAt(this.current.offset);
|
|
6385
6588
|
while (this.current.kind !== "end" && !stops.has(this.current.value)) {
|
|
6589
|
+
this.budget.admit();
|
|
6386
6590
|
const pipelines = [this.pipeline()];
|
|
6387
6591
|
const operators = [];
|
|
6388
6592
|
while (this.is("&&") || this.is("||")) {
|
|
@@ -6399,21 +6603,25 @@ var Parser = class {
|
|
|
6399
6603
|
this.newlines();
|
|
6400
6604
|
} else if (!this.isEnd() && !this.is(")") && !(stops.has(this.current.value) && [";;", ";&", ";;&"].includes(this.current.value))) this.error("Expected command separator");
|
|
6401
6605
|
}
|
|
6402
|
-
const printed = printedSimpleLines(lists, separators);
|
|
6606
|
+
const printed = printedSimpleLines(lists, separators, this.budget);
|
|
6403
6607
|
return { lists, line, ...printed };
|
|
6404
6608
|
}
|
|
6405
6609
|
pipeline() {
|
|
6610
|
+
this.budget.admit();
|
|
6406
6611
|
const negate = this.is("!");
|
|
6407
6612
|
if (negate) this.advance();
|
|
6408
6613
|
const commands = [this.command()];
|
|
6409
6614
|
while (this.is("|") || this.is("|&")) {
|
|
6410
6615
|
const operator = this.advance();
|
|
6411
|
-
if (operator.value === "|&")
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6616
|
+
if (operator.value === "|&") {
|
|
6617
|
+
this.budget.admit(3);
|
|
6618
|
+
commands.at(-1).redirects.push({
|
|
6619
|
+
descriptor: 2,
|
|
6620
|
+
operator: ">&",
|
|
6621
|
+
implicitPipeline: true,
|
|
6622
|
+
target: { offset: operator.offset, plain: "1", spelling: "1", parts: [{ kind: "text", value: "1", quoted: false }] }
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6417
6625
|
this.newlines();
|
|
6418
6626
|
commands.push(this.command());
|
|
6419
6627
|
}
|
|
@@ -6421,10 +6629,12 @@ var Parser = class {
|
|
|
6421
6629
|
}
|
|
6422
6630
|
command() {
|
|
6423
6631
|
if (++this.nesting + this.lexer.depth > 64) this.error("Syntax nesting exceeds 64");
|
|
6632
|
+
this.budget.admit();
|
|
6424
6633
|
const line = this.lexer.lineAt(this.current.offset);
|
|
6425
6634
|
this.openCommands.push({ name: this.current.value, line });
|
|
6426
6635
|
try {
|
|
6427
6636
|
const command2 = this.commandInner();
|
|
6637
|
+
this.budget.admit();
|
|
6428
6638
|
return { ...command2, line: command2.line ?? line };
|
|
6429
6639
|
} finally {
|
|
6430
6640
|
this.nesting--;
|
|
@@ -6432,6 +6642,7 @@ var Parser = class {
|
|
|
6432
6642
|
}
|
|
6433
6643
|
}
|
|
6434
6644
|
commandInner() {
|
|
6645
|
+
this.budget.admit();
|
|
6435
6646
|
let command2;
|
|
6436
6647
|
if (this.is("[[")) {
|
|
6437
6648
|
const start = this.current.end;
|
|
@@ -6453,7 +6664,9 @@ var Parser = class {
|
|
|
6453
6664
|
admit2();
|
|
6454
6665
|
this.advance();
|
|
6455
6666
|
const expression2 = primary(depth + 1);
|
|
6456
|
-
|
|
6667
|
+
if (expression2.kind === "not" && !grouped.has(expression2)) return expression2.operand;
|
|
6668
|
+
this.budget.admit();
|
|
6669
|
+
return { kind: "not", operand: expression2 };
|
|
6457
6670
|
}
|
|
6458
6671
|
if (this.is("(")) {
|
|
6459
6672
|
this.advance();
|
|
@@ -6463,6 +6676,7 @@ var Parser = class {
|
|
|
6463
6676
|
return expression2;
|
|
6464
6677
|
}
|
|
6465
6678
|
admit2();
|
|
6679
|
+
this.budget.admit();
|
|
6466
6680
|
const first = operand();
|
|
6467
6681
|
if (conditionalBinary.has(this.current.value)) {
|
|
6468
6682
|
const operator = this.current.value;
|
|
@@ -6478,6 +6692,7 @@ var Parser = class {
|
|
|
6478
6692
|
let expression2 = primary(depth);
|
|
6479
6693
|
while (this.is("&&")) {
|
|
6480
6694
|
admit2();
|
|
6695
|
+
this.budget.admit();
|
|
6481
6696
|
this.advance();
|
|
6482
6697
|
expression2 = { kind: "and", left: expression2, right: primary(depth) };
|
|
6483
6698
|
}
|
|
@@ -6487,6 +6702,7 @@ var Parser = class {
|
|
|
6487
6702
|
let expression2 = conjunction(depth);
|
|
6488
6703
|
while (this.is("||")) {
|
|
6489
6704
|
admit2();
|
|
6705
|
+
this.budget.admit();
|
|
6490
6706
|
this.advance();
|
|
6491
6707
|
expression2 = { kind: "or", left: expression2, right: conjunction(depth) };
|
|
6492
6708
|
}
|
|
@@ -6502,7 +6718,7 @@ var Parser = class {
|
|
|
6502
6718
|
const start = this.current.offset + 2;
|
|
6503
6719
|
const end = arithmeticEnd(this.lexer.source, start);
|
|
6504
6720
|
const source = this.lexer.source.slice(start, end);
|
|
6505
|
-
command2 = { kind: "arithmetic", expression: prepareArithmetic(source), source, redirects: [] };
|
|
6721
|
+
command2 = { kind: "arithmetic", expression: prepareArithmetic(source, this.budget), source, redirects: [] };
|
|
6506
6722
|
this.lexer.position = end + 2;
|
|
6507
6723
|
this.lookahead = void 0;
|
|
6508
6724
|
this.current = this.lexer.next();
|
|
@@ -6520,6 +6736,7 @@ var Parser = class {
|
|
|
6520
6736
|
const condition = this.nonemptyScript(/* @__PURE__ */ new Set(["then"]));
|
|
6521
6737
|
this.expect("then");
|
|
6522
6738
|
const body = this.nonemptyScript(/* @__PURE__ */ new Set(["elif", "else", "fi"]));
|
|
6739
|
+
this.budget.admit();
|
|
6523
6740
|
branches.push({ condition, body });
|
|
6524
6741
|
if (!this.is("elif")) break;
|
|
6525
6742
|
this.advance();
|
|
@@ -6552,6 +6769,7 @@ var Parser = class {
|
|
|
6552
6769
|
const body = this.script(/* @__PURE__ */ new Set([";;", ";&", ";;&", "esac"]));
|
|
6553
6770
|
if (![";;", ";&", ";;&", "esac"].includes(this.current.value)) this.error("Expected case terminator");
|
|
6554
6771
|
const terminator = this.current.value;
|
|
6772
|
+
this.budget.admit();
|
|
6555
6773
|
clauses.push({ patterns: patterns2, body, terminator });
|
|
6556
6774
|
if (terminator === "esac") break;
|
|
6557
6775
|
this.advance();
|
|
@@ -6595,7 +6813,7 @@ var Parser = class {
|
|
|
6595
6813
|
this.newlines();
|
|
6596
6814
|
if (!this.is("{")) this.error("Expected brace function body");
|
|
6597
6815
|
command2 = { kind: "function", name: name2, body: this.command(), redirects: [] };
|
|
6598
|
-
} else if (this.current.kind === "word" && !compoundHead(this.current.word) && this.peek().value === "(") {
|
|
6816
|
+
} else if (this.current.kind === "word" && !compoundHead(this.current.word, this.budget) && this.peek().value === "(") {
|
|
6599
6817
|
const name2 = this.advance().value;
|
|
6600
6818
|
if (!/^[a-zA-Z_][a-zA-Z_0-9]*$/u.test(name2)) this.error("Invalid function name");
|
|
6601
6819
|
this.expect("(");
|
|
@@ -6617,21 +6835,22 @@ var Parser = class {
|
|
|
6617
6835
|
} else if (this.current.kind === "word") {
|
|
6618
6836
|
line ??= wordLine;
|
|
6619
6837
|
let word = this.advance().word;
|
|
6620
|
-
const head = compoundHead(word);
|
|
6838
|
+
const head = compoundHead(word, this.budget);
|
|
6621
6839
|
if (head && this.is("(")) {
|
|
6622
6840
|
this.advance();
|
|
6623
6841
|
const entries = [];
|
|
6624
6842
|
this.newlines();
|
|
6625
6843
|
while (this.current.kind === "word") {
|
|
6626
|
-
entries.push(compoundEntry(this.advance().word));
|
|
6844
|
+
entries.push(compoundEntry(this.advance().word, this.budget));
|
|
6627
6845
|
this.newlines();
|
|
6628
6846
|
}
|
|
6629
6847
|
if (!this.is(")")) this.error("Unsupported indexed-array compound assignment");
|
|
6630
6848
|
const end = this.advance().end;
|
|
6849
|
+
this.budget.admit(2);
|
|
6631
6850
|
word = { offset: word.offset, parts: word.parts, spelling: this.lexer.source.slice(word.offset, end) };
|
|
6632
6851
|
setArrayAssignment(word, { kind: "compound", ...head, entries });
|
|
6633
6852
|
} else {
|
|
6634
|
-
const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word) : void 0;
|
|
6853
|
+
const assignment = words.every((previous) => getArrayAssignment(previous) || scalarAssignmentName(previous)) ? elementAssignment(word, this.budget) : void 0;
|
|
6635
6854
|
if (assignment) setArrayAssignment(word, assignment);
|
|
6636
6855
|
}
|
|
6637
6856
|
words.push(word);
|
|
@@ -6657,6 +6876,7 @@ var Parser = class {
|
|
|
6657
6876
|
if (/^(?:>|>>|<|<<|<<-|<<<|>&|<&|>\|)$/u.test(next.value) && this.current.end === next.offset) descriptor2 = Number(this.advance().value);
|
|
6658
6877
|
}
|
|
6659
6878
|
if (!/^(?:>|>>|<|<<|<<-|<<<|>&|<&|>\||&>)$/u.test(this.current.value) || this.current.kind !== "operator") return void 0;
|
|
6879
|
+
this.budget.admit();
|
|
6660
6880
|
const operator = this.advance().value;
|
|
6661
6881
|
descriptor2 ??= operator.startsWith("<") ? 0 : 1;
|
|
6662
6882
|
if (!Number.isSafeInteger(descriptor2) || descriptor2 > 255) this.error("File descriptor must be between 0 and 255");
|
|
@@ -6665,29 +6885,40 @@ var Parser = class {
|
|
|
6665
6885
|
return { descriptor: descriptor2, operator, target: target.word, line: this.lexer.lineAt(Math.max(target.offset, target.end - 1)), ...(operator === "<&" || operator === ">&") && this.lexer.source[target.end - 1] === "-" ? { move: true } : {}, ...target.document ? { document: target.document } : {} };
|
|
6666
6886
|
}
|
|
6667
6887
|
};
|
|
6668
|
-
function parseShell(source, depth = 0) {
|
|
6888
|
+
function parseShell(source, depth = 0, options3 = {}) {
|
|
6889
|
+
const budget = new ParseBudget(options3.maxParseUnits);
|
|
6669
6890
|
const warnings = [];
|
|
6670
|
-
const script = parseSource(source, depth, warnings);
|
|
6891
|
+
const script = parseSource(source, depth, warnings, 0, false, budget);
|
|
6892
|
+
budget.admit();
|
|
6671
6893
|
return { ...script, ...warnings.length ? { warnings } : {} };
|
|
6672
6894
|
}
|
|
6673
|
-
function* hereDocumentWords(document, line, byteLocale2, warnings) {
|
|
6674
|
-
if (document.quoted)
|
|
6675
|
-
|
|
6895
|
+
function* hereDocumentWords(document, line, byteLocale2, warnings, budget = new ParseBudget()) {
|
|
6896
|
+
if (document.quoted) {
|
|
6897
|
+
budget.admit(2);
|
|
6898
|
+
yield { offset: document.offset, parts: [{ kind: "text", value: document.body, quoted: true }] };
|
|
6899
|
+
} else {
|
|
6900
|
+
budget.admit();
|
|
6901
|
+
yield* new Lexer(budget, document.body, document.depth, warnings, line - 1, byteLocale2, line).documentWords();
|
|
6902
|
+
}
|
|
6676
6903
|
}
|
|
6677
|
-
function parseShellUnit(source, position = 0, byteLocale2 = false) {
|
|
6904
|
+
function parseShellUnit(source, position = 0, byteLocale2 = false, budget = new ParseBudget()) {
|
|
6678
6905
|
const warnings = [];
|
|
6679
|
-
|
|
6906
|
+
budget.admit();
|
|
6907
|
+
const parser = new Parser(budget, source, 0, warnings, 0, position, byteLocale2);
|
|
6680
6908
|
const script = parser.script(/* @__PURE__ */ new Set(), true);
|
|
6681
6909
|
const next = parser.current.end;
|
|
6682
6910
|
const nul = source.indexOf("\0", position);
|
|
6683
6911
|
if (nul >= 0 && nul < next) throw new ShellSyntaxError("NUL bytes are not valid shell source", nul);
|
|
6912
|
+
budget.admit(2);
|
|
6684
6913
|
return { script: { ...script, ...warnings.length ? { warnings } : {} }, next };
|
|
6685
6914
|
}
|
|
6686
|
-
function parseShellInputUnit(source, byteLocale2 = false) {
|
|
6915
|
+
function parseShellInputUnit(source, byteLocale2 = false, budget = new ParseBudget()) {
|
|
6687
6916
|
const warnings = [];
|
|
6688
6917
|
try {
|
|
6689
|
-
|
|
6918
|
+
budget.admit();
|
|
6919
|
+
const parser = new Parser(budget, source, 0, warnings, 0, 0, byteLocale2, true);
|
|
6690
6920
|
const script = parser.script(/* @__PURE__ */ new Set(), true);
|
|
6921
|
+
budget.admit(2);
|
|
6691
6922
|
return { script: { ...script, ...warnings.length ? { warnings } : {} }, next: parser.current.end };
|
|
6692
6923
|
} catch (error) {
|
|
6693
6924
|
if (error instanceof IncompleteShellInput) return void 0;
|
|
@@ -6695,8 +6926,9 @@ function parseShellInputUnit(source, byteLocale2 = false) {
|
|
|
6695
6926
|
throw error;
|
|
6696
6927
|
}
|
|
6697
6928
|
}
|
|
6698
|
-
function parseSource(source, depth, warnings, lineOffset
|
|
6699
|
-
|
|
6929
|
+
function parseSource(source, depth, warnings, lineOffset, byteLocale2, budget) {
|
|
6930
|
+
budget.admit();
|
|
6931
|
+
const parser = new Parser(budget, source, depth, warnings, lineOffset, void 0, byteLocale2);
|
|
6700
6932
|
const script = parser.script();
|
|
6701
6933
|
if (parser.current.kind !== "end") parser.error("Unexpected token");
|
|
6702
6934
|
return script;
|
|
@@ -7101,7 +7333,7 @@ function evaluatePositionalArithmetic(program, options3, evaluate) {
|
|
|
7101
7333
|
reserve(bytes2);
|
|
7102
7334
|
const expanded = chunks.join("");
|
|
7103
7335
|
options3.checkpoint();
|
|
7104
|
-
return evaluate(prepareArithmetic(expanded));
|
|
7336
|
+
return evaluate(prepareArithmetic(expanded, options3.parseBudget));
|
|
7105
7337
|
} finally {
|
|
7106
7338
|
for (let position = admissions.length - 1; position >= 0; position--) admissions[position].release();
|
|
7107
7339
|
header?.release();
|
|
@@ -10063,6 +10295,7 @@ async function matchEre(program, subject, ledger, signal) {
|
|
|
10063
10295
|
|
|
10064
10296
|
// packages/safe-bash/src/shell/runtime.ts
|
|
10065
10297
|
var defaultLimits = {
|
|
10298
|
+
maxParseUnits: defaultMaxParseUnits,
|
|
10066
10299
|
maxInputBytes: 32 * 1024 * 1024,
|
|
10067
10300
|
maxOutputBytes: 16 * 1024 * 1024,
|
|
10068
10301
|
maxCommands: 1e4,
|
|
@@ -10153,11 +10386,13 @@ var Budget = class {
|
|
|
10153
10386
|
constructor(limits, signal) {
|
|
10154
10387
|
this.limits = limits;
|
|
10155
10388
|
this.signal = signal ? AbortSignal.any([signal, this.controller.signal]) : this.controller.signal;
|
|
10389
|
+
this.parsing = new ParseBudget(limits.maxParseUnits, this.signal, (error) => this.controller.abort(error));
|
|
10156
10390
|
this.values = new ValueArena(limits.maxExpansionBytes, limits.maxExpansionFields, () => this.signal.throwIfAborted(), (limit) => this.fail(limit));
|
|
10157
10391
|
this.#wallClockDeadline = Date.now() + limits.maxWallClockMs;
|
|
10158
10392
|
this.#armWallClock();
|
|
10159
10393
|
}
|
|
10160
10394
|
limits;
|
|
10395
|
+
parsing;
|
|
10161
10396
|
values;
|
|
10162
10397
|
commands = 0;
|
|
10163
10398
|
iterations = 0;
|
|
@@ -11297,7 +11532,7 @@ var Runtime = class _Runtime {
|
|
|
11297
11532
|
if (shellValueByteLength(value2) > this.budget.limits.maxExpansionBytes) this.budget.fail("maxExpansionBytes");
|
|
11298
11533
|
if (name2 === "OPTIND" && state.getopts?.integer && origin !== "arithmetic") {
|
|
11299
11534
|
try {
|
|
11300
|
-
value2 = String(evaluateArithmetic(prepareArithmetic(shellValueText(value2) || "0"), this.arithmeticVariables(state)));
|
|
11535
|
+
value2 = String(evaluateArithmetic(prepareArithmetic(shellValueText(value2) || "0", this.budget.parsing), this.arithmeticVariables(state), this.budget.parsing));
|
|
11301
11536
|
} catch (error) {
|
|
11302
11537
|
this.rethrowArithmeticControl(error);
|
|
11303
11538
|
throw new ExpansionFailure(message(error));
|
|
@@ -12003,6 +12238,7 @@ var Runtime = class _Runtime {
|
|
|
12003
12238
|
if (command2.kind === "arithmetic") {
|
|
12004
12239
|
try {
|
|
12005
12240
|
return Number(evaluatePositionalArithmetic(command2.expression, {
|
|
12241
|
+
parseBudget: this.budget.parsing,
|
|
12006
12242
|
positional: state.positional,
|
|
12007
12243
|
arg0: state.arg0 ?? "virtual-bash",
|
|
12008
12244
|
owner: arrayStore(state)?.owner,
|
|
@@ -12010,7 +12246,7 @@ var Runtime = class _Runtime {
|
|
|
12010
12246
|
checkpoint: () => this.signal.throwIfAborted(),
|
|
12011
12247
|
requireParameter: (name2, value2) => this.requireParameter(value2, name2, state, io),
|
|
12012
12248
|
limit: () => this.budget.fail("maxExpansionBytes")
|
|
12013
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine))) === 0n);
|
|
12249
|
+
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine), this.budget.parsing)) === 0n);
|
|
12014
12250
|
} catch (error) {
|
|
12015
12251
|
this.rethrowArithmeticControl(error);
|
|
12016
12252
|
throw new Error(`((: ${message(error)}`);
|
|
@@ -12164,7 +12400,7 @@ var Runtime = class _Runtime {
|
|
|
12164
12400
|
let words = 0;
|
|
12165
12401
|
const warnings = [];
|
|
12166
12402
|
try {
|
|
12167
|
-
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings)) {
|
|
12403
|
+
for (const word of hereDocumentWords(document, line, byteLocale(state.variables), warnings, this.budget.parsing)) {
|
|
12168
12404
|
this.signal.throwIfAborted();
|
|
12169
12405
|
for (const warning of warnings.splice(0)) await writeText(io.stderr, `shell: warning: ${warning}
|
|
12170
12406
|
`);
|
|
@@ -13056,7 +13292,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13056
13292
|
try {
|
|
13057
13293
|
do {
|
|
13058
13294
|
this.signal.throwIfAborted();
|
|
13059
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables));
|
|
13295
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
|
|
13060
13296
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName}: warning: ${warning}
|
|
13061
13297
|
`);
|
|
13062
13298
|
if (unit.script.lists.length) {
|
|
@@ -13093,7 +13329,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13093
13329
|
if (bytes2) source += this.sourceText(bytes2, io.scriptName ?? "shell");
|
|
13094
13330
|
const unitIO = { ...io, diagnosticOffset: offset };
|
|
13095
13331
|
try {
|
|
13096
|
-
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables)) : parseShellInputUnit(source, byteLocale(state.variables));
|
|
13332
|
+
const unit = eof ? parseShellUnit(source, 0, byteLocale(state.variables), this.budget.parsing) : parseShellInputUnit(source, byteLocale(state.variables), this.budget.parsing);
|
|
13097
13333
|
if (unit) {
|
|
13098
13334
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName}: warning: ${warning}
|
|
13099
13335
|
`);
|
|
@@ -13357,7 +13593,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13357
13593
|
let position = 0;
|
|
13358
13594
|
do {
|
|
13359
13595
|
this.signal.throwIfAborted();
|
|
13360
|
-
const unit = parseShellUnit(source, position, byteLocale(context.env));
|
|
13596
|
+
const unit = parseShellUnit(source, position, byteLocale(context.env), this.budget.parsing);
|
|
13361
13597
|
units.push(unit.script);
|
|
13362
13598
|
position = unit.next;
|
|
13363
13599
|
} while (position < source.length);
|
|
@@ -13390,7 +13626,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13390
13626
|
try {
|
|
13391
13627
|
do {
|
|
13392
13628
|
this.signal.throwIfAborted();
|
|
13393
|
-
const unit = parseShellUnit(source, position, byteLocale(state.variables));
|
|
13629
|
+
const unit = parseShellUnit(source, position, byteLocale(state.variables), this.budget.parsing);
|
|
13394
13630
|
for (const warning of unit.script.warnings ?? []) await writeText(io.stderr, `${io.scriptName ?? "shell"}: warning: ${warning}
|
|
13395
13631
|
`);
|
|
13396
13632
|
if (unit.script.lists.length) {
|
|
@@ -13704,7 +13940,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
13704
13940
|
for (let index = offset; index < args.length; index++) {
|
|
13705
13941
|
this.signal.throwIfAborted();
|
|
13706
13942
|
try {
|
|
13707
|
-
value2 = evaluateArithmetic(prepareArithmetic(args[index]), variables);
|
|
13943
|
+
value2 = evaluateArithmetic(prepareArithmetic(args[index], this.budget.parsing), variables, this.budget.parsing);
|
|
13708
13944
|
} catch (error) {
|
|
13709
13945
|
this.rethrowArithmeticControl(error);
|
|
13710
13946
|
throw new Error(`let: ${message(error)}`);
|
|
@@ -14369,8 +14605,10 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14369
14605
|
else {
|
|
14370
14606
|
let index;
|
|
14371
14607
|
try {
|
|
14372
|
-
index = numericIndex(literalIndex(selector, 0));
|
|
14373
|
-
} catch {
|
|
14608
|
+
index = numericIndex(literalIndex(selector, 0, this.budget.parsing));
|
|
14609
|
+
} catch (error) {
|
|
14610
|
+
this.signal.throwIfAborted();
|
|
14611
|
+
if (error instanceof ShellLimitError) throw error;
|
|
14374
14612
|
await this.diagnostic(context, "indexed array: unsupported subscript");
|
|
14375
14613
|
status = 2;
|
|
14376
14614
|
continue;
|
|
@@ -14574,6 +14812,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14574
14812
|
if (part.kind === "arithmetic") {
|
|
14575
14813
|
try {
|
|
14576
14814
|
return String(evaluatePositionalArithmetic(part.expression, {
|
|
14815
|
+
parseBudget: this.budget.parsing,
|
|
14577
14816
|
positional: state.positional,
|
|
14578
14817
|
arg0: state.arg0 ?? "virtual-bash",
|
|
14579
14818
|
owner: arrayStore(state)?.owner,
|
|
@@ -14581,7 +14820,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14581
14820
|
checkpoint: () => this.signal.throwIfAborted(),
|
|
14582
14821
|
requireParameter: (name2, value3) => this.requireParameter(value3, name2, state, io, part.line),
|
|
14583
14822
|
limit: () => this.budget.fail("maxExpansionBytes")
|
|
14584
|
-
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line))));
|
|
14823
|
+
}, (prepared) => evaluateArithmetic(prepared, this.arithmeticVariables(state, io.diagnosticLine ?? part.line), this.budget.parsing)));
|
|
14585
14824
|
} catch (error) {
|
|
14586
14825
|
this.rethrowArithmeticControl(error);
|
|
14587
14826
|
throw new ExpansionFailure(message(error), io.diagnosticLine ?? part.line);
|
|
@@ -14720,7 +14959,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14720
14959
|
}
|
|
14721
14960
|
this.signal.throwIfAborted();
|
|
14722
14961
|
try {
|
|
14723
|
-
return { value: evaluateArithmetic(prepareArithmetic(source), variables), source };
|
|
14962
|
+
return { value: evaluateArithmetic(prepareArithmetic(source, this.budget.parsing), variables, this.budget.parsing), source };
|
|
14724
14963
|
} catch (error) {
|
|
14725
14964
|
this.rethrowArithmeticControl(error);
|
|
14726
14965
|
throw new ExpansionFailure(`${part.name}: ${message(error)}`, line);
|
|
@@ -14850,29 +15089,67 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14850
15089
|
const arrayOwned = word.parts.some((part) => part.kind === "variable" && (getArraySelector(part) !== void 0 || arrayStore(state)?.get(part.name) !== void 0));
|
|
14851
15090
|
const owner = arrayOwned ? requireArrays(state).owner : void 0;
|
|
14852
15091
|
const holding = owner?.hold();
|
|
15092
|
+
const scratch = !owner && split && state.variables.IFS !== "" && word.parts.some((part) => !part.quoted && part.kind !== "text") ? this.budget.values.scope() : void 0;
|
|
14853
15093
|
try {
|
|
14854
15094
|
if (owner) await this.prepareArrayObservers(state, owner);
|
|
14855
15095
|
owner?.reserve({ metadata: 128 + word.parts.length * 32, allocatedSlots: word.parts.length + 1, work: word.parts.length + 5 });
|
|
14856
|
-
|
|
15096
|
+
scratch?.reserve(word.parts.length * 32, 0);
|
|
15097
|
+
const fields = [];
|
|
15098
|
+
const addField = () => {
|
|
15099
|
+
if (fields.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
15100
|
+
scratch?.reserve(32, 0);
|
|
15101
|
+
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
15102
|
+
fields.push({ fragments: [], bytes: false, patterns: void 0, present: false });
|
|
15103
|
+
};
|
|
15104
|
+
addField();
|
|
14857
15105
|
let expansionBytes = 0;
|
|
14858
15106
|
const append = (value2, glob, present) => {
|
|
14859
15107
|
const text = shellValueText(value2);
|
|
14860
15108
|
const size = shellValueByteLength(value2);
|
|
14861
15109
|
if (size > this.budget.limits.maxExpansionBytes - expansionBytes) this.budget.fail("maxExpansionBytes");
|
|
14862
15110
|
expansionBytes += size;
|
|
14863
|
-
regexAppend?.(text, !glob);
|
|
14864
15111
|
const field = fields.at(-1);
|
|
14865
|
-
|
|
15112
|
+
let escapes = 0;
|
|
15113
|
+
if (!glob) {
|
|
15114
|
+
const special2 = conditionalPattern ? "\\*?[]-^()|+!@" : "\\*?[]-^";
|
|
15115
|
+
for (const character of text) if (special2.includes(character)) escapes++;
|
|
15116
|
+
}
|
|
15117
|
+
scratch?.reserve(32, 0);
|
|
15118
|
+
if (owner) owner.reserve({ payload: size + (escapes ? size + escapes : 0), metadata: 64, work: text.length + 8 });
|
|
15119
|
+
if (escapes) {
|
|
15120
|
+
scratch?.reserve((field.patterns ? 32 : 32 * (field.fragments.length + 1)) + (text.length + escapes) * 2, 0);
|
|
15121
|
+
field.patterns ??= field.fragments.map(shellValueText);
|
|
15122
|
+
field.patterns.push(text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&"));
|
|
15123
|
+
} else if (field.patterns) {
|
|
15124
|
+
scratch?.reserve(32, 0);
|
|
15125
|
+
field.patterns.push(text);
|
|
15126
|
+
}
|
|
14866
15127
|
if (typeof value2 !== "string" || field.bytes) {
|
|
14867
|
-
io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
|
|
15128
|
+
if (!scratch) io[valueScope]?.reserve(32 * (field.bytes ? 1 : field.fragments.length + 1), field.bytes ? 1 : field.fragments.length + 1);
|
|
14868
15129
|
if (typeof value2 !== "string") io[valueScope]?.hold(value2);
|
|
14869
15130
|
field.bytes = true;
|
|
14870
15131
|
}
|
|
15132
|
+
regexAppend?.(text, !glob);
|
|
14871
15133
|
field.fragments.push(value2);
|
|
14872
|
-
field.value += text;
|
|
14873
|
-
field.pattern += glob ? text : text.replace(conditionalPattern ? /[\\*?[\]\-^()|+!@]/gu : /[\\*?[\]\-^]/gu, "\\$&");
|
|
14874
15134
|
field.present ||= present;
|
|
14875
15135
|
};
|
|
15136
|
+
const appendSplit = async (value2) => {
|
|
15137
|
+
const separators = state.variables.IFS ?? " \n";
|
|
15138
|
+
let boundary = false;
|
|
15139
|
+
for await (const piece of this.splitValue(value2, separators, io, scratch)) {
|
|
15140
|
+
if (typeof piece === "string" && separators.includes(piece)) {
|
|
15141
|
+
if (!" \n".includes(piece)) {
|
|
15142
|
+
fields.at(-1).present = true;
|
|
15143
|
+
addField();
|
|
15144
|
+
} else if (fields.at(-1).present) boundary = true;
|
|
15145
|
+
} else {
|
|
15146
|
+
if (boundary) addField();
|
|
15147
|
+
boundary = false;
|
|
15148
|
+
append(piece, true, true);
|
|
15149
|
+
}
|
|
15150
|
+
}
|
|
15151
|
+
if (boundary) addField();
|
|
15152
|
+
};
|
|
14876
15153
|
const parts = word.parts.map((part) => ({ part, splitText: false }));
|
|
14877
15154
|
for (let index = 0; index < parts.length; index++) {
|
|
14878
15155
|
const { part, splitText } = parts[index];
|
|
@@ -14881,6 +15158,7 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14881
15158
|
const value2 = this.variable(state, part.name);
|
|
14882
15159
|
const missing = value2 === void 0 || part.operator.startsWith(":") && value2 === "";
|
|
14883
15160
|
if (part.operator.endsWith("+") ? !missing : missing) {
|
|
15161
|
+
scratch?.reserve(part.alternate.parts.length * 32, 0);
|
|
14884
15162
|
const alternate = part.alternate.parts.map((entry) => ({ part: copyArraySelector(entry, { ...entry, quoted: entry.quoted || part.quoted }), splitText: true }));
|
|
14885
15163
|
if (!alternate.length && part.quoted) append("", false, true);
|
|
14886
15164
|
parts.splice(index + 1, 0, ...alternate);
|
|
@@ -14891,37 +15169,10 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14891
15169
|
if (part.kind === "variable" && selector?.kind === "members" && !part.length && split && (!part.quoted || selector.separator === "@")) {
|
|
14892
15170
|
const members = await this.arrayMembers(part.name, state);
|
|
14893
15171
|
for (let position = 0; position < members.length; position++) {
|
|
14894
|
-
if (position > 0)
|
|
14895
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14896
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14897
|
-
}
|
|
15172
|
+
if (position > 0) addField();
|
|
14898
15173
|
const value2 = members[position];
|
|
14899
15174
|
if (part.quoted || state.variables.IFS === "") append(value2, !part.quoted, part.quoted || value2.length > 0);
|
|
14900
|
-
else
|
|
14901
|
-
const separators = state.variables.IFS ?? " \n";
|
|
14902
|
-
let boundary = false;
|
|
14903
|
-
for (const character of value2) {
|
|
14904
|
-
if (separators.includes(character)) {
|
|
14905
|
-
if (!/[ \t\n]/u.test(character)) {
|
|
14906
|
-
fields.at(-1).present = true;
|
|
14907
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14908
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14909
|
-
} else if (fields.at(-1).present) boundary = true;
|
|
14910
|
-
} else {
|
|
14911
|
-
if (boundary) {
|
|
14912
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14913
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14914
|
-
}
|
|
14915
|
-
boundary = false;
|
|
14916
|
-
append(character, true, true);
|
|
14917
|
-
}
|
|
14918
|
-
await owner.ledger.checkpoint(this.signal);
|
|
14919
|
-
}
|
|
14920
|
-
if (boundary) {
|
|
14921
|
-
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14922
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14923
|
-
}
|
|
14924
|
-
}
|
|
15175
|
+
else await appendSplit(value2);
|
|
14925
15176
|
}
|
|
14926
15177
|
} else if (part.kind === "text" && !splitText) {
|
|
14927
15178
|
let value2 = invokedValues.get(part) ?? part.byteValue ?? part.value;
|
|
@@ -14929,32 +15180,14 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14929
15180
|
append(value2, !part.quoted, quotedPresence || shellValueByteLength(value2) > 0);
|
|
14930
15181
|
} else if (part.kind === "variable" && part.name === "@" && part.quoted && !part.operator && split) {
|
|
14931
15182
|
for (let position = 0; position < state.positional.length; position++) {
|
|
14932
|
-
if (position > 0)
|
|
15183
|
+
if (position > 0) addField();
|
|
14933
15184
|
append(stateMonitor(state)?.positionals.get(String(position), state.positional[position]) ?? state.positional[position], false, true);
|
|
14934
15185
|
}
|
|
14935
15186
|
if (state.positional.length === 0 && word.parts.every((entry) => entry.kind === "text" && entry.value === "" || entry === part)) fields[0].present = false;
|
|
14936
15187
|
} else {
|
|
14937
15188
|
const value2 = part.kind === "text" ? part.byteValue ?? part.value : await this.valuePart(part, state, io, hereString);
|
|
14938
15189
|
if (part.quoted || !split || state.variables.IFS === "") append(value2, !part.quoted, quotedPresence || !split || shellValueByteLength(value2) > 0);
|
|
14939
|
-
else
|
|
14940
|
-
const separators = state.variables.IFS ?? " \n";
|
|
14941
|
-
let boundary = false;
|
|
14942
|
-
const pieces = this.splitValue(value2, separators, io);
|
|
14943
|
-
for (const character of pieces) {
|
|
14944
|
-
const text = shellValueText(character);
|
|
14945
|
-
if (typeof character === "string" && separators.includes(character)) {
|
|
14946
|
-
if (!/[ \t\n]/u.test(text)) {
|
|
14947
|
-
fields.at(-1).present = true;
|
|
14948
|
-
fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14949
|
-
} else if (fields.at(-1).present) boundary = true;
|
|
14950
|
-
} else {
|
|
14951
|
-
if (boundary) fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14952
|
-
boundary = false;
|
|
14953
|
-
append(character, true, true);
|
|
14954
|
-
}
|
|
14955
|
-
}
|
|
14956
|
-
if (boundary) fields.push({ value: "", fragments: [], bytes: false, pattern: "", present: false });
|
|
14957
|
-
}
|
|
15190
|
+
else await appendSplit(value2);
|
|
14958
15191
|
}
|
|
14959
15192
|
if (fields.length > this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
14960
15193
|
if (owner) await owner.ledger.checkpoint(this.signal);
|
|
@@ -14963,14 +15196,23 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14963
15196
|
let resultBytes = 0;
|
|
14964
15197
|
for (const field of fields) {
|
|
14965
15198
|
if (!field.present && split) continue;
|
|
15199
|
+
if (scratch && field.fragments.length > 1 && !field.bytes) scratch.reserve(field.fragments.reduce((bytes2, value2) => bytes2 + shellValueText(value2).length * 2, 0), 0);
|
|
14966
15200
|
const assembled = concatShellValues(field.fragments, io[valueScope]);
|
|
14967
15201
|
const projection = shellValueText(assembled);
|
|
14968
|
-
|
|
15202
|
+
if (field.bytes && field.fragments.length > 1 && !field.patterns) {
|
|
15203
|
+
scratch?.reserve(field.fragments.length * 32, 0);
|
|
15204
|
+
field.patterns = field.fragments.map(shellValueText);
|
|
15205
|
+
}
|
|
15206
|
+
if (scratch && field.patterns && field.patterns.length > 1) scratch.reserve(field.patterns.reduce((bytes2, text) => bytes2 + text.length * 2, 0), 0);
|
|
15207
|
+
const fieldPattern = field.patterns ? field.patterns.join("") : projection;
|
|
15208
|
+
const expanded = split ? await this.glob(projection, fieldPattern, state) : [pattern ? fieldPattern : projection];
|
|
14969
15209
|
for (const text of expanded) {
|
|
15210
|
+
if (result.length >= this.budget.limits.maxExpansionFields) this.budget.fail("maxExpansionFields");
|
|
14970
15211
|
const value2 = !pattern && expanded.length === 1 && text === projection ? assembled : text;
|
|
14971
15212
|
const size = shellValueByteLength(value2);
|
|
14972
15213
|
if (size > this.budget.limits.maxExpansionBytes - resultBytes) this.budget.fail("maxExpansionBytes");
|
|
14973
15214
|
resultBytes += size;
|
|
15215
|
+
scratch?.reserve(32, 0);
|
|
14974
15216
|
owner?.reserve({ metadata: 32, allocatedSlots: 1, work: 3 });
|
|
14975
15217
|
result.push(value2);
|
|
14976
15218
|
}
|
|
@@ -14978,17 +15220,50 @@ ${prefix2} line ${offset + line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
14978
15220
|
}
|
|
14979
15221
|
return result;
|
|
14980
15222
|
} finally {
|
|
15223
|
+
scratch?.close();
|
|
14981
15224
|
holding?.release();
|
|
14982
15225
|
}
|
|
14983
15226
|
}
|
|
14984
|
-
|
|
15227
|
+
splitWork;
|
|
15228
|
+
async *splitValue(value2, separators, io, scratch) {
|
|
15229
|
+
this.budget.cpuCheckpoint();
|
|
15230
|
+
const work = this.splitWork ??= { scanned: 0 };
|
|
14985
15231
|
if (typeof value2 === "string" || Array.from(separators).some((character) => character.charCodeAt(0) > 127)) {
|
|
14986
|
-
|
|
15232
|
+
const text = shellValueText(value2);
|
|
15233
|
+
const slice = (start3, end) => {
|
|
15234
|
+
if (start3 === 0 && end === text.length) return text;
|
|
15235
|
+
scratch?.reserve((end - start3) * 2, 0);
|
|
15236
|
+
return text.slice(start3, end);
|
|
15237
|
+
};
|
|
15238
|
+
let start2 = 0;
|
|
15239
|
+
for (let index = 0; index < text.length; ) {
|
|
15240
|
+
const character = String.fromCodePoint(text.codePointAt(index));
|
|
15241
|
+
const end = index + character.length;
|
|
15242
|
+
if (separators.includes(character)) {
|
|
15243
|
+
if (start2 < index) yield slice(start2, index);
|
|
15244
|
+
yield character;
|
|
15245
|
+
start2 = end;
|
|
15246
|
+
} else if (end - start2 >= 4096) {
|
|
15247
|
+
yield slice(start2, end);
|
|
15248
|
+
start2 = end;
|
|
15249
|
+
}
|
|
15250
|
+
work.scanned += character.length;
|
|
15251
|
+
index = end;
|
|
15252
|
+
if (work.scanned >= 4096) {
|
|
15253
|
+
work.scanned = 0;
|
|
15254
|
+
await yieldTurn(this.signal);
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
if (start2 < text.length) yield slice(start2, text.length);
|
|
14987
15258
|
return;
|
|
14988
15259
|
}
|
|
14989
15260
|
const bytes2 = shellValueBytes(value2, io[valueScope]);
|
|
14990
15261
|
let start = 0;
|
|
14991
15262
|
for (let index = 0; index < bytes2.length; index++) {
|
|
15263
|
+
if (++work.scanned >= 4096) {
|
|
15264
|
+
work.scanned = 0;
|
|
15265
|
+
await yieldTurn(this.signal);
|
|
15266
|
+
}
|
|
14992
15267
|
const byte = bytes2[index];
|
|
14993
15268
|
if (byte > 127 || !separators.includes(String.fromCharCode(byte))) continue;
|
|
14994
15269
|
if (start < index) yield shellValueFromBytes(bytes2.subarray(start, index), io[valueScope]);
|
|
@@ -15679,7 +15954,7 @@ var Shell = class {
|
|
|
15679
15954
|
let failed = false;
|
|
15680
15955
|
try {
|
|
15681
15956
|
try {
|
|
15682
|
-
let unit = parseShellUnit(source, 0, byteLocale({ ...this.#options.env, ...options3.env }));
|
|
15957
|
+
let unit = parseShellUnit(source, 0, byteLocale({ ...this.#options.env, ...options3.env }), budget.parsing);
|
|
15683
15958
|
stdin = new ShellInput(typeof options3.stdin === "string" || options3.stdin instanceof Uint8Array ? toByteSource(options3.stdin) : options3.stdin ?? toByteSource(""), budget);
|
|
15684
15959
|
io.stdin = stdin;
|
|
15685
15960
|
await interruptible(this.#ready, budget.signal);
|
|
@@ -15736,7 +16011,7 @@ var Shell = class {
|
|
|
15736
16011
|
}
|
|
15737
16012
|
if (unit.next >= source.length) break;
|
|
15738
16013
|
budget.signal.throwIfAborted();
|
|
15739
|
-
unit = parseShellUnit(source, unit.next, byteLocale(state.variables));
|
|
16014
|
+
unit = parseShellUnit(source, unit.next, byteLocale(state.variables), budget.parsing);
|
|
15740
16015
|
}
|
|
15741
16016
|
} catch (error) {
|
|
15742
16017
|
if (!(error instanceof ShellSyntaxError)) throw error;
|
|
@@ -15813,6 +16088,7 @@ shell: -c: line ${line}: \`${source.split("\n")[line - 1] ?? ""}'
|
|
|
15813
16088
|
// packages/safe-bash/src/shell/worker-limits.ts
|
|
15814
16089
|
init_platform();
|
|
15815
16090
|
var cloudflareWorkerLimits = Object.freeze({
|
|
16091
|
+
maxParseUnits: 65536,
|
|
15816
16092
|
maxInputBytes: 4 * 1024 * 1024,
|
|
15817
16093
|
maxOutputBytes: 4 * 1024 * 1024,
|
|
15818
16094
|
maxCommands: 1e3,
|
|
@@ -18696,7 +18972,7 @@ function descriptor(value2, limits) {
|
|
|
18696
18972
|
for (const flag of flags) if (typeof value2[flag] !== "boolean") fail("protocol", `invalid ${flag} flag`);
|
|
18697
18973
|
if (kind.value === "rg" && !["sensitive", "insensitive", "smart"].includes(value2.case)) fail("protocol", "invalid case flag");
|
|
18698
18974
|
if (value2.word || kind.value === "grep" && value2.insensitive || kind.value === "rg" && value2.case !== "sensitive") fail("unsupported", "only case-sensitive, non-word selection is supported");
|
|
18699
|
-
if (kind.value === "rg" && !value2.fixed) fail("unsupported", "rg regex
|
|
18975
|
+
if (kind.value === "rg" && !value2.fixed) fail("unsupported", "rg regex modes are unsupported; use fixed UTF-8 patterns");
|
|
18700
18976
|
array(value2.patterns, limits.maxPatterns, "pattern");
|
|
18701
18977
|
let bytes2 = 0;
|
|
18702
18978
|
for (let index = 0; index < value2.patterns.length; index++) {
|
|
@@ -18724,7 +19000,7 @@ function admit(input3, limits, signal) {
|
|
|
18724
19000
|
bytes2 += length;
|
|
18725
19001
|
}
|
|
18726
19002
|
const ledger = new EreLedger({ maxExpansionBytes: 1048576, maxExpansionFields: 8192 }, {
|
|
18727
|
-
patternBytes: limits.maxPatternBytes + 4,
|
|
19003
|
+
patternBytes: limits.maxPatternBytes + (selected.fixed ? 0 : 4),
|
|
18728
19004
|
subjectBytes: limits.maxInputBytes,
|
|
18729
19005
|
work: limits.maxWork,
|
|
18730
19006
|
allocationUnits: limits.maxAllocationUnits,
|
|
@@ -18770,8 +19046,116 @@ async function admitBre(pattern, ledger, signal) {
|
|
|
18770
19046
|
}
|
|
18771
19047
|
}
|
|
18772
19048
|
}
|
|
19049
|
+
async function validateUtf8(input3, ledger, signal) {
|
|
19050
|
+
for (let index = 0; index < input3.length; ) {
|
|
19051
|
+
const first = typeof input3 === "string" ? input3.charCodeAt(index) : input3[index];
|
|
19052
|
+
const width = first < 128 ? 1 : first >= 194 && first <= 223 ? 2 : first >= 224 && first <= 239 ? 3 : first >= 240 && first <= 244 ? 4 : 0;
|
|
19053
|
+
ledger.charge("work", Math.max(width, 1), signal);
|
|
19054
|
+
if (first === 0 || width === 0 || width > input3.length - index) fail("unsupported", "literal patterns and subjects require valid non-NUL UTF-8 bytes");
|
|
19055
|
+
for (let continuation = 1; continuation < width; continuation++) {
|
|
19056
|
+
const byte = typeof input3 === "string" ? input3.charCodeAt(index + continuation) : input3[index + continuation];
|
|
19057
|
+
if (byte < 128 || byte > 191 || continuation === 1 && (first === 224 && byte < 160 || first === 237 && byte > 159 || first === 240 && byte < 144 || first === 244 && byte > 143)) fail("unsupported", "literal patterns and subjects require valid non-NUL UTF-8 bytes");
|
|
19058
|
+
}
|
|
19059
|
+
index += width;
|
|
19060
|
+
await ledger.checkpoint(signal);
|
|
19061
|
+
}
|
|
19062
|
+
}
|
|
19063
|
+
async function literalBytes(pattern, selected, ledger, signal) {
|
|
19064
|
+
const { kind } = selected;
|
|
19065
|
+
let length = 0;
|
|
19066
|
+
if (kind === "grep") {
|
|
19067
|
+
await validateUtf8(pattern, ledger, signal);
|
|
19068
|
+
length = pattern.length;
|
|
19069
|
+
} else {
|
|
19070
|
+
for (let index = 0; index < pattern.length; ) {
|
|
19071
|
+
const scalar = pattern.codePointAt(index);
|
|
19072
|
+
const units = scalar > 65535 ? 2 : 1;
|
|
19073
|
+
ledger.charge("work", units, signal);
|
|
19074
|
+
if (scalar === 0 || scalar >= 55296 && scalar <= 57343) fail("unsupported", "rg literal patterns require non-NUL Unicode scalars for UTF-8");
|
|
19075
|
+
if (scalar === 10 && selected.kind === "rg" && !selected.nullData) fail("unsupported", "rg multiline matching is unsupported");
|
|
19076
|
+
length += scalar < 128 ? 1 : scalar < 2048 ? 2 : scalar < 65536 ? 3 : 4;
|
|
19077
|
+
index += units;
|
|
19078
|
+
await ledger.checkpoint(signal);
|
|
19079
|
+
}
|
|
19080
|
+
}
|
|
19081
|
+
ledger.charge("patternBytes", length, signal);
|
|
19082
|
+
ledger.charge("allocationUnits", length + 1, signal);
|
|
19083
|
+
const bytes2 = new Uint8Array(length);
|
|
19084
|
+
let offset = 0;
|
|
19085
|
+
for (let index = 0; index < pattern.length; ) {
|
|
19086
|
+
const scalar = kind === "grep" ? pattern.charCodeAt(index) : pattern.codePointAt(index);
|
|
19087
|
+
const width = kind === "grep" || scalar < 128 ? 1 : scalar < 2048 ? 2 : scalar < 65536 ? 3 : 4;
|
|
19088
|
+
ledger.charge("work", width, signal);
|
|
19089
|
+
if (width === 1) bytes2[offset++] = scalar;
|
|
19090
|
+
else {
|
|
19091
|
+
bytes2[offset++] = width === 2 ? 192 | scalar >> 6 : width === 3 ? 224 | scalar >> 12 : 240 | scalar >> 18;
|
|
19092
|
+
if (width === 4) bytes2[offset++] = 128 | scalar >> 12 & 63;
|
|
19093
|
+
if (width >= 3) bytes2[offset++] = 128 | scalar >> 6 & 63;
|
|
19094
|
+
bytes2[offset++] = 128 | scalar & 63;
|
|
19095
|
+
}
|
|
19096
|
+
index += kind === "rg" && scalar > 65535 ? 2 : 1;
|
|
19097
|
+
await ledger.checkpoint(signal);
|
|
19098
|
+
}
|
|
19099
|
+
return bytes2;
|
|
19100
|
+
}
|
|
19101
|
+
async function compileLiteral(bytes2, ledger, signal) {
|
|
19102
|
+
ledger.charge("states", bytes2.length, signal);
|
|
19103
|
+
ledger.charge("allocationUnits", bytes2.length * 4 + 3, signal);
|
|
19104
|
+
const fallback = new Uint32Array(bytes2.length);
|
|
19105
|
+
for (let index = 1, prefix2 = 0; index < bytes2.length; ) {
|
|
19106
|
+
ledger.charge("work", 1, signal);
|
|
19107
|
+
if (bytes2[index] === bytes2[prefix2]) fallback[index++] = ++prefix2;
|
|
19108
|
+
else if (prefix2 > 0) prefix2 = fallback[prefix2 - 1];
|
|
19109
|
+
else index++;
|
|
19110
|
+
await ledger.checkpoint(signal);
|
|
19111
|
+
}
|
|
19112
|
+
return { bytes: bytes2, fallback };
|
|
19113
|
+
}
|
|
19114
|
+
async function literalStart(program, subject, whole, ledger, signal) {
|
|
19115
|
+
const { bytes: bytes2, fallback } = program;
|
|
19116
|
+
ledger.charge("work", 1, signal);
|
|
19117
|
+
await ledger.checkpoint(signal);
|
|
19118
|
+
if (whole && bytes2.length !== subject.length || bytes2.length > subject.length) return -1;
|
|
19119
|
+
if (bytes2.length === 0) return 0;
|
|
19120
|
+
for (let index = 0, prefix2 = 0; index < subject.length; ) {
|
|
19121
|
+
ledger.charge("work", 1, signal);
|
|
19122
|
+
if (subject[index] === bytes2[prefix2]) {
|
|
19123
|
+
index++;
|
|
19124
|
+
if (++prefix2 === bytes2.length) return index - prefix2;
|
|
19125
|
+
} else if (prefix2 > 0) prefix2 = fallback[prefix2 - 1];
|
|
19126
|
+
else index++;
|
|
19127
|
+
await ledger.checkpoint(signal);
|
|
19128
|
+
}
|
|
19129
|
+
return -1;
|
|
19130
|
+
}
|
|
19131
|
+
async function executeLiteral(input3, signal) {
|
|
19132
|
+
const { descriptor: selected, rows, ledger } = input3;
|
|
19133
|
+
const programs2 = [];
|
|
19134
|
+
for (const pattern of selected.patterns) {
|
|
19135
|
+
programs2.push(await compileLiteral(await literalBytes(pattern, selected, ledger, signal), ledger, signal));
|
|
19136
|
+
}
|
|
19137
|
+
const results = [];
|
|
19138
|
+
for (const row of rows) {
|
|
19139
|
+
await validateUtf8(row.bytes, ledger, signal);
|
|
19140
|
+
let start = -1;
|
|
19141
|
+
let end = -1;
|
|
19142
|
+
for (const program of programs2) {
|
|
19143
|
+
const candidate = await literalStart(program, row.bytes, selected.whole, ledger, signal);
|
|
19144
|
+
if (candidate < 0) continue;
|
|
19145
|
+
if (start < 0 || candidate < start) {
|
|
19146
|
+
start = candidate;
|
|
19147
|
+
end = start + program.bytes.length;
|
|
19148
|
+
}
|
|
19149
|
+
if (selected.kind === "grep") break;
|
|
19150
|
+
}
|
|
19151
|
+
signal.throwIfAborted();
|
|
19152
|
+
results.push(start < 0 ? new Float64Array() : new Float64Array([start, end]));
|
|
19153
|
+
}
|
|
19154
|
+
return { id: input3.id, results };
|
|
19155
|
+
}
|
|
18773
19156
|
async function execute2(input3, signal) {
|
|
18774
19157
|
const { descriptor: selected, rows, ledger } = input3;
|
|
19158
|
+
if (selected.fixed) return executeLiteral(input3, signal);
|
|
18775
19159
|
const programs2 = [];
|
|
18776
19160
|
for (const pattern of selected.patterns) {
|
|
18777
19161
|
if (selected.kind === "grep" && !selected.fixed && !selected.extended) await admitBre(pattern, ledger, signal);
|