Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix quoted name and variant character support #2555

Closed
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
2 changes: 1 addition & 1 deletion src/api/CompileTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export namespace CompileTools {
commandResult = await connection.sendQsh({
command: [
...options.noLibList? [] : buildLiblistCommands(connection, ileSetup),
...commands,
...commands.map(command => IBMi.escapeForShell((command)))
].join(` && `),
directory: cwd,
...callbacks
Expand Down
19 changes: 15 additions & 4 deletions src/api/IBMi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1057,7 +1057,7 @@ export default class IBMi {
}

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

async sendQsh(options: CommandData) {
Expand Down Expand Up @@ -1241,6 +1241,11 @@ export default class IBMi {
* @returns {string} result
*/
sysNameInAmerican(string: string) {
// Keep local variants for quoted names
if (string.startsWith('"')) {
return string;
}

const fromChars = this.variantChars.local;
const toChars = this.variantChars.american;

Expand All @@ -1250,7 +1255,7 @@ export default class IBMi {
result = result.replace(new RegExp(`[${fromChars[i]}]`, `g`), toChars[i]);
};

return result
return result;
}

getLastDownloadLocation() {
Expand Down Expand Up @@ -1441,9 +1446,15 @@ export default class IBMi {
this.variantChars.qsysNameRegex = new RegExp(regexTest);
}

if (name.length > 10) return false;
name = this.upperCaseName(name);
return this.variantChars.qsysNameRegex.test(name);

if (name.startsWith(`"`)) {
// TOOD: Could improve this given https://www.ibm.com/docs/en/i/7.1?topic=rules-names-name
const qsysQuotedNameRegex = new RegExp(`^".{0,8}"$`);
return qsysQuotedNameRegex.test(name);
} else {
return this.variantChars.qsysNameRegex.test(name);
}
}

getCcsid() {
Expand Down
40 changes: 14 additions & 26 deletions src/api/IBMiContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default class IBMiContent {
const member = this.ibmi.upperCaseName(smallSignature ? sourceFileOrMember : String(memberOrLocalPath));

const asp = await this.ibmi.lookupLibraryIAsp(library);
const path = Tools.qualifyPath(library, sourceFile, member, asp, true);
const path = Tools.qualifyPath(library, sourceFile, member, asp);
const tempRmt = this.getTempRemote(path);
let retry = false;
while (true) {
Expand All @@ -186,7 +186,7 @@ export default class IBMiContent {
}
else {
copyResult = await this.ibmi.runCommand({
command: `QSYS/CPYTOSTMF FROMMBR('${path}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
command: `QSYS/CPYTOSTMF FROMMBR('''${path}''') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
noLibList: true
});
}
Expand Down Expand Up @@ -255,7 +255,7 @@ export default class IBMiContent {

try {
await writeFileAsync(tmpobj, content || memberOrContent, `utf8`);
const path = Tools.qualifyPath(library, sourceFile, member, asp, true);
const path = Tools.qualifyPath(library, sourceFile, member, asp);
const tempRmt = this.getTempRemote(path);
await client.putFile(tmpobj, tempRmt);

Expand All @@ -275,7 +275,7 @@ export default class IBMiContent {
}
else {
copyResult = await this.ibmi.runCommand({
command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${path}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('''${path}''') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
noLibList: true
});
}
Expand Down Expand Up @@ -453,17 +453,11 @@ export default class IBMiContent {

newLibl = newLibl
.filter(lib => {
if (lib.match(/^\d/)) {
const isValid = this.ibmi.validQsysName(lib);
if(!isValid) {
badLibs.push(lib);
return false;
}

if (lib.length > 10) {
badLibs.push(lib);
return false;
}

return true;
return isValid;
});

const sanitized = Tools.sanitizeObjNamesForPase(newLibl);
Expand Down Expand Up @@ -880,11 +874,11 @@ export default class IBMiContent {
const asp = file.asp || this.ibmi.getCurrentIAspName();
if (asp && asp.length > 0) {
return [
Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), asp, true),
Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), undefined, true)
Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), asp),
Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), undefined)
].join(` `);
} else {
return Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), undefined, true);
return Tools.qualifyPath(inAmerican(file.library), inAmerican(file.name), inAmerican(member), undefined);
}
})
.join(` `)
Expand Down Expand Up @@ -1033,24 +1027,18 @@ export default class IBMiContent {
const assumeMember = typeof localPath === `object`;
let target: string;

let result: CommandResult;

if (assumeMember) {
// If it's an object, we assume it's a member, therefore let's let qsh handle it (better for variants)
localPath.asp = localPath.asp ? this.ibmi.sysNameInAmerican(localPath.asp) : undefined;
localPath.library = this.ibmi.sysNameInAmerican(localPath.library);
localPath.name = this.ibmi.sysNameInAmerican(localPath.name);
localPath.member = localPath.member ? this.ibmi.sysNameInAmerican(localPath.member) : undefined;
target = Tools.qualifyPath(localPath.library, localPath.name, localPath.member || '', localPath.asp || '', true);
} else {
target = localPath;
}

let result: CommandResult;

if (assumeMember) {
target = IBMi.escapeForShell(target);
target = IBMi.escapeForShell(Tools.qualifyPath(localPath.library, localPath.name, localPath.member || '', localPath.asp || ''));
result = await this.ibmi.sendQsh({ command: `${this.ibmi.remoteFeatures.attr} -p ${target} ${operands.join(" ")}` });
} else {
target = Tools.escapePath(target, true);
target = Tools.escapePath(localPath, true);
// Take {DOES_THIS_WORK: `YESITDOES`} away, and all of a sudden names with # aren't found.
result = await this.ibmi.sendCommand({ command: `${this.ibmi.remoteFeatures.attr} -p "${target}" ${operands.join(" ")}`, env: { DOES_THIS_WORK: `YESITDOES` } });
}
Expand Down
12 changes: 3 additions & 9 deletions src/api/Tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,17 +176,11 @@ export namespace Tools {
* @param member Optional
* @param iasp Optional: an iASP name
*/
export function qualifyPath(library: string, object: string, member?: string, iasp?: string, noEscape?: boolean) {
[library, object] = Tools.sanitizeObjNamesForPase([library, object]);
member = member ? Tools.sanitizeObjNamesForPase([member])[0] : undefined;
iasp = iasp ? Tools.sanitizeObjNamesForPase([iasp])[0] : undefined;

export function qualifyPath(library: string, object: string, member?: string, iasp?: string) {
const libraryPath = library === `QSYS` ? `QSYS.LIB` : `QSYS.LIB/${library}.LIB`;
const filePath = object ? `${object}.FILE` : '';
const memberPath = member ? `/${member}.MBR` : '';
const fullPath = `${libraryPath}/${filePath}${memberPath}`;

const result = (iasp && iasp.length > 0 ? `/${iasp}` : ``) + `/${noEscape ? fullPath : Tools.escapePath(fullPath)}`;
const memberPath = member ? `${member}.MBR` : '';
const result = (iasp && iasp.length > 0 ? `/${iasp}` : ``) + `/${libraryPath}/${filePath}/${memberPath}`;
return result;
}

Expand Down
4 changes: 3 additions & 1 deletion src/api/tests/suites/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,13 @@ describe('Content Tests', {concurrent: true}, () => {
it('Test validateLibraryList', async () => {
const content = connection.getContent();

const badLibs = await content.validateLibraryList(['SCOOBY', 'QSYSINC', 'BEEPBOOP']);
const badLibs = await content.validateLibraryList(['SCOOBY', 'QSYSINC', 'BEEPBOOP', '"QUOTES', '"YOYOYO"']);

expect(badLibs?.includes('BEEPBOOP')).toBe(true);
expect(badLibs?.includes('QSYSINC')).toBe(false);
expect(badLibs?.includes('SCOOBY')).toBe(true);
expect(badLibs?.includes('"QUOTES')).toBe(true);
expect(badLibs?.includes('"YOYOYO"')).toBe(false);
});

it('Test getFileList', async () => {
Expand Down
19 changes: 7 additions & 12 deletions src/filesystems/qsys/QSysFs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,30 +151,25 @@ export class QSysFS implements vscode.FileSystemProvider {
memberContent = this.extendedMemberSupport ?
await this.extendedContent.downloadMemberContentWithDates(uri) :
await contentApi.downloadMemberContent(library, file, member);
}
catch (error) {
if (await this.stat(uri)) { //Check if exists on an iASP and retry if so
return this.readFile(uri);
} catch (error) {
if (!retrying && await this.stat(uri)) { //Check if exists on an iASP and retry if so
return this.readFile(uri, true);
}
throw error;
}
if (memberContent !== undefined) {
return new Uint8Array(Buffer.from(memberContent, `utf8`));
}
else {
} else {
throw new FileSystemError(`Couldn't read ${uri}; check IBM i connection.`);
}
}
else {
} else {
if (retrying) {
throw new FileSystemError("Not connected to IBM i");
}
else {
} else {
if (await reconnectFS(uri)) {
this.updateMemberSupport(); //this needs to be done right after reconnecting, before the member is read (the connect event may be triggered too late at this point)
return this.readFile(uri, true);
}
else {
} else {
return Buffer.alloc(0);
}
}
Expand Down
Loading