From fd1619179f5106c76ae3e4cfa2b3009be4c9765f Mon Sep 17 00:00:00 2001 From: Khafra <42794878+KhafraDev@users.noreply.github.com> Date: Sat, 16 Jul 2022 05:06:05 -0400 Subject: [PATCH] fix(body mixin): only allow Uint8Array chunks (#1550) --- lib/fetch/body.js | 16 ++++++++++ test/fetch/response.js | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 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..9252c875e49 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,68 @@ 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) + // 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() + }) + + t.end() +})