mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-14 12:11:27 +08:00
feat(lib): extract shell command-substitution parser to shared lib
Extract the `extractCommandSubstitutions` function originally introduced in scripts/hooks/gateguard-fact-force.js (PR #1853 round 2) into scripts/lib/shell-substitution.js so other PreToolUse hooks can reuse the same single-quote-aware, double-quote-aware, nested-subshell-aware parser without duplicating it. No behavior change in this commit — the function body is copied verbatim and exposed via `module.exports`. The next commit wires it into scripts/hooks/pre-bash-dev-server-block.js to close that hook's own subshell-bypass holes. gateguard-fact-force.js still defines its own private copy of the function; consolidating both call sites onto this shared lib is a follow-up worth doing once this PR lands, but is intentionally out of scope here to keep the diff focused on the dev-server-block fix.
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract executable command-substitution bodies from a shell line.
|
||||||
|
*
|
||||||
|
* Single quotes are literal, so substitutions inside them are ignored;
|
||||||
|
* double quotes still permit substitutions, so those bodies are scanned
|
||||||
|
* before quoted text is stripped. Returns each substitution body plus
|
||||||
|
* any nested substitutions discovered recursively.
|
||||||
|
*
|
||||||
|
* Originally introduced in scripts/hooks/gateguard-fact-force.js
|
||||||
|
* (PR #1853 round 2). Extracted to a shared lib so other PreToolUse
|
||||||
|
* hooks that need the same "scan inside `$(...)` and backticks"
|
||||||
|
* behavior can reuse it without duplicating the parser.
|
||||||
|
*
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
function extractCommandSubstitutions(input) {
|
||||||
|
const source = String(input || '');
|
||||||
|
const substitutions = [];
|
||||||
|
let inSingle = false;
|
||||||
|
let inDouble = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < source.length; i++) {
|
||||||
|
const ch = source[i];
|
||||||
|
const prev = source[i - 1];
|
||||||
|
|
||||||
|
if (ch === '\\' && !inSingle) {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === "'" && !inDouble && prev !== '\\') {
|
||||||
|
inSingle = !inSingle;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '"' && !inSingle && prev !== '\\') {
|
||||||
|
inDouble = !inDouble;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inSingle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '`') {
|
||||||
|
let body = '';
|
||||||
|
i += 1;
|
||||||
|
while (i < source.length) {
|
||||||
|
const inner = source[i];
|
||||||
|
if (inner === '\\') {
|
||||||
|
body += inner;
|
||||||
|
if (i + 1 < source.length) {
|
||||||
|
body += source[i + 1];
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inner === '`') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
body += inner;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (body.trim()) {
|
||||||
|
substitutions.push(body);
|
||||||
|
substitutions.push(...extractCommandSubstitutions(body));
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch === '$' && source[i + 1] === '(') {
|
||||||
|
let depth = 1;
|
||||||
|
let body = '';
|
||||||
|
i += 2;
|
||||||
|
while (i < source.length && depth > 0) {
|
||||||
|
const inner = source[i];
|
||||||
|
if (inner === '\\') {
|
||||||
|
body += inner;
|
||||||
|
if (i + 1 < source.length) {
|
||||||
|
body += source[i + 1];
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inner === '(') {
|
||||||
|
depth += 1;
|
||||||
|
} else if (inner === ')') {
|
||||||
|
depth -= 1;
|
||||||
|
if (depth === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body += inner;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if (body.trim()) {
|
||||||
|
substitutions.push(body);
|
||||||
|
substitutions.push(...extractCommandSubstitutions(body));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return substitutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { extractCommandSubstitutions };
|
||||||
Reference in New Issue
Block a user