fix(fetch): remove Content-Type without boundary for FormData (#7314)

* fix(fetch): remove Content-Type without boundary for FormData

When using the fetch adapter with FormData and manually setting
Content-Type to 'multipart/form-data' (without boundary), the
request would fail because the boundary is required.

This fix detects when Content-Type is set to multipart/form-data
without a boundary and removes it, allowing fetch to automatically
set the correct Content-Type header with the proper boundary.

Fixes #7054

* chore: update fixes

---------

Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
This commit is contained in:
Darwin ❤️❤️❤️ 2026-04-09 03:07:51 +08:00 committed by GitHub
parent c44f4d728b
commit 62f6281660
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 61 additions and 0 deletions

View File

@ -221,6 +221,19 @@ const factory = (env) => {
// see https://github.com/cloudflare/workerd/issues/902
const isCredentialsSupported = isRequestSupported && 'credentials' in Request.prototype;
// If data is FormData and Content-Type is multipart/form-data without boundary,
// delete it so fetch can set it correctly with the boundary
if (utils.isFormData(data)) {
const contentType = headers.getContentType();
if (
contentType &&
/^multipart\/form-data/i.test(contentType) &&
!/boundary=/i.test(contentType)
) {
headers.delete('content-type');
}
}
const resolvedOptions = {
...fetchOptions,
signal: composedSignal,

View File

@ -550,6 +550,54 @@ describe.runIf(typeof fetch === 'function')('supports fetch with nodejs', () =>
await stopHTTPServer(server);
}
});
it('should remove manually set Content-Type without boundary for FormData', async () => {
const form = new FormData();
form.append('foo', 'bar');
const server = await startHTTPServer(
(req, res) => {
const contentType = req.headers['content-type'];
assert.match(contentType, /^multipart\/form-data; boundary=/i);
res.end('OK');
},
{ port: SERVER_PORT }
);
try {
await fetchAxios.post(`http://localhost:${server.address().port}/form`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
} finally {
await stopHTTPServer(server);
}
});
it('should preserve Content-Type if it already has boundary', async () => {
const form = new FormData();
form.append('foo', 'bar');
const customBoundary = '----CustomBoundary123';
const server = await startHTTPServer(
(req, res) => {
const contentType = req.headers['content-type'];
assert.ok(contentType.includes(customBoundary));
res.end('OK');
},
{ port: SERVER_PORT }
);
try {
await fetchAxios.post(`http://localhost:${server.address().port}/form`, form, {
headers: {
'Content-Type': `multipart/form-data; boundary=${customBoundary}`,
},
});
} finally {
await stopHTTPServer(server);
}
});
});
describe('env config', () => {