Skip to content

Commit

Permalink
fix(body mixin): only allow Uint8Array chunks (nodejs#1550)
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored and crysmags committed Feb 27, 2024
1 parent 8705767 commit b1e691e
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 0 deletions.
16 changes: 16 additions & 0 deletions lib/fetch/body.js
Expand Up @@ -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]))
Expand All @@ -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
}
Expand All @@ -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
}
Expand All @@ -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 })
}

Expand Down
66 changes: 66 additions & 0 deletions test/fetch/response.js
Expand Up @@ -4,6 +4,7 @@ const { test } = require('tap')
const {
Response
} = require('../../')
const { ReadableStream } = require('stream/web')

test('arg validation', async (t) => {
// constructor
Expand Down Expand Up @@ -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()
})

0 comments on commit b1e691e

Please sign in to comment.