TERMINAL: Prevent recursive aliases from being resolved. (#741)

This commit is contained in:
Michael Ficocelli 2023-08-28 14:26:25 -04:00 committed by GitHub
parent 51b03003f6
commit 93235570d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 33 deletions

@ -65,39 +65,51 @@ export function removeAlias(name: string): boolean {
/** /**
* Returns the original string with any aliases substituted in. * Returns the original string with any aliases substituted in.
* Aliases are only applied to "whole words", one level deep * Aliases are only applied to "whole words", one level deep
* @param origCommand the original command string
*/ */
export function substituteAliases(origCommand: string): string { export function substituteAliases(origCommand: string): string {
const commandArray = origCommand.split(" "); return applyAliases(origCommand);
if (commandArray.length > 0) { }
// For the alias and unalias commands, don't substitute
if (commandArray[0] === "unalias" || commandArray[0] === "alias") { /**
return commandArray.join(" "); * Recursively evaluates aliases and applies them to the command string,
} * unless there are any reference loops or the reference chain is too deep
* @param origCommand the original command string
let somethingSubstituted = true; * @param depth the current recursion depth
let depth = 0; * @param currentlyProcessingAliases any aliases that have been applied in the recursive evaluation leading to this point
let lastAlias; * @return { string } the provided command with all of its referenced aliases evaluated
*/
while (somethingSubstituted && depth < 10) { function applyAliases(origCommand: string, depth = 0, currentlyProcessingAliases: string[] = []) {
depth++; if (!origCommand) {
somethingSubstituted = false; return origCommand;
const alias = Aliases.get(commandArray[0])?.split(" "); }
if (alias !== undefined) { const commandArray = origCommand.split(" ");
somethingSubstituted = true;
commandArray.splice(0, 1, ...alias); // Do not apply aliases when defining a new alias
//commandArray[0] = alias; if (commandArray[0] === "unalias" || commandArray[0] === "alias") {
} return commandArray.join(" ");
for (let i = 0; i < commandArray.length; ++i) { }
const alias = GlobalAliases.get(commandArray[i])?.split(" ");
if (alias !== undefined && (commandArray[i] != lastAlias || somethingSubstituted)) { // First get non-global aliases, and recursively apply them
somethingSubstituted = true; // (unless there are any reference loops or the reference chain is too deep)
lastAlias = commandArray[i]; const localAlias = Aliases.get(commandArray[0]);
commandArray.splice(i, 1, ...alias); if (localAlias && !currentlyProcessingAliases.includes(localAlias)) {
i += alias.length - 1; const appliedAlias = applyAliases(localAlias, depth + 1, [commandArray[0], ...currentlyProcessingAliases]);
//commandArray[i] = alias; commandArray.splice(0, 1, ...appliedAlias.split(" "));
} }
}
} // Once local aliasing is complete (or if none are present) handle any global aliases
} const processedCommands = commandArray.reduce((resolvedCommandArray: string[], command) => {
return commandArray.join(" "); const globalAlias = GlobalAliases.get(command);
if (globalAlias && !currentlyProcessingAliases.includes(globalAlias)) {
const appliedAlias = applyAliases(globalAlias, depth + 1, [command, ...currentlyProcessingAliases]);
resolvedCommandArray.push(appliedAlias);
} else {
// If there is no alias, or if the alias has a circular reference, leave the command as-is
resolvedCommandArray.push(command);
}
return resolvedCommandArray;
}, []);
return processedCommands.join(" ");
} }

@ -0,0 +1,52 @@
import { substituteAliases, parseAliasDeclaration } from "../../../src/Alias";
describe("substituteAliases Tests", () => {
it("Should gracefully handle recursive local aliases", () => {
parseAliasDeclaration("recursiveAlias=b");
parseAliasDeclaration("b=c");
parseAliasDeclaration("c=d");
parseAliasDeclaration("d=recursiveAlias");
const result = substituteAliases("recursiveAlias");
expect(result).toEqual("d");
});
it("Should only change local aliases if they are the start of the command", () => {
parseAliasDeclaration("a=b");
parseAliasDeclaration("b=c");
parseAliasDeclaration("c=d");
parseAliasDeclaration("d=e");
const result = substituteAliases("a b c d");
expect(result).toEqual("e b c d");
});
it("Should gracefully handle recursive global aliases", () => {
parseAliasDeclaration("a=b", true);
parseAliasDeclaration("b=c", true);
parseAliasDeclaration("c=d", true);
parseAliasDeclaration("d=a", true);
const result = substituteAliases("a b c d");
expect(result).toEqual("d a b c");
});
it("Should gracefully handle recursive mixed local and global aliases", () => {
parseAliasDeclaration("recursiveAlias=b", true);
parseAliasDeclaration("b=c", false);
parseAliasDeclaration("c=d", true);
parseAliasDeclaration("d=recursiveAlias", false);
const result = substituteAliases("recursiveAlias");
expect(result).toEqual("d");
});
it("Should replace chained aliases", () => {
parseAliasDeclaration("a=b", true);
parseAliasDeclaration("b=c", true);
parseAliasDeclaration("c=d", true);
parseAliasDeclaration("d=e", true);
const result = substituteAliases("a");
expect(result).toEqual("e");
});
});