Skip to content

Commit

Permalink
fix(fetch): FormData fixes + coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
ronag committed Aug 18, 2021
1 parent c32ed9d commit 075d80a
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 16 deletions.
24 changes: 9 additions & 15 deletions lib/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ async function * blobGen (blob) {
if (blob.stream) {
yield * blob.stream()
} else {
// istanbul ignore next: node < 16.7
yield await blob.arrayBuffer()
}
}
Expand Down Expand Up @@ -71,14 +72,11 @@ function extractBody (object, keepalive = false) {
source = new Uint8Array(object)
} else if (object instanceof FormData) {
const boundary = '----formdata-undici-' + Math.random()
const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`
const prefix = `--${boundary}\r\nContent-Disposition: form-data`

/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
const escape = (str, filename) =>
(filename ? str : str.replace(/\r?\n|\r/g, '\r\n'))
.replace(/\n/g, '%0A')
.replace(/\r/g, '%0D')
.replace(/"/g, '%22')
const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22')
const normalizeLinefeeds = value => value.replace(/\r?\n|\r/g, '\r\n')

// Set action to this step: run the multipart/form-data
// encoding algorithm, with object’s entry list and UTF-8.
Expand All @@ -89,14 +87,14 @@ function extractBody (object, keepalive = false) {
if (typeof value === 'string') {
yield enc.encode(
prefix +
escape(name) +
`"\r\n\r\n${value.replace(/\r(?!\n)|(?<!\r)\n/g, '\r\n')}\r\n`
`; name="${escape(normalizeLinefeeds(name))}"` +
`\r\n\r\n${normalizeLinefeeds(value)}\r\n`
)
} else {
yield enc.encode(
prefix +
escape(name) +
`"; filename="${escape(value.name, 1)}"\r\n` +
`; name="${escape(normalizeLinefeeds(name))}"` +
(value.filename ? `; filename="${escape(value.filename)}"` : '') + '\r\n' +
`Content-Type: ${
value.type || 'application/octet-stream'
}\r\n\r\n`
Expand Down Expand Up @@ -125,11 +123,7 @@ function extractBody (object, keepalive = false) {
// Blob

// Set action to this step: read object.
action = async function * (object) {
for await (const chunk of blobGen(object)) {
yield chunk
}
}
action = blobGen

// Set source to object.
source = object
Expand Down
77 changes: 76 additions & 1 deletion test/client-fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ const { test } = require('tap')
const { createServer } = require('http')
const nodeMajor = Number(process.versions.node.split('.')[0])
const { ReadableStream } = require('stream/web')
const { Blob } = require('buffer')

test('fetch', {
skip: nodeMajor < 16
}, t => {
const { fetch } = require('..')
const { fetch, Response, Request, FormData, File } = require('..')

t.test('request json', (t) => {
t.plan(1)
Expand Down Expand Up @@ -249,5 +250,79 @@ test('fetch', {
})
})

t.test('fail to extract locked body', (t) => {
t.plan(1)

const stream = new ReadableStream({})
const reader = stream.getReader()
try {
// eslint-disable-next-line
new Response(stream)
} catch (err) {
t.equal(err.name, 'TypeError')
}
reader.cancel()
})

t.test('fail to extract locked body', (t) => {
t.plan(1)

const stream = new ReadableStream({})
const reader = stream.getReader()
try {
// eslint-disable-next-line
new Request('http://asd', {
method: 'PUT',
body: stream,
keepalive: true
})
} catch (err) {
t.equal(err.message, 'keepalive')
}
reader.cancel()
})

t.test('post FormData with Blob', (t) => {
t.plan(1)

const body = new FormData()
body.append('field1', new Blob(['asd1']))

const server = createServer((req, res) => {
req.pipe(res)
})
t.teardown(server.close.bind(server))

server.listen(0, async () => {
const res = await fetch(`http://localhost:${server.address().port}`, {
method: 'PUT',
body
})
t.ok(/asd1/.test(await res.text()))
})
})

t.test('post FormData with File', (t) => {
t.plan(2)

const body = new FormData()
body.append('field1', new File(['asd1'], 'filename123'))

const server = createServer((req, res) => {
req.pipe(res)
})
t.teardown(server.close.bind(server))

server.listen(0, async () => {
const res = await fetch(`http://localhost:${server.address().port}`, {
method: 'PUT',
body
})
const result = await res.text()
t.ok(/asd1/.test(result))
t.ok(/filename123/.test(result))
})
})

t.end()
})

0 comments on commit 075d80a

Please sign in to comment.