mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
fix: clear RN FormData content type (#10898)
* fix: clear RN FormData content type * docs: add React Native FormData release note --------- Co-authored-by: cyphercodes <cyphercodes@users.noreply.github.com> Co-authored-by: Jason Saayman <jasonsaayman@gmail.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
## Bug Fixes
|
||||
|
||||
- **AxiosHeaders:** Silently skip empty response header names emitted by some React Native Android responses instead of throwing. (**#6959**, **#10875**)
|
||||
- **React Native FormData:** Clear the default `Content-Type` header for React Native `FormData` requests so Android can build multipart bodies with the correct boundary. (**#10898**)
|
||||
- **Request Data:** Preserve enumerable symbol keys when merging plain request data before `transformRequest`. (**#6392**)
|
||||
|
||||
## Release Documentation TODO
|
||||
|
||||
@@ -70,8 +70,12 @@ function resolveConfig(config) {
|
||||
}
|
||||
|
||||
if (utils.isFormData(data)) {
|
||||
if (platform.hasStandardBrowserEnv || platform.hasStandardBrowserWebWorkerEnv) {
|
||||
headers.setContentType(undefined); // browser handles it
|
||||
if (
|
||||
platform.hasStandardBrowserEnv ||
|
||||
platform.hasStandardBrowserWebWorkerEnv ||
|
||||
utils.isReactNative(data)
|
||||
) {
|
||||
headers.setContentType(undefined); // browser/web worker/RN handles it
|
||||
} else if (utils.isFunction(data.getHeaders)) {
|
||||
// Node.js FormData (like form-data package)
|
||||
setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
|
||||
|
||||
@@ -3,6 +3,19 @@ import assert from 'assert';
|
||||
import dispatchRequest from '../../../lib/core/dispatchRequest.js';
|
||||
import AxiosError from '../../../lib/core/AxiosError.js';
|
||||
import defaults from '../../../lib/defaults/index.js';
|
||||
import resolveConfig from '../../../lib/helpers/resolveConfig.js';
|
||||
|
||||
class ReactNativeFormData {
|
||||
append() {}
|
||||
|
||||
getParts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'FormData';
|
||||
}
|
||||
}
|
||||
|
||||
function baseConfig(overrides = {}) {
|
||||
return {
|
||||
@@ -122,6 +135,45 @@ describe('core::dispatchRequest', () => {
|
||||
});
|
||||
|
||||
describe('happy path', () => {
|
||||
it('clears default Content-Type for React Native FormData before adapter headers are sent', async () => {
|
||||
const data = new ReactNativeFormData();
|
||||
const response = {
|
||||
data: '{"ok":true}',
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config: null,
|
||||
request: {},
|
||||
};
|
||||
const config = baseConfig({
|
||||
method: 'post',
|
||||
data,
|
||||
adapter: (adapterConfig) => {
|
||||
assert.strictEqual(
|
||||
adapterConfig.headers.getContentType(),
|
||||
'application/x-www-form-urlencoded',
|
||||
'dispatchRequest should apply the default POST Content-Type first'
|
||||
);
|
||||
|
||||
const resolvedConfig = resolveConfig(adapterConfig);
|
||||
|
||||
assert.strictEqual(resolvedConfig.data, data);
|
||||
assert.strictEqual(resolvedConfig.headers.getContentType(), undefined);
|
||||
assert.strictEqual(
|
||||
Object.prototype.hasOwnProperty.call(resolvedConfig.headers.toJSON(), 'Content-Type'),
|
||||
false,
|
||||
'resolved adapter headers must omit Content-Type for React Native FormData'
|
||||
);
|
||||
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
});
|
||||
|
||||
const result = await dispatchRequest(config);
|
||||
|
||||
assert.deepStrictEqual(result.data, { ok: true });
|
||||
});
|
||||
|
||||
it('cleans up config.response after a successful resolution', async () => {
|
||||
const response = {
|
||||
data: '{"ok":true}',
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { describe, it } from 'vitest';
|
||||
import assert from 'assert';
|
||||
import resolveConfig from '../../../lib/helpers/resolveConfig.js';
|
||||
|
||||
class ReactNativeFormData {
|
||||
append() {}
|
||||
|
||||
getParts() {
|
||||
return [];
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return 'FormData';
|
||||
}
|
||||
}
|
||||
|
||||
describe('helpers::resolveConfig', () => {
|
||||
it('clears Content-Type for React Native FormData', () => {
|
||||
const data = new ReactNativeFormData();
|
||||
const config = resolveConfig({
|
||||
url: '/upload',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(config.data, data);
|
||||
assert.strictEqual(config.headers.getContentType(), undefined);
|
||||
assert.strictEqual(
|
||||
Object.prototype.hasOwnProperty.call(config.headers.toJSON(), 'Content-Type'),
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user