Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/api/CompileTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ export namespace CompileTools {

function buildLiblistCommands(connection: IBMi, config: ILELibrarySettings): string[] {
return [
`liblist -d ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase(...connection.defaultUserLibraries).join(` `))}`,
`liblist -c ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase(config.currentLibrary || "*CRTDFT")[0])}`,
`liblist -a ${IBMi.escapeForShell(Tools.sanitizeObjNamesForPase(...buildLibraryList(config)).join(` `))}`
`liblist -d ${Tools.escapeForShell(Tools.sanitizeObjNamesForPase(...connection.defaultUserLibraries).join(` `))}`,
`liblist -c ${Tools.escapeForShell(Tools.sanitizeObjNamesForPase(config.currentLibrary || "*CRTDFT")[0])}`,
`liblist -a ${Tools.escapeForShell(Tools.sanitizeObjNamesForPase(...buildLibraryList(config)).join(` `))}`
];
}
}
12 changes: 4 additions & 8 deletions src/api/IBMi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -985,8 +985,8 @@ export default class IBMi {

const result = await this.sendQsh({
command: [
`liblist -d ` + IBMi.escapeForShell(this.defaultUserLibraries.join(` `)),
...this.config.libraryList.map(lib => `liblist -a ` + IBMi.escapeForShell(lib))
`liblist -d ` + Tools.escapeForShell(this.defaultUserLibraries.join(` `)),
...this.config.libraryList.map(lib => `liblist -a ` + Tools.escapeForShell(lib))
].join(`; `)
});

Expand Down Expand Up @@ -1185,10 +1185,6 @@ export default class IBMi {
return CompileTools.runCommand(this, data);
}

static escapeForShell(command: string) {
return command.replace(/\$/g, `\\$`)
}

async sendQsh(options: CommandData) {
options.stdin = options.command;

Expand All @@ -1208,10 +1204,10 @@ export default class IBMi {
let commands: string[] = [];
if (options.env) {
if (this.usingBash()) {
commands.push(...Object.entries(options.env).map(([key, value]) => `export ${key}="${value ? IBMi.escapeForShell(value) : ``}"`));
commands.push(...Object.entries(options.env).map(([key, value]) => `export ${key}="${value ? Tools.escapeForShell(value) : ``}"`));
} else {
// bourne shell doesn't support the same export syntax as bash
commands.push(...Object.entries(options.env).map(([key, value]) => `${key}="${value ? IBMi.escapeForShell(value) : ``}" export ${key}`));
commands.push(...Object.entries(options.env).map(([key, value]) => `${key}="${value ? Tools.escapeForShell(value) : ``}" export ${key}`));
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ export default class IBMiContent {
// Escape names for shell
const pathList: string[] = [];
for (const file of files) {
pathList.push(Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), await this.ibmi.getLibraryIAsp(file.library), true));
pathList.push(Tools.escapeForShell(Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), await this.ibmi.getLibraryIAsp(file.library), true)));
}

const command = `for f in ${pathList.join(' ').toUpperCase()}; do if [ -f $f ]; then echo $f; break; fi; done`;
Expand All @@ -975,7 +975,7 @@ export default class IBMiContent {
}

async objectResolve(object: string, libraries: string[]): Promise<string | undefined> {
const command = `for f in ${libraries.map(lib => `/QSYS.LIB/${this.ibmi.sysNameInAmerican(lib)}.LIB/${this.ibmi.sysNameInAmerican(object)}.*`).join(` `)}; do if [ -f $f ] || [ -d $f ]; then echo $f; break; fi; done`;
const command = `for f in ${Tools.escapeForShell(libraries.map(lib => `/QSYS.LIB/${this.ibmi.sysNameInAmerican(lib)}.LIB/${this.ibmi.sysNameInAmerican(object)}.*`).join(` `))}; do if [ -f $f ] || [ -d $f ]; then echo $f; break; fi; done`;

const result = await this.ibmi.sendCommand({
command,
Expand Down Expand Up @@ -1109,7 +1109,7 @@ export default class IBMiContent {
let result: CommandResult;

if (assumeMember) {
target = IBMi.escapeForShell(target);
target = Tools.escapeForShell(target);
result = await this.ibmi.sendQsh({ command: `${this.ibmi.remoteFeatures.attr} -p ${target} ${operands.join(" ")}` });
} else {
target = Tools.escapePath(target, true);
Expand Down
4 changes: 4 additions & 0 deletions src/api/Tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,8 @@ export namespace Tools {
export function getCurLib(value?:string){
return !value || /\*CRTDFT/i.test(value) ? "*CURLIB" : value;
}

export function escapeForShell(command: string) {
return command.replace(/\$/g, `\\$`)
}
}
65 changes: 65 additions & 0 deletions src/api/tests/suites/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,42 @@ describe('Content Tests', { concurrent: true }, () => {
}
});

it.each(['$', '#', '#$'])('memberResolve with %s', async (prefix) => {
const content = connection.getContent();
const tempLib = connection.getConfig().tempLibrary;
const tempSPF = `${prefix}ABCD`.concat(connection.variantChars.local);
const tempMbr = tempSPF;

try {
const result = await connection.runCommand({
command: `QSYS/CRTSRCPF ${tempLib}/${tempSPF} MBR(${tempMbr})`,
environment: 'ile'
});
expect(result.code).toBe(0);

const member = await content.memberResolve(tempMbr, [
{ library: 'QSYSINC', name: 'MIH' }, // Doesn't exist here
{ library: 'NOEXIST', name: 'SUP' }, // Doesn't exist here
{ library: tempLib, name: tempSPF } // Does exist here
]);

expect(member).toEqual({
asp: undefined,
library: tempLib,
file: tempSPF,
name: tempMbr,
extension: 'MBR',
basename: `${tempMbr}.MBR`
});
}
finally {
await connection.runCommand({
command: `QSYS/DLTF ${tempLib}/${tempSPF}`,
environment: 'ile'
})
}
});

it('memberResolve with bad name', async () => {
const content = connection.getContent();

Expand Down Expand Up @@ -152,6 +188,35 @@ describe('Content Tests', { concurrent: true }, () => {
}
});

it.each(['$', '#', '#$'])('objectResolve with %s', async (prefix) => {
const content = connection.getContent();
const tempLib = connection.getConfig().tempLibrary;
const tempObj = `${prefix}ABCD`.concat(connection.variantChars.local);

try {
const result = await connection.runCommand({
command: `QSYS/CRTDTAARA ${tempLib}/${tempObj} TYPE(*CHAR)`,
environment: 'ile'
});
expect(result.code).toBe(0);

const lib = await content.objectResolve(tempObj, [
'QSYSINC', // Doesn't exist here
'QSYS2', // Doesn't exist here
tempLib // Does exist here
]);

expect(lib).toBe(tempLib);
}
finally{
// Cleanup...
await connection.runCommand({
command: `QSYS/DLTDTAARA ${tempLib}/${tempObj}`,
environment: 'ile'
})
}
});

it('objectResolve with bad name', async () => {
const content = connection.getContent();

Expand Down
2 changes: 1 addition & 1 deletion src/api/tests/suites/encoding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ describe('Encoding tests', { concurrent: true }, () => {

it('Run variants through shells', async () => {
const text = `Hello${connection?.variantChars.local}world`;
const basicCommandA = `echo "${IBMi.escapeForShell(text)}"`;
const basicCommandA = `echo "${Tools.escapeForShell(text)}"`;
const basicCommandB = `echo '${text}'`;
const basicCommandC = `echo 'abc'\\''123'`;
const printEscapeChar = `echo "\\\\"`;
Expand Down
6 changes: 6 additions & 0 deletions src/api/tests/suites/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,10 @@ describe('Tools.parseFilterDate tests', { concurrent: true }, () => {
it('should reject an SQL injection attempt', () => {
expect(Tools.parseFilterDate(`2024-01-01') OR 1=1 --`)).toBeUndefined();
});

it('should escape $ for shell', () => {
expect(Tools.escapeForShell('$ABCD')).toBe('\\$ABCD');
expect(Tools.escapeForShell('#$ABC$D')).toBe('#\\$ABC\\$D');
expect(Tools.escapeForShell('ABCD')).toBe('ABCD');
})
});
Loading