Skip to content

Commit

Permalink
Delete Content-Type header for multipart requests (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
gydroperit committed Mar 3, 2020
1 parent 3f8969f commit d547e89
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 7 deletions.
7 changes: 7 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ for (const property of globalProperties) {
const isObject = value => value !== null && typeof value === 'object';
const supportsAbortController = typeof globals.AbortController === 'function';
const supportsStreams = typeof globals.ReadableStream === 'function';
const supportsFormData = typeof globals.FormData === 'function';

const deepMerge = (...sources) => {
let returnValue = {};
Expand Down Expand Up @@ -240,6 +241,12 @@ class Ky {
if (this._options.searchParams) {
const url = new URL(this.request.url);
url.search = new URLSearchParams(this._options.searchParams);

// To provide correct form boundary, Content-Type header should be deleted each time when new Request instantiated from another one
if (((supportsFormData && this._options.body instanceof globals.FormData) || this._options.body instanceof URLSearchParams) && !(this._options.headers && this._options.headers['content-type'])) {
this.request.headers.delete('content-type');
}

this.request = new globals.Request(new globals.Request(url, this.request), this._options);
}

Expand Down
2 changes: 2 additions & 0 deletions test/_require.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import fetch, {Headers, Request, Response} from 'node-fetch';
import AbortController from 'abort-controller';
import FormData from 'form-data';

global.fetch = fetch;
global.Headers = Headers;
global.Request = Request;
global.Response = Response;
global.AbortController = AbortController;
global.FormData = FormData;
17 changes: 10 additions & 7 deletions test/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,20 @@ test('throws if does not support ReadableStream', withPage, async (t, page) => {
});

test('FormData with searchParams', withPage, async (t, page) => {
t.plan(2);
t.plan(3);

const server = await createTestServer();
server.get('/', (request, response) => {
response.end();
});
server.post('/', async (request, response) => {
const requestBody = await pBody(request);
const contentType = request.headers['content-type'];
const boundary = contentType.split('boundary=')[1];

t.truthy(requestBody.includes(boundary));
t.regex(requestBody, /bubblegum pie/);
t.deepEqual(request.query, {foo: '1'});

response.end();
});

Expand All @@ -203,8 +205,7 @@ test('FormData with searchParams', withPage, async (t, page) => {
await server.close();
});

// FIXME: More detailed test that reproduces the bug described in https://github.com/sindresorhus/ky/issues/209
test.failing('FormData with searchParams ("multipart/form-data" parser)', withPage, async (t, page) => {
test('FormData with searchParams ("multipart/form-data" parser)', withPage, async (t, page) => {
t.plan(3);
const server = await createTestServer();
server.get('/', (request, response) => {
Expand All @@ -213,7 +214,7 @@ test.failing('FormData with searchParams ("multipart/form-data" parser)', withPa
server.post('/', async (request, response) => {
const [body, error] = await new Promise(resolve => {
const busboy = new Busboy({headers: request.headers});
busboy.on('error', error => resolve(null, error));
busboy.on('error', error => resolve([null, error]));
busboy.on('file', async (fieldname, file, filename, encoding, mimetype) => {
let fileContent = '';
try {
Expand All @@ -226,12 +227,14 @@ test.failing('FormData with searchParams ("multipart/form-data" parser)', withPa
resolve([null, error_]);
}
});
busboy.on('finish', () => {
response.writeHead(303, {Connection: 'close', Location: '/'});
response.end();
});
setTimeout(() => resolve([null, new Error('Timeout')]), 3000);
request.pipe(busboy);
});

response.end();

t.falsy(error);
t.deepEqual(request.query, {foo: '1'});
t.deepEqual(body, {
Expand Down
31 changes: 31 additions & 0 deletions test/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,37 @@ test('form-data automatic `content-type` header', async t => {
t.is(headers['content-type'], `multipart/form-data;boundary=${form.getBoundary()}`);
});

test('form-data manual `content-type` header with search params', async t => {
const server = await createTestServer();
server.post('/', echoHeaders);

const form = new FormData();
form.append('a', 'b');
const headers = await ky.post(server.url, {
searchParams: 'foo=1',
headers: {
'content-type': 'custom'
},
body: form
}).json();

t.is(headers['content-type'], 'custom');
});

test('form-data automatic `content-type` header with search params', async t => {
const server = await createTestServer();
server.post('/', echoHeaders);

const form = new FormData();
form.append('a', 'b');
const headers = await ky.post(server.url, {
searchParams: 'foo=1',
body: form
}).json();

t.is(headers['content-type'], `multipart/form-data;boundary=${form.getBoundary()}`);
});

test('form-data sets `content-length` header', async t => {
const server = await createTestServer();
server.post('/', echoHeaders);
Expand Down

0 comments on commit d547e89

Please sign in to comment.