mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix(fetch-adapter): set correct Content-Type for Node FormData (#6998)
* fix(fetch-adapter): set correct Content-Type for Node FormData * Update lib/helpers/resolveConfig.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * test(fetch): replace chai expect with Node assert * fix: define formHeaders for FormData to resolve no-undef error * fix: filter headers to only update the target headers Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
committed by
GitHub
parent
066b39195a
commit
a9f47afbf3
@@ -10,7 +10,7 @@ import buildURL from "./buildURL.js";
|
|||||||
export default (config) => {
|
export default (config) => {
|
||||||
const newConfig = mergeConfig({}, config);
|
const newConfig = mergeConfig({}, config);
|
||||||
|
|
||||||
let {data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth} = newConfig;
|
let { data, withXSRFToken, xsrfHeaderName, xsrfCookieName, headers, auth } = newConfig;
|
||||||
|
|
||||||
newConfig.headers = headers = AxiosHeaders.from(headers);
|
newConfig.headers = headers = AxiosHeaders.from(headers);
|
||||||
|
|
||||||
@@ -23,15 +23,19 @@ export default (config) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let contentType;
|
|
||||||
|
|
||||||
if (utils.isFormData(data)) {
|
if (utils.isFormData(data)) {
|
||||||
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
||||||
headers.setContentType(undefined); // Let the browser set it
|
headers.setContentType(undefined); // browser handles it
|
||||||
} else if ((contentType = headers.getContentType()) !== false) {
|
} else if (utils.isFunction(data.getHeaders)) {
|
||||||
// fix semicolon duplication issue for ReactNative FormData implementation
|
// Node.js FormData (like form-data package)
|
||||||
const [type, ...tokens] = contentType ? contentType.split(';').map(token => token.trim()).filter(Boolean) : [];
|
const formHeaders = data.getHeaders();
|
||||||
headers.setContentType([type || 'multipart/form-data', ...tokens].join('; '));
|
// Only set safe headers to avoid overwriting security headers
|
||||||
|
const allowedHeaders = ['content-type', 'content-length'];
|
||||||
|
Object.entries(formHeaders).forEach(([key, val]) => {
|
||||||
|
if (allowedHeaders.includes(key.toLowerCase())) {
|
||||||
|
headers.set(key, val);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-25
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '../../helpers/server.js';
|
} from '../../helpers/server.js';
|
||||||
import axios from '../../../index.js';
|
import axios from '../../../index.js';
|
||||||
import stream from "stream";
|
import stream from "stream";
|
||||||
import {AbortController} from "abortcontroller-polyfill/dist/cjs-ponyfill.js";
|
import { AbortController } from "abortcontroller-polyfill/dist/cjs-ponyfill.js";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
|
|
||||||
const pipelineAsync = util.promisify(stream.pipeline);
|
const pipelineAsync = util.promisify(stream.pipeline);
|
||||||
@@ -41,7 +41,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'text'
|
responseType: 'text'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'arraybuffer'
|
responseType: 'arraybuffer'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
server = await startHTTPServer((req, res) => res.end(originalData));
|
server = await startHTTPServer((req, res) => res.end(originalData));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'stream'
|
responseType: 'stream'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
res.end(await response.text());
|
res.end(await response.text());
|
||||||
});
|
});
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'formdata'
|
responseType: 'formdata'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,11 +114,11 @@ describe('supports fetch with nodejs', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it(`should support json response type`, async () => {
|
it(`should support json response type`, async () => {
|
||||||
const originalData = {x: 'my data'};
|
const originalData = { x: 'my data' };
|
||||||
|
|
||||||
server = await startHTTPServer((req, res) => res.end(JSON.stringify(originalData)));
|
server = await startHTTPServer((req, res) => res.end(JSON.stringify(originalData)));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'json'
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,8 +153,8 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
const samples = [];
|
const samples = [];
|
||||||
|
|
||||||
const {data} = await fetchAxios.post('/', readable, {
|
const { data } = await fetchAxios.post('/', readable, {
|
||||||
onUploadProgress: ({loaded, total, progress, bytes, upload}) => {
|
onUploadProgress: ({ loaded, total, progress, bytes, upload }) => {
|
||||||
console.log(`Upload Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`);
|
console.log(`Upload Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`);
|
||||||
|
|
||||||
samples.push({
|
samples.push({
|
||||||
@@ -188,10 +188,10 @@ describe('supports fetch with nodejs', function () {
|
|||||||
}()));
|
}()));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fail with get method', async() => {
|
it('should not fail with get method', async () => {
|
||||||
server = await startHTTPServer((req, res) => res.end('OK'));
|
server = await startHTTPServer((req, res) => res.end('OK'));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
onUploadProgress() {
|
onUploadProgress() {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -227,8 +227,8 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
const samples = [];
|
const samples = [];
|
||||||
|
|
||||||
const {data} = await fetchAxios.post('/', readable, {
|
const { data } = await fetchAxios.post('/', readable, {
|
||||||
onDownloadProgress: ({loaded, total, progress, bytes, download}) => {
|
onDownloadProgress: ({ loaded, total, progress, bytes, download }) => {
|
||||||
console.log(`Download Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`);
|
console.log(`Download Progress ${loaded} from ${total} bytes (${(progress * 100).toFixed(1)}%)`);
|
||||||
|
|
||||||
samples.push({
|
samples.push({
|
||||||
@@ -269,8 +269,8 @@ describe('supports fetch with nodejs', function () {
|
|||||||
server = await startHTTPServer((req, res) => res.end(req.headers.authorization));
|
server = await startHTTPServer((req, res) => res.end(req.headers.authorization));
|
||||||
|
|
||||||
const user = 'foo';
|
const user = 'foo';
|
||||||
const headers = {Authorization: 'Bearer 1234'};
|
const headers = { Authorization: 'Bearer 1234' };
|
||||||
const res = await axios.get('http://' + user + '@localhost:4444/', {headers: headers});
|
const res = await axios.get('http://' + user + '@localhost:4444/', { headers: headers });
|
||||||
|
|
||||||
const base64 = Buffer.from(user + ':', 'utf8').toString('base64');
|
const base64 = Buffer.from(user + ':', 'utf8').toString('base64');
|
||||||
assert.equal(res.data, 'Basic ' + base64);
|
assert.equal(res.data, 'Basic ' + base64);
|
||||||
@@ -279,12 +279,12 @@ describe('supports fetch with nodejs', function () {
|
|||||||
it("should support stream.Readable as a payload", async () => {
|
it("should support stream.Readable as a payload", async () => {
|
||||||
server = await startHTTPServer();
|
server = await startHTTPServer();
|
||||||
|
|
||||||
const {data} = await fetchAxios.post('/', stream.Readable.from('OK'));
|
const { data } = await fetchAxios.post('/', stream.Readable.from('OK'));
|
||||||
|
|
||||||
assert.strictEqual(data, 'OK');
|
assert.strictEqual(data, 'OK');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('request aborting', function() {
|
describe('request aborting', function () {
|
||||||
it('should be able to abort the request stream', async function () {
|
it('should be able to abort the request stream', async function () {
|
||||||
server = await startHTTPServer({
|
server = await startHTTPServer({
|
||||||
rate: 100000,
|
rate: 100000,
|
||||||
@@ -316,7 +316,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
controller.abort(new Error('test'));
|
controller.abort(new Error('test'));
|
||||||
}, 800);
|
}, 800);
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/', {
|
const { data } = await fetchAxios.get('/', {
|
||||||
responseType: 'stream',
|
responseType: 'stream',
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
});
|
});
|
||||||
@@ -328,7 +328,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should support a timeout', async () => {
|
it('should support a timeout', async () => {
|
||||||
server = await startHTTPServer(async(req, res) => {
|
server = await startHTTPServer(async (req, res) => {
|
||||||
await setTimeoutAsync(1000);
|
await setTimeoutAsync(1000);
|
||||||
res.end('OK');
|
res.end('OK');
|
||||||
});
|
});
|
||||||
@@ -337,7 +337,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
|
|
||||||
const ts = Date.now();
|
const ts = Date.now();
|
||||||
|
|
||||||
await assert.rejects(async() => {
|
await assert.rejects(async () => {
|
||||||
await fetchAxios('/', {
|
await fetchAxios('/', {
|
||||||
timeout
|
timeout
|
||||||
})
|
})
|
||||||
@@ -358,10 +358,10 @@ describe('supports fetch with nodejs', function () {
|
|||||||
assert.equal(res.config.url, '/foo');
|
assert.equal(res.config.url, '/foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support params', async() => {
|
it('should support params', async () => {
|
||||||
server = await startHTTPServer((req, res) => res.end(req.url));
|
server = await startHTTPServer((req, res) => res.end(req.url));
|
||||||
|
|
||||||
const {data} = await fetchAxios.get('/?test=1', {
|
const { data } = await fetchAxios.get('/?test=1', {
|
||||||
params: {
|
params: {
|
||||||
foo: 1,
|
foo: 1,
|
||||||
bar: 2
|
bar: 2
|
||||||
@@ -372,7 +372,7 @@ describe('supports fetch with nodejs', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle fetch failed error as an AxiosError with ERR_NETWORK code', async () => {
|
it('should handle fetch failed error as an AxiosError with ERR_NETWORK code', async () => {
|
||||||
try{
|
try {
|
||||||
await fetchAxios('http://notExistsUrl.in.nowhere');
|
await fetchAxios('http://notExistsUrl.in.nowhere');
|
||||||
assert.fail('should fail');
|
assert.fail('should fail');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -387,10 +387,26 @@ describe('supports fetch with nodejs', function () {
|
|||||||
res.end(req.url)
|
res.end(req.url)
|
||||||
});
|
});
|
||||||
|
|
||||||
const {headers} = await fetchAxios.get('/', {
|
const { headers } = await fetchAxios.get('/', {
|
||||||
responseType: 'stream'
|
responseType: 'stream'
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.strictEqual(headers.get('foo'), 'bar');
|
assert.strictEqual(headers.get('foo'), 'bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('fetch adapter - Content-Type handling', function () {
|
||||||
|
it('should set correct Content-Type for FormData automatically', async function () {
|
||||||
|
const FormData = (await import('form-data')).default; // Node FormData
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('foo', 'bar');
|
||||||
|
|
||||||
|
server = await startHTTPServer((req, res) => {
|
||||||
|
const contentType = req.headers['content-type'];
|
||||||
|
assert.match(contentType, /^multipart\/form-data; boundary=/i);
|
||||||
|
res.end('OK');
|
||||||
|
});
|
||||||
|
|
||||||
|
await fetchAxios.post('/form', form);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user