Clone URLSearchParams to avoid mutation (#547)

* And make sure Request/Response set Content-Type per Fetch Spec
* And make sure users can read the body as string via text()
This commit is contained in:
Jimmy Wärting 2018-11-13 17:36:44 +01:00 committed by David Frank
parent 5367fe6a97
commit 2d0fc689c6
4 changed files with 51 additions and 9 deletions

View File

@ -37,6 +37,7 @@ export default function Body(body, {
} else if (typeof body === 'string') {
// body is string
} else if (isURLSearchParams(body)) {
body = Buffer.from(body.toString());
// body is a URLSearchParams
} else if (body instanceof Blob) {
// body is blob
@ -415,9 +416,7 @@ export function clone(instance) {
*
* @param Mixed instance Response or Request instance
*/
export function extractContentType(instance) {
const {body} = instance;
export function extractContentType(body) {
// istanbul ignore if: Currently, because of a guard in Request, body
// can never be null. Included here for completeness.
if (body === null) {
@ -444,10 +443,13 @@ export function extractContentType(instance) {
} else if (typeof body.getBoundary === 'function') {
// detect form data input from form-data module
return `multipart/form-data;boundary=${body.getBoundary()}`;
} else {
} else if (body instanceof Stream) {
// body is stream
// can't really do much about this
return null;
} else {
// Body constructor defaults other things to string
return 'text/plain;charset=UTF-8';
}
}

View File

@ -90,9 +90,9 @@ export default class Request {
const headers = new Headers(init.headers || input.headers || {});
if (init.body != null) {
const contentType = extractContentType(this);
if (contentType !== null && !headers.has('Content-Type')) {
if (inputBody != null && !headers.has('Content-Type')) {
const contentType = extractContentType(inputBody);
if (contentType) {
headers.append('Content-Type', contentType);
}
}

View File

@ -8,7 +8,7 @@
import http from 'http';
import Headers from './headers.js';
import Body, { clone } from './body';
import Body, { clone, extractContentType } from './body';
const INTERNALS = Symbol('Response internals');
@ -27,12 +27,20 @@ export default class Response {
Body.call(this, body, opts);
const status = opts.status || 200;
const headers = new Headers(opts.headers)
if (body != null && !headers.has('Content-Type')) {
const contentType = extractContentType(body);
if (contentType) {
headers.append('Content-Type', contentType);
}
}
this[INTERNALS] = {
url: opts.url,
status,
statusText: opts.statusText || STATUS_CODES[status],
headers: new Headers(opts.headers)
headers
};
}

View File

@ -1365,6 +1365,38 @@ describe('node-fetch', () => {
});
const itUSP = typeof URLSearchParams === 'function' ? it : it.skip;
itUSP('constructing a Response with URLSearchParams as body should have a Content-Type', function() {
const params = new URLSearchParams();
const res = new Response(params);
res.headers.get('Content-Type');
expect(res.headers.get('Content-Type')).to.equal('application/x-www-form-urlencoded;charset=UTF-8');
});
itUSP('constructing a Request with URLSearchParams as body should have a Content-Type', function() {
const params = new URLSearchParams();
const req = new Request(base, { method: 'POST', body: params });
expect(req.headers.get('Content-Type')).to.equal('application/x-www-form-urlencoded;charset=UTF-8');
});
itUSP('Reading a body with URLSearchParams should echo back the result', function() {
const params = new URLSearchParams();
params.append('a','1');
return new Response(params).text().then(text => {
expect(text).to.equal('a=1');
});
});
// Body should been cloned...
itUSP('constructing a Request/Response with URLSearchParams and mutating it should not affected body', function() {
const params = new URLSearchParams();
const req = new Request(`${base}inspect`, { method: 'POST', body: params })
params.append('a','1')
return req.text().then(text => {
expect(text).to.equal('');
});
});
itUSP('should allow POST request with URLSearchParams as body', function() {
const params = new URLSearchParams();
params.append('a','1');