From c5f4400787b52e7f4d6f255cfaface6899be3082 Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Mon, 18 Mar 2024 11:55:10 +0100 Subject: [PATCH 1/3] Added upperCaseName method to UC objet names with variant chars Signed-off-by: Seb Julliand --- src/api/IBMi.ts | 23 +++++++++++++++++++++-- src/api/IBMiContent.ts | 12 ++++++------ src/api/Tools.ts | 3 +-- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index cd012d919..000a5cdf8 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -1044,12 +1044,13 @@ export default class IBMi { } } - parserMemberPath(string: string): MemberParts { + parserMemberPath(string: string): MemberParts { const variant_chars_local = this.variantChars.local; const validQsysName = new RegExp(`^[A-Z0-9${variant_chars_local}][A-Z0-9_${variant_chars_local}.]{0,9}$`); // Remove leading slash - const path = string.startsWith(`/`) ? string.substring(1).toUpperCase().split(`/`) : string.toUpperCase().split(`/`); + const upperCasedString = this.upperCaseName(string); + const path = upperCasedString.startsWith(`/`) ? upperCasedString.substring(1).split(`/`) : upperCasedString.split(`/`); const basename = path[path.length - 1]; const file = path[path.length - 2]; @@ -1192,4 +1193,22 @@ export default class IBMi { throw new Error(`Failed to create temporary directory ${tempDirectory}: ${prepareDirectory.stderr}`); } } + + /** + * Uppercases an object name, keeping the variant chars case intact + * @param name + */ + upperCaseName(name : string){ + const upperCased = []; + for(const char of name){ + const upChar = char.toLocaleUpperCase(); + if(new RegExp(`[A-Z${this.variantChars.local}]`).test(upChar)){ + upperCased.push(upChar); + } + else{ + upperCased.push(char); + } + } + return upperCased.join(""); + } } \ No newline at end of file diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index 7bf92ffeb..f3cdf4273 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -144,9 +144,9 @@ export default class IBMiContent { */ async downloadMemberContent(asp: string | undefined, library: string, sourceFile: string, member: string, localPath?: string) { asp = asp || this.config.sourceASP; - library = library.toUpperCase(); - sourceFile = sourceFile.toUpperCase(); - member = member.toUpperCase(); + library = this.ibmi.upperCaseName(library); + sourceFile = this.ibmi.upperCaseName(sourceFile); + member = this.ibmi.upperCaseName(member); let retry = false; let path = Tools.qualifyPath(library, sourceFile, member, asp); @@ -197,9 +197,9 @@ export default class IBMiContent { */ async uploadMemberContent(asp: string | undefined, library: string, sourceFile: string, member: string, content: string | Uint8Array) { asp = asp || this.config.sourceASP; - library = library.toUpperCase(); - sourceFile = sourceFile.toUpperCase(); - member = member.toUpperCase(); + library = this.ibmi.upperCaseName(library); + sourceFile = this.ibmi.upperCaseName(sourceFile); + member = this.ibmi.upperCaseName(member); const client = this.ibmi.client; const tmpobj = await tmpFile(); diff --git a/src/api/Tools.ts b/src/api/Tools.ts index 2befcb355..5512c3c7a 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -146,9 +146,8 @@ export namespace Tools { * @param iasp Optional: an iASP name */ export function qualifyPath(library: string, object: string, member: string, iasp?: string, sanitise?: boolean) { - library = library.toUpperCase(); const libraryPath = library === `QSYS` ? `QSYS.LIB` : `QSYS.LIB/${Tools.sanitizeLibraryNames([library]).join(``)}.LIB`; - const memberPath = `${object.toUpperCase()}.FILE/${member.toUpperCase()}.MBR` + const memberPath = `${object}.FILE/${member}.MBR` const memberSubpath = sanitise ? Tools.escapePath(memberPath) : memberPath; const result = (iasp && iasp.length > 0 ? `/${iasp}` : ``) + `/${libraryPath}/${memberSubpath}`; From 036a61275a9e6a5969804fa475c29056cb56a3ac Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Mon, 18 Mar 2024 13:31:14 +0100 Subject: [PATCH 2/3] Use temporary member to open/save members with variant characters Signed-off-by: Seb Julliand --- src/api/IBMiContent.ts | 47 +++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index f3cdf4273..abcd60228 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -152,10 +152,25 @@ export default class IBMiContent { let path = Tools.qualifyPath(library, sourceFile, member, asp); const tempRmt = this.getTempRemote(path); while (true) { - const copyResult = await this.ibmi.runCommand({ - command: `CPYTOSTMF FROMMBR('${path}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, - noLibList: true - }); + let copyResult: CommandResult; + if (new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { + copyResult = { code: 0, stdout: '', stderr: '' }; + try { + await this.runSQL([ + `@QSYS/CPYF FROMFILE(${library}/${sourceFile}) TOFILE(QTEMP/QTEMPSRC) FROMMBR(${member}) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`, + `@QSYS/CPYTOSTMF FROMMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID});` + ].join("\n")); + } catch (error: any) { + copyResult.code = -1; + copyResult.stderr = String(error); + } + } + else { + copyResult = await this.ibmi.runCommand({ + command: `QSYS/CPYTOSTMF FROMMBR('${path}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, + noLibList: true + }); + } if (copyResult.code === 0) { if (!localPath) { @@ -212,10 +227,26 @@ export default class IBMiContent { await client.putFile(tmpobj, tempRmt); while (true) { - const copyResult = await this.ibmi.runCommand({ - command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${path}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, - noLibList: true - }); + let copyResult: CommandResult; + if (new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { + copyResult = { code: 0, stdout: '', stderr: '' }; + try { + await this.runSQL([ + `@QSYS/CPYF FROMFILE(${library}/${sourceFile}) FROMMBR(${member}) TOFILE(QTEMP/QTEMPSRC) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`, + `@QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, + `@QSYS/CPYF FROMFILE(QTEMP/QTEMPSRC) FROMMBR(TEMPMEMBER) TOFILE(${library}/${sourceFile}) TOMBR(${member}) MBROPT(*REPLACE);` + ].join("\n")); + } catch (error: any) { + copyResult.code = -1; + copyResult.stderr = String(error); + } + } + else { + copyResult = await this.ibmi.runCommand({ + command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${path}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`, + noLibList: true + }); + } if (copyResult.code === 0) { const messages = Tools.parseMessages(copyResult.stderr); From 11ece4dc9401762108fbf652e8785c071e63d32f Mon Sep 17 00:00:00 2001 From: Seb Julliand Date: Mon, 18 Mar 2024 14:31:04 +0100 Subject: [PATCH 3/3] Only apply special variant methods to dangerous variants Signed-off-by: Seb Julliand --- src/api/IBMi.ts | 38 ++++++++++++++++++++++++-------------- src/api/IBMiContent.ts | 4 ++-- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/api/IBMi.ts b/src/api/IBMi.ts index 000a5cdf8..08bd82fcf 100644 --- a/src/api/IBMi.ts +++ b/src/api/IBMi.ts @@ -61,6 +61,8 @@ export default class IBMi { commandsExecuted: number = 0; + dangerousVariants = false; + constructor() { this.client = new node_ssh.NodeSSH; this.currentHost = ``; @@ -877,6 +879,9 @@ export default class IBMi { defaultCCSID: this.defaultCCSID }); + //Keep track of variant characters that can be uppercased + this.dangerousVariants = this.variantChars.local !== this.variantChars.local.toLocaleUpperCase(); + return { success: true }; @@ -1044,7 +1049,7 @@ export default class IBMi { } } - parserMemberPath(string: string): MemberParts { + parserMemberPath(string: string): MemberParts { const variant_chars_local = this.variantChars.local; const validQsysName = new RegExp(`^[A-Z0-9${variant_chars_local}][A-Z0-9_${variant_chars_local}.]{0,9}$`); @@ -1149,12 +1154,12 @@ export default class IBMi { } getLastDownloadLocation() { - if(this.config?.lastDownloadLocation && existsSync(Tools.fixWindowsPath(this.config.lastDownloadLocation))){ + if (this.config?.lastDownloadLocation && existsSync(Tools.fixWindowsPath(this.config.lastDownloadLocation))) { return this.config.lastDownloadLocation; } - else{ + else { return os.homedir(); - } + } } async setLastDownloadLocation(location: string) { @@ -1198,17 +1203,22 @@ export default class IBMi { * Uppercases an object name, keeping the variant chars case intact * @param name */ - upperCaseName(name : string){ - const upperCased = []; - for(const char of name){ - const upChar = char.toLocaleUpperCase(); - if(new RegExp(`[A-Z${this.variantChars.local}]`).test(upChar)){ - upperCased.push(upChar); - } - else{ - upperCased.push(char); + upperCaseName(name: string) { + if (this.dangerousVariants && new RegExp(`[${this.variantChars.local}]`).test(name)) { + const upperCased = []; + for (const char of name) { + const upChar = char.toLocaleUpperCase(); + if (new RegExp(`[A-Z${this.variantChars.local}]`).test(upChar)) { + upperCased.push(upChar); + } + else { + upperCased.push(char); + } } + return upperCased.join(""); + } + else{ + return name.toLocaleUpperCase(); } - return upperCased.join(""); } } \ No newline at end of file diff --git a/src/api/IBMiContent.ts b/src/api/IBMiContent.ts index abcd60228..b67be79b7 100644 --- a/src/api/IBMiContent.ts +++ b/src/api/IBMiContent.ts @@ -153,7 +153,7 @@ export default class IBMiContent { const tempRmt = this.getTempRemote(path); while (true) { let copyResult: CommandResult; - if (new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { + if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { copyResult = { code: 0, stdout: '', stderr: '' }; try { await this.runSQL([ @@ -228,7 +228,7 @@ export default class IBMiContent { while (true) { let copyResult: CommandResult; - if (new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { + if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) { copyResult = { code: 0, stdout: '', stderr: '' }; try { await this.runSQL([