From f31a8f9039fd7655363389cff295ad346f598e65 Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:33:06 -0400 Subject: [PATCH 1/2] fix(body mixin): only allow Uint8Array chunks --- lib/fetch/body.js | 16 +++++++++++ test/fetch/response.js | 64 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/lib/fetch/body.js b/lib/fetch/body.js index 3fa70297948..2a9f1c83d88 100644 --- a/lib/fetch/body.js +++ b/lib/fetch/body.js @@ -291,6 +291,10 @@ function bodyMixinMethods (instance) { const chunks = [] for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + // Assemble one final large blob with Uint8Array's can exhaust memory. // That's why we create create multiple blob's and using references chunks.push(new Blob([chunk])) @@ -314,6 +318,10 @@ function bodyMixinMethods (instance) { let offset = 0 for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + buffer.set(chunk, offset) offset += chunk.length } @@ -331,6 +339,10 @@ function bodyMixinMethods (instance) { let size = 0 for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + chunks.push(chunk) size += chunk.byteLength } @@ -355,6 +367,10 @@ function bodyMixinMethods (instance) { const textDecoder = new TextDecoder() for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + result += textDecoder.decode(chunk, { stream: true }) } diff --git a/test/fetch/response.js b/test/fetch/response.js index a060f04e927..461759e259a 100644 --- a/test/fetch/response.js +++ b/test/fetch/response.js @@ -4,6 +4,7 @@ const { test } = require('tap') const { Response } = require('../../') +const { ReadableStream } = require('stream/web') test('arg validation', async (t) => { // constructor @@ -164,3 +165,66 @@ test('Modifying headers using Headers.prototype.set', (t) => { t.end() }) + +// https://github.com/nodejs/node/issues/43838 +test('constructing a Response with a ReadableStream body', async (t) => { + const text = '{"foo":"bar"}' + const uint8 = new TextEncoder().encode(text) + + t.test('Readable stream with Uint8Array chunks', async (t) => { + const readable = new ReadableStream({ + start (controller) { + controller.enqueue(uint8) + controller.close() + } + }) + + const response1 = new Response(readable) + const response2 = response1.clone() + const response3 = response1.clone() + + t.equal(await response1.text(), text) + t.same(await response2.arrayBuffer(), uint8.buffer) + t.same(await response3.json(), JSON.parse(text)) + + t.end() + }) + + t.test('Readable stream with non-Uint8Array chunks', async (t) => { + const readable = new ReadableStream({ + start (controller) { + controller.enqueue(text) // string + controller.close() + } + }) + + const response = new Response(readable) + + await t.rejects(response.text(), TypeError) + + t.end() + }) + + t.test('Readable with ArrayBuffer chunk still throws', async (t) => { + const readable = new ReadableStream({ + start (controller) { + controller.enqueue(uint8.buffer) + controller.close() + } + }) + + const response1 = new Response(readable) + const response2 = response1.clone() + const response3 = response1.clone() + const response4 = response1.clone() + + await t.rejects(response1.arrayBuffer(), TypeError) + await t.rejects(response2.text(), TypeError) + await t.rejects(response3.json(), TypeError) + await t.rejects(response4.blob(), TypeError) + + t.end() + }) + + t.end() +}) From dd80fec19dec86c6d9915a7a6cb817cd7e06a839 Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Fri, 15 Jul 2022 15:54:56 -0400 Subject: [PATCH 2/2] fix: remove test that fails on node v16.8 --- test/fetch/response.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/fetch/response.js b/test/fetch/response.js index 461759e259a..9252c875e49 100644 --- a/test/fetch/response.js +++ b/test/fetch/response.js @@ -216,12 +216,14 @@ test('constructing a Response with a ReadableStream body', async (t) => { const response1 = new Response(readable) const response2 = response1.clone() const response3 = response1.clone() - const response4 = response1.clone() + // const response4 = response1.clone() await t.rejects(response1.arrayBuffer(), TypeError) await t.rejects(response2.text(), TypeError) await t.rejects(response3.json(), TypeError) - await t.rejects(response4.blob(), TypeError) + // TODO: on Node v16.8.0, this throws a TypeError + // because the body is detected as disturbed. + // await t.rejects(response4.blob(), TypeError) t.end() })