From 9c463f4c26005f8bcb5ae517f968387d995c7524 Mon Sep 17 00:00:00 2001 From: "Andrew W. Harn" Date: Wed, 4 Oct 2023 09:09:40 -0400 Subject: [PATCH] Port fixes to V1 Signed-off-by: Andrew W. Harn --- CHANGELOG.md | 4 ++ .../client/AbstractRestClient.test.ts | 40 +++++++++++++++++++ .../rest/src/client/AbstractRestClient.ts | 16 +++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5013fa324..67b413e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to the Imperative package will be documented in this file. +## Recent Changes + +- BugFix: Fixed normalization on stream chunk boundaries [#1815](https://github.com/zowe/zowe-cli/issues/1815) + ## `4.18.18` - BugFix: Replaced use of `node-keytar` with the new `keyring` module from `@zowe/secrets-for-zowe-sdk`. [zowe-cli#1622](https://github.com/zowe/zowe-cli/issues/1622) diff --git a/packages/rest/__tests__/client/AbstractRestClient.test.ts b/packages/rest/__tests__/client/AbstractRestClient.test.ts index 8fc277bc1..455bed608 100644 --- a/packages/rest/__tests__/client/AbstractRestClient.test.ts +++ b/packages/rest/__tests__/client/AbstractRestClient.test.ts @@ -499,6 +499,46 @@ describe("AbstractRestClient tests", () => { expect(caughtError).toBeUndefined(); }); + it("should not error when streaming normalized data", async () => { + const fakeRequestStream = new PassThrough(); + const emitter = new MockHttpRequestResponse(); + const receivedArray: string[] = []; + jest.spyOn(emitter, "write").mockImplementation((data) => { + receivedArray.push(data.toString()); + }); + const requestFnc = jest.fn((options, callback) => { + ProcessUtils.nextTick(async () => { + const newEmit = new MockHttpRequestResponse(); + callback(newEmit); + await ProcessUtils.nextTick(() => { + newEmit.emit("end"); + }); + }); + return emitter; + }); + (https.request as any) = requestFnc; + let caughtError; + try { + await ProcessUtils.nextTick(() => { + fakeRequestStream.write(Buffer.from("ChunkOne\r", "utf8")); + }); + await ProcessUtils.nextTick(() => { + fakeRequestStream.write(Buffer.from("\nChunkTwo\r", "utf8")); + }); + await ProcessUtils.nextTick(() => { + fakeRequestStream.end(); + }); + await RestClient.putStreamed(new Session({ + hostname: "test", + }), "/resource", [Headers.APPLICATION_JSON], null, fakeRequestStream, false, true); + } catch (error) { + caughtError = error; + } + expect(caughtError).toBeUndefined(); + expect(receivedArray.length).toEqual(3); + expect(receivedArray).toEqual(["ChunkOne", "\nChunkTwo", "\r"]); + }); + it("should return full response when requested", async () => { const emitter = new MockHttpRequestResponse(); const requestFnc = jest.fn((options, callback) => { diff --git a/packages/rest/src/client/AbstractRestClient.ts b/packages/rest/src/client/AbstractRestClient.ts index a9bd6ee18..04bb5cae6 100644 --- a/packages/rest/src/client/AbstractRestClient.ts +++ b/packages/rest/src/client/AbstractRestClient.ts @@ -327,11 +327,21 @@ export abstract class AbstractRestClient { // if the user requested streaming write of data to the request, // write the data chunk by chunk to the server let bytesUploaded = 0; + let heldByte: string; options.requestStream.on("data", (data: Buffer) => { this.log.debug("Writing data chunk of length %d from requestStream to clientRequest", data.byteLength); if (this.mNormalizeRequestNewlines) { this.log.debug("Normalizing new lines in request chunk to \\n"); - data = Buffer.from(data.toString().replace(/\r?\n/g, "\n")); + let dataString = data.toString(); + if (heldByte != null) { + dataString = heldByte + dataString; + heldByte = undefined; + } + if (dataString.charAt(dataString.length - 1) === "\r") { + heldByte = dataString.charAt(dataString.length - 1); + dataString = dataString.slice(0,-1); + } + data = Buffer.from(dataString.replace(/\r?\n/g, "\n")); } if (this.mTask != null) { bytesUploaded += data.byteLength; @@ -353,6 +363,10 @@ export abstract class AbstractRestClient { })); }); options.requestStream.on("end", () => { + if (heldByte != null) { + clientRequest.write(Buffer.from(heldByte)); + heldByte = undefined; + } this.log.debug("Finished reading requestStream"); // finish the request clientRequest.end();