Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/form data in response #1118

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class Body {
}

this[INTERNALS] = {
/** @type {Stream|Buffer|Blob|null} */
body,
boundary,
disturbed: false,
Expand Down Expand Up @@ -197,14 +198,15 @@ async function consumeBody(data) {

try {
for await (const chunk of body) {
if (data.size > 0 && accumBytes + chunk.length > data.size) {
const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
if (data.size > 0 && accumBytes + bytes.byteLength > data.size) {
const err = new FetchError(`content size at ${data.url} over limit: ${data.size}`, 'max-size');
body.destroy(err);
throw err;
}

accumBytes += chunk.length;
accum.push(chunk);
accumBytes += bytes.byteLength;
accum.push(bytes);
}
} catch (error) {
if (error instanceof FetchBaseError) {
Expand All @@ -217,10 +219,6 @@ async function consumeBody(data) {

if (body.readableEnded === true || body._readableState.ended === true) {
try {
if (accum.every(c => typeof c === 'string')) {
return Buffer.from(accum.join(''));
}

return Buffer.concat(accum, accumBytes);
} catch (error) {
throw new FetchError(`Could not create Buffer from response body for ${data.url}: ${error.message}`, 'system', error);
Expand Down Expand Up @@ -323,7 +321,7 @@ export const extractContentType = (body, request) => {
*
* ref: https://fetch.spec.whatwg.org/#concept-body-total-bytes
*
* @param {any} obj.body Body object from the Body instance.
* @param {Body} request Body object from the Body instance.
* @returns {number | null}
*/
export const getTotalBytes = request => {
Expand Down
2 changes: 1 addition & 1 deletion src/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class Response extends Body {
const headers = new Headers(options.headers);

if (body !== null && !headers.has('Content-Type')) {
const contentType = extractContentType(body);
const contentType = extractContentType(body, this);
if (contentType) {
headers.append('Content-Type', contentType);
}
Expand Down
8 changes: 4 additions & 4 deletions src/utils/is.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const NAME = Symbol.toStringTag;
* ref: https://github.com/node-fetch/node-fetch/issues/296#issuecomment-307598143
*
* @param {*} obj
* @return {boolean}
* @return {object is URLSearchParams}
*/
export const isURLSearchParameters = object => {
return (
Expand All @@ -31,7 +31,7 @@ export const isURLSearchParameters = object => {
* Check if `object` is a W3C `Blob` object (which `File` inherits from)
*
* @param {*} obj
* @return {boolean}
* @return {object is Blob}
*/
export const isBlob = object => {
return (
Expand All @@ -48,7 +48,7 @@ export const isBlob = object => {
* Check if `obj` is a spec-compliant `FormData` object
*
* @param {*} object
* @return {boolean}
* @return {object is FormData}
*/
export function isFormData(object) {
return (
Expand All @@ -70,7 +70,7 @@ export function isFormData(object) {
* Check if `obj` is an instance of AbortSignal.
*
* @param {*} obj
* @return {boolean}
* @return {object is AbortSignal}
*/
export const isAbortSignal = object => {
return (
Expand Down
28 changes: 28 additions & 0 deletions test/form-data.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import FormData from 'formdata-node';
import Blob from 'fetch-blob';
import {Response, Request} from '../src/index.js';
import {getTotalBytes} from '../src/body.js';

import chai from 'chai';

Expand Down Expand Up @@ -101,4 +103,30 @@ describe('FormData', () => {

expect(String(await read(formDataIterator(form, boundary)))).to.be.equal(expected);
});

it('Response supports FormData body', async () => {
const form = new FormData();
form.set('blob', new Blob(['Hello, World!'], {type: 'text/plain'}));

const response = new Response(form);
const type = response.headers.get('content-type') || '';
expect(type).to.match(/multipart\/form-data;\s*boundary=/);
expect(await response.text()).to.have.string('Hello, World!');
// Note: getTotalBytes assumes body could be form data but it never is
// because it gets normalized into a stream.
expect(getTotalBytes({...response, body: form})).to.be.greaterThan(20);
});

it('Request supports FormData body', async () => {
const form = new FormData();
form.set('blob', new Blob(['Hello, World!'], {type: 'text/plain'}));

const request = new Request('https://github.com/node-fetch/', {
body: form,
method: 'POST'
});
const type = request.headers.get('content-type') || '';
expect(type).to.match(/multipart\/form-data;\s*boundary=/);
expect(await request.text()).to.have.string('Hello, World!');
});
});