refactor: share shell wrapper traversal

This commit is contained in:
Peter Steinberger
2026-04-20 13:33:46 +01:00
parent abe2296daf
commit 0f1a938a3e

View File

@@ -57,46 +57,75 @@ export type ShellWrapperCommand = {
command: string | null;
};
function resolveShellWrapperSpecAndArgvInternal(
argv: string[],
depth: number,
): { argv: string[]; wrapper: ShellWrapperSpec; payload: string } | null {
if (!isWithinDispatchClassificationDepth(depth)) {
type ShellWrapperCandidate<TState> = {
argv: string[];
token0: string;
state: TState;
};
function resolveShellWrapperCandidate<TState>(params: {
argv: string[];
depth: number;
state: TState;
onDispatchUnwrap?: (state: TState, wrappedArgv: string[]) => TState;
}): ShellWrapperCandidate<TState> | null {
if (!isWithinDispatchClassificationDepth(params.depth)) {
return null;
}
const token0 = argv[0]?.trim();
const token0 = params.argv[0]?.trim();
if (!token0) {
return null;
}
const dispatchUnwrap = unwrapKnownDispatchWrapperInvocation(argv);
const dispatchUnwrap = unwrapKnownDispatchWrapperInvocation(params.argv);
if (dispatchUnwrap.kind === "blocked") {
return null;
}
if (dispatchUnwrap.kind === "unwrapped") {
return resolveShellWrapperSpecAndArgvInternal(dispatchUnwrap.argv, depth + 1);
return resolveShellWrapperCandidate({
...params,
argv: dispatchUnwrap.argv,
depth: params.depth + 1,
state: params.onDispatchUnwrap?.(params.state, params.argv) ?? params.state,
});
}
const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(argv);
const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(params.argv);
if (shellMultiplexerUnwrap.kind === "blocked") {
return null;
}
if (shellMultiplexerUnwrap.kind === "unwrapped") {
return resolveShellWrapperSpecAndArgvInternal(shellMultiplexerUnwrap.argv, depth + 1);
return resolveShellWrapperCandidate({
...params,
argv: shellMultiplexerUnwrap.argv,
depth: params.depth + 1,
});
}
const wrapper = findShellWrapperSpec(normalizeExecutableToken(token0));
return { argv: params.argv, token0, state: params.state };
}
function resolveShellWrapperSpecAndArgvInternal(
argv: string[],
depth: number,
): { argv: string[]; wrapper: ShellWrapperSpec; payload: string } | null {
const candidate = resolveShellWrapperCandidate({ argv, depth, state: null });
if (!candidate) {
return null;
}
const wrapper = findShellWrapperSpec(normalizeExecutableToken(candidate.token0));
if (!wrapper) {
return null;
}
const payload = extractShellWrapperPayload(argv, wrapper);
const payload = extractShellWrapperPayload(candidate.argv, wrapper);
if (!payload) {
return null;
}
return { argv, wrapper, payload };
return { argv: candidate.argv, wrapper, payload };
}
function isWithinDispatchClassificationDepth(depth: number): boolean {
@@ -108,31 +137,8 @@ export function isShellWrapperExecutable(token: string): boolean {
}
function isShellWrapperInvocationInternal(argv: string[], depth: number): boolean {
if (!isWithinDispatchClassificationDepth(depth)) {
return false;
}
const token0 = argv[0]?.trim();
if (!token0) {
return false;
}
const dispatchUnwrap = unwrapKnownDispatchWrapperInvocation(argv);
if (dispatchUnwrap.kind === "blocked") {
return false;
}
if (dispatchUnwrap.kind === "unwrapped") {
return isShellWrapperInvocationInternal(dispatchUnwrap.argv, depth + 1);
}
const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(argv);
if (shellMultiplexerUnwrap.kind === "blocked") {
return false;
}
if (shellMultiplexerUnwrap.kind === "unwrapped") {
return isShellWrapperInvocationInternal(shellMultiplexerUnwrap.argv, depth + 1);
}
return isShellWrapperExecutable(token0);
const candidate = resolveShellWrapperCandidate({ argv, depth, state: null });
return candidate ? isShellWrapperExecutable(candidate.token0) : false;
}
export function isShellWrapperInvocation(argv: string[]): boolean {
@@ -235,49 +241,25 @@ function hasEnvManipulationBeforeShellWrapperInternal(
depth: number,
envManipulationSeen: boolean,
): boolean {
if (!isWithinDispatchClassificationDepth(depth)) {
const candidate = resolveShellWrapperCandidate({
argv,
depth,
state: envManipulationSeen,
onDispatchUnwrap: (state, wrappedArgv) => state || hasDispatchEnvManipulation(wrappedArgv),
});
if (!candidate) {
return false;
}
const token0 = argv[0]?.trim();
if (!token0) {
return false;
}
const dispatchUnwrap = unwrapKnownDispatchWrapperInvocation(argv);
if (dispatchUnwrap.kind === "blocked") {
return false;
}
if (dispatchUnwrap.kind === "unwrapped") {
const nextEnvManipulationSeen = envManipulationSeen || hasDispatchEnvManipulation(argv);
return hasEnvManipulationBeforeShellWrapperInternal(
dispatchUnwrap.argv,
depth + 1,
nextEnvManipulationSeen,
);
}
const shellMultiplexerUnwrap = unwrapKnownShellMultiplexerInvocation(argv);
if (shellMultiplexerUnwrap.kind === "blocked") {
return false;
}
if (shellMultiplexerUnwrap.kind === "unwrapped") {
return hasEnvManipulationBeforeShellWrapperInternal(
shellMultiplexerUnwrap.argv,
depth + 1,
envManipulationSeen,
);
}
const wrapper = findShellWrapperSpec(normalizeExecutableToken(token0));
const wrapper = findShellWrapperSpec(normalizeExecutableToken(candidate.token0));
if (!wrapper) {
return false;
}
const payload = extractShellWrapperPayload(argv, wrapper);
const payload = extractShellWrapperPayload(candidate.argv, wrapper);
if (!payload) {
return false;
}
return envManipulationSeen;
return candidate.state;
}
export function hasEnvManipulationBeforeShellWrapper(argv: string[]): boolean {