Skip to content

Commit 555563c

Browse files
authored
Merge pull request #1940 from codefori/fix/upperCasedMemberName
Correctly handle source members with variant characters in their name
2 parents 95da1ef + 11ece4d commit 555563c

File tree

3 files changed

+79
-20
lines changed

3 files changed

+79
-20
lines changed

src/api/IBMi.ts

+33-4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export default class IBMi {
6464

6565
commandsExecuted: number = 0;
6666

67+
dangerousVariants = false;
68+
6769
constructor() {
6870
this.client = new node_ssh.NodeSSH;
6971
this.currentHost = ``;
@@ -883,6 +885,9 @@ export default class IBMi {
883885
defaultCCSID: this.defaultCCSID
884886
});
885887

888+
//Keep track of variant characters that can be uppercased
889+
this.dangerousVariants = this.variantChars.local !== this.variantChars.local.toLocaleUpperCase();
890+
886891
return {
887892
success: true
888893
};
@@ -1059,7 +1064,8 @@ export default class IBMi {
10591064
const validQsysName = new RegExp(`^[A-Z0-9${variant_chars_local}][A-Z0-9_${variant_chars_local}.]{0,9}$`);
10601065

10611066
// Remove leading slash
1062-
const path = string.startsWith(`/`) ? string.substring(1).toUpperCase().split(`/`) : string.toUpperCase().split(`/`);
1067+
const upperCasedString = this.upperCaseName(string);
1068+
const path = upperCasedString.startsWith(`/`) ? upperCasedString.substring(1).split(`/`) : upperCasedString.split(`/`);
10631069

10641070
const basename = path[path.length - 1];
10651071
const file = path[path.length - 2];
@@ -1158,12 +1164,12 @@ export default class IBMi {
11581164
}
11591165

11601166
getLastDownloadLocation() {
1161-
if(this.config?.lastDownloadLocation && existsSync(Tools.fixWindowsPath(this.config.lastDownloadLocation))){
1167+
if (this.config?.lastDownloadLocation && existsSync(Tools.fixWindowsPath(this.config.lastDownloadLocation))) {
11621168
return this.config.lastDownloadLocation;
11631169
}
1164-
else{
1170+
else {
11651171
return os.homedir();
1166-
}
1172+
}
11671173
}
11681174

11691175
async setLastDownloadLocation(location: string) {
@@ -1202,4 +1208,27 @@ export default class IBMi {
12021208
throw new Error(`Failed to create temporary directory ${tempDirectory}: ${prepareDirectory.stderr}`);
12031209
}
12041210
}
1211+
1212+
/**
1213+
* Uppercases an object name, keeping the variant chars case intact
1214+
* @param name
1215+
*/
1216+
upperCaseName(name: string) {
1217+
if (this.dangerousVariants && new RegExp(`[${this.variantChars.local}]`).test(name)) {
1218+
const upperCased = [];
1219+
for (const char of name) {
1220+
const upChar = char.toLocaleUpperCase();
1221+
if (new RegExp(`[A-Z${this.variantChars.local}]`).test(upChar)) {
1222+
upperCased.push(upChar);
1223+
}
1224+
else {
1225+
upperCased.push(char);
1226+
}
1227+
}
1228+
return upperCased.join("");
1229+
}
1230+
else{
1231+
return name.toLocaleUpperCase();
1232+
}
1233+
}
12051234
}

src/api/IBMiContent.ts

+45-14
Original file line numberDiff line numberDiff line change
@@ -144,18 +144,33 @@ export default class IBMiContent {
144144
*/
145145
async downloadMemberContent(asp: string | undefined, library: string, sourceFile: string, member: string, localPath?: string) {
146146
asp = asp || this.config.sourceASP;
147-
library = library.toUpperCase();
148-
sourceFile = sourceFile.toUpperCase();
149-
member = member.toUpperCase();
147+
library = this.ibmi.upperCaseName(library);
148+
sourceFile = this.ibmi.upperCaseName(sourceFile);
149+
member = this.ibmi.upperCaseName(member);
150150

151151
let retry = false;
152152
let path = Tools.qualifyPath(library, sourceFile, member, asp);
153153
const tempRmt = this.getTempRemote(path);
154154
while (true) {
155-
const copyResult = await this.ibmi.runCommand({
156-
command: `CPYTOSTMF FROMMBR('${path}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
157-
noLibList: true
158-
});
155+
let copyResult: CommandResult;
156+
if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) {
157+
copyResult = { code: 0, stdout: '', stderr: '' };
158+
try {
159+
await this.runSQL([
160+
`@QSYS/CPYF FROMFILE(${library}/${sourceFile}) TOFILE(QTEMP/QTEMPSRC) FROMMBR(${member}) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`,
161+
`@QSYS/CPYTOSTMF FROMMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID});`
162+
].join("\n"));
163+
} catch (error: any) {
164+
copyResult.code = -1;
165+
copyResult.stderr = String(error);
166+
}
167+
}
168+
else {
169+
copyResult = await this.ibmi.runCommand({
170+
command: `QSYS/CPYTOSTMF FROMMBR('${path}') TOSTMF('${tempRmt}') STMFOPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
171+
noLibList: true
172+
});
173+
}
159174

160175
if (copyResult.code === 0) {
161176
if (!localPath) {
@@ -197,9 +212,9 @@ export default class IBMiContent {
197212
*/
198213
async uploadMemberContent(asp: string | undefined, library: string, sourceFile: string, member: string, content: string | Uint8Array) {
199214
asp = asp || this.config.sourceASP;
200-
library = library.toUpperCase();
201-
sourceFile = sourceFile.toUpperCase();
202-
member = member.toUpperCase();
215+
library = this.ibmi.upperCaseName(library);
216+
sourceFile = this.ibmi.upperCaseName(sourceFile);
217+
member = this.ibmi.upperCaseName(member);
203218

204219
const client = this.ibmi.client;
205220
const tmpobj = await tmpFile();
@@ -212,10 +227,26 @@ export default class IBMiContent {
212227
await client.putFile(tmpobj, tempRmt);
213228

214229
while (true) {
215-
const copyResult = await this.ibmi.runCommand({
216-
command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${path}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
217-
noLibList: true
218-
});
230+
let copyResult: CommandResult;
231+
if (this.ibmi.dangerousVariants && new RegExp(`[${this.ibmi.variantChars.local}]`).test(path)) {
232+
copyResult = { code: 0, stdout: '', stderr: '' };
233+
try {
234+
await this.runSQL([
235+
`@QSYS/CPYF FROMFILE(${library}/${sourceFile}) FROMMBR(${member}) TOFILE(QTEMP/QTEMPSRC) TOMBR(TEMPMEMBER) MBROPT(*REPLACE) CRTFILE(*YES);`,
236+
`@QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${Tools.qualifyPath("QTEMP", "QTEMPSRC", "TEMPMEMBER", undefined)}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
237+
`@QSYS/CPYF FROMFILE(QTEMP/QTEMPSRC) FROMMBR(TEMPMEMBER) TOFILE(${library}/${sourceFile}) TOMBR(${member}) MBROPT(*REPLACE);`
238+
].join("\n"));
239+
} catch (error: any) {
240+
copyResult.code = -1;
241+
copyResult.stderr = String(error);
242+
}
243+
}
244+
else {
245+
copyResult = await this.ibmi.runCommand({
246+
command: `QSYS/CPYFRMSTMF FROMSTMF('${tempRmt}') TOMBR('${path}') MBROPT(*REPLACE) STMFCCSID(1208) DBFCCSID(${this.config.sourceFileCCSID})`,
247+
noLibList: true
248+
});
249+
}
219250

220251
if (copyResult.code === 0) {
221252
const messages = Tools.parseMessages(copyResult.stderr);

src/api/Tools.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,8 @@ export namespace Tools {
146146
* @param iasp Optional: an iASP name
147147
*/
148148
export function qualifyPath(library: string, object: string, member: string, iasp?: string, sanitise?: boolean) {
149-
library = library.toUpperCase();
150149
const libraryPath = library === `QSYS` ? `QSYS.LIB` : `QSYS.LIB/${Tools.sanitizeLibraryNames([library]).join(``)}.LIB`;
151-
const memberPath = `${object.toUpperCase()}.FILE/${member.toUpperCase()}.MBR`
150+
const memberPath = `${object}.FILE/${member}.MBR`
152151
const memberSubpath = sanitise ? Tools.escapePath(memberPath) : memberPath;
153152

154153
const result = (iasp && iasp.length > 0 ? `/${iasp}` : ``) + `/${libraryPath}/${memberSubpath}`;

0 commit comments

Comments
 (0)