Skip to content
Merged
38 changes: 36 additions & 2 deletions lib/fetch/body.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict'

const Busboy = require('busboy')
Comment thread
mcollina marked this conversation as resolved.
const util = require('../core/util')
const { ReadableStreamFrom, toUSVString, isBlobLike } = require('./util')
const { FormData } = require('./formdata')
Expand All @@ -8,9 +9,9 @@ const { webidl } = require('./webidl')
const { Blob } = require('buffer')
const { kBodyUsed } = require('../core/symbols')
const assert = require('assert')
const { NotSupportedError } = require('../core/errors')
const { isErrored } = require('../core/util')
const { isUint8Array, isArrayBuffer } = require('util/types')
const { File } = require('./file')

let ReadableStream

Expand Down Expand Up @@ -397,7 +398,40 @@ function bodyMixinMethods (instance) {

// If mimeType’s essence is "multipart/form-data", then:
if (/multipart\/form-data/.test(contentType)) {
throw new NotSupportedError('multipart/form-data not supported')
const headers = {}
for (const [key, value] of this.headers) headers[key.toLowerCase()] = value

const responseFormData = new FormData()

try {
const busboy = Busboy({ headers })
busboy.on('field', (name, value) => {
responseFormData.append(name, value)
})
busboy.on('file', (name, value, info) => {
const { filename, encoding, mimeType } = info
const base64 = encoding.toLowerCase() === 'base64'
const chunks = []
value.on('data', (chunk) => {
if (base64) chunk = Buffer.from(chunk.toString(), 'base64')
chunks.push(chunk)
Comment thread
ronag marked this conversation as resolved.
Outdated
})
value.on('end', () => {
const file = new File(chunks, filename, { type: mimeType })
responseFormData.append(name, file)
})
})
const busboyResolve = new Promise((resolve) => busboy.on('finish', resolve))

if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk)
busboy.end()
await busboyResolve
} catch (err) {
// busboy may fail to parse a malformed body, so throw a type error
throw Object.assign(new TypeError(), { cause: err })
}

return responseFormData
} else if (/application\/x-www-form-urlencoded/.test(contentType)) {
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:

Expand Down
35 changes: 26 additions & 9 deletions test/fetch/client-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,21 +165,38 @@ test('unsupported formData 1', (t) => {
})
})

test('unsupported formData 2', (t) => {
t.plan(1)
test('multipart formdata', async (t) => {
t.plan(2)

// Construct example form data, with text and file fields
const formData = new FormData()
formData.append('field1', 'value1')
const blob = new Blob(['example\ntext file'], { type: 'text/plain' })
formData.append('field2', blob, 'file.txt')

const tempRes = new Response(formData)
const boundary = tempRes.headers.get('content-type').split('boundary=')[1]
const formRaw = await tempRes.text()

const server = createServer((req, res) => {
res.setHeader('content-type', 'multipart/form-data')
res.setHeader('content-type', 'multipart/form-data; boundary=' + boundary)
res.write(formRaw)
res.end()
})
t.teardown(server.close.bind(server))

server.listen(0, () => {
fetch(`http://localhost:${server.address().port}`)
.then(res => res.formData())
.catch(err => {
t.equal(err.name, 'NotSupportedError')
})
await new Promise((resolve) => {
server.listen(0, () => {
fetch(`http://localhost:${server.address().port}`)
.then(res => res.formData())
.then(formData => {
t.equal(formData.get('field1'), 'value1')
return formData.get('field2').text()
}).then(text => {
t.equal(text, 'example\ntext file')
resolve()
})
})
})
})

Expand Down