@sema-agent/core 7.45.0 → 7.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +298 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +6 -4
- package/dist/core/allow-entry.js +6 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +24 -7
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +12 -4
- package/dist/core/gate-fold.js +5 -2
- package/dist/core/gate-lanes.js +11 -4
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +59 -24
- package/dist/core/permission-rule-model.js +48 -44
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +26 -45
- package/dist/core/permission-rules.js +77 -57
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +11 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +13 -5
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +12 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +40 -17
- package/dist/core/tool-policy.js +11 -9
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +14 -3
- package/dist/core/write-protect.js +41 -10
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +22 -0
- package/dist/shell/lex.js +75 -13
- package/dist/tools/fs/bash-program-position.d.ts +14 -90
- package/dist/tools/fs/bash-program-position.js +41 -74
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +711 -0
- package/dist/tools/fs/program-read-model.js +1259 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +624 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +34 -24
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +86 -0
- package/dist/tools/fs/shell-read-argv.js +388 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +300 -0
- package/dist/tools/fs/shell-read-bindings.js +755 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +82 -8
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +33 -8
- package/dist/tools/fs/shell-read-plan.js +309 -407
- package/dist/tools/fs/shell-read-word.d.ts +60 -26
- package/dist/tools/fs/shell-read-word.js +47 -37
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +85 -137
- package/dist/tools/fs/shell-reentry-text.d.ts +136 -6
- package/dist/tools/fs/shell-reentry-text.js +203 -53
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +25 -5
- package/wire-changes/core@7.46.0.json +1522 -0
- package/wire-changes/core@7.47.0.json +412 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf } from "./safety.js";
|
|
2
2
|
import { PARSE_FAILURE_SENTENCE, UNRESOLVED_SENTENCE, argvOf, lexShell, literalTildeSpelling, } from "../../shell/lex.js";
|
|
3
|
-
import {
|
|
3
|
+
import { textBindsHome } from "./shell-read-bindings.js";
|
|
4
|
+
import { NOT_AUTO_ALLOWED, argvReentryUnjudged, isBareControlWord, programPositionRefusalSentence, readProgramPosition, } from "./bash-program-position.js";
|
|
5
|
+
import { BLOCK_WORD_NAMES, attachedStdinFile, declaresOption, firstOperandSlot, opensNoOperand, programDelivery, programRow, programTextReadings, readArgRoles, readArgs, readProgramTextLanguage, runsShellText, selectedOptions, slotValueCandidates, suppliesFirstOperandSlot, takesSeparatedDatum, valuesAreLiteral, writeReadings, writesPath, } from "./program-read-model.js";
|
|
4
6
|
export const BASH_READONLY_DEFAULT_ALLOW = [
|
|
5
7
|
"ls", "cat", "head", "tail", "wc", "pwd", "echo", "whoami", "uname",
|
|
6
8
|
"grep", "cut", "tr", "basename", "dirname", "stat", "du", "df", "which",
|
|
@@ -121,7 +123,7 @@ export function strictResidualRefusal(lexed, source, opts = {}) {
|
|
|
121
123
|
}
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
|
-
const home = opts.homeDir !== undefined && !
|
|
126
|
+
const home = opts.homeDir !== undefined && !textBindsHome(source) ? opts.homeDir : undefined;
|
|
125
127
|
for (const w of words) {
|
|
126
128
|
if (expandsDollar(w) && !(home !== undefined && homeSpelledForClassify(w, home) !== undefined))
|
|
127
129
|
return reason;
|
|
@@ -159,99 +161,6 @@ export function formatOutOfRootReadApprovalOption(directory) {
|
|
|
159
161
|
const leaf = cut >= 0 ? trimmed.slice(cut + 1) : trimmed;
|
|
160
162
|
return `Yes, allow reading from ${leaf || directory}/ from this project`;
|
|
161
163
|
}
|
|
162
|
-
export const NO_PATH_OPERAND_COMMANDS = new Set([
|
|
163
|
-
"pwd", "echo", "printf", "whoami", "uname", "which", "tr", "basename", "dirname",
|
|
164
|
-
"cal", "uptime", "id", "groups", "nproc", "locale", "free", "true", "false", "seq", "expr", "type", "sleep",
|
|
165
|
-
"test", "[",
|
|
166
|
-
]);
|
|
167
|
-
export const NO_PATH_OPERAND_EVALUATORS = new Set(["test", "["]);
|
|
168
|
-
export const DECLARATION_BUILTINS = new Set(["export", "declare", "typeset", "local", "readonly"]);
|
|
169
|
-
export const INERT_BUILTINS = new Set(["echo", "true", "false", "pwd", ...DECLARATION_BUILTINS]);
|
|
170
|
-
const MESSAGE_SLOTS = {
|
|
171
|
-
git: {
|
|
172
|
-
globalPlain: ["-p", "--paginate", "-P", "--no-pager", "--bare", "--no-replace-objects", "--literal-pathspecs", "--glob-pathspecs", "--noglob-pathspecs", "--icase-pathspecs", "--no-optional-locks"],
|
|
173
|
-
globalValue: ["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env", "--super-prefix"],
|
|
174
|
-
subcommands: {
|
|
175
|
-
commit: { plain: "apsnvqeioz", optional: "Su", value: "mFCct", longValue: ["message", "file", "template", "pathspec-from-file", "author", "date", "cleanup", "fixup", "squash", "trailer", "reuse-message", "reedit-message"] },
|
|
176
|
-
tag: { plain: "asfdvle", optional: "n", value: "mFu", longValue: ["message", "file", "local-user", "cleanup", "trailer", "sort", "format", "contains", "no-contains", "points-at", "merged", "no-merged"] },
|
|
177
|
-
merge: { plain: "nqve", optional: "S", value: "mFsX", longValue: ["message", "file", "strategy", "strategy-option", "into-name", "cleanup"] },
|
|
178
|
-
stash: { plain: "pkuaqS", optional: "", value: "m", longValue: ["message", "pathspec-from-file"] },
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
export function messageSlotWords(program, words, programIndex) {
|
|
183
|
-
const out = new Set();
|
|
184
|
-
const row = MESSAGE_SLOTS[program];
|
|
185
|
-
if (row === undefined)
|
|
186
|
-
return out;
|
|
187
|
-
let j = programIndex + 1;
|
|
188
|
-
for (; j < words.length; j++) {
|
|
189
|
-
const w = words[j].folded;
|
|
190
|
-
if (!w.startsWith("-"))
|
|
191
|
-
break;
|
|
192
|
-
if (row.globalValue.includes(w))
|
|
193
|
-
j++;
|
|
194
|
-
else if (!row.globalPlain.includes(w) && !row.globalValue.some((o) => o.startsWith("--") && w.startsWith(`${o}=`)))
|
|
195
|
-
return out;
|
|
196
|
-
}
|
|
197
|
-
const sub = row.subcommands[words[j]?.folded ?? ""];
|
|
198
|
-
if (sub === undefined)
|
|
199
|
-
return out;
|
|
200
|
-
const settled = (k) => words[k] !== undefined && words[k].expands === false;
|
|
201
|
-
let pending;
|
|
202
|
-
for (let k = j + 1; k < words.length; k++) {
|
|
203
|
-
const w = words[k].folded;
|
|
204
|
-
if (pending !== undefined) {
|
|
205
|
-
if (pending === "message" && settled(k))
|
|
206
|
-
out.add(k);
|
|
207
|
-
pending = undefined;
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
if (w === "--")
|
|
211
|
-
break;
|
|
212
|
-
if (!settled(k)) {
|
|
213
|
-
pending = "other";
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (!w.startsWith("-") || w === "-")
|
|
217
|
-
continue;
|
|
218
|
-
if (w.startsWith("--")) {
|
|
219
|
-
const eq = w.indexOf("=");
|
|
220
|
-
const name = w.slice(2, eq < 0 ? undefined : eq);
|
|
221
|
-
const takers = sub.longValue.filter((n) => n.startsWith(name));
|
|
222
|
-
const message = name.length > 0 && takers.length > 0 && takers.every((n) => n === "message") || name === "message";
|
|
223
|
-
if (eq >= 0) {
|
|
224
|
-
if (message)
|
|
225
|
-
out.add(k);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (takers.length > 0)
|
|
229
|
-
pending = message ? "message" : "other";
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const letters = w.slice(1);
|
|
233
|
-
for (let i = 0; i < letters.length; i++) {
|
|
234
|
-
const c = letters[i];
|
|
235
|
-
if (sub.plain.includes(c))
|
|
236
|
-
continue;
|
|
237
|
-
if (sub.optional.includes(c))
|
|
238
|
-
break;
|
|
239
|
-
if (sub.value.includes(c)) {
|
|
240
|
-
if (i < letters.length - 1) {
|
|
241
|
-
if (c === "m")
|
|
242
|
-
out.add(k);
|
|
243
|
-
}
|
|
244
|
-
else
|
|
245
|
-
pending = c === "m" ? "message" : "other";
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
pending = "other";
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return out;
|
|
253
|
-
}
|
|
254
|
-
const XARGS_TARGETS_ACCEPTED_UNJUDGED = NO_PATH_OPERAND_COMMANDS;
|
|
255
164
|
export function isPathShapedToken(tok) {
|
|
256
165
|
return tok.includes("/") || tok.startsWith("~") || tok === "." || tok === "..";
|
|
257
166
|
}
|
|
@@ -287,7 +196,6 @@ function spellingsOf(absolute) {
|
|
|
287
196
|
function reheadSegment(toks, head, from = 0) {
|
|
288
197
|
return { folded: [head, ...toks.folded.slice(from + 1)], raw: [head, ...toks.raw.slice(from + 1)] };
|
|
289
198
|
}
|
|
290
|
-
export const SHELL_REENTRY_PROGRAM_RE = /^(?:(?:ba|z|da|k|fi|a|mk|ya|pdk|c|tc)?sh|eval|source|\.|parallel)$/;
|
|
291
199
|
function segmentSpellings(words, homeDir, homeRebound) {
|
|
292
200
|
const folded = [];
|
|
293
201
|
const raw = [];
|
|
@@ -303,6 +211,8 @@ export function expansionInWord(w) {
|
|
|
303
211
|
const k = p.expansion?.kind;
|
|
304
212
|
if (k === "parameter" || k === "command" || k === "process" || k === "arithmetic" || k === "brace")
|
|
305
213
|
return true;
|
|
214
|
+
if (k === "glob" && /[$`]/.test(p.expansion?.body ?? ""))
|
|
215
|
+
return true;
|
|
306
216
|
if (p.literal === undefined)
|
|
307
217
|
continue;
|
|
308
218
|
if (p.quotedBy === undefined && /[{}$`]/.test(p.literal))
|
|
@@ -322,437 +232,6 @@ export function globInWord(w) {
|
|
|
322
232
|
}
|
|
323
233
|
return false;
|
|
324
234
|
}
|
|
325
|
-
function longOptionNameOf(tok) {
|
|
326
|
-
if (!tok.startsWith("--") || tok.length === 2)
|
|
327
|
-
return undefined;
|
|
328
|
-
const body = tok.slice(2);
|
|
329
|
-
const eq = body.indexOf("=");
|
|
330
|
-
const name = eq >= 0 ? body.slice(0, eq) : body;
|
|
331
|
-
return name.length > 0 ? name : undefined;
|
|
332
|
-
}
|
|
333
|
-
function isLongOptionAbbrevOf(name, full) {
|
|
334
|
-
return full.startsWith(name);
|
|
335
|
-
}
|
|
336
|
-
export function suppliesFirstOperandSlot(slot, tok) {
|
|
337
|
-
if (tok.startsWith("--")) {
|
|
338
|
-
const long = longOptionNameOf(tok);
|
|
339
|
-
return long !== undefined && slot.suppliedByLongNames.some((full) => isLongOptionAbbrevOf(long, full));
|
|
340
|
-
}
|
|
341
|
-
return new RegExp(`^-[A-Za-z0-9]*[${slot.suppliedByLetters}]`).test(tok);
|
|
342
|
-
}
|
|
343
|
-
function isGrepClusterFileStdin(tok) {
|
|
344
|
-
if (tok.startsWith("--") || !/^-[A-Za-z0-9]/.test(tok))
|
|
345
|
-
return false;
|
|
346
|
-
for (let i = 1; i < tok.length; i++) {
|
|
347
|
-
const ch = tok[i];
|
|
348
|
-
if (ch === "e")
|
|
349
|
-
return false;
|
|
350
|
-
if (ch === "f")
|
|
351
|
-
return tok.slice(i + 1) === "-";
|
|
352
|
-
if (!/[A-Za-z0-9]/.test(ch))
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
return false;
|
|
356
|
-
}
|
|
357
|
-
function valueNamesAFile(kind) {
|
|
358
|
-
return kind === "path" || kind === "indirect" || kind === "form" || kind === "header" || kind === "url";
|
|
359
|
-
}
|
|
360
|
-
function widestValueKind(kinds) {
|
|
361
|
-
if (kinds.has("indirect"))
|
|
362
|
-
return "indirect";
|
|
363
|
-
if (kinds.has("path"))
|
|
364
|
-
return "path";
|
|
365
|
-
if (kinds.has("form"))
|
|
366
|
-
return "form";
|
|
367
|
-
if (kinds.has("header"))
|
|
368
|
-
return "header";
|
|
369
|
-
if (kinds.has("url"))
|
|
370
|
-
return "url";
|
|
371
|
-
return [...kinds][0];
|
|
372
|
-
}
|
|
373
|
-
const VERB_OPTION_SLOTS = {
|
|
374
|
-
head: {
|
|
375
|
-
shortValue: { c: "count", n: "count" },
|
|
376
|
-
shortPlain: "qvz",
|
|
377
|
-
longValue: { bytes: "count", lines: "count" },
|
|
378
|
-
longOptionalValue: {},
|
|
379
|
-
longPlain: ["help", "quiet", "silent", "verbose", "version", "zero-terminated"],
|
|
380
|
-
},
|
|
381
|
-
tail: {
|
|
382
|
-
shortValue: { b: "count", c: "count", n: "count", s: "count" },
|
|
383
|
-
shortPlain: "fFqvz",
|
|
384
|
-
longValue: { bytes: "count", lines: "count", "max-unchanged-stats": "count", pid: "count", "sleep-interval": "count" },
|
|
385
|
-
longOptionalValue: { follow: "label" },
|
|
386
|
-
longPlain: ["help", "quiet", "retry", "silent", "verbose", "version", "zero-terminated"],
|
|
387
|
-
},
|
|
388
|
-
cut: {
|
|
389
|
-
shortValue: { b: "count", c: "count", d: "delimiter", f: "count" },
|
|
390
|
-
shortPlain: "nsz",
|
|
391
|
-
longValue: { bytes: "count", characters: "count", delimiter: "delimiter", fields: "count", "output-delimiter": "delimiter" },
|
|
392
|
-
longOptionalValue: {},
|
|
393
|
-
longPlain: ["complement", "help", "only-delimited", "version", "zero-terminated"],
|
|
394
|
-
},
|
|
395
|
-
grep: {
|
|
396
|
-
shortValue: { A: "count", B: "count", C: "count", D: "label", d: "label", e: "pattern", f: "path", m: "count" },
|
|
397
|
-
shortPlain: "0123456789abcEFGhHiIlLnoPqrRsUvVwxyzZ",
|
|
398
|
-
longValue: {
|
|
399
|
-
"after-context": "count",
|
|
400
|
-
"before-context": "count",
|
|
401
|
-
"binary-files": "label",
|
|
402
|
-
devices: "label",
|
|
403
|
-
directories: "label",
|
|
404
|
-
exclude: "name-pattern",
|
|
405
|
-
"exclude-dir": "name-pattern",
|
|
406
|
-
"exclude-from": "path",
|
|
407
|
-
file: "path",
|
|
408
|
-
"group-separator": "label",
|
|
409
|
-
include: "name-pattern",
|
|
410
|
-
label: "label",
|
|
411
|
-
"max-count": "count",
|
|
412
|
-
regexp: "pattern",
|
|
413
|
-
},
|
|
414
|
-
longOptionalValue: { color: "label", colour: "label", context: "count" },
|
|
415
|
-
firstOperand: { kind: "pattern", suppliedByLetters: "ef", suppliedByLongNames: ["regexp", "file"] },
|
|
416
|
-
longPlain: [
|
|
417
|
-
"basic-regexp", "byte-offset", "count", "dereference-recursive", "extended-regexp",
|
|
418
|
-
"files-with-matches", "files-without-match", "fixed-strings", "help", "ignore-case", "initial-tab",
|
|
419
|
-
"invert-match", "line-buffered", "line-number", "line-regexp", "no-filename", "no-group-separator",
|
|
420
|
-
"no-messages", "null", "null-data", "only-matching", "perl-regexp", "quiet", "recursive", "silent",
|
|
421
|
-
"text", "version", "with-filename", "word-regexp",
|
|
422
|
-
],
|
|
423
|
-
},
|
|
424
|
-
diff: {
|
|
425
|
-
shortValue: { C: "count", D: "label", F: "pattern", I: "pattern", L: "label", S: "path", U: "count", W: "count", X: "path", x: "name-pattern" },
|
|
426
|
-
shortPlain: "abBcdeEHilnNpqrstTuvwyZ",
|
|
427
|
-
longValue: {
|
|
428
|
-
exclude: "name-pattern",
|
|
429
|
-
"exclude-from": "path",
|
|
430
|
-
"from-file": "path",
|
|
431
|
-
"horizon-lines": "count",
|
|
432
|
-
ifdef: "label",
|
|
433
|
-
"ignore-matching-lines": "pattern",
|
|
434
|
-
label: "label",
|
|
435
|
-
"show-function-line": "pattern",
|
|
436
|
-
"starting-file": "path",
|
|
437
|
-
tabsize: "count",
|
|
438
|
-
"to-file": "path",
|
|
439
|
-
width: "count",
|
|
440
|
-
},
|
|
441
|
-
longOptionalValue: { color: "label", context: "count", unified: "count" },
|
|
442
|
-
longPlain: [
|
|
443
|
-
"brief", "ed", "expand-tabs", "help", "ignore-all-space", "ignore-blank-lines", "ignore-case",
|
|
444
|
-
"ignore-space-change", "ignore-tab-expansion", "ignore-trailing-space", "initial-tab", "minimal",
|
|
445
|
-
"new-file", "normal", "paginate", "recursive", "report-identical-files", "rcs", "show-c-function",
|
|
446
|
-
"side-by-side", "speed-large-files", "suppress-blank-empty", "text", "version",
|
|
447
|
-
],
|
|
448
|
-
},
|
|
449
|
-
cmp: {
|
|
450
|
-
shortValue: { i: "count", n: "count" },
|
|
451
|
-
shortPlain: "blsv",
|
|
452
|
-
longValue: { bytes: "count", "ignore-initial": "count" },
|
|
453
|
-
longOptionalValue: {},
|
|
454
|
-
longPlain: ["help", "print-bytes", "quiet", "silent", "verbose", "version"],
|
|
455
|
-
},
|
|
456
|
-
sed: {
|
|
457
|
-
shortValue: { e: "script", f: "path" },
|
|
458
|
-
shortPlain: "nrEszu",
|
|
459
|
-
longValue: { expression: "script", file: "path", "line-length": "count" },
|
|
460
|
-
longOptionalValue: { "in-place": "label" },
|
|
461
|
-
firstOperand: { kind: "script", suppliedByLetters: "ef", suppliedByLongNames: ["expression", "file"] },
|
|
462
|
-
longPlain: [
|
|
463
|
-
"debug", "follow-symlinks", "help", "null-data", "posix", "quiet", "regexp-extended", "sandbox",
|
|
464
|
-
"separate", "silent", "unbuffered", "version", "zero-terminated",
|
|
465
|
-
],
|
|
466
|
-
},
|
|
467
|
-
date: {
|
|
468
|
-
shortValue: { d: "count", f: "path", r: "path", s: "count" },
|
|
469
|
-
shortPlain: "uR",
|
|
470
|
-
longValue: { date: "count", file: "path", reference: "path", "rfc-3339": "count", set: "count" },
|
|
471
|
-
longOptionalValue: { "iso-8601": "label" },
|
|
472
|
-
longPlain: ["debug", "help", "rfc-822", "rfc-email", "universal", "utc", "version"],
|
|
473
|
-
},
|
|
474
|
-
tar: {
|
|
475
|
-
shortValue: { b: "count", C: "path", f: "path", T: "path", X: "path" },
|
|
476
|
-
shortPlain: "acdhjJkmOpPrStuvwWxzZ",
|
|
477
|
-
longValue: {
|
|
478
|
-
"blocking-factor": "count",
|
|
479
|
-
directory: "path",
|
|
480
|
-
exclude: "name-pattern",
|
|
481
|
-
"exclude-from": "path",
|
|
482
|
-
file: "path",
|
|
483
|
-
"files-from": "path",
|
|
484
|
-
"strip-components": "count",
|
|
485
|
-
},
|
|
486
|
-
longOptionalValue: {},
|
|
487
|
-
longPlain: [
|
|
488
|
-
"absolute-names", "append", "create", "dereference", "extract", "get", "help", "interactive",
|
|
489
|
-
"keep-old-files", "list", "preserve-permissions", "sparse", "to-stdout", "touch", "update",
|
|
490
|
-
"verbose", "verify", "version",
|
|
491
|
-
],
|
|
492
|
-
},
|
|
493
|
-
find: {
|
|
494
|
-
shortValue: { D: "label", O: "count", f: "path" },
|
|
495
|
-
shortPlain: "HLPdsx",
|
|
496
|
-
longValue: {},
|
|
497
|
-
longOptionalValue: {},
|
|
498
|
-
longPlain: ["help", "version"],
|
|
499
|
-
dashWordValue: {
|
|
500
|
-
amin: "count", anewer: "path", atime: "count", cmin: "count", cnewer: "path", context: "label",
|
|
501
|
-
ctime: "count", fstype: "label", gid: "count", group: "label", ilname: "path", iname: "name-pattern",
|
|
502
|
-
inum: "count", ipath: "path", iregex: "name-pattern", iwholename: "path", links: "count",
|
|
503
|
-
lname: "path", maxdepth: "count", mindepth: "count", mmin: "count", mtime: "count", name: "name-pattern",
|
|
504
|
-
newer: "path", newerat: "path", neweraa: "path", neweram: "path", newerac: "path",
|
|
505
|
-
newerct: "path", newerca: "path", newercm: "path", newercc: "path",
|
|
506
|
-
newermt: "path", newerma: "path", newermm: "path", newermc: "path",
|
|
507
|
-
newerbt: "path", newerba: "path", newerbm: "path", newerbc: "path",
|
|
508
|
-
path: "path", perm: "label", printf: "label", regex: "name-pattern", regextype: "label",
|
|
509
|
-
samefile: "path", size: "count", type: "label", uid: "count", used: "count", user: "label",
|
|
510
|
-
wholename: "path", xtype: "label",
|
|
511
|
-
},
|
|
512
|
-
dashWordPlain: [
|
|
513
|
-
"and", "daystart", "depth", "empty", "executable", "false", "follow", "help", "ignore_readdir_race",
|
|
514
|
-
"mount", "noignore_readdir_race", "noleaf", "not", "nogroup", "nouser", "or", "print",
|
|
515
|
-
"print0", "prune", "quit", "readable", "true", "version", "writable", "xdev",
|
|
516
|
-
],
|
|
517
|
-
optionsArePositionIndependent: true,
|
|
518
|
-
},
|
|
519
|
-
curl: {
|
|
520
|
-
shortValue: {
|
|
521
|
-
A: "label", b: "indirect", C: "count", c: "path", D: "path", d: "indirect", E: "path", e: "label",
|
|
522
|
-
F: "form", H: "header", K: "path", m: "count", o: "path", P: "label", Q: "label", r: "count",
|
|
523
|
-
T: "path", t: "label", U: "label", u: "label", w: "indirect", X: "label", x: "label", Y: "count",
|
|
524
|
-
y: "count", z: "path",
|
|
525
|
-
},
|
|
526
|
-
shortPlain: "#0123456aBfgGhiIjJklLMnNOpqRsSvVZ",
|
|
527
|
-
longValue: {
|
|
528
|
-
cacert: "path",
|
|
529
|
-
capath: "path",
|
|
530
|
-
cert: "path",
|
|
531
|
-
config: "path",
|
|
532
|
-
"connect-timeout": "count",
|
|
533
|
-
cookie: "indirect",
|
|
534
|
-
"cookie-jar": "path",
|
|
535
|
-
data: "indirect",
|
|
536
|
-
"data-ascii": "indirect",
|
|
537
|
-
"data-binary": "indirect",
|
|
538
|
-
"data-raw": "label",
|
|
539
|
-
"data-urlencode": "form",
|
|
540
|
-
"dump-header": "path",
|
|
541
|
-
form: "form",
|
|
542
|
-
"form-string": "label",
|
|
543
|
-
header: "header",
|
|
544
|
-
key: "path",
|
|
545
|
-
"max-time": "count",
|
|
546
|
-
"netrc-file": "path",
|
|
547
|
-
output: "path",
|
|
548
|
-
proxy: "label",
|
|
549
|
-
range: "count",
|
|
550
|
-
referer: "label",
|
|
551
|
-
request: "label",
|
|
552
|
-
resolve: "label",
|
|
553
|
-
trace: "path",
|
|
554
|
-
"time-cond": "path",
|
|
555
|
-
"trace-ascii": "path",
|
|
556
|
-
"upload-file": "path",
|
|
557
|
-
url: "url",
|
|
558
|
-
user: "label",
|
|
559
|
-
"user-agent": "label",
|
|
560
|
-
"write-out": "indirect",
|
|
561
|
-
},
|
|
562
|
-
longOptionalValue: {},
|
|
563
|
-
longPlain: [
|
|
564
|
-
"append", "compressed", "disable", "fail", "get", "globoff", "head", "help", "include", "insecure",
|
|
565
|
-
"junk-session-cookies", "list-only", "location", "manual", "netrc", "next", "no-buffer", "parallel",
|
|
566
|
-
"proxytunnel", "remote-header-name", "remote-name", "remote-time", "show-error", "silent",
|
|
567
|
-
"use-ascii", "verbose", "version",
|
|
568
|
-
],
|
|
569
|
-
},
|
|
570
|
-
};
|
|
571
|
-
export function readVerbOptionSlot(name, tok) {
|
|
572
|
-
const row = VERB_OPTION_SLOTS[name];
|
|
573
|
-
if (row === undefined)
|
|
574
|
-
return undefined;
|
|
575
|
-
if (!tok.startsWith("-") || tok === "-" || tok === "--")
|
|
576
|
-
return { form: "unexplained" };
|
|
577
|
-
if (tok.startsWith("--")) {
|
|
578
|
-
const long = longOptionNameOf(tok);
|
|
579
|
-
if (long === undefined)
|
|
580
|
-
return { form: "unexplained" };
|
|
581
|
-
const eq = tok.indexOf("=");
|
|
582
|
-
const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
|
|
583
|
-
const withValue = (kind) => attached !== undefined ? { form: "attached", value: attached, kind } : { form: "separated", kind };
|
|
584
|
-
const exact = row.longValue[long];
|
|
585
|
-
if (exact !== undefined)
|
|
586
|
-
return withValue(exact);
|
|
587
|
-
const exactOptional = row.longOptionalValue[long];
|
|
588
|
-
if (exactOptional !== undefined)
|
|
589
|
-
return attached !== undefined ? { form: "attached", value: attached, kind: exactOptional } : { form: "no-value" };
|
|
590
|
-
if (row.longPlain.includes(long))
|
|
591
|
-
return attached === undefined ? { form: "no-value" } : { form: "unexplained" };
|
|
592
|
-
const valueNames = [
|
|
593
|
-
...Object.keys(row.longValue),
|
|
594
|
-
...(attached !== undefined ? Object.keys(row.longOptionalValue) : []),
|
|
595
|
-
].filter((full) => isLongOptionAbbrevOf(long, full));
|
|
596
|
-
const plainNames = [
|
|
597
|
-
...row.longPlain,
|
|
598
|
-
...(attached === undefined ? Object.keys(row.longOptionalValue) : []),
|
|
599
|
-
].filter((full) => isLongOptionAbbrevOf(long, full));
|
|
600
|
-
if (valueNames.length === 0)
|
|
601
|
-
return plainNames.length > 0 && attached === undefined ? { form: "no-value" } : { form: "unexplained" };
|
|
602
|
-
const kinds = new Set(valueNames.map((full) => (row.longValue[full] ?? row.longOptionalValue[full])));
|
|
603
|
-
if (attached !== undefined)
|
|
604
|
-
return { form: "attached", value: attached, kind: widestValueKind(kinds) };
|
|
605
|
-
if (plainNames.length > 0 || kinds.size !== 1)
|
|
606
|
-
return { form: "unexplained" };
|
|
607
|
-
return withValue([...kinds][0]);
|
|
608
|
-
}
|
|
609
|
-
const word = row.dashWordValue !== undefined || row.dashWordPlain !== undefined ? /^-([A-Za-z_][\w-]+)$/.exec(tok)?.[1] : undefined;
|
|
610
|
-
if (word !== undefined) {
|
|
611
|
-
if (row.shortValue[word[0]] !== undefined)
|
|
612
|
-
return { form: "unexplained" };
|
|
613
|
-
const kind = row.dashWordValue?.[word];
|
|
614
|
-
if (kind !== undefined)
|
|
615
|
-
return { form: "separated", kind };
|
|
616
|
-
if (row.dashWordPlain?.includes(word) === true)
|
|
617
|
-
return { form: "no-value" };
|
|
618
|
-
return { form: "unexplained" };
|
|
619
|
-
}
|
|
620
|
-
for (let i = 1; i < tok.length; i++) {
|
|
621
|
-
const ch = tok[i];
|
|
622
|
-
const kind = row.shortValue[ch];
|
|
623
|
-
if (kind !== undefined) {
|
|
624
|
-
const rest = tok.slice(i + 1);
|
|
625
|
-
return rest !== "" ? { form: "attached", value: rest, kind } : { form: "separated", kind };
|
|
626
|
-
}
|
|
627
|
-
if (!row.shortPlain.includes(ch))
|
|
628
|
-
return { form: "unexplained" };
|
|
629
|
-
}
|
|
630
|
-
return { form: "no-value" };
|
|
631
|
-
}
|
|
632
|
-
export function verbOptionsArePositionIndependent(name) {
|
|
633
|
-
return VERB_OPTION_SLOTS[name]?.optionsArePositionIndependent === true;
|
|
634
|
-
}
|
|
635
|
-
export function verbFirstOperandSlot(name) {
|
|
636
|
-
return VERB_OPTION_SLOTS[name]?.firstOperand;
|
|
637
|
-
}
|
|
638
|
-
function localFileUrlPath(value) {
|
|
639
|
-
const m = /^file:(?:\/\/[^/?#]*)?(\/[^?#]*)/i.exec(value.trim());
|
|
640
|
-
if (m === null)
|
|
641
|
-
return undefined;
|
|
642
|
-
const raw = m[1];
|
|
643
|
-
try {
|
|
644
|
-
return decodeURIComponent(raw);
|
|
645
|
-
}
|
|
646
|
-
catch {
|
|
647
|
-
return raw;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
function formSpecFileNames(value) {
|
|
651
|
-
const out = [];
|
|
652
|
-
const after = (s) => {
|
|
653
|
-
const at = s.search(/[@<]/);
|
|
654
|
-
if (at !== -1 && at < s.length - 1)
|
|
655
|
-
out.push(s.slice(at + 1));
|
|
656
|
-
};
|
|
657
|
-
after(value);
|
|
658
|
-
for (const part of value.split(/[;,]/))
|
|
659
|
-
after(part);
|
|
660
|
-
return [...new Set(out)];
|
|
661
|
-
}
|
|
662
|
-
export function slotValueCandidates(value, kind) {
|
|
663
|
-
if (!valueNamesAFile(kind))
|
|
664
|
-
return [];
|
|
665
|
-
if (kind === "url") {
|
|
666
|
-
const local = localFileUrlPath(value);
|
|
667
|
-
return local !== undefined ? [local] : [];
|
|
668
|
-
}
|
|
669
|
-
if (kind === "form")
|
|
670
|
-
return formSpecFileNames(value).map(literalTildeSpelling);
|
|
671
|
-
const named = value.startsWith("@") && value.length > 1 ? literalTildeSpelling(value.slice(1)) : undefined;
|
|
672
|
-
if (kind === "header")
|
|
673
|
-
return named !== undefined ? [value, named] : [];
|
|
674
|
-
const out = [value];
|
|
675
|
-
if (kind === "indirect" && named !== undefined)
|
|
676
|
-
out.push(named);
|
|
677
|
-
return out;
|
|
678
|
-
}
|
|
679
|
-
const SEPARATED_VALUE_OPTIONS = {
|
|
680
|
-
head: { short: "cn", long: ["bytes", "lines"] },
|
|
681
|
-
tail: { short: "cns", long: ["bytes", "lines", "sleep-interval", "pid", "max-unchanged-stats"] },
|
|
682
|
-
cut: { short: "bcdf", long: ["bytes", "characters", "delimiter", "fields", "output-delimiter"] },
|
|
683
|
-
grep: {
|
|
684
|
-
ownsValueButNotSkippable: "ef",
|
|
685
|
-
short: "mABCdD",
|
|
686
|
-
long: [
|
|
687
|
-
"max-count",
|
|
688
|
-
"after-context",
|
|
689
|
-
"before-context",
|
|
690
|
-
"context",
|
|
691
|
-
"devices",
|
|
692
|
-
"directories",
|
|
693
|
-
"label",
|
|
694
|
-
"binary-files",
|
|
695
|
-
"group-separator",
|
|
696
|
-
"include",
|
|
697
|
-
"exclude",
|
|
698
|
-
"exclude-dir",
|
|
699
|
-
],
|
|
700
|
-
},
|
|
701
|
-
cmp: { short: "ni", long: ["bytes", "ignore-initial"] },
|
|
702
|
-
diff: { short: "UCWIFxL", ownsValueButNotSkippable: "SX", long: ["unified", "context", "width", "ignore-matching-lines", "show-function-line", "exclude", "label", "horizon-lines", "tabsize"] },
|
|
703
|
-
};
|
|
704
|
-
function takesSeparatedValue(name, tok) {
|
|
705
|
-
const model = SEPARATED_VALUE_OPTIONS[name];
|
|
706
|
-
if (model === undefined)
|
|
707
|
-
return false;
|
|
708
|
-
if (tok.startsWith("--")) {
|
|
709
|
-
if (tok.includes("="))
|
|
710
|
-
return false;
|
|
711
|
-
const long = longOptionNameOf(tok);
|
|
712
|
-
return long !== undefined && model.long.some((full) => isLongOptionAbbrevOf(long, full));
|
|
713
|
-
}
|
|
714
|
-
if (!/^-[A-Za-z0-9]/.test(tok))
|
|
715
|
-
return false;
|
|
716
|
-
for (let i = 1; i < tok.length; i++) {
|
|
717
|
-
const ch = tok[i];
|
|
718
|
-
if ((model.ownsValueButNotSkippable ?? "").includes(ch))
|
|
719
|
-
return false;
|
|
720
|
-
if (model.short.includes(ch))
|
|
721
|
-
return i === tok.length - 1;
|
|
722
|
-
if (!/[A-Za-z0-9]/.test(ch))
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
return false;
|
|
726
|
-
}
|
|
727
|
-
const GREP_RECURSIVE_READ_FORM = {
|
|
728
|
-
discloses: "contents",
|
|
729
|
-
shortLetters: "rR",
|
|
730
|
-
valueOwners: "efmABCD",
|
|
731
|
-
longNames: ["recursive", "dereference-recursive"],
|
|
732
|
-
enumOptions: [{ shortLetter: "d", longName: "directories", recursiveValue: "recurse" }],
|
|
733
|
-
dashIsStdin: true,
|
|
734
|
-
};
|
|
735
|
-
export const RECURSIVE_READ_FORMS = {
|
|
736
|
-
grep: GREP_RECURSIVE_READ_FORM,
|
|
737
|
-
egrep: GREP_RECURSIVE_READ_FORM,
|
|
738
|
-
fgrep: GREP_RECURSIVE_READ_FORM,
|
|
739
|
-
ls: { discloses: "names", shortLetters: "R", longNames: ["recursive"] },
|
|
740
|
-
du: { discloses: "names", always: true },
|
|
741
|
-
find: { discloses: "names", always: true },
|
|
742
|
-
rg: { discloses: "contents", always: true, dashIsStdin: true },
|
|
743
|
-
tree: { discloses: "names", always: true },
|
|
744
|
-
ag: { discloses: "contents", always: true, dashIsStdin: true },
|
|
745
|
-
ack: { discloses: "contents", always: true, dashIsStdin: true },
|
|
746
|
-
tar: { discloses: "contents", shortLetters: "cru", valueOwners: "fCTXbg", longNames: ["create", "append", "update"], bundledModeLetters: "cru", dashIsStdin: true },
|
|
747
|
-
diff: { discloses: "contents", shortLetters: "r", valueOwners: "UCWISFXx", longNames: ["recursive"], dashIsStdin: true },
|
|
748
|
-
};
|
|
749
|
-
const FIND_DANGEROUS_PREDICATES = new Set([
|
|
750
|
-
"-delete", "-exec", "-execdir", "-ok", "-okdir", "-fprint", "-fprint0", "-fls", "-fprintf", "-files0-from",
|
|
751
|
-
]);
|
|
752
|
-
const SED_READONLY_FLAGS = new Set([
|
|
753
|
-
"-n", "--quiet", "--silent", "-E", "--regexp-extended", "-r", "-z", "--zero-terminated", "--posix",
|
|
754
|
-
]);
|
|
755
|
-
const SED_READONLY_CLUSTER_LETTERS = new Set(["n", "E", "r", "z"]);
|
|
756
235
|
const SED_READONLY_SCRIPT_PIECE = /^(?:\d+|\d+,\d+)?p$/;
|
|
757
236
|
const SED_FILE_OR_EXEC_COMMANDS = new Set(["r", "R", "w", "W", "e"]);
|
|
758
237
|
const SED_PLAIN_COMMANDS = new Set(["=", "d", "D", "F", "g", "G", "h", "H", "n", "N", "p", "P", "x", "z"]);
|
|
@@ -989,103 +468,18 @@ function readSedScript(script) {
|
|
|
989
468
|
return failed();
|
|
990
469
|
return anyName() ? named() : "clean";
|
|
991
470
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
{ separated: "ef", suffix: "iI", stopsAtFirstOperand: true },
|
|
995
|
-
];
|
|
996
|
-
function sedSeparatedSuffixTaken(tok) {
|
|
997
|
-
return tok !== undefined && (tok === "" || tok.startsWith("."));
|
|
998
|
-
}
|
|
999
|
-
const SED_LONG_VALUE_OPTIONS = ["expression", "line-length"];
|
|
1000
|
-
const SED_LONG_PLAIN_OPTIONS = [
|
|
1001
|
-
"debug", "follow-symlinks", "help", "in-place", "null-data", "posix", "quiet", "regexp-extended",
|
|
1002
|
-
"sandbox", "separate", "silent", "unbuffered", "version", "zero-terminated",
|
|
1003
|
-
];
|
|
1004
|
-
function readSedInvocation(args, dialect) {
|
|
1005
|
-
return sedReadingKind(readSedInvocationDetailed(args, dialect));
|
|
1006
|
-
}
|
|
1007
|
-
function readSedInvocationDetailed(args, dialect) {
|
|
1008
|
-
const scripts = [];
|
|
1009
|
-
let scriptSupplied = false;
|
|
1010
|
-
let endOfOptions = false;
|
|
1011
|
-
let firstOperand;
|
|
1012
|
-
for (let k = 0; k < args.length; k++) {
|
|
1013
|
-
const tok = args[k];
|
|
1014
|
-
if (!endOfOptions && tok === "--") {
|
|
1015
|
-
endOfOptions = true;
|
|
1016
|
-
continue;
|
|
1017
|
-
}
|
|
1018
|
-
if (endOfOptions || !tok.startsWith("-") || tok === "-") {
|
|
1019
|
-
if (firstOperand === undefined) {
|
|
1020
|
-
firstOperand = tok;
|
|
1021
|
-
if (dialect.stopsAtFirstOperand)
|
|
1022
|
-
endOfOptions = true;
|
|
1023
|
-
}
|
|
1024
|
-
continue;
|
|
1025
|
-
}
|
|
1026
|
-
if (tok.startsWith("--")) {
|
|
1027
|
-
const long = longOptionNameOf(tok);
|
|
1028
|
-
if (long === undefined)
|
|
1029
|
-
return "unparsed";
|
|
1030
|
-
const eq = tok.indexOf("=");
|
|
1031
|
-
const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
|
|
1032
|
-
if (isLongOptionAbbrevOf(long, "file"))
|
|
1033
|
-
return "unseen";
|
|
1034
|
-
if (isLongOptionAbbrevOf(long, "expression")) {
|
|
1035
|
-
const value = attached ?? args[++k];
|
|
1036
|
-
if (value === undefined)
|
|
1037
|
-
return "unparsed";
|
|
1038
|
-
scripts.push(value);
|
|
1039
|
-
scriptSupplied = true;
|
|
1040
|
-
continue;
|
|
1041
|
-
}
|
|
1042
|
-
if (SED_LONG_VALUE_OPTIONS.some((full) => isLongOptionAbbrevOf(long, full))) {
|
|
1043
|
-
if (attached === undefined)
|
|
1044
|
-
k++;
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
if (SED_LONG_PLAIN_OPTIONS.some((full) => isLongOptionAbbrevOf(long, full)))
|
|
1048
|
-
continue;
|
|
1049
|
-
return "unparsed";
|
|
1050
|
-
}
|
|
1051
|
-
for (let c = 1; c < tok.length; c++) {
|
|
1052
|
-
const letter = tok[c];
|
|
1053
|
-
if (dialect.suffix.includes(letter)) {
|
|
1054
|
-
if (c + 1 === tok.length && sedSeparatedSuffixTaken(args[k + 1]))
|
|
1055
|
-
k++;
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
if (dialect.separated.includes(letter)) {
|
|
1059
|
-
const rest = tok.slice(c + 1);
|
|
1060
|
-
const value = rest !== "" ? rest : args[++k];
|
|
1061
|
-
if (letter === "f")
|
|
1062
|
-
return "unseen";
|
|
1063
|
-
if (letter === "e") {
|
|
1064
|
-
if (value === undefined)
|
|
1065
|
-
return "unparsed";
|
|
1066
|
-
scripts.push(value);
|
|
1067
|
-
scriptSupplied = true;
|
|
1068
|
-
}
|
|
1069
|
-
break;
|
|
1070
|
-
}
|
|
1071
|
-
if (!SED_SHORT_PLAIN_LETTERS.includes(letter))
|
|
1072
|
-
return "unparsed";
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
const script = scriptSupplied ? scripts.join("\n") : firstOperand;
|
|
1076
|
-
if (script === undefined)
|
|
1077
|
-
return "clean";
|
|
1078
|
-
return readSedScript(script);
|
|
471
|
+
function scriptReadings(row, args) {
|
|
472
|
+
return programTextReadings(row, args).map((r) => (r === "from-file" ? "unseen" : r === "unparsed" ? "unparsed" : r === "none" ? "clean" : readSedScript(r.text)));
|
|
1079
473
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
474
|
+
export function programTextSlotFiles(row, args) {
|
|
475
|
+
if (readProgramTextLanguage(row) !== "sed")
|
|
476
|
+
return undefined;
|
|
1082
477
|
const reads = new Set();
|
|
1083
478
|
const writes = new Set();
|
|
1084
479
|
const commands = new Set();
|
|
1085
480
|
let executesPatternSpace = false;
|
|
1086
481
|
const kinds = [];
|
|
1087
|
-
for (const
|
|
1088
|
-
const reading = readSedInvocationDetailed(args, dialect);
|
|
482
|
+
for (const reading of scriptReadings(row, args)) {
|
|
1089
483
|
kinds.push(sedReadingKind(reading));
|
|
1090
484
|
if (typeof reading === "string")
|
|
1091
485
|
continue;
|
|
@@ -1104,335 +498,111 @@ export function sedScriptSlotFiles(args) {
|
|
|
1104
498
|
: undefined;
|
|
1105
499
|
return { reads: [...reads], writes: [...writes], commands: [...commands], executesPatternSpace, ...(unread !== undefined ? { unread } : {}) };
|
|
1106
500
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
501
|
+
function scriptSlotHidesNames(row, args) {
|
|
502
|
+
if (readProgramTextLanguage(row) !== "sed")
|
|
503
|
+
return false;
|
|
504
|
+
const readings = scriptReadings(row, args).map(sedReadingKind);
|
|
505
|
+
if (readings.includes("names") || readings.includes("unseen") || readings.includes("oversized"))
|
|
506
|
+
return true;
|
|
507
|
+
return readings.every((reading) => reading === "unparsed");
|
|
508
|
+
}
|
|
1115
509
|
const INLINE_PROGRAM_TEXT = "runs its argument as an interpreted program";
|
|
1116
510
|
const INLINE_PROGRAM_STDIN = "reads the program it runs from its standard input";
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
}
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
...BLANK_INLINE, textLetters: "e", separatedValueLetters: "IrECFKT", plainLetters: "nplawcsSU",
|
|
1131
|
-
bannerLetters: "vh", stdinWhenUnnamed: true,
|
|
1132
|
-
};
|
|
1133
|
-
const PYTHON_INLINE = {
|
|
1134
|
-
...BLANK_INLINE, textLetters: "c", fileLetters: "m", separatedValueLetters: "WXQ",
|
|
1135
|
-
plainLetters: "uBEIsSqbdvxOtR", bannerLetters: "Vh?", interactiveLetters: "i",
|
|
1136
|
-
fileOptionEndsOptions: true, stdinWhenUnnamed: true,
|
|
1137
|
-
};
|
|
1138
|
-
const NODE_INLINE = {
|
|
1139
|
-
...BLANK_INLINE, longText: new Set(["--eval", "--print"]), textLetters: "ep",
|
|
1140
|
-
longValue: new Set(["--import", "--loader", "--experimental-loader", "--require", "--env-file", "--conditions", "--max-old-space-size"]),
|
|
1141
|
-
longNamesProgram: new Set(["--test"]),
|
|
1142
|
-
longPlain: new Set(["--inspect", "--inspect-brk", "--no-warnings", "--trace-warnings", "--watch", "--enable-source-maps", "--experimental-modules", "--no-deprecation", "--check"]),
|
|
1143
|
-
separatedValueLetters: "r", plainLetters: "c", bannerLetters: "vh", interactiveLetters: "i",
|
|
1144
|
-
stdinWhenUnnamed: true,
|
|
1145
|
-
};
|
|
1146
|
-
const PHP_INLINE = {
|
|
1147
|
-
...BLANK_INLINE, longText: new Set(["--run", "--process-begin", "--process-code", "--process-end"]),
|
|
1148
|
-
textLetters: "rRBE", fileLetters: "fF", separatedValueLetters: "dcz", plainLetters: "nqswl",
|
|
1149
|
-
bannerLetters: "vhi", interactiveLetters: "a", stdinWhenUnnamed: true,
|
|
1150
|
-
};
|
|
1151
|
-
const LUA_INLINE = {
|
|
1152
|
-
...BLANK_INLINE, textLetters: "e", separatedValueLetters: "l", bannerLetters: "vh",
|
|
1153
|
-
interactiveLetters: "i", stdinWhenUnnamed: true,
|
|
1154
|
-
};
|
|
1155
|
-
const TCL_INLINE = { ...BLANK_INLINE, stdinWhenUnnamed: true };
|
|
1156
|
-
const EXPECT_INLINE = {
|
|
1157
|
-
...BLANK_INLINE, textLetters: "c", separatedValueLetters: "Db", plainLetters: "dN",
|
|
1158
|
-
bannerLetters: "vh", interactiveLetters: "i", stdinWhenUnnamed: true,
|
|
1159
|
-
};
|
|
1160
|
-
const AWK_INLINE = {
|
|
1161
|
-
...BLANK_INLINE, longText: new Set(["--source"]), textLetters: "e",
|
|
1162
|
-
longFile: new Set(["--file", "--exec"]), fileLetters: "fE", separatedValueLetters: "vF",
|
|
1163
|
-
bannerLetters: "V", programOperand: { at: 0, isText: true }, stdinWhenUnnamed: false,
|
|
1164
|
-
};
|
|
1165
|
-
const JQ_INLINE = {
|
|
1166
|
-
...BLANK_INLINE, longFile: new Set(["--from-file"]), fileLetters: "f", separatedValueLetters: "L",
|
|
1167
|
-
plainLetters: "nrjcsSaCMRe", bannerLetters: "h", programOperand: { at: 0, isText: true },
|
|
1168
|
-
stdinWhenUnnamed: false,
|
|
1169
|
-
};
|
|
1170
|
-
const BC_INLINE = {
|
|
1171
|
-
...BLANK_INLINE, longText: new Set(["--expression"]), textLetters: "e", plainLetters: "lqsw",
|
|
1172
|
-
bannerLetters: "vh", interactiveLetters: "i", programOperand: undefined, stdinWhenUnnamed: true,
|
|
1173
|
-
};
|
|
1174
|
-
const DC_INLINE = {
|
|
1175
|
-
...BLANK_INLINE, longText: new Set(["--expression"]), textLetters: "e",
|
|
1176
|
-
longFile: new Set(["--file"]), fileLetters: "f", bannerLetters: "hV",
|
|
1177
|
-
programOperand: undefined, stdinWhenUnnamed: true,
|
|
1178
|
-
};
|
|
1179
|
-
const SQLITE_INLINE = {
|
|
1180
|
-
...BLANK_INLINE, longText: new Set(["-cmd"]), longSupplemental: new Set(["-init"]),
|
|
1181
|
-
programOperand: { at: 1, isText: true }, stdinWhenUnnamed: true,
|
|
1182
|
-
};
|
|
1183
|
-
const PSQL_INLINE = {
|
|
1184
|
-
...BLANK_INLINE, longText: new Set(["--command"]), textLetters: "c",
|
|
1185
|
-
longFile: new Set(["--file"]), fileLetters: "f", separatedValueLetters: "hpUdoF",
|
|
1186
|
-
plainLetters: "aeEqtxXwW", bannerLetters: "lV?", programOperand: undefined, stdinWhenUnnamed: true,
|
|
1187
|
-
};
|
|
1188
|
-
const OSASCRIPT_INLINE = {
|
|
1189
|
-
...BLANK_INLINE, textLetters: "e", separatedValueLetters: "ls", plainLetters: "i", bannerLetters: "h",
|
|
1190
|
-
stdinWhenUnnamed: true,
|
|
1191
|
-
};
|
|
1192
|
-
const RSCRIPT_INLINE = {
|
|
1193
|
-
...BLANK_INLINE, textLetters: "e", bannerLetters: "h", stdinWhenUnnamed: false,
|
|
1194
|
-
};
|
|
1195
|
-
const JULIA_INLINE = {
|
|
1196
|
-
...BLANK_INLINE, longText: new Set(["--eval", "--print"]), textLetters: "eE",
|
|
1197
|
-
separatedValueLetters: "JLp", plainLetters: "q", bannerLetters: "vh", interactiveLetters: "i",
|
|
1198
|
-
stdinWhenUnnamed: true,
|
|
1199
|
-
};
|
|
1200
|
-
const GHCI_INLINE = {
|
|
1201
|
-
...BLANK_INLINE, textLetters: "e", separatedValueLetters: "io", programOperand: undefined,
|
|
1202
|
-
stdinWhenUnnamed: true,
|
|
1203
|
-
};
|
|
1204
|
-
const GROOVY_INLINE = {
|
|
1205
|
-
...BLANK_INLINE, textLetters: "e", separatedValueLetters: "Dcl", plainLetters: "n",
|
|
1206
|
-
bannerLetters: "vh", interactiveLetters: "i", stdinWhenUnnamed: false,
|
|
1207
|
-
};
|
|
1208
|
-
const BUN_INLINE = {
|
|
1209
|
-
...BLANK_INLINE, longText: new Set(["--eval", "--print"]), textLetters: "ep",
|
|
1210
|
-
separatedValueLetters: "r", bannerLetters: "vh", stdinWhenUnnamed: false,
|
|
1211
|
-
};
|
|
1212
|
-
export const INTERPRETER_INLINE_PROGRAMS = new Map([
|
|
1213
|
-
["perl", PERL_INLINE],
|
|
1214
|
-
["ruby", RUBY_INLINE],
|
|
1215
|
-
["python", PYTHON_INLINE],
|
|
1216
|
-
["node", NODE_INLINE],
|
|
1217
|
-
["nodejs", NODE_INLINE],
|
|
1218
|
-
["php", PHP_INLINE],
|
|
1219
|
-
["lua", LUA_INLINE],
|
|
1220
|
-
["luajit", LUA_INLINE],
|
|
1221
|
-
["tclsh", TCL_INLINE],
|
|
1222
|
-
["wish", TCL_INLINE],
|
|
1223
|
-
["expect", EXPECT_INLINE],
|
|
1224
|
-
["awk", AWK_INLINE],
|
|
1225
|
-
["gawk", AWK_INLINE],
|
|
1226
|
-
["mawk", AWK_INLINE],
|
|
1227
|
-
["nawk", AWK_INLINE],
|
|
1228
|
-
["jq", JQ_INLINE],
|
|
1229
|
-
["bc", BC_INLINE],
|
|
1230
|
-
["dc", DC_INLINE],
|
|
1231
|
-
["sqlite", SQLITE_INLINE],
|
|
1232
|
-
["psql", PSQL_INLINE],
|
|
1233
|
-
["osascript", OSASCRIPT_INLINE],
|
|
1234
|
-
["Rscript", RSCRIPT_INLINE],
|
|
1235
|
-
["julia", JULIA_INLINE],
|
|
1236
|
-
["ghci", GHCI_INLINE],
|
|
1237
|
-
["groovy", GROOVY_INLINE],
|
|
1238
|
-
["bun", BUN_INLINE],
|
|
1239
|
-
]);
|
|
1240
|
-
export function interpreterKey(name) {
|
|
1241
|
-
return name.replace(/\d+(?:\.\d+)*$/, "");
|
|
511
|
+
function unjudgedProgramTextRefusal(written, clause) {
|
|
512
|
+
return `command "${written}" ${clause}, which this walk does not judge ${NOT_AUTO_ALLOWED}`;
|
|
513
|
+
}
|
|
514
|
+
function argumentAsProgramRefusal(written, name, args) {
|
|
515
|
+
const row = programRow(name);
|
|
516
|
+
if (row !== undefined && scriptSlotHidesNames(row, args)) {
|
|
517
|
+
return unjudgedProgramTextRefusal(written, "names files or runs commands inside its script text");
|
|
518
|
+
}
|
|
519
|
+
const delivery = programDelivery(row, args);
|
|
520
|
+
return delivery === undefined ? undefined : unjudgedProgramTextRefusal(written, delivery === "text" ? INLINE_PROGRAM_TEXT : INLINE_PROGRAM_STDIN);
|
|
521
|
+
}
|
|
522
|
+
function makesMoreThanARead(spec) {
|
|
523
|
+
return spec.effect === "writes" || spec.effect === "executes" || spec.delivers !== undefined || spec.selects === "edit-in-place" || writesPath(spec.value);
|
|
1242
524
|
}
|
|
1243
|
-
|
|
525
|
+
function writesWordsItIsHanded(row, args) {
|
|
526
|
+
if (row === undefined)
|
|
527
|
+
return false;
|
|
528
|
+
const operands = args.some((w) => !w.startsWith("-") || w === "-");
|
|
529
|
+
return writeReadings(row, args).some((r) => r.written.length > 0 || (r.unexplained && r.operandRole !== undefined && operands));
|
|
530
|
+
}
|
|
531
|
+
function inReadOnlyForm(row, flag) {
|
|
532
|
+
const plain = (spec) => spec !== undefined && spec.arity === 0 && spec.effect === "none" && spec.fillsSlot !== true;
|
|
533
|
+
const merged = (table, key) => {
|
|
534
|
+
const specs = row.grammars.map((g) => g[table][key]).filter((x) => x !== undefined);
|
|
535
|
+
return specs.length > 0 && specs.every(plain) ? specs[0] : undefined;
|
|
536
|
+
};
|
|
537
|
+
if (flag.startsWith("--"))
|
|
538
|
+
return !flag.includes("=") && merged("long", flag.slice(2)) !== undefined;
|
|
539
|
+
return /^-[A-Za-z]+$/.test(flag) && [...flag.slice(1)].every((ch) => merged("short", ch) !== undefined);
|
|
540
|
+
}
|
|
541
|
+
function selectsQuiet(row, flag) {
|
|
542
|
+
const quiet = (spec) => spec?.mode === "quiet";
|
|
543
|
+
if (flag.startsWith("--"))
|
|
544
|
+
return row.grammars.some((g) => quiet(g.long[flag.slice(2)]));
|
|
545
|
+
return /^-[A-Za-z]+$/.test(flag) && [...flag.slice(1)].some((ch) => row.grammars.some((g) => quiet(g.short[ch])));
|
|
546
|
+
}
|
|
547
|
+
function readOnlyFormReason(name, row, rest, args) {
|
|
548
|
+
for (const w of args) {
|
|
549
|
+
if (globInWord(w) || expansionInWord(w)) {
|
|
550
|
+
return `\`${name}\` is given the unquoted pattern "${w.raw}", which the shell may expand into other arguments (a file named like a flag becomes a live option) before ${name} runs ${NOT_AUTO_ALLOWED}`;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const flags = [];
|
|
554
|
+
const operands = [];
|
|
1244
555
|
let endOfOptions = false;
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
let named = false;
|
|
1248
|
-
let stdin = false;
|
|
1249
|
-
let explained = true;
|
|
1250
|
-
let operands = 0;
|
|
1251
|
-
scan: for (let i = 0; i < args.length; i++) {
|
|
1252
|
-
const arg = args[i];
|
|
1253
|
-
if (!endOfOptions && arg === "--") {
|
|
556
|
+
for (const t of rest) {
|
|
557
|
+
if (!endOfOptions && t === "--") {
|
|
1254
558
|
endOfOptions = true;
|
|
1255
559
|
continue;
|
|
1256
560
|
}
|
|
1257
|
-
if (
|
|
1258
|
-
|
|
1259
|
-
stdin = true;
|
|
1260
|
-
continue;
|
|
1261
|
-
}
|
|
1262
|
-
if (!endOfOptions && arg.startsWith("-")) {
|
|
1263
|
-
const eq = arg.indexOf("=");
|
|
1264
|
-
const whole = eq < 0 ? arg : arg.slice(0, eq);
|
|
1265
|
-
const valueOf = () => (eq < 0 ? args[++i] : arg.slice(eq + 1));
|
|
1266
|
-
if (model.longText.has(whole))
|
|
1267
|
-
return INLINE_PROGRAM_TEXT;
|
|
1268
|
-
if (model.longFile.has(whole)) {
|
|
1269
|
-
if (valueOf() === "-") {
|
|
1270
|
-
stdin = true;
|
|
1271
|
-
continue;
|
|
1272
|
-
}
|
|
1273
|
-
named = true;
|
|
1274
|
-
if (explained && model.fileOptionEndsOptions)
|
|
1275
|
-
break scan;
|
|
1276
|
-
continue;
|
|
1277
|
-
}
|
|
1278
|
-
if (model.longNamesProgram.has(whole)) {
|
|
1279
|
-
named = true;
|
|
1280
|
-
continue;
|
|
1281
|
-
}
|
|
1282
|
-
if (model.longSupplemental.has(whole)) {
|
|
1283
|
-
valueOf();
|
|
1284
|
-
continue;
|
|
1285
|
-
}
|
|
1286
|
-
if (INTERPRETER_BANNER_OPTIONS.has(whole)) {
|
|
1287
|
-
banner = true;
|
|
1288
|
-
continue;
|
|
1289
|
-
}
|
|
1290
|
-
if (model.longValue.has(whole)) {
|
|
1291
|
-
valueOf();
|
|
1292
|
-
continue;
|
|
1293
|
-
}
|
|
1294
|
-
if (model.longPlain.has(whole))
|
|
1295
|
-
continue;
|
|
1296
|
-
if (arg.startsWith("--")) {
|
|
1297
|
-
if (eq < 0)
|
|
1298
|
-
explained = false;
|
|
1299
|
-
continue;
|
|
1300
|
-
}
|
|
1301
|
-
for (let k = 1; k < arg.length; k++) {
|
|
1302
|
-
const ch = arg[k];
|
|
1303
|
-
if (ch >= "0" && ch <= "9")
|
|
1304
|
-
continue;
|
|
1305
|
-
if (model.textLetters.includes(ch))
|
|
1306
|
-
return INLINE_PROGRAM_TEXT;
|
|
1307
|
-
if (model.fileLetters.includes(ch)) {
|
|
1308
|
-
const value = k + 1 < arg.length ? arg.slice(k + 1) : args[++i];
|
|
1309
|
-
if (value === "-") {
|
|
1310
|
-
stdin = true;
|
|
1311
|
-
break;
|
|
1312
|
-
}
|
|
1313
|
-
named = true;
|
|
1314
|
-
if (explained && model.fileOptionEndsOptions)
|
|
1315
|
-
break scan;
|
|
1316
|
-
break;
|
|
1317
|
-
}
|
|
1318
|
-
if (model.attachedValueLetters.includes(ch)) {
|
|
1319
|
-
if (model.bannerLetters.includes(ch))
|
|
1320
|
-
banner = true;
|
|
1321
|
-
break;
|
|
1322
|
-
}
|
|
1323
|
-
if (model.bannerLetters.includes(ch)) {
|
|
1324
|
-
banner = true;
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
if (model.interactiveLetters.includes(ch)) {
|
|
1328
|
-
interactive = true;
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
if (model.separatedValueLetters.includes(ch)) {
|
|
1332
|
-
if (k + 1 >= arg.length)
|
|
1333
|
-
i++;
|
|
1334
|
-
break;
|
|
1335
|
-
}
|
|
1336
|
-
if (!model.plainLetters.includes(ch))
|
|
1337
|
-
explained = false;
|
|
1338
|
-
}
|
|
561
|
+
if (!endOfOptions && t.startsWith("-")) {
|
|
562
|
+
flags.push(t);
|
|
1339
563
|
continue;
|
|
1340
564
|
}
|
|
1341
|
-
operands
|
|
1342
|
-
if (!named && explained && model.programOperand !== undefined && operands === model.programOperand.at + 1) {
|
|
1343
|
-
if (model.programOperand.isText)
|
|
1344
|
-
return INLINE_PROGRAM_TEXT;
|
|
1345
|
-
named = true;
|
|
1346
|
-
break;
|
|
1347
|
-
}
|
|
565
|
+
operands.push(t);
|
|
1348
566
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
if (slot !== undefined && slot(args)) {
|
|
1361
|
-
return unjudgedProgramTextRefusal(written, "names files or runs commands inside its script text");
|
|
567
|
+
for (const f of flags)
|
|
568
|
+
if (!inReadOnlyForm(row, f))
|
|
569
|
+
return `\`${name} ${f}\` is outside the read-only ${name} form (-n plus print-only script) ${NOT_AUTO_ALLOWED}`;
|
|
570
|
+
if (!flags.some((f) => selectsQuiet(row, f)))
|
|
571
|
+
return `\`${name}\` without \`-n\` echoes its whole input — only the -n print-only form is read-classified ${NOT_AUTO_ALLOWED}`;
|
|
572
|
+
const script = operands[0];
|
|
573
|
+
if (script === undefined)
|
|
574
|
+
return `\`${name}\` with no script ${NOT_AUTO_ALLOWED}`;
|
|
575
|
+
for (const piece of script.split(";")) {
|
|
576
|
+
if (!SED_READONLY_SCRIPT_PIECE.test(piece.trim()))
|
|
577
|
+
return `\`${name}\` script "${script}" is not a pure print command (N[,M]p) ${NOT_AUTO_ALLOWED}`;
|
|
1362
578
|
}
|
|
1363
|
-
|
|
1364
|
-
if (interpreter === undefined)
|
|
1365
|
-
return undefined;
|
|
1366
|
-
const clause = inlineProgramClause(interpreter, args);
|
|
1367
|
-
return clause === undefined ? undefined : unjudgedProgramTextRefusal(written, clause);
|
|
579
|
+
return undefined;
|
|
1368
580
|
}
|
|
1369
581
|
function guardedVerbFormReason(name, toks, args) {
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
582
|
+
const row = programRow(name);
|
|
583
|
+
const rest = toks.slice(1);
|
|
584
|
+
if (row !== undefined && readProgramTextLanguage(row) !== undefined)
|
|
585
|
+
return readOnlyFormReason(name, row, rest, args);
|
|
586
|
+
if (row !== undefined && declaresOption(row, makesMoreThanARead)) {
|
|
587
|
+
for (const t of rest) {
|
|
588
|
+
if (selectedOptions(row, [t]).some(makesMoreThanARead))
|
|
589
|
+
return `\`${name} ${t}\` executes a command or writes a file — not a read ${NOT_AUTO_ALLOWED}`;
|
|
1375
590
|
}
|
|
1376
591
|
for (const w of args) {
|
|
1377
592
|
if (globInWord(w) || expansionInWord(w)) {
|
|
1378
|
-
return
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
return undefined;
|
|
1382
|
-
}
|
|
1383
|
-
if (name === "sed") {
|
|
1384
|
-
for (const w of args) {
|
|
1385
|
-
if (globInWord(w) || expansionInWord(w)) {
|
|
1386
|
-
return `\`sed\` is given the unquoted pattern "${w.raw}", which the shell may expand into other arguments (a file named like a flag becomes a live option) before sed runs ${NOT_AUTO_ALLOWED}`;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
const flags = [];
|
|
1390
|
-
const operands = [];
|
|
1391
|
-
let endOfOptions = false;
|
|
1392
|
-
for (const t of toks.slice(1)) {
|
|
1393
|
-
if (!endOfOptions && t === "--") {
|
|
1394
|
-
endOfOptions = true;
|
|
1395
|
-
continue;
|
|
1396
|
-
}
|
|
1397
|
-
if (!endOfOptions && t.startsWith("-")) {
|
|
1398
|
-
flags.push(t);
|
|
1399
|
-
continue;
|
|
1400
|
-
}
|
|
1401
|
-
operands.push(t);
|
|
1402
|
-
}
|
|
1403
|
-
for (const f of flags) {
|
|
1404
|
-
if (SED_READONLY_FLAGS.has(f))
|
|
1405
|
-
continue;
|
|
1406
|
-
if (/^-[A-Za-z]+$/.test(f) && f.length > 2 && [...f.slice(1)].every((ch) => SED_READONLY_CLUSTER_LETTERS.has(ch)))
|
|
1407
|
-
continue;
|
|
1408
|
-
return `\`sed ${f}\` is outside the read-only sed form (-n plus print-only script) ${NOT_AUTO_ALLOWED}`;
|
|
1409
|
-
}
|
|
1410
|
-
const quiet = flags.some((f) => f === "-n" || f === "--quiet" || f === "--silent" || (/^-[A-Za-z]+$/.test(f) && f.includes("n")));
|
|
1411
|
-
if (!quiet)
|
|
1412
|
-
return `\`sed\` without \`-n\` echoes its whole input — only the -n print-only form is read-classified ${NOT_AUTO_ALLOWED}`;
|
|
1413
|
-
const script = operands[0];
|
|
1414
|
-
if (script === undefined)
|
|
1415
|
-
return `\`sed\` with no script ${NOT_AUTO_ALLOWED}`;
|
|
1416
|
-
for (const piece of script.split(";")) {
|
|
1417
|
-
if (!SED_READONLY_SCRIPT_PIECE.test(piece.trim())) {
|
|
1418
|
-
return `\`sed\` script "${script}" is not a pure print command (N[,M]p) ${NOT_AUTO_ALLOWED}`;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
return undefined;
|
|
1422
|
-
}
|
|
1423
|
-
if (name === "free") {
|
|
1424
|
-
for (const t of toks.slice(1)) {
|
|
1425
|
-
if (/^--seconds(=.*)?$/.test(t) || /^-[A-Za-z0-9]*s[A-Za-z0-9]*$/.test(t)) {
|
|
1426
|
-
return `\`free ${t}\` repeats until killed — it never terminates on its own ${NOT_AUTO_ALLOWED}`;
|
|
593
|
+
return `\`${name}\` is given the unquoted pattern "${w.raw}", which the shell may expand into other arguments before ${name} runs ${NOT_AUTO_ALLOWED}`;
|
|
1427
594
|
}
|
|
1428
595
|
}
|
|
1429
596
|
return undefined;
|
|
1430
597
|
}
|
|
598
|
+
const repeats = rest.find((t) => selectedOptions(row, [t]).some((s) => s.mode === "repeats"));
|
|
599
|
+
if (repeats !== undefined)
|
|
600
|
+
return `\`${name} ${repeats}\` repeats until killed — it never terminates on its own ${NOT_AUTO_ALLOWED}`;
|
|
1431
601
|
if (name === "sleep") {
|
|
1432
602
|
return pollLoopSleepReason(toks);
|
|
1433
603
|
}
|
|
1434
604
|
if (name === "seq") {
|
|
1435
|
-
for (const t of
|
|
605
|
+
for (const t of rest) {
|
|
1436
606
|
if (t.startsWith("-") && t !== "-" && !/^-\d/.test(t))
|
|
1437
607
|
continue;
|
|
1438
608
|
if (!/^-?\d{1,6}$/.test(t)) {
|
|
@@ -1441,92 +611,38 @@ function guardedVerbFormReason(name, toks, args) {
|
|
|
1441
611
|
}
|
|
1442
612
|
return undefined;
|
|
1443
613
|
}
|
|
1444
|
-
if (name === "diff") {
|
|
1445
|
-
for (const t of toks.slice(1)) {
|
|
1446
|
-
if (/^--[A-Za-z-]+=-$/.test(t) || /^-[A-Za-z]*[SX]-$/.test(t)) {
|
|
1447
|
-
return `\`diff ${t}\` names stdin inside a fused option — a read this check cannot see through ${NOT_AUTO_ALLOWED}`;
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
614
|
return undefined;
|
|
1452
615
|
}
|
|
1453
|
-
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
continue;
|
|
1472
|
-
if (t === "--") {
|
|
1473
|
-
const prev = k > 0 ? args[k - 1] : undefined;
|
|
1474
|
-
const prevMayOwnValue = prev !== undefined && prev.startsWith("--") && prev.length > 2 && !prev.includes("=");
|
|
1475
|
-
if (!prevMayOwnValue)
|
|
1476
|
-
endOfOptions = true;
|
|
1477
|
-
continue;
|
|
1478
|
-
}
|
|
1479
|
-
if (t.startsWith("--")) {
|
|
1480
|
-
const long = longOptionNameOf(t);
|
|
1481
|
-
if (long === undefined)
|
|
1482
|
-
continue;
|
|
1483
|
-
if (model.longNames?.some((full) => isLongOptionAbbrevOf(long, full)) === true)
|
|
1484
|
-
return true;
|
|
1485
|
-
for (const en of model.enumOptions ?? []) {
|
|
1486
|
-
if (en.longName === undefined || !isLongOptionAbbrevOf(long, en.longName))
|
|
1487
|
-
continue;
|
|
1488
|
-
const eq = t.indexOf("=");
|
|
1489
|
-
const v = eq >= 0 ? t.slice(eq + 1) : args[k + 1];
|
|
1490
|
-
if (v !== undefined && v.length > 0 && en.recursiveValue.startsWith(v.toLowerCase()))
|
|
1491
|
-
return true;
|
|
1492
|
-
}
|
|
1493
|
-
continue;
|
|
616
|
+
function openableWordsOf(row, args) {
|
|
617
|
+
const out = [];
|
|
618
|
+
const globs = valuesAreLiteral(row, args) ? "literal" : "expanded";
|
|
619
|
+
const offer = (value, role) => {
|
|
620
|
+
const named = slotValueCandidates(value, role, globs);
|
|
621
|
+
out.push(...(named ?? [value]));
|
|
622
|
+
};
|
|
623
|
+
for (const e of readArgs(row, args.map((folded) => ({ folded })), { firstOperandSlot: true })) {
|
|
624
|
+
const t = args[e.at];
|
|
625
|
+
if (e.kind === "dash-word-operand")
|
|
626
|
+
out.push(t);
|
|
627
|
+
else if (e.kind === "value")
|
|
628
|
+
offer(t, e.role);
|
|
629
|
+
else if (e.kind === "option") {
|
|
630
|
+
if (e.reading === "speculative")
|
|
631
|
+
out.push(...attachedOptionPayloads(t));
|
|
632
|
+
else if (e.reading !== "no-value")
|
|
633
|
+
offer(literalTildeSpelling(e.reading.attached), e.reading.role);
|
|
1494
634
|
}
|
|
1495
|
-
if (
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
const en = (model.enumOptions ?? []).find((e) => e.shortLetter === ch);
|
|
1502
|
-
if (en !== undefined) {
|
|
1503
|
-
const v = i === t.length - 1 ? args[k + 1] : t.slice(i + 1);
|
|
1504
|
-
if (v !== undefined && v.length > 0 && en.recursiveValue.startsWith(v.toLowerCase()))
|
|
1505
|
-
return true;
|
|
1506
|
-
if (i === t.length - 1)
|
|
1507
|
-
k++;
|
|
1508
|
-
break;
|
|
1509
|
-
}
|
|
1510
|
-
if (model.valueOwners?.includes(ch) === true) {
|
|
1511
|
-
if (i === t.length - 1)
|
|
1512
|
-
k++;
|
|
1513
|
-
break;
|
|
1514
|
-
}
|
|
1515
|
-
if (!/[A-Za-z0-9]/.test(ch))
|
|
1516
|
-
break;
|
|
635
|
+
else if (e.kind === "operand") {
|
|
636
|
+
const role = e.slot ?? e.role;
|
|
637
|
+
if (role !== undefined)
|
|
638
|
+
offer(t, role);
|
|
639
|
+
else
|
|
640
|
+
out.push(t);
|
|
1517
641
|
}
|
|
1518
642
|
}
|
|
1519
|
-
return
|
|
1520
|
-
}
|
|
1521
|
-
function isGrepFileStdinLongOption(tok) {
|
|
1522
|
-
const eq = tok.indexOf("=");
|
|
1523
|
-
if (eq < 0 || tok.slice(eq + 1) !== "-")
|
|
1524
|
-
return false;
|
|
1525
|
-
const name = longOptionNameOf(tok);
|
|
1526
|
-
return name !== undefined && isLongOptionAbbrevOf(name, "file");
|
|
643
|
+
return out;
|
|
1527
644
|
}
|
|
1528
645
|
const CLUSTER_OPTION_CHAR = /[A-Za-z0-9]/;
|
|
1529
|
-
export const ASSIGNMENT_VALUE_NOT_PATHS = new Set(["make", "gmake", "bmake", "cmake", "ninja", "meson", "docker", "podman"]);
|
|
1530
646
|
export function argvProgramOf(words) {
|
|
1531
647
|
const pos = readProgramPosition(words);
|
|
1532
648
|
if (pos.program === undefined)
|
|
@@ -1580,26 +696,6 @@ const CLASSIFY_HOME_REREADABLE = /["$`\\\n\r]/;
|
|
|
1580
696
|
function homeSpelledForClassify(w, home) {
|
|
1581
697
|
return CLASSIFY_HOME_REREADABLE.test(home) ? undefined : homeExpansionOf(w, home)?.spelled;
|
|
1582
698
|
}
|
|
1583
|
-
const HOME_PLAIN_READ = /\$HOME(?![A-Za-z0-9_])|\$\{HOME\}/g;
|
|
1584
|
-
const HOME_NAME = /HOME(?![A-Za-z0-9_])/;
|
|
1585
|
-
export function homeMayBeRebound(text) {
|
|
1586
|
-
const unquoted = text.replace(/\\\r?\n/g, "").replace(HOME_PLAIN_READ, " ").replace(/\$?["']|\\/g, "");
|
|
1587
|
-
if (HOME_NAME.test(unquoted))
|
|
1588
|
-
return true;
|
|
1589
|
-
if (hasUnquotedExtglobOpener(text))
|
|
1590
|
-
return true;
|
|
1591
|
-
if (!unquoted.includes("printf") && !/[*?[{]/.test(unquoted))
|
|
1592
|
-
return false;
|
|
1593
|
-
const lexed = lexShell(text);
|
|
1594
|
-
return !("parseFailure" in lexed) && lexed.segments.some((seg) => {
|
|
1595
|
-
const words = argvOf(seg);
|
|
1596
|
-
return printfNamesComputed(words) || words.some((w) => globMayMatchHome(w) || optionWordCarriesGlob(w) || wordHasBraceList(w));
|
|
1597
|
-
});
|
|
1598
|
-
}
|
|
1599
|
-
function hasUnquotedExtglobOpener(text) {
|
|
1600
|
-
const lexed = lexShell(text);
|
|
1601
|
-
return "parseFailure" in lexed || extglobOpenerIn(lexed, text);
|
|
1602
|
-
}
|
|
1603
699
|
export function extglobOpenerIn(lexed, text) {
|
|
1604
700
|
for (const seg of lexed.segments) {
|
|
1605
701
|
for (const [k, tok] of seg.tokens.entries()) {
|
|
@@ -1623,147 +719,6 @@ export function extglobOpenerIn(lexed, text) {
|
|
|
1623
719
|
}
|
|
1624
720
|
return false;
|
|
1625
721
|
}
|
|
1626
|
-
function wordHasBraceList(w) {
|
|
1627
|
-
return w.pieces.some((p) => p.expansion?.kind === "brace");
|
|
1628
|
-
}
|
|
1629
|
-
function optionWordCarriesGlob(w) {
|
|
1630
|
-
if (!w.pieces.some(isGlobPiece))
|
|
1631
|
-
return false;
|
|
1632
|
-
const atoms = patternAtoms(w.pieces);
|
|
1633
|
-
if (atoms === undefined)
|
|
1634
|
-
return UNREADABLE_GLOB_IS_A_REBINDING;
|
|
1635
|
-
try {
|
|
1636
|
-
const reversed = atoms.slice().reverse();
|
|
1637
|
-
return closureReaches(atoms, "-") && (closureReaches(reversed, "EMOH") || closureReaches(reversed, "]"));
|
|
1638
|
-
}
|
|
1639
|
-
catch {
|
|
1640
|
-
return UNREADABLE_GLOB_IS_A_REBINDING;
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
const UNREADABLE_GLOB_IS_A_REBINDING = true;
|
|
1644
|
-
function escapeRegExp(s) {
|
|
1645
|
-
return s.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
1646
|
-
}
|
|
1647
|
-
function globMayMatchHome(w) {
|
|
1648
|
-
if (!w.pieces.some(isGlobPiece))
|
|
1649
|
-
return false;
|
|
1650
|
-
const atoms = patternAtoms(w.pieces);
|
|
1651
|
-
if (atoms === undefined)
|
|
1652
|
-
return UNREADABLE_GLOB_IS_A_REBINDING;
|
|
1653
|
-
try {
|
|
1654
|
-
if (new RegExp(`^${atoms.join("")}$`).test("HOME"))
|
|
1655
|
-
return true;
|
|
1656
|
-
return closureReaches(atoms, "HOME[") && closureReaches(atoms.slice().reverse(), "]");
|
|
1657
|
-
}
|
|
1658
|
-
catch {
|
|
1659
|
-
return UNREADABLE_GLOB_IS_A_REBINDING;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
function patternAtoms(pieces) {
|
|
1663
|
-
const atoms = [];
|
|
1664
|
-
for (const p of pieces) {
|
|
1665
|
-
if (p.literal !== undefined && p.expansion === undefined) {
|
|
1666
|
-
for (const ch of p.literal)
|
|
1667
|
-
atoms.push(caseFoldedChar(ch));
|
|
1668
|
-
continue;
|
|
1669
|
-
}
|
|
1670
|
-
if (isGlobPiece(p)) {
|
|
1671
|
-
const body = p.expansion.body;
|
|
1672
|
-
if (body === undefined) {
|
|
1673
|
-
atoms.push(RUN_ATOM);
|
|
1674
|
-
continue;
|
|
1675
|
-
}
|
|
1676
|
-
const cls = translateBracket(body);
|
|
1677
|
-
if (cls === undefined)
|
|
1678
|
-
return undefined;
|
|
1679
|
-
atoms.push(cls);
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
atoms.push(RUN_ATOM);
|
|
1683
|
-
}
|
|
1684
|
-
return atoms;
|
|
1685
|
-
}
|
|
1686
|
-
const RUN_ATOM = ".*";
|
|
1687
|
-
function caseFoldedChar(ch) {
|
|
1688
|
-
const lower = ch.toLowerCase();
|
|
1689
|
-
const upper = ch.toUpperCase();
|
|
1690
|
-
return lower !== upper ? `[${escapeRegExp(lower)}${escapeRegExp(upper)}]` : escapeRegExp(ch);
|
|
1691
|
-
}
|
|
1692
|
-
function closureReaches(atoms, target) {
|
|
1693
|
-
let re = "";
|
|
1694
|
-
for (let i = atoms.length - 1; i >= 0; i--)
|
|
1695
|
-
re = `(?:${atoms[i]}${re})?`;
|
|
1696
|
-
return new RegExp(`^${re}$`).test(target);
|
|
1697
|
-
}
|
|
1698
|
-
function scanBracketBody(body) {
|
|
1699
|
-
const members = [];
|
|
1700
|
-
let computed = false;
|
|
1701
|
-
for (let i = 0; i < body.length; i++) {
|
|
1702
|
-
const c = body[i];
|
|
1703
|
-
if (c === "\\") {
|
|
1704
|
-
if (i + 1 < body.length)
|
|
1705
|
-
members.push({ ch: body[++i], unquoted: false });
|
|
1706
|
-
continue;
|
|
1707
|
-
}
|
|
1708
|
-
if (c === "$" || c === "`" || c === "{")
|
|
1709
|
-
computed = true;
|
|
1710
|
-
members.push({ ch: c, unquoted: true });
|
|
1711
|
-
}
|
|
1712
|
-
return { members, computed };
|
|
1713
|
-
}
|
|
1714
|
-
function membersOpenClassExpression(members) {
|
|
1715
|
-
return members.some((m, i) => m.unquoted && m.ch === "[" && members[i + 1]?.unquoted === true && (members[i + 1].ch === ":" || members[i + 1].ch === "=" || members[i + 1].ch === "."));
|
|
1716
|
-
}
|
|
1717
|
-
function translateBracket(body) {
|
|
1718
|
-
if (body.includes('"') || body.includes("'"))
|
|
1719
|
-
return undefined;
|
|
1720
|
-
const { members, computed } = scanBracketBody(body);
|
|
1721
|
-
if (computed || membersOpenClassExpression(members))
|
|
1722
|
-
return undefined;
|
|
1723
|
-
let i = 0;
|
|
1724
|
-
let negated = false;
|
|
1725
|
-
if (members[0]?.unquoted === true && (members[0].ch === "!" || members[0].ch === "^")) {
|
|
1726
|
-
negated = true;
|
|
1727
|
-
i = 1;
|
|
1728
|
-
}
|
|
1729
|
-
const first = i;
|
|
1730
|
-
let cls = "";
|
|
1731
|
-
for (; i < members.length; i++) {
|
|
1732
|
-
const { ch, unquoted } = members[i];
|
|
1733
|
-
if (ch === "-" && unquoted && i > first && i < members.length - 1) {
|
|
1734
|
-
if (!negated)
|
|
1735
|
-
return undefined;
|
|
1736
|
-
cls += "-";
|
|
1737
|
-
continue;
|
|
1738
|
-
}
|
|
1739
|
-
const escaped = ch.replace(/[\\\]\[^-]/g, "\\$&");
|
|
1740
|
-
cls += !negated && ch.toLowerCase() !== ch.toUpperCase() ? `${escapeRegExp(ch.toLowerCase())}${escapeRegExp(ch.toUpperCase())}` : escaped;
|
|
1741
|
-
}
|
|
1742
|
-
return `[${negated ? "^" : ""}${cls || "\\]"}]`;
|
|
1743
|
-
}
|
|
1744
|
-
const literalWord = (w) => (w !== undefined && w.pieces.every((p) => p.literal !== undefined && p.expansion === undefined) ? w.pieces.map((p) => p.literal).join("") : undefined);
|
|
1745
|
-
const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1746
|
-
function printfNamesComputed(words) {
|
|
1747
|
-
for (let i = 0; i < words.length; i++) {
|
|
1748
|
-
const head = literalWord(words[i]);
|
|
1749
|
-
if (head === undefined || programBasename(head) !== "printf")
|
|
1750
|
-
continue;
|
|
1751
|
-
for (let j = i + 1; j < words.length; j++) {
|
|
1752
|
-
const lit = literalWord(words[j]);
|
|
1753
|
-
const opt = lit ?? words[j].pieces[0]?.literal ?? "";
|
|
1754
|
-
if (lit === "--" || !opt.startsWith("-v"))
|
|
1755
|
-
break;
|
|
1756
|
-
if (lit === undefined)
|
|
1757
|
-
return true;
|
|
1758
|
-
const name = lit === "-v" ? words[++j] : undefined;
|
|
1759
|
-
if (lit === "-v" && name === undefined)
|
|
1760
|
-
break;
|
|
1761
|
-
if (!IDENTIFIER.test(lit === "-v" ? (literalWord(name) ?? "") : lit.slice(2)))
|
|
1762
|
-
return true;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
return false;
|
|
1766
|
-
}
|
|
1767
722
|
function segmentCompoundForReadonly(command, homeDir) {
|
|
1768
723
|
const trimmed = command.trim();
|
|
1769
724
|
if (!trimmed)
|
|
@@ -1776,7 +731,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1776
731
|
if ("reject" in split)
|
|
1777
732
|
return { reason: split.reject };
|
|
1778
733
|
const { segments, pipeFed } = split;
|
|
1779
|
-
const homeRebound = boundary?.homeDir !== undefined &&
|
|
734
|
+
const homeRebound = boundary?.homeDir !== undefined && textBindsHome(command);
|
|
1780
735
|
let nameRefusal;
|
|
1781
736
|
const unlistedSegments = new Set();
|
|
1782
737
|
const reheaded = new Map();
|
|
@@ -1791,7 +746,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1791
746
|
return { reason: "empty command" };
|
|
1792
747
|
if (written === "coproc")
|
|
1793
748
|
return { reason: "`coproc` backgrounds a command — a backgrounded process outlives the command" };
|
|
1794
|
-
if (isBareControlWord(words, from,
|
|
749
|
+
if (isBareControlWord(words, from, BLOCK_WORD_NAMES)) {
|
|
1795
750
|
unlistedSegments.add(si);
|
|
1796
751
|
nameRefusal ??= `command "${written}" is not in the read-only allowlist`;
|
|
1797
752
|
continue;
|
|
@@ -1819,14 +774,14 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1819
774
|
continue;
|
|
1820
775
|
}
|
|
1821
776
|
nameRefusal ??= `command "${parsed.name}" is not in the read-only allowlist`;
|
|
1822
|
-
if (
|
|
777
|
+
if (runsShellText(name))
|
|
1823
778
|
return { reason: unjudgedProgramTextRefusal(parsed.name, "runs its argument as shell text") };
|
|
1824
779
|
if (pos.launchers.length > 0) {
|
|
1825
780
|
const listed = program !== undefined && (allow.has(program.basename) || program.basename === "cd");
|
|
1826
|
-
if (program !== undefined && !listed &&
|
|
781
|
+
if (program !== undefined && !listed && runsShellText(program.basename)) {
|
|
1827
782
|
return { reason: `"${written}" runs "${program.basename}", which runs its argument as shell text this classifier does not read ${NOT_AUTO_ALLOWED}` };
|
|
1828
783
|
}
|
|
1829
|
-
if (argvReentryUnjudged(pos,
|
|
784
|
+
if (argvReentryUnjudged(pos, opensNoOperand)) {
|
|
1830
785
|
return { reason: `"${written}" takes the operands of the program it runs from its standard input, which this classifier does not read ${NOT_AUTO_ALLOWED}` };
|
|
1831
786
|
}
|
|
1832
787
|
if (!listed || program === undefined)
|
|
@@ -1838,34 +793,29 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1838
793
|
unlistedSegments.add(si);
|
|
1839
794
|
}
|
|
1840
795
|
const HEAD_AUTO_ALLOW_MAX = 1_000_000;
|
|
1841
|
-
const
|
|
1842
|
-
if (
|
|
796
|
+
const byteBoundIsSmall = (row, args) => {
|
|
797
|
+
if (row === undefined)
|
|
1843
798
|
return false;
|
|
1844
799
|
let byteBound = false;
|
|
1845
|
-
for (
|
|
1846
|
-
|
|
1847
|
-
if (!t.startsWith("-") || t === "-")
|
|
800
|
+
for (const e of readArgRoles(row, args)) {
|
|
801
|
+
if (e.kind === "operand" || e.kind === "end-of-options")
|
|
1848
802
|
continue;
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
if (Number(inline[1]) > HEAD_AUTO_ALLOW_MAX)
|
|
803
|
+
if (e.kind === "value" && e.spec?.bounds === "bytes") {
|
|
804
|
+
if (!/^\d+$/.test(args[e.at]) || Number(args[e.at]) > HEAD_AUTO_ALLOW_MAX)
|
|
1852
805
|
return false;
|
|
1853
806
|
byteBound = true;
|
|
1854
807
|
continue;
|
|
1855
808
|
}
|
|
1856
|
-
if (
|
|
1857
|
-
|
|
1858
|
-
if (v === undefined || !/^\d+$/.test(v) || Number(v) > HEAD_AUTO_ALLOW_MAX)
|
|
809
|
+
if (e.kind === "option" && typeof e.reading === "object" && e.reading.spec?.bounds === "bytes") {
|
|
810
|
+
if (!/^\d+$/.test(e.reading.attached) || Number(e.reading.attached) > HEAD_AUTO_ALLOW_MAX)
|
|
1859
811
|
return false;
|
|
1860
812
|
byteBound = true;
|
|
1861
|
-
k++;
|
|
1862
813
|
continue;
|
|
1863
814
|
}
|
|
1864
815
|
return false;
|
|
1865
816
|
}
|
|
1866
817
|
return byteBound;
|
|
1867
818
|
};
|
|
1868
|
-
const STDIN_FILE_FLOOR = { cat: 1, grep: 2, head: 1, tail: 1, wc: 1, cut: 1, tr: Infinity, diff: 2, cmp: 2, comm: 2, sed: 2 };
|
|
1869
819
|
const foldedSegments = [];
|
|
1870
820
|
let shapeRefusal = nameRefusal;
|
|
1871
821
|
for (let si = 0; si < segments.length; si++) {
|
|
@@ -1881,35 +831,38 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1881
831
|
continue;
|
|
1882
832
|
foldedSegments.push(segmentTokens);
|
|
1883
833
|
const name = toks[0];
|
|
1884
|
-
const guardReason = guardedVerbFormReason(name, toks, argWords);
|
|
834
|
+
const guardReason = guardedVerbFormReason(name, toks, argWords) ?? (writesWordsItIsHanded(programRow(name), toks.slice(1)) ? `\`${name}\` writes a file it is handed — not a read ${NOT_AUTO_ALLOWED}` : undefined);
|
|
1885
835
|
if (guardReason !== undefined) {
|
|
1886
836
|
shapeRefusal ??= guardReason;
|
|
1887
837
|
continue;
|
|
1888
838
|
}
|
|
839
|
+
const row = programRow(name);
|
|
840
|
+
const restArgs = toks.slice(1);
|
|
1889
841
|
if (!(pipeFed[si] ?? false)) {
|
|
1890
|
-
const floor =
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
842
|
+
const floor = row?.operands?.stdinBelow;
|
|
843
|
+
if (floor === "always") {
|
|
844
|
+
shapeRefusal ??= `"${name}" with no file argument reads stdin and would block until the tool timeout ${NOT_AUTO_ALLOWED}`;
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (floor !== undefined) {
|
|
848
|
+
const floorFirstOperandSlot = firstOperandSlot(row);
|
|
849
|
+
const floorSlotSuppliedByFlag = floorFirstOperandSlot !== undefined && restArgs.some((t) => suppliesFirstOperandSlot(floorFirstOperandSlot, t));
|
|
850
|
+
let sawNonFlagOperand = false;
|
|
851
|
+
let hasStdinDash = false;
|
|
852
|
+
let operandCount = 0;
|
|
853
|
+
let endOfOptions = false;
|
|
1899
854
|
for (let k = 0; k < restArgs.length; k++) {
|
|
1900
855
|
const t = restArgs[k];
|
|
1901
|
-
if (endOfOptions) {
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
else {
|
|
1912
|
-
if (name === "grep" && (isGrepClusterFileStdin(t) || isGrepFileStdinLongOption(t))) {
|
|
856
|
+
if (!endOfOptions) {
|
|
857
|
+
if (t === "--") {
|
|
858
|
+
endOfOptions = true;
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (takesSeparatedDatum(row, t)) {
|
|
862
|
+
k++;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (attachedStdinFile(row, t)) {
|
|
1913
866
|
hasStdinDash = true;
|
|
1914
867
|
break;
|
|
1915
868
|
}
|
|
@@ -1926,31 +879,24 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1926
879
|
break;
|
|
1927
880
|
}
|
|
1928
881
|
}
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
882
|
+
if (hasStdinDash) {
|
|
883
|
+
shapeRefusal ??= `"${name}" reads stdin via an explicit "-" argument and would block until the tool timeout ${NOT_AUTO_ALLOWED}`;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (operandCount < floor) {
|
|
887
|
+
shapeRefusal ??= `"${name}" with no file argument reads stdin and would block until the tool timeout ${NOT_AUTO_ALLOWED}`;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
1937
890
|
}
|
|
1938
891
|
}
|
|
1939
|
-
if (
|
|
1940
|
-
shapeRefusal ??=
|
|
892
|
+
if (selectedOptions(row, restArgs).some((o) => o.mode === "follows")) {
|
|
893
|
+
shapeRefusal ??= `\`${name}\` in follow mode never terminates ${NOT_AUTO_ALLOWED}`;
|
|
1941
894
|
continue;
|
|
1942
895
|
}
|
|
1943
896
|
const GENERATOR_DEVICES = new Set(["/dev/zero", "/dev/random", "/dev/urandom", "/dev/full"]);
|
|
1944
|
-
const deviceCandidates =
|
|
1945
|
-
for (const t of toks.slice(1)) {
|
|
1946
|
-
if (t.startsWith("-") && t !== "-") {
|
|
1947
|
-
deviceCandidates.push(...attachedOptionPayloads(t));
|
|
1948
|
-
continue;
|
|
1949
|
-
}
|
|
1950
|
-
deviceCandidates.push(t);
|
|
1951
|
-
}
|
|
897
|
+
const deviceCandidates = openableWordsOf(row, restArgs);
|
|
1952
898
|
const deviceArgs = deviceCandidates.map((t) => resolveOperandLexically(undefined, t, boundary?.homeDir) ?? normalizeAbsPathLexically(t)).filter(isBlockedDevicePath);
|
|
1953
|
-
const rescuedByHead =
|
|
899
|
+
const rescuedByHead = byteBoundIsSmall(row, restArgs) && deviceArgs.every((d) => GENERATOR_DEVICES.has(d));
|
|
1954
900
|
if (!rescuedByHead && deviceArgs.length > 0) {
|
|
1955
901
|
shapeRefusal ??= "reads a device/special file that is either unbounded (/dev/zero, /dev/stdin, /proc/<pid>/fd/0, … — blocks the pipeline until the tool timeout) or process-private (/proc/<pid>/environ, /proc/<pid>/mem, …) " + NOT_AUTO_ALLOWED;
|
|
1956
902
|
}
|
|
@@ -1960,8 +906,6 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary) {
|
|
|
1960
906
|
shapeRefusal ??= `multiple directory changes in one command require approval for clarity ${NOT_AUTO_ALLOWED}`;
|
|
1961
907
|
return shapeRefusal !== undefined ? { reason: shapeRefusal } : {};
|
|
1962
908
|
}
|
|
1963
|
-
export const UNMODELLED_BASE_MOVERS = new Set(["pushd", "popd"]);
|
|
1964
|
-
export const COMMAND_LAUNCHERS_FOR_TEST = COMMAND_LAUNCHERS;
|
|
1965
909
|
export function classifyCompoundReadonly(command, allow, boundary) {
|
|
1966
910
|
return classifyCompoundReadonlyDetailed(command, allow, boundary).reason;
|
|
1967
911
|
}
|
|
@@ -2055,7 +999,7 @@ export function classifyBoundedReadonlyPollLoopDetailed(command, allow, boundary
|
|
|
2055
999
|
if (beats > POLL_LOOP_MAX_BEATS) {
|
|
2056
1000
|
return { reason: `the loop runs ${beats} iterations, above the ${POLL_LOOP_MAX_BEATS}-iteration cap for auto-allow` };
|
|
2057
1001
|
}
|
|
2058
|
-
const homeRebound = boundary?.homeDir !== undefined &&
|
|
1002
|
+
const homeRebound = boundary?.homeDir !== undefined && textBindsHome(command);
|
|
2059
1003
|
const bodies = shape.body.map((b) => ({ ...b, spelled: segmentSpellings(b.words, boundary?.homeDir, homeRebound).raw }));
|
|
2060
1004
|
for (const b of bodies) {
|
|
2061
1005
|
if (b.segment.connector !== ";" || b.segment.tokens.some((t) => t.kind !== "word") || b.spelled.some((raw) => POLL_BODY_HARD_REJECT.test(raw))) {
|