diff --git a/.eslintrc.js b/.eslintrc.js
index 0802a14f1b..efbcf49f2b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -54,17 +54,17 @@ module.exports = {
/**
* Best practices
*/
- 'consistent-return': 2, // http://eslint.org/docs/rules/consistent-return
+ 'consistent-return': 0, // http://eslint.org/docs/rules/consistent-return
'curly': [2, 'multi-line'], // http://eslint.org/docs/rules/curly
'default-case': 2, // http://eslint.org/docs/rules/default-case
'dot-notation': [2, { // http://eslint.org/docs/rules/dot-notation
'allowKeywords': true
}],
- 'eqeqeq': 2, // http://eslint.org/docs/rules/eqeqeq
+ 'eqeqeq': [2, "smart"], // http://eslint.org/docs/rules/eqeqeq
'guard-for-in': 2, // http://eslint.org/docs/rules/guard-for-in
'no-caller': 2, // http://eslint.org/docs/rules/no-caller
'no-else-return': 2, // http://eslint.org/docs/rules/no-else-return
- 'no-eq-null': 2, // http://eslint.org/docs/rules/no-eq-null
+ 'no-eq-null': 0, // http://eslint.org/docs/rules/no-eq-null
'no-eval': 2, // http://eslint.org/docs/rules/no-eval
'no-extend-native': 2, // http://eslint.org/docs/rules/no-extend-native
'no-extra-bind': 2, // http://eslint.org/docs/rules/no-extra-bind
@@ -80,7 +80,7 @@ module.exports = {
'no-new-wrappers': 2, // http://eslint.org/docs/rules/no-new-wrappers
'no-octal': 2, // http://eslint.org/docs/rules/no-octal
'no-octal-escape': 2, // http://eslint.org/docs/rules/no-octal-escape
- 'no-param-reassign': 2, // http://eslint.org/docs/rules/no-param-reassign
+ 'no-param-reassign': 0, // http://eslint.org/docs/rules/no-param-reassign
'no-proto': 2, // http://eslint.org/docs/rules/no-proto
'no-redeclare': 2, // http://eslint.org/docs/rules/no-redeclare
'no-return-assign': 2, // http://eslint.org/docs/rules/no-return-assign
@@ -114,9 +114,7 @@ module.exports = {
}],
'comma-style': [2, 'last'], // http://eslint.org/docs/rules/comma-style
'eol-last': 2, // http://eslint.org/docs/rules/eol-last
- 'func-names': [
- 1, 'as-needed'
- ], // http://eslint.org/docs/rules/func-names
+ 'func-names': 0, // http://eslint.org/docs/rules/func-names
'key-spacing': [2, { // http://eslint.org/docs/rules/key-spacing
'beforeColon': false,
'afterColon': true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d1475ae36a..f81e012c61 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,9 @@ name: ci
on:
push:
- branches: [master, 'release/*', dev]
+ branches: [master, 'v1.x/*', 'v2.x/*']
pull_request:
- branches: [master, 'release/*', dev]
+ branches: [master, 'v1.x/*', 'v2.x/*']
jobs:
build:
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
- node-version: [12.x, 14.x, 16.x]
+ node-version: [12.x, 14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@v2
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 51040e49f1..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-services:
- - xvfb
-language: node_js
-node_js:
- - 10
- - 12
- - 14
-email:
- on_failure: change
- on_success: never
-after_success:
-- npm run coveralls
-env:
- global:
- - secure: LlXIBEaBLgJznkHWfTV6aftkGoBjH2vik4ZQhKq4k5pvoPLD+n5n28+0bjwlzDIHUdHb+n2YXtyM2PGvGzuqwltV+UY1gu0uG2RNR+5CBsp0pOr0FfGXK6YMXn0BYER6tGYIhaG7ElHBEO0SLcQeQV/xN/m3leyawbKEMBUGizU=
- - secure: XbXYzVddHJSVdbJRd/YtsdNu6Wlgx3pXvpuBpg9qBc3TytAF4LzhJNI8u1p4D1Gn8wANlxv1GNgEgkecxbzlTPST+mUrd6KlPLa1+Cmffgajr4oQjsh9ILKMe5Haqx8FOVrPK/leB1mi52liNLlkuo3/BK2r/tC2kMji+2zbses=
diff --git a/README.md b/README.md
index 94511703c1..45b80b2d62 100755
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@
[![gitter chat](https://img.shields.io/gitter/room/mzabriskie/axios.svg?style=flat-square)](https://gitter.im/mzabriskie/axios)
[![code helpers](https://www.codetriage.com/axios/axios/badges/users.svg)](https://www.codetriage.com/axios/axios)
[![Known Vulnerabilities](https://snyk.io/test/npm/axios/badge.svg)](https://snyk.io/test/npm/axios)
+![npm bundle size](https://img.shields.io/bundlephobia/minzip/axios)
Promise based HTTP client for the browser and node.js
@@ -39,12 +40,14 @@ Promise based HTTP client for the browser and node.js
- [AbortController](#abortcontroller)
- [CancelToken π](#canceltoken-deprecated)
- [Using application/x-www-form-urlencoded format](#using-applicationx-www-form-urlencoded-format)
- - [Browser](#browser)
- - [Node.js](#nodejs)
- - [Query string](#query-string)
- - [Form data](#form-data)
- - [Automatic serialization](#-automatic-serialization)
- - [Manual FormData passing](#manual-formdata-passing)
+ - [URLSearchParams](#urlsearchparams)
+ - [Query string](#query-string-older-browsers)
+ - [π Automatic serialization](#-automatic-serialization-to-urlsearchparams)
+ - [Using multipart/form-data format](#using-multipartform-data-format)
+ - [FormData](#formdata)
+ - [π Automatic serialization](#-automatic-serialization-to-formdata)
+ - [Files Posting](#files-posting)
+ - [HTML Form Posting](#html-form-posting-browser)
- [Semver](#semver)
- [Promises](#promises)
- [TypeScript](#typescript)
@@ -61,6 +64,7 @@ Promise based HTTP client for the browser and node.js
- Transform request and response data
- Cancel requests
- Automatic transforms for JSON data
+- π Automatic data object serialization to `multipart/form-data` and `x-www-form-urlencoded` body encodings
- Client side support for protecting against [XSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery)
## Browser Support
@@ -336,10 +340,9 @@ These are the available config options for making requests. Only the `url` is re
ID: 12345
},
- // `paramsSerializer` is an optional function in charge of serializing `params`
- // (e.g. https://www.npmjs.com/package/qs, https://api.jquery.com/jquery.param/)
- paramsSerializer: function (params) {
- return Qs.stringify(params, {arrayFormat: 'brackets'})
+ // `paramsSerializer` is an optional config in charge of serializing `params`
+ paramsSerializer: {
+ indexes: null // array indexes format (null - no brackets, false - empty brackets, true - brackets with indexes)
},
// `data` is the data to be sent as the request body
@@ -509,6 +512,13 @@ These are the available config options for making requests. Only the `url` is re
env: {
// The FormData class to be used to automatically serialize the payload into a FormData object
FormData: window?.FormData || global?.FormData
+ },
+
+ formSerializer: {
+ visitor: (value, key, path, helpers)=> {}; // custom visitor funaction to serrialize form values
+ dots: boolean; // use dots instead of brackets format
+ metaTokens: boolean; // keep special endings like {} in parameter key
+ indexes: boolean; // array indexes format null - no brackets, false - empty brackets, true - brackets with indexes
}
}
```
@@ -814,7 +824,9 @@ cancel();
> During the transition period, you can use both cancellation APIs, even for the same request:
-## Using application/x-www-form-urlencoded format
+## Using `application/x-www-form-urlencoded` format
+
+### URLSearchParams
By default, axios serializes JavaScript objects to `JSON`. To send data in the [`application/x-www-form-urlencoded` format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST) instead, you can use the [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) API, which is [supported](http://www.caniuse.com/#feat=urlsearchparams) in the vast majority of browsers, [and Node](https://nodejs.org/api/url.html#url_class_urlsearchparams) starting with v10 (released in 2018).
@@ -824,7 +836,7 @@ params.append('extraparam', 'value');
axios.post('/foo', params);
```
-### Older browsers
+### Query string (Older browsers)
For compatibility with very old browsers, there is a [polyfill](https://github.com/WebReflection/url-search-params) available (make sure to polyfill the global environment).
@@ -863,9 +875,83 @@ You can also use the [`qs`](https://github.com/ljharb/qs) library.
> NOTE:
> The `qs` library is preferable if you need to stringify nested objects, as the `querystring` method has [known issues](https://github.com/nodejs/node-v0.x-archive/issues/1665) with that use case.
-#### Form data
+### π Automatic serialization to URLSearchParams
+
+Axios will automatically serialize the data object to urlencoded format if the content-type header is set to "application/x-www-form-urlencoded".
+
+```
+const data = {
+ x: 1,
+ arr: [1, 2, 3],
+ arr2: [1, [2], 3],
+ users: [{name: 'Peter', surname: 'Griffin'}, {name: 'Thomas', surname: 'Anderson'}],
+};
+
+await axios.postForm('https://postman-echo.com/post', data,
+ {headers: {'content-type': 'application/x-www-form-urlencoded'}}
+);
+```
+
+The server will handle it as
+
+```js
+ {
+ x: '1',
+ 'arr[]': [ '1', '2', '3' ],
+ 'arr2[0]': '1',
+ 'arr2[1][0]': '2',
+ 'arr2[2]': '3',
+ 'arr3[]': [ '1', '2', '3' ],
+ 'users[0][name]': 'Peter',
+ 'users[0][surname]': 'griffin',
+ 'users[1][name]': 'Thomas',
+ 'users[1][surname]': 'Anderson'
+ }
+````
+
+If your backend body-parser (like `body-parser` of `express.js`) supports nested objects decoding, you will get the same object on the server-side automatically
+
+```js
+ var app = express();
+
+ app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
+
+ app.post('/', function (req, res, next) {
+ // echo body as JSON
+ res.send(JSON.stringify(req.body));
+ });
+
+ server = app.listen(3000);
+```
+
+## Using `multipart/form-data` format
+
+### FormData
+
+To send the data as a `multipart/formdata` you need to pass a formData instance as a payload.
+Setting the `Content-Type` header is not required as Axios guesses it based on the payload type.
+
+```js
+const formData = new FormData();
+formData.append('foo', 'bar');
-##### π Automatic serialization
+axios.post('https://httpbin.org/post', formData);
+```
+
+In node.js, you can use the [`form-data`](https://github.com/form-data/form-data) library as follows:
+
+```js
+const FormData = require('form-data');
+
+const form = new FormData();
+form.append('my_field', 'my value');
+form.append('my_buffer', new Buffer(10));
+form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
+
+axios.post('https://example.com', form)
+```
+
+### π Automatic serialization to FormData
Starting from `v0.27.0`, Axios supports automatic object serialization to a FormData object if the request `Content-Type`
header is set to `multipart/form-data`.
@@ -904,7 +990,7 @@ Axios FormData serializer supports some special endings to perform the following
- `[]` - unwrap the array-like object as separate fields with the same key
> NOTE:
-> unwrap/expand operation will be used by default on array-like objects
+> unwrap/expand operation will be used by default on arrays and FileList objects
FormData serializer supports additional options via `config.formSerializer: object` property to handle rare cases:
@@ -952,45 +1038,96 @@ formData.append('users[1][surname]', 'Anderson');
formData.append('obj2{}', '[{"x":1}]');
```
+Axios supports the following shortcut methods: `postForm`, `putForm`, `patchForm`
+which are just the corresponding http methods with the `Content-Type` header preset to `multipart/form-data`.
+
+## Files Posting
+
+You can easily sumbit a single file
+
```js
-const axios= require('axios');
+await axios.postForm('https://httpbin.org/post', {
+ 'myVar' : 'foo',
+ 'file': document.querySelector('#fileInput').files[0]
+});
+```
-axios.post('https://httpbin.org/post', {
- 'myObj{}': {x: 1, s: "foo"},
+or multiple files as `multipart/form-data`.
+
+```js
+await axios.postForm('https://httpbin.org/post', {
'files[]': document.querySelector('#fileInput').files
-}, {
- headers: {
- 'Content-Type': 'multipart/form-data'
- }
-}).then(({data})=> console.log(data));
+});
```
-Axios supports the following shortcut methods: `postForm`, `putForm`, `patchForm`
-which are just the corresponding http methods with the content-type header preset to `multipart/form-data`.
-
`FileList` object can be passed directly:
```js
await axios.postForm('https://httpbin.org/post', document.querySelector('#fileInput').files)
```
-All files will be sent with the same field names: `files[]`;
+All files will be sent with the same field names: `files[]`.
-##### Manual FormData passing
-
-In node.js, you can use the [`form-data`](https://github.com/form-data/form-data) library as follows:
+## π HTML Form Posting (browser)
+
+Pass HTML Form element as a payload to submit it as `multipart/form-data` content.
```js
-const FormData = require('form-data');
-
-const form = new FormData();
-form.append('my_field', 'my value');
-form.append('my_buffer', new Buffer(10));
-form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
+await axios.postForm('https://httpbin.org/post', document.querySelector('#htmlForm'));
+```
-axios.post('https://example.com', form)
+`FormData` and `HTMLForm` objects can also be posted as `JSON` by explicitly setting the `Content-Type` header to `application/json`:
+
+```js
+await axios.post('https://httpbin.org/post', document.querySelector('#htmlForm'), {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+})
+```
+
+For example, the Form
+
+```html
+
```
+will be submitted as the following JSON object:
+
+```js
+{
+ "foo": "1",
+ "deep": {
+ "prop": {
+ "spaced": "3"
+ }
+ },
+ "baz": [
+ "4",
+ "5"
+ ],
+ "user": {
+ "age": "value2"
+ }
+}
+````
+
+Sending `Blobs`/`Files` as JSON (`base64`) is not currently supported.
+
## Semver
Until axios reaches a `1.0` release, breaking changes will be released with a new minor version. For example `0.5.1`, and `0.5.4` will have the same API, but `0.6.0` will have breaking changes.
diff --git a/bin/ssl_hotfix.js b/bin/ssl_hotfix.js
new file mode 100644
index 0000000000..e79819f2e2
--- /dev/null
+++ b/bin/ssl_hotfix.js
@@ -0,0 +1,22 @@
+const {spawn} = require('child_process');
+
+const args = process.argv.slice(2);
+
+console.log(`Running ${args.join(' ')} on ${process.version}\n`);
+
+const match = /v(\d+)/.exec(process.version);
+
+const isHotfixNeeded = match && match[1] > 16;
+
+isHotfixNeeded && console.warn('Setting --openssl-legacy-provider as ssl hotfix');
+
+const test = spawn('cross-env',
+ isHotfixNeeded ? ['NODE_OPTIONS=--openssl-legacy-provider', ...args] : args, {
+ shell: true,
+ stdio: 'inherit'
+ }
+);
+
+test.on('exit', function (code) {
+ process.exit(code)
+})
diff --git a/index.d.ts b/index.d.ts
index cd3a1f8577..bbf8390fb6 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1,5 +1,15 @@
-// TypeScript Version: 3.0
-export type AxiosRequestHeaders = Record;
+// TypeScript Version: 4.1
+type AxiosHeaders = Record;
+
+type MethodsHeaders = {
+ [Key in Method as Lowercase]: AxiosHeaders;
+};
+
+interface CommonHeaders {
+ common: AxiosHeaders;
+}
+
+export type AxiosRequestHeaders = Partial;
export type AxiosResponseHeaders = Record & {
"set-cookie"?: string[]
@@ -80,22 +90,40 @@ export interface GenericAbortSignal {
}
export interface FormDataVisitorHelpers {
- defaultVisitor: FormDataVisitor;
+ defaultVisitor: SerializerVisitor;
convertValue: (value: any) => any;
isVisitable: (value: any) => boolean;
}
-export interface FormDataVisitor {
- (value: any, key: string | number, path: null | Array, helpers: FormDataVisitorHelpers): boolean;
+export interface SerializerVisitor {
+ (
+ this: GenericFormData,
+ value: any,
+ key: string | number,
+ path: null | Array,
+ helpers: FormDataVisitorHelpers
+ ): boolean;
}
-export interface FormSerializerOptions {
- visitor?: FormDataVisitor;
+export interface SerializerOptions {
+ visitor?: SerializerVisitor;
dots?: boolean;
metaTokens?: boolean;
indexes?: boolean;
}
+// tslint:disable-next-line
+export interface FormSerializerOptions extends SerializerOptions {
+}
+
+export interface ParamEncoder {
+ (value: any, defaultEncoder: (value: any) => any): any;
+}
+
+export interface ParamsSerializerOptions extends SerializerOptions {
+ encode?: ParamEncoder;
+}
+
export interface AxiosRequestConfig {
url?: string;
method?: Method | string;
@@ -104,7 +132,7 @@ export interface AxiosRequestConfig {
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: AxiosRequestHeaders;
params?: any;
- paramsSerializer?: (params: any) => string;
+ paramsSerializer?: ParamsSerializerOptions;
data?: D;
timeout?: number;
timeoutErrorMessage?: string;
@@ -115,8 +143,8 @@ export interface AxiosRequestConfig {
responseEncoding?: responseEncoding | string;
xsrfCookieName?: string;
xsrfHeaderName?: string;
- onUploadProgress?: (progressEvent: any) => void;
- onDownloadProgress?: (progressEvent: any) => void;
+ onUploadProgress?: (progressEvent: ProgressEvent) => void;
+ onDownloadProgress?: (progressEvent: ProgressEvent) => void;
maxContentLength?: number;
validateStatus?: ((status: number) => boolean) | null;
maxBodyLength?: number;
@@ -182,8 +210,9 @@ export class AxiosError extends Error {
request?: any;
response?: AxiosResponse;
isAxiosError: boolean;
- status?: string;
+ status?: number;
toJSON: () => object;
+ cause?: Error;
static readonly ERR_FR_TOO_MANY_REDIRECTS = "ERR_FR_TOO_MANY_REDIRECTS";
static readonly ERR_BAD_OPTION_VALUE = "ERR_BAD_OPTION_VALUE";
static readonly ERR_BAD_OPTION = "ERR_BAD_OPTION";
@@ -191,6 +220,8 @@ export class AxiosError extends Error {
static readonly ERR_DEPRECATED = "ERR_DEPRECATED";
static readonly ERR_BAD_RESPONSE = "ERR_BAD_RESPONSE";
static readonly ERR_BAD_REQUEST = "ERR_BAD_REQUEST";
+ static readonly ERR_NOT_SUPPORT = "ERR_NOT_SUPPORT";
+ static readonly ERR_INVALID_URL = "ERR_INVALID_URL";
static readonly ERR_CANCELED = "ERR_CANCELED";
static readonly ECONNABORTED = "ECONNABORTED";
static readonly ETIMEDOUT = "ETIMEDOUT";
@@ -210,7 +241,7 @@ export interface Cancel {
}
export interface Canceler {
- (message?: string): void;
+ (message?: string, config?: AxiosRequestConfig, request?: any): void;
}
export interface CancelTokenStatic {
@@ -261,8 +292,8 @@ export class Axios {
}
export interface AxiosInstance extends Axios {
- (config: AxiosRequestConfig): AxiosPromise;
- (url: string, config?: AxiosRequestConfig): AxiosPromise;
+ , D = any>(config: AxiosRequestConfig): AxiosPromise;
+ , D = any>(url: string, config?: AxiosRequestConfig): AxiosPromise;
defaults: Omit & {
headers: HeadersDefaults & {
@@ -275,6 +306,12 @@ export interface GenericFormData {
append(name: string, value: any, options?: any): any;
}
+export interface GenericHTMLFormElement {
+ name: string;
+ method: string;
+ submit(): void;
+}
+
export interface AxiosStatic extends AxiosInstance {
create(config?: CreateAxiosDefaults): AxiosInstance;
Cancel: CancelStatic;
@@ -282,11 +319,12 @@ export interface AxiosStatic extends AxiosInstance {
Axios: typeof Axios;
AxiosError: typeof AxiosError;
readonly VERSION: string;
- isCancel(value: any): boolean;
+ isCancel(value: any): value is Cancel;
all(values: Array>): Promise;
spread(callback: (...args: T[]) => R): (array: T[]) => R;
isAxiosError(payload: any): payload is AxiosError;
toFormData(sourceObj: object, targetFormData?: GenericFormData, options?: FormSerializerOptions): GenericFormData;
+ formToJSON(form: GenericFormData|GenericHTMLFormElement): object;
}
declare const axios: AxiosStatic;
diff --git a/lib/adapters/http.js b/lib/adapters/http.js
index 66aa3f27b1..fc872514c5 100755
--- a/lib/adapters/http.js
+++ b/lib/adapters/http.js
@@ -7,18 +7,32 @@ var buildURL = require('./../helpers/buildURL');
var getProxyForUrl = require('proxy-from-env').getProxyForUrl;
var http = require('http');
var https = require('https');
-var httpFollow = require('follow-redirects').http;
-var httpsFollow = require('follow-redirects').https;
+var httpFollow = require('follow-redirects/http');
+var httpsFollow = require('follow-redirects/https');
var url = require('url');
var zlib = require('zlib');
var VERSION = require('./../env/data').version;
var transitionalDefaults = require('../defaults/transitional');
var AxiosError = require('../core/AxiosError');
var CanceledError = require('../cancel/CanceledError');
+var platform = require('../platform');
+var fromDataURI = require('../helpers/fromDataURI');
+var stream = require('stream');
var isHttps = /https:?/;
-var supportedProtocols = [ 'http:', 'https:', 'file:' ];
+var supportedProtocols = platform.protocols.map(function(protocol) {
+ return protocol + ':';
+});
+
+function dispatchBeforeRedirect(options) {
+ if (options.beforeRedirects.proxy) {
+ options.beforeRedirects.proxy(options);
+ }
+ if (options.beforeRedirects.config) {
+ options.beforeRedirects.config(options);
+ }
+}
/**
*
@@ -59,7 +73,7 @@ function setProxy(options, configProxy, location) {
}
}
- options.beforeRedirect = function beforeRedirect(redirectOptions) {
+ options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
// Configure proxy for redirected request, passing the original config proxy to apply
// the exact same logic as if the redirected request was performed by axios directly.
setProxy(redirectOptions, configProxy, redirectOptions.href);
@@ -90,6 +104,62 @@ module.exports = function httpAdapter(config) {
rejectPromise(value);
};
var data = config.data;
+ var responseType = config.responseType;
+ var responseEncoding = config.responseEncoding;
+ var method = config.method.toUpperCase();
+
+ // Parse url
+ var fullPath = buildFullPath(config.baseURL, config.url);
+ var parsed = url.parse(fullPath);
+ var protocol = parsed.protocol || supportedProtocols[0];
+
+ if (protocol === 'data:') {
+ var convertedData;
+
+ if (method !== 'GET') {
+ return settle(resolve, reject, {
+ status: 405,
+ statusText: 'method not allowed',
+ headers: {},
+ config: config
+ });
+ }
+
+ try {
+ convertedData = fromDataURI(config.url, responseType === 'blob', {
+ Blob: config.env && config.env.Blob
+ });
+ } catch (err) {
+ throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
+ }
+
+ if (responseType === 'text') {
+ convertedData = convertedData.toString(responseEncoding);
+
+ if (!responseEncoding || responseEncoding === 'utf8') {
+ data = utils.stripBOM(convertedData);
+ }
+ } else if (responseType === 'stream') {
+ convertedData = stream.Readable.from(convertedData);
+ }
+
+ return settle(resolve, reject, {
+ data: convertedData,
+ status: 200,
+ statusText: 'OK',
+ headers: {},
+ config: config
+ });
+ }
+
+ if (supportedProtocols.indexOf(protocol) === -1) {
+ return reject(new AxiosError(
+ 'Unsupported protocol ' + protocol,
+ AxiosError.ERR_BAD_REQUEST,
+ config
+ ));
+ }
+
var headers = config.headers;
var headerNames = {};
@@ -150,19 +220,6 @@ module.exports = function httpAdapter(config) {
auth = username + ':' + password;
}
- // Parse url
- var fullPath = buildFullPath(config.baseURL, config.url);
- var parsed = url.parse(fullPath);
- var protocol = parsed.protocol || supportedProtocols[0];
-
- if (supportedProtocols.indexOf(protocol) === -1) {
- return reject(new AxiosError(
- 'Unsupported protocol ' + protocol,
- AxiosError.ERR_BAD_REQUEST,
- config
- ));
- }
-
if (!auth && parsed.auth) {
var urlAuth = parsed.auth.split(':');
var urlUsername = urlAuth[0] || '';
@@ -186,11 +243,13 @@ module.exports = function httpAdapter(config) {
var options = {
path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
- method: config.method.toUpperCase(),
+ method: method,
headers: headers,
agents: { http: config.httpAgent, https: config.httpsAgent },
auth: auth,
- protocol: protocol
+ protocol: protocol,
+ beforeRedirect: dispatchBeforeRedirect,
+ beforeRedirects: {}
};
if (config.socketPath) {
@@ -213,13 +272,16 @@ module.exports = function httpAdapter(config) {
options.maxRedirects = config.maxRedirects;
}
if (config.beforeRedirect) {
- options.beforeRedirect = config.beforeRedirect;
+ options.beforeRedirects.config = config.beforeRedirect;
}
transport = isHttpsRequest ? httpsFollow : httpFollow;
}
if (config.maxBodyLength > -1) {
options.maxBodyLength = config.maxBodyLength;
+ } else {
+ // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
+ options.maxBodyLength = Infinity;
}
if (config.insecureHTTPParser) {
@@ -231,7 +293,7 @@ module.exports = function httpAdapter(config) {
if (req.aborted) return;
// uncompress the response body transparently if required
- var stream = res;
+ var responseStream = res;
// return the last request in case of redirects
var lastRequest = res.req || req;
@@ -250,7 +312,7 @@ module.exports = function httpAdapter(config) {
case 'compress':
case 'deflate':
// add the unzipper to the body stream processing pipeline
- stream = stream.pipe(zlib.createUnzip());
+ responseStream = responseStream.pipe(zlib.createUnzip());
// remove the content-encoding in order to not confuse downstream operations
delete res.headers['content-encoding'];
@@ -266,13 +328,13 @@ module.exports = function httpAdapter(config) {
request: lastRequest
};
- if (config.responseType === 'stream') {
- response.data = stream;
+ if (responseType === 'stream') {
+ response.data = responseStream;
settle(resolve, reject, response);
} else {
var responseBuffer = [];
var totalResponseBytes = 0;
- stream.on('data', function handleStreamData(chunk) {
+ responseStream.on('data', function handleStreamData(chunk) {
responseBuffer.push(chunk);
totalResponseBytes += chunk.length;
@@ -280,17 +342,17 @@ module.exports = function httpAdapter(config) {
if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
// stream.destroy() emit aborted event before calling reject() on Node.js v16
rejected = true;
- stream.destroy();
+ responseStream.destroy();
reject(new AxiosError('maxContentLength size of ' + config.maxContentLength + ' exceeded',
AxiosError.ERR_BAD_RESPONSE, config, lastRequest));
}
});
- stream.on('aborted', function handlerStreamAborted() {
+ responseStream.on('aborted', function handlerStreamAborted() {
if (rejected) {
return;
}
- stream.destroy();
+ responseStream.destroy();
reject(new AxiosError(
'maxContentLength size of ' + config.maxContentLength + ' exceeded',
AxiosError.ERR_BAD_RESPONSE,
@@ -299,17 +361,17 @@ module.exports = function httpAdapter(config) {
));
});
- stream.on('error', function handleStreamError(err) {
+ responseStream.on('error', function handleStreamError(err) {
if (req.aborted) return;
reject(AxiosError.from(err, null, config, lastRequest));
});
- stream.on('end', function handleStreamEnd() {
+ responseStream.on('end', function handleStreamEnd() {
try {
var responseData = responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
- if (config.responseType !== 'arraybuffer') {
- responseData = responseData.toString(config.responseEncoding);
- if (!config.responseEncoding || config.responseEncoding === 'utf8') {
+ if (responseType !== 'arraybuffer') {
+ responseData = responseData.toString(responseEncoding);
+ if (!responseEncoding || responseEncoding === 'utf8') {
responseData = utils.stripBOM(responseData);
}
}
@@ -358,9 +420,13 @@ module.exports = function httpAdapter(config) {
// ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
req.setTimeout(timeout, function handleRequestTimeout() {
req.abort();
+ var timeoutErrorMessage = config.timeout ? 'timeout of ' + config.timeout + 'ms exceeded' : 'timeout exceeded';
var transitional = config.transitional || transitionalDefaults;
+ if (config.timeoutErrorMessage) {
+ timeoutErrorMessage = config.timeoutErrorMessage;
+ }
reject(new AxiosError(
- 'timeout of ' + timeout + 'ms exceeded',
+ timeoutErrorMessage,
transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
config,
req
diff --git a/lib/adapters/xhr.js b/lib/adapters/xhr.js
index c22783f179..8612a77802 100644
--- a/lib/adapters/xhr.js
+++ b/lib/adapters/xhr.js
@@ -11,6 +11,7 @@ var transitionalDefaults = require('../defaults/transitional');
var AxiosError = require('../core/AxiosError');
var CanceledError = require('../cancel/CanceledError');
var parseProtocol = require('../helpers/parseProtocol');
+var platform = require('../platform');
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
@@ -210,7 +211,7 @@ module.exports = function xhrAdapter(config) {
var protocol = parseProtocol(fullPath);
- if (protocol && [ 'http', 'https', 'file', 'blob' ].indexOf(protocol) === -1) {
+ if (protocol && platform.protocols.indexOf(protocol) === -1) {
reject(new AxiosError('Unsupported protocol ' + protocol + ':', AxiosError.ERR_BAD_REQUEST, config));
return;
}
diff --git a/lib/axios.js b/lib/axios.js
index cf6583cfe5..bbbe301bf9 100644
--- a/lib/axios.js
+++ b/lib/axios.js
@@ -5,7 +5,7 @@ var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');
-
+var formDataToJSON = require('./helpers/formDataToJSON');
/**
* Create an instance of Axios
*
@@ -58,6 +58,10 @@ axios.spread = require('./helpers/spread');
// Expose isAxiosError
axios.isAxiosError = require('./helpers/isAxiosError');
+axios.formToJSON = function(thing) {
+ return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
+};
+
module.exports = axios;
// Allow use of default import syntax in TypeScript
diff --git a/lib/cancel/CancelToken.js b/lib/cancel/CancelToken.js
index 07ec10e3b2..c2fe64c7fd 100644
--- a/lib/cancel/CancelToken.js
+++ b/lib/cancel/CancelToken.js
@@ -49,13 +49,13 @@ function CancelToken(executor) {
return promise;
};
- executor(function cancel(message) {
+ executor(function cancel(message, config, request) {
if (token.reason) {
// Cancellation has already been requested
return;
}
- token.reason = new CanceledError(message);
+ token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason);
});
}
diff --git a/lib/core/Axios.js b/lib/core/Axios.js
index 3a79171099..ef7e810129 100644
--- a/lib/core/Axios.js
+++ b/lib/core/Axios.js
@@ -25,7 +25,8 @@ function Axios(instanceConfig) {
/**
* Dispatch a request
*
- * @param {Object} config The config specific for this request (merged with this.defaults)
+ * @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
+ * @param {?Object} config
*/
Axios.prototype.request = function request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
diff --git a/lib/core/AxiosError.js b/lib/core/AxiosError.js
index 2125a4e6aa..291dc33c49 100644
--- a/lib/core/AxiosError.js
+++ b/lib/core/AxiosError.js
@@ -16,7 +16,9 @@ function AxiosError(message, code, config, request, response) {
Error.call(this);
if (Error.captureStackTrace) {
- Error.captureStackTrace(this);
+ Error.captureStackTrace(this, this.constructor);
+ } else {
+ this.stack = (new Error()).stack;
}
this.message = message;
@@ -62,7 +64,9 @@ var descriptors = {};
'ERR_DEPRECATED',
'ERR_BAD_RESPONSE',
'ERR_BAD_REQUEST',
- 'ERR_CANCELED'
+ 'ERR_CANCELED',
+ 'ERR_NOT_SUPPORT',
+ 'ERR_INVALID_URL'
// eslint-disable-next-line func-names
].forEach(function(code) {
descriptors[code] = {value: code};
@@ -81,6 +85,8 @@ AxiosError.from = function(error, code, config, request, response, customProps)
AxiosError.call(axiosError, error.message, code, config, request, response);
+ axiosError.cause = error;
+
axiosError.name = error.name;
customProps && Object.assign(axiosError, customProps);
diff --git a/lib/core/dispatchRequest.js b/lib/core/dispatchRequest.js
index 997d4c952e..6ac7bc0e42 100644
--- a/lib/core/dispatchRequest.js
+++ b/lib/core/dispatchRequest.js
@@ -5,6 +5,7 @@ var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var CanceledError = require('../cancel/CanceledError');
+var normalizeHeaderName = require('../helpers/normalizeHeaderName');
/**
* Throws a `CanceledError` if cancellation has been requested.
@@ -40,6 +41,9 @@ module.exports = function dispatchRequest(config) {
config.transformRequest
);
+ normalizeHeaderName(config.headers, 'Accept');
+ normalizeHeaderName(config.headers, 'Content-Type');
+
// Flatten headers
config.headers = utils.merge(
config.headers.common || {},
diff --git a/lib/defaults/index.js b/lib/defaults/index.js
index 1be97ffd22..d7ed025937 100644
--- a/lib/defaults/index.js
+++ b/lib/defaults/index.js
@@ -5,6 +5,9 @@ var normalizeHeaderName = require('../helpers/normalizeHeaderName');
var AxiosError = require('../core/AxiosError');
var transitionalDefaults = require('./transitional');
var toFormData = require('../helpers/toFormData');
+var toURLEncodedForm = require('../helpers/toURLEncodedForm');
+var platform = require('../platform');
+var formDataToJSON = require('../helpers/formDataToJSON');
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
@@ -53,8 +56,24 @@ var defaults = {
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
- if (utils.isFormData(data) ||
- utils.isArrayBuffer(data) ||
+ var contentType = headers && headers['Content-Type'] || '';
+ var hasJSONContentType = contentType.indexOf('application/json') > -1;
+ var isObjectPayload = utils.isObject(data);
+
+ if (isObjectPayload && utils.isHTMLForm(data)) {
+ data = new FormData(data);
+ }
+
+ var isFormData = utils.isFormData(data);
+
+ if (isFormData) {
+ if (!hasJSONContentType) {
+ return data;
+ }
+ return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
+ }
+
+ if (utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
@@ -70,19 +89,25 @@ var defaults = {
return data.toString();
}
- var isObjectPayload = utils.isObject(data);
- var contentType = headers && headers['Content-Type'];
-
var isFileList;
- if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) {
- var _FormData = this.env && this.env.FormData;
- return toFormData(
- isFileList ? {'files[]': data} : data,
- _FormData && new _FormData(),
- this.formSerializer
- );
- } else if (isObjectPayload || contentType === 'application/json') {
+ if (isObjectPayload) {
+ if (contentType.indexOf('application/x-www-form-urlencoded') !== -1) {
+ return toURLEncodedForm(data, this.formSerializer).toString();
+ }
+
+ if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
+ var _FormData = this.env && this.env.FormData;
+
+ return toFormData(
+ isFileList ? {'files[]': data} : data,
+ _FormData && new _FormData(),
+ this.formSerializer
+ );
+ }
+ }
+
+ if (isObjectPayload || hasJSONContentType ) {
setContentTypeIfUnset(headers, 'application/json');
return stringifySafely(data);
}
@@ -92,11 +117,13 @@ var defaults = {
transformResponse: [function transformResponse(data) {
var transitional = this.transitional || defaults.transitional;
- var silentJSONParsing = transitional && transitional.silentJSONParsing;
var forcedJSONParsing = transitional && transitional.forcedJSONParsing;
- var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';
+ var JSONRequested = this.responseType === 'json';
+
+ if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) {
+ var silentJSONParsing = transitional && transitional.silentJSONParsing;
+ var strictJSONParsing = !silentJSONParsing && JSONRequested;
- if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
try {
return JSON.parse(data);
} catch (e) {
@@ -125,7 +152,8 @@ var defaults = {
maxBodyLength: -1,
env: {
- FormData: require('./env/FormData')
+ FormData: platform.classes.FormData,
+ Blob: platform.classes.Blob
},
validateStatus: function validateStatus(status) {
diff --git a/lib/defaults/env/FormData.js b/lib/env/classes/FormData.js
similarity index 100%
rename from lib/defaults/env/FormData.js
rename to lib/env/classes/FormData.js
diff --git a/lib/helpers/AxiosURLSearchParams.js b/lib/helpers/AxiosURLSearchParams.js
new file mode 100644
index 0000000000..7c49f185f7
--- /dev/null
+++ b/lib/helpers/AxiosURLSearchParams.js
@@ -0,0 +1,42 @@
+'use strict';
+
+var toFormData = require('./toFormData');
+
+function encode(str) {
+ var charMap = {
+ '!': '%21',
+ "'": '%27',
+ '(': '%28',
+ ')': '%29',
+ '~': '%7E',
+ '%20': '+',
+ '%00': '\x00'
+ };
+ return encodeURIComponent(str).replace(/[!'\(\)~]|%20|%00/g, function replacer(match) {
+ return charMap[match];
+ });
+}
+
+function AxiosURLSearchParams(params, options) {
+ this._pairs = [];
+
+ params && toFormData(params, this, options);
+}
+
+var prototype = AxiosURLSearchParams.prototype;
+
+prototype.append = function append(name, value) {
+ this._pairs.push([name, value]);
+};
+
+prototype.toString = function toString(encoder) {
+ var _encode = encoder ? function(value) {
+ return encoder.call(this, value, encode);
+ } : encode;
+
+ return this._pairs.map(function each(pair) {
+ return _encode(pair[0]) + '=' + _encode(pair[1]);
+ }, '').join('&');
+};
+
+module.exports = AxiosURLSearchParams;
diff --git a/lib/helpers/buildURL.js b/lib/helpers/buildURL.js
index 31595c33a8..bebacb3aee 100644
--- a/lib/helpers/buildURL.js
+++ b/lib/helpers/buildURL.js
@@ -1,6 +1,7 @@
'use strict';
-var utils = require('./../utils');
+var utils = require('../utils');
+var AxiosURLSearchParams = require('../helpers/AxiosURLSearchParams');
function encode(val) {
return encodeURIComponent(val).
@@ -17,53 +18,29 @@ function encode(val) {
*
* @param {string} url The base of the url (e.g., http://www.google.com)
* @param {object} [params] The params to be appended
+ * @param {?object} options
* @returns {string} The formatted url
*/
-module.exports = function buildURL(url, params, paramsSerializer) {
+module.exports = function buildURL(url, params, options) {
/*eslint no-param-reassign:0*/
if (!params) {
return url;
}
- var serializedParams;
- if (paramsSerializer) {
- serializedParams = paramsSerializer(params);
- } else if (utils.isURLSearchParams(params)) {
- serializedParams = params.toString();
- } else {
- var parts = [];
+ var hashmarkIndex = url.indexOf('#');
- utils.forEach(params, function serialize(val, key) {
- if (val === null || typeof val === 'undefined') {
- return;
- }
-
- if (utils.isArray(val)) {
- key = key + '[]';
- } else {
- val = [val];
- }
-
- utils.forEach(val, function parseValue(v) {
- if (utils.isDate(v)) {
- v = v.toISOString();
- } else if (utils.isObject(v)) {
- v = JSON.stringify(v);
- }
- parts.push(encode(key) + '=' + encode(v));
- });
- });
-
- serializedParams = parts.join('&');
+ if (hashmarkIndex !== -1) {
+ url = url.slice(0, hashmarkIndex);
}
- if (serializedParams) {
- var hashmarkIndex = url.indexOf('#');
- if (hashmarkIndex !== -1) {
- url = url.slice(0, hashmarkIndex);
- }
+ var _encode = options && options.encode || encode;
+
+ var serializerParams = utils.isURLSearchParams(params) ?
+ params.toString() :
+ new AxiosURLSearchParams(params, options).toString(_encode);
- url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
+ if (serializerParams) {
+ url += (url.indexOf('?') === -1 ? '?' : '&') + serializerParams;
}
return url;
diff --git a/lib/helpers/formDataToJSON.js b/lib/helpers/formDataToJSON.js
new file mode 100644
index 0000000000..45a10364e4
--- /dev/null
+++ b/lib/helpers/formDataToJSON.js
@@ -0,0 +1,71 @@
+'use strict';
+
+var utils = require('../utils');
+
+function parsePropPath(name) {
+ // foo[x][y][z]
+ // foo.x.y.z
+ // foo-x-y-z
+ // foo x y z
+ return utils.matchAll(/\w+|\[(\w*)]/g, name).map(function(match) {
+ return match[0] === '[]' ? '' : match[1] || match[0];
+ });
+}
+
+function arrayToObject(arr) {
+ var obj = {};
+ var keys = Object.keys(arr);
+ var i;
+ var len = keys.length;
+ var key;
+ for (i = 0; i < len; i++) {
+ key = keys[i];
+ obj[key] = arr[key];
+ }
+ return obj;
+}
+
+function formDataToJSON(formData) {
+ function buildPath(path, value, target, index) {
+ var name = path[index++];
+ var isNumericKey = Number.isFinite(+name);
+ var isLast = index >= path.length;
+ name = !name && utils.isArray(target) ? target.length : name;
+
+ if (isLast) {
+ if (utils.hasOwnProperty(target, name)) {
+ target[name] = [target[name], value];
+ } else {
+ target[name] = value;
+ }
+
+ return !isNumericKey;
+ }
+
+ if (!target[name] || !utils.isObject(target[name])) {
+ target[name] = [];
+ }
+
+ var result = buildPath(path, value, target[name], index);
+
+ if (result && utils.isArray(target[name])) {
+ target[name] = arrayToObject(target[name]);
+ }
+
+ return !isNumericKey;
+ }
+
+ if (utils.isFormData(formData) && utils.isFunction(formData.entries)) {
+ var obj = {};
+
+ utils.forEachEntry(formData, function(name, value) {
+ buildPath(parsePropPath(name), value, obj, 0);
+ });
+
+ return obj;
+ }
+
+ return null;
+}
+
+module.exports = formDataToJSON;
diff --git a/lib/helpers/fromDataURI.js b/lib/helpers/fromDataURI.js
new file mode 100644
index 0000000000..3ee2ab925a
--- /dev/null
+++ b/lib/helpers/fromDataURI.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var AxiosError = require('../core/AxiosError');
+var parseProtocol = require('./parseProtocol');
+var platform = require('../platform');
+
+var DATA_URL_PATTERN = /^(?:([^;]+);)?(?:[^;]+;)?(base64|),([\s\S]*)$/;
+
+/**
+ * Parse data uri to a Buffer or Blob
+ * @param {String} uri
+ * @param {?Boolean} asBlob
+ * @param {?Object} options
+ * @param {?Function} options.Blob
+ * @returns {Buffer|Blob}
+ */
+module.exports = function fromDataURI(uri, asBlob, options) {
+ var _Blob = options && options.Blob || platform.classes.Blob;
+ var protocol = parseProtocol(uri);
+
+ if (asBlob === undefined && _Blob) {
+ asBlob = true;
+ }
+
+ if (protocol === 'data') {
+ uri = protocol.length ? uri.slice(protocol.length + 1) : uri;
+
+ var match = DATA_URL_PATTERN.exec(uri);
+
+ if (!match) {
+ throw new AxiosError('Invalid URL', AxiosError.ERR_INVALID_URL);
+ }
+
+ var mime = match[1];
+ var isBase64 = match[2];
+ var body = match[3];
+ var buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');
+
+ if (asBlob) {
+ if (!_Blob) {
+ throw new AxiosError('Blob is not supported', AxiosError.ERR_NOT_SUPPORT);
+ }
+
+ return new _Blob([buffer], {type: mime});
+ }
+
+ return buffer;
+ }
+
+ throw new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_NOT_SUPPORT);
+};
diff --git a/lib/helpers/toFormData.js b/lib/helpers/toFormData.js
index 9cfdbd76a6..6dcb9d2337 100644
--- a/lib/helpers/toFormData.js
+++ b/lib/helpers/toFormData.js
@@ -1,7 +1,8 @@
'use strict';
var utils = require('../utils');
-var envFormData = require('../defaults/env/FormData');
+var AxiosError = require('../core/AxiosError');
+var envFormData = require('../env/classes/FormData');
function isVisitable(thing) {
return utils.isPlainObject(thing) || utils.isArray(thing);
@@ -28,6 +29,10 @@ var predicates = utils.toFlatObject(utils, {}, null, function filter(prop) {
return /^is[A-Z]/.test(prop);
});
+function isSpecCompliant(thing) {
+ return thing && utils.isFunction(thing.append) && thing[Symbol.toStringTag] === 'FormData' && thing[Symbol.iterator];
+}
+
/**
* Convert a data object to FormData
* @param {Object} obj
@@ -41,6 +46,10 @@ var predicates = utils.toFlatObject(utils, {}, null, function filter(prop) {
**/
function toFormData(obj, formData, options) {
+ if (!utils.isObject(obj)) {
+ throw new TypeError('target must be an object');
+ }
+
// eslint-disable-next-line no-param-reassign
formData = formData || new (envFormData || FormData)();
@@ -59,6 +68,8 @@ function toFormData(obj, formData, options) {
var visitor = options.visitor || defaultVisitor;
var dots = options.dots;
var indexes = options.indexes;
+ var _Blob = options.Blob || typeof Blob !== 'undefined' && Blob;
+ var useBlob = _Blob && isSpecCompliant(formData);
if (!utils.isFunction(visitor)) {
throw new TypeError('visitor must be a function');
@@ -71,14 +82,17 @@ function toFormData(obj, formData, options) {
return value.toISOString();
}
+ if (!useBlob && utils.isBlob(value)) {
+ throw new AxiosError('Blob is not supported. Use a Buffer instead.');
+ }
+
if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) {
- return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value);
+ return useBlob && typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value);
}
return value;
}
-
/**
*
* @param {*} value
@@ -88,7 +102,7 @@ function toFormData(obj, formData, options) {
* @returns {boolean} return true to visit the each prop of the value recursively
*/
function defaultVisitor(value, key, path) {
- var arr;
+ var arr = value;
if (value && !path && typeof value === 'object') {
if (utils.endsWith(key, '{}')) {
@@ -96,7 +110,10 @@ function toFormData(obj, formData, options) {
key = metaTokens ? key : key.slice(0, -2);
// eslint-disable-next-line no-param-reassign
value = JSON.stringify(value);
- } else if (!utils.isPlainObject(value) && (arr = utils.toArray(value)) && isFlatArray(arr)) {
+ } else if (
+ (utils.isArray(value) && isFlatArray(value)) ||
+ (utils.isFileList(value) || utils.endsWith(key, '[]') && (arr = utils.toArray(value))
+ )) {
// eslint-disable-next-line no-param-reassign
key = removeBrackets(key);
@@ -138,7 +155,7 @@ function toFormData(obj, formData, options) {
stack.push(value);
utils.forEach(value, function each(el, key) {
- var result = !utils.isUndefined(el) && defaultVisitor.call(
+ var result = !utils.isUndefined(el) && visitor.call(
formData, el, utils.isString(key) ? key.trim() : key, path, exposedHelpers
);
@@ -150,8 +167,8 @@ function toFormData(obj, formData, options) {
stack.pop();
}
- if (!utils.isPlainObject(obj)) {
- throw new TypeError('data must be a plain object');
+ if (!utils.isObject(obj)) {
+ throw new TypeError('data must be an object');
}
build(obj);
diff --git a/lib/helpers/toURLEncodedForm.js b/lib/helpers/toURLEncodedForm.js
new file mode 100644
index 0000000000..947a39a6ce
--- /dev/null
+++ b/lib/helpers/toURLEncodedForm.js
@@ -0,0 +1,18 @@
+'use strict';
+
+var utils = require('../utils');
+var toFormData = require('./toFormData');
+var platform = require('../platform/');
+
+module.exports = function toURLEncodedForm(data, options) {
+ return toFormData(data, new platform.classes.URLSearchParams(), Object.assign({
+ visitor: function(value, key, path, helpers) {
+ if (platform.isNode && utils.isBuffer(value)) {
+ this.append(key, value.toString('base64'));
+ return false;
+ }
+
+ return helpers.defaultVisitor.apply(this, arguments);
+ }
+ }, options));
+};
diff --git a/lib/platform/browser/classes/FormData.js b/lib/platform/browser/classes/FormData.js
new file mode 100644
index 0000000000..6af83c9fda
--- /dev/null
+++ b/lib/platform/browser/classes/FormData.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = FormData;
diff --git a/lib/platform/browser/classes/URLSearchParams.js b/lib/platform/browser/classes/URLSearchParams.js
new file mode 100644
index 0000000000..65f63d8827
--- /dev/null
+++ b/lib/platform/browser/classes/URLSearchParams.js
@@ -0,0 +1,5 @@
+'use strict';
+
+var AxiosURLSearchParams = require('../../../helpers/AxiosURLSearchParams');
+
+module.exports = typeof URLSearchParams !== 'undefined' ? URLSearchParams : AxiosURLSearchParams;
diff --git a/lib/platform/browser/index.js b/lib/platform/browser/index.js
new file mode 100644
index 0000000000..c254a1e38d
--- /dev/null
+++ b/lib/platform/browser/index.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = {
+ isBrowser: true,
+ classes: {
+ URLSearchParams: require('./classes/URLSearchParams'),
+ FormData: require('./classes/FormData'),
+ Blob: Blob
+ },
+ protocols: ['http', 'https', 'file', 'blob', 'url']
+};
diff --git a/lib/platform/index.js b/lib/platform/index.js
new file mode 100644
index 0000000000..8560532753
--- /dev/null
+++ b/lib/platform/index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('./node/');
diff --git a/lib/platform/node/classes/FormData.js b/lib/platform/node/classes/FormData.js
new file mode 100644
index 0000000000..a186bc0bfd
--- /dev/null
+++ b/lib/platform/node/classes/FormData.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('form-data');
diff --git a/lib/platform/node/classes/URLSearchParams.js b/lib/platform/node/classes/URLSearchParams.js
new file mode 100644
index 0000000000..1ae3fc58df
--- /dev/null
+++ b/lib/platform/node/classes/URLSearchParams.js
@@ -0,0 +1,5 @@
+'use strict';
+
+var url = require('url');
+
+module.exports = url.URLSearchParams;
diff --git a/lib/platform/node/index.js b/lib/platform/node/index.js
new file mode 100644
index 0000000000..b41ff36f38
--- /dev/null
+++ b/lib/platform/node/index.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = {
+ isNode: true,
+ classes: {
+ URLSearchParams: require('./classes/URLSearchParams'),
+ FormData: require('./classes/FormData'),
+ Blob: typeof Blob !== 'undefined' && Blob || null
+ },
+ protocols: [ 'http', 'https', 'file', 'data' ]
+};
diff --git a/lib/utils.js b/lib/utils.js
index 1ecd51ee09..577462f924 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -229,15 +229,16 @@ function trim(str) {
* navigator.product -> 'NativeScript' or 'NS'
*/
function isStandardBrowserEnv() {
- if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
- navigator.product === 'NativeScript' ||
- navigator.product === 'NS')) {
+ var product;
+ if (typeof navigator !== 'undefined' && (
+ (product = navigator.product) === 'ReactNative' ||
+ product === 'NativeScript' ||
+ product === 'NS')
+ ) {
return false;
}
- return (
- typeof window !== 'undefined' &&
- typeof document !== 'undefined'
- );
+
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
}
/**
@@ -440,6 +441,38 @@ var isTypedArray = (function(TypedArray) {
};
})(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array));
+function forEachEntry(obj, fn) {
+ var generator = obj && obj[Symbol.iterator];
+
+ var iterator = generator.call(obj);
+
+ var result;
+
+ while ((result = iterator.next()) && !result.done) {
+ var pair = result.value;
+ fn.call(obj, pair[0], pair[1]);
+ }
+}
+
+function matchAll(regExp, str) {
+ var matches;
+ var arr = [];
+
+ while ((matches = regExp.exec(str)) !== null) {
+ arr.push(matches);
+ }
+
+ return arr;
+}
+
+var isHTMLForm = kindOfTest('HTMLFormElement');
+
+var hasOwnProperty = (function resolver(_hasOwnProperty) {
+ return function(obj, prop) {
+ return _hasOwnProperty.call(obj, prop);
+ };
+})(Object.prototype.hasOwnProperty);
+
module.exports = {
isArray: isArray,
isArrayBuffer: isArrayBuffer,
@@ -470,5 +503,9 @@ module.exports = {
endsWith: endsWith,
toArray: toArray,
isTypedArray: isTypedArray,
- isFileList: isFileList
+ isFileList: isFileList,
+ forEachEntry: forEachEntry,
+ matchAll: matchAll,
+ isHTMLForm: isHTMLForm,
+ hasOwnProperty: hasOwnProperty
};
diff --git a/package-lock.json b/package-lock.json
index e537d68521..bdb974ece4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.0",
- "form-data": "^4.0.0"
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
},
"devDependencies": {
"@rollup/plugin-babel": "^5.3.0",
@@ -19,7 +20,9 @@
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"abortcontroller-polyfill": "^1.7.3",
+ "body-parser": "^1.20.0",
"coveralls": "^3.1.1",
+ "cross-env": "^7.0.3",
"dtslint": "^4.2.1",
"es6-promise": "^4.2.8",
"express": "^4.18.1",
@@ -3813,6 +3816,24 @@
"sha.js": "^2.4.8"
}
},
+ "node_modules/cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.1"
+ },
+ "bin": {
+ "cross-env": "src/bin/cross-env.js",
+ "cross-env-shell": "src/bin/cross-env-shell.js"
+ },
+ "engines": {
+ "node": ">=10.14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -7036,9 +7057,9 @@
}
},
"node_modules/grunt": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.2.tgz",
- "integrity": "sha512-XCtfaIu72OyDqK24MjWiGC9SwlkuhkS1mrULr1xzuJ2XqAFhP3ZAchZGHJeSCY6mkaOXU4F7SbmmCF7xIVoC9w==",
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz",
+ "integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==",
"dev": true,
"dependencies": {
"dateformat": "~3.0.3",
@@ -11989,8 +12010,7 @@
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "dev": true
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/prr": {
"version": "1.0.1",
@@ -20214,6 +20234,15 @@
"sha.js": "^2.4.8"
}
},
+ "cross-env": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
+ "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.1"
+ }
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -22851,9 +22880,9 @@
"dev": true
},
"grunt": {
- "version": "1.5.2",
- "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.2.tgz",
- "integrity": "sha512-XCtfaIu72OyDqK24MjWiGC9SwlkuhkS1mrULr1xzuJ2XqAFhP3ZAchZGHJeSCY6mkaOXU4F7SbmmCF7xIVoC9w==",
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz",
+ "integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==",
"dev": true,
"requires": {
"dateformat": "~3.0.3",
@@ -26801,8 +26830,7 @@
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "dev": true
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"prr": {
"version": "1.0.1",
diff --git a/package.json b/package.json
index c262de8feb..7f08ba5892 100644
--- a/package.json
+++ b/package.json
@@ -5,10 +5,10 @@
"main": "index.js",
"types": "index.d.ts",
"scripts": {
- "test": "grunt test && dtslint",
+ "test": "node bin/ssl_hotfix.js grunt test && node bin/ssl_hotfix.js dtslint",
"start": "node ./sandbox/server.js",
"preversion": "grunt version && npm test",
- "build": "NODE_ENV=production grunt build",
+ "build": "cross-env NODE_ENV=production grunt build",
"examples": "node ./examples/server.js",
"coveralls": "cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"fix": "eslint --fix lib/**/*.js"
@@ -37,7 +37,9 @@
"@rollup/plugin-multi-entry": "^4.0.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"abortcontroller-polyfill": "^1.7.3",
+ "body-parser": "^1.20.0",
"coveralls": "^3.1.1",
+ "cross-env": "^7.0.3",
"dtslint": "^4.2.1",
"es6-promise": "^4.2.8",
"express": "^4.18.1",
@@ -79,7 +81,7 @@
},
"browser": {
"./lib/adapters/http.js": "./lib/adapters/xhr.js",
- "./lib/defaults/env/FormData.js": "./lib/helpers/null.js"
+ "./lib/platform/node/index.js": "./lib/platform/browser/index.js"
},
"jsdelivr": "dist/axios.min.js",
"unpkg": "dist/axios.min.js",
diff --git a/test/specs/helpers/buildURL.spec.js b/test/specs/helpers/buildURL.spec.js
index 7adf57449e..0bee3e9a05 100644
--- a/test/specs/helpers/buildURL.spec.js
+++ b/test/specs/helpers/buildURL.spec.js
@@ -17,7 +17,7 @@ describe('helpers::buildURL', function () {
foo: {
bar: 'baz'
}
- })).toEqual('/foo?foo=' + encodeURI('{"bar":"baz"}'));
+ })).toEqual('/foo?foo[bar]=baz');
});
it('should support date params', function () {
@@ -60,15 +60,6 @@ describe('helpers::buildURL', function () {
})).toEqual('/foo?foo=bar&query=baz');
});
- it('should use serializer if provided', function () {
- serializer = sinon.stub();
- params = {foo: 'bar'};
- serializer.returns('foo=bar');
- expect(buildURL('/foo', params, serializer)).toEqual('/foo?foo=bar');
- expect(serializer.calledOnce).toBe(true);
- expect(serializer.calledWith(params)).toBe(true);
- });
-
it('should support URLSearchParams', function () {
expect(buildURL('/foo', new URLSearchParams('bar=baz'))).toEqual('/foo?bar=baz');
});
diff --git a/test/specs/helpers/formDataToJSON.spec.js b/test/specs/helpers/formDataToJSON.spec.js
new file mode 100644
index 0000000000..69ea1a6d51
--- /dev/null
+++ b/test/specs/helpers/formDataToJSON.spec.js
@@ -0,0 +1,50 @@
+var formDataToJSON = require('../../../lib/helpers/formDataToJSON');
+
+describe('formDataToJSON', function () {
+ it('should convert a FormData Object to JSON Object', function () {
+ const formData = new FormData();
+
+ formData.append('foo[bar][baz]', '123');
+
+ expect(formDataToJSON(formData)).toEqual({
+ foo: {
+ bar: {
+ baz: '123'
+ }
+ }
+ });
+ });
+
+ it('should convert repeatable values as an array', function () {
+ const formData = new FormData();
+
+ formData.append('foo', '1');
+ formData.append('foo', '2');
+
+ expect(formDataToJSON(formData)).toEqual({
+ foo: ['1', '2']
+ });
+ });
+
+ it('should convert props with empty brackets to arrays', function () {
+ const formData = new FormData();
+
+ formData.append('foo[]', '1');
+ formData.append('foo[]', '2');
+
+ expect(formDataToJSON(formData)).toEqual({
+ foo: ['1', '2']
+ });
+ });
+
+ it('should supported indexed arrays', function () {
+ const formData = new FormData();
+
+ formData.append('foo[0]', '1');
+ formData.append('foo[1]', '2');
+
+ expect(formDataToJSON(formData)).toEqual({
+ foo: ['1', '2']
+ });
+ });
+});
diff --git a/test/specs/instance.spec.js b/test/specs/instance.spec.js
index e1b9a7c9d3..e29c2f7109 100644
--- a/test/specs/instance.spec.js
+++ b/test/specs/instance.spec.js
@@ -25,7 +25,8 @@ describe('instance', function () {
'isAxiosError',
'VERSION',
'default',
- 'toFormData'
+ 'toFormData',
+ 'formToJSON'
].indexOf(prop) > -1) {
continue;
}
diff --git a/test/specs/transform.spec.js b/test/specs/transform.spec.js
index f7e62cb957..1ae052f5b9 100644
--- a/test/specs/transform.spec.js
+++ b/test/specs/transform.spec.js
@@ -177,4 +177,24 @@ describe('transform', function () {
done();
});
});
+
+ it('should normalize \'content-type\' header when using a custom transformRequest', function (done) {
+ var data = {
+ foo: 'bar'
+ };
+
+ axios.post('/foo', data, {
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
+ transformRequest: [
+ function () {
+ return 'aa=44'
+ }
+ ]
+ });
+
+ getAjaxRequest().then(function (request) {
+ expect(request.requestHeaders['Content-Type']).toEqual('application/x-www-form-urlencoded');
+ done();
+ });
+ });
});
diff --git a/test/typescript/axios.ts b/test/typescript/axios.ts
index 94d41602c1..c0c61599e1 100644
--- a/test/typescript/axios.ts
+++ b/test/typescript/axios.ts
@@ -20,7 +20,10 @@ const config: AxiosRequestConfig = {
],
headers: { 'X-FOO': 'bar' },
params: { id: 12345 },
- paramsSerializer: (params: any) => 'id=12345',
+ paramsSerializer: {
+ indexes: true,
+ encode: (value) => value
+ },
data: { foo: 'bar' },
timeout: 10000,
withCredentials: true,
diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js
index c92e0afd2d..ed3db64b5f 100644
--- a/test/unit/adapters/http.js
+++ b/test/unit/adapters/http.js
@@ -12,8 +12,12 @@ var server, proxy;
var AxiosError = require('../../../lib/core/AxiosError');
var FormData = require('form-data');
var formidable = require('formidable');
-const express = require('express');
-const multer = require('multer');
+var express = require('express');
+var multer = require('multer');
+var bodyParser = require('body-parser');
+const isBlobSupported = typeof Blob !== 'undefined';
+
+var noop = ()=> {};
describe('supports http with nodejs', function () {
@@ -142,7 +146,7 @@ describe('supports http with nodejs', function () {
assert.strictEqual(success, false, 'request should not succeed');
assert.strictEqual(failure, true, 'request should fail');
assert.strictEqual(error.code, 'ECONNABORTED');
- assert.strictEqual(error.message, 'timeout of 250ms exceeded');
+ assert.strictEqual(error.message, 'oops, timeout');
done();
}, 300);
});
@@ -204,7 +208,7 @@ describe('supports http with nodejs', function () {
assert.equal(res.data, str);
assert.equal(res.request.path, '/two');
done();
- }).catch(done);;
+ }).catch(done);
});
});
@@ -223,7 +227,7 @@ describe('supports http with nodejs', function () {
assert.equal(res.status, 302);
assert.equal(res.headers['location'], '/foo');
done();
- }).catch(done);;
+ }).catch(done);
});
});
@@ -241,7 +245,7 @@ describe('supports http with nodejs', function () {
assert.equal(error.code, AxiosError.ERR_FR_TOO_MANY_REDIRECTS);
assert.equal(error.message, 'Maximum number of redirects exceeded');
done();
- });
+ }).catch(done);
});
});
@@ -263,6 +267,56 @@ describe('supports http with nodejs', function () {
}).catch(function (error) {
assert.equal(error.message, 'Provided path is not allowed');
done();
+ }).catch(done);
+ });
+ });
+
+ it('should support beforeRedirect and proxy with redirect', function (done) {
+ var requestCount = 0;
+ var totalRedirectCount = 5;
+ server = http.createServer(function (req, res) {
+ requestCount += 1;
+ if (requestCount <= totalRedirectCount) {
+ res.setHeader('Location', 'http://localhost:4444');
+ res.writeHead(302);
+ }
+ res.end();
+ }).listen(4444, function () {
+ var proxyUseCount = 0;
+ proxy = http.createServer(function (request, response) {
+ proxyUseCount += 1;
+ var parsed = url.parse(request.url);
+ var opts = {
+ host: parsed.hostname,
+ port: parsed.port,
+ path: parsed.path
+ };
+
+ http.get(opts, function (res) {
+ response.writeHead(res.statusCode, res.headers);
+ res.on('data', function (data) {
+ response.write(data)
+ });
+ res.on('end', function () {
+ response.end();
+ });
+ });
+ }).listen(4000, function () {
+ var configBeforeRedirectCount = 0;
+ axios.get('http://localhost:4444/', {
+ proxy: {
+ host: 'localhost',
+ port: 4000
+ },
+ maxRedirects: totalRedirectCount,
+ beforeRedirect: function (options) {
+ configBeforeRedirectCount += 1;
+ }
+ }).then(function (res) {
+ assert.equal(totalRedirectCount, configBeforeRedirectCount, 'should invoke config.beforeRedirect option on every redirect');
+ assert.equal(totalRedirectCount + 1, proxyUseCount, 'should go through proxy on every redirect');
+ done();
+ }).catch(done);
});
});
});
@@ -515,6 +569,32 @@ describe('supports http with nodejs', function () {
});
});
+ it('should properly support default max body length (follow-redirects as well)', function (done) {
+ // taken from https://github.com/follow-redirects/follow-redirects/blob/22e81fc37132941fb83939d1dc4c2282b5c69521/index.js#L461
+ var followRedirectsMaxBodyDefaults = 10 * 1024 *1024;
+ var data = Array(2 * followRedirectsMaxBodyDefaults).join('ΠΆ');
+
+ server = http.createServer(function (req, res) {
+ // consume the req stream
+ req.on('data', noop);
+ // and wait for the end before responding, otherwise an ECONNRESET error will be thrown
+ req.on('end', ()=> {
+ res.end('OK');
+ });
+ }).listen(4444, function (err) {
+ if (err) {
+ return done(err);
+ }
+ // send using the default -1 (unlimited axios maxBodyLength)
+ axios.post('http://localhost:4444/', {
+ data: data
+ }).then(function (res) {
+ assert.equal(res.data, 'OK', 'should handle response');
+ done();
+ }).catch(done);
+ });
+ });
+
it('should display error while parsing params', function (done) {
server = http.createServer(function () {
@@ -1287,9 +1367,11 @@ describe('supports http with nodejs', function () {
}).catch(done);
});
});
+
describe('toFormData helper', function () {
it('should properly serialize nested objects for parsing with multer.js (express.js)', function (done) {
- const app = express();
+ var app = express();
+
var obj = {
arr1: ['1', '2', '3'],
arr2: ['1', ['2'], '3'],
@@ -1324,4 +1406,130 @@ describe('supports http with nodejs', function () {
});
});
});
+
+ describe('URLEncoded Form', function () {
+ it('should post object data as url-encoded form if content-type is application/x-www-form-urlencoded', function (done) {
+ var app = express();
+
+ var obj = {
+ arr1: ['1', '2', '3'],
+ arr2: ['1', ['2'], '3'],
+ obj: {x: '1', y: {z: '1'}},
+ users: [{name: 'Peter', surname: 'griffin'}, {name: 'Thomas', surname: 'Anderson'}]
+ };
+
+ app.use(bodyParser.urlencoded({ extended: true }));
+
+ app.post('/', function (req, res, next) {
+ res.send(JSON.stringify(req.body));
+ });
+
+ server = app.listen(3001, function () {
+ return axios.post('http://localhost:3001/', obj, {
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded'
+ }
+ })
+ .then(function (res) {
+ assert.deepStrictEqual(res.data, obj);
+ done();
+ }).catch(done);
+ });
+ });
+ });
+
+ it('should respect formSerializer config', function (done) {
+ const obj = {
+ arr1: ['1', '2', '3'],
+ arr2: ['1', ['2'], '3'],
+ };
+
+ const form = new URLSearchParams();
+
+ form.append('arr1[0]', '1');
+ form.append('arr1[1]', '2');
+ form.append('arr1[2]', '3');
+
+ form.append('arr2[0]', '1');
+ form.append('arr2[1][0]', '2');
+ form.append('arr2[2]', '3');
+
+ server = http.createServer(function (req, res) {
+ req.pipe(res);
+ }).listen(3001, () => {
+ return axios.post('http://localhost:3001/', obj, {
+ headers: {
+ 'content-type': 'application/x-www-form-urlencoded'
+ },
+ formSerializer: {
+ indexes: true
+ }
+ })
+ .then(function (res) {
+ assert.strictEqual(res.data, form.toString());
+ done();
+ }).catch(done);
+ });
+ });
+
+ describe('Data URL', function () {
+ it('should support requesting data URL as a Buffer', function (done) {
+ const buffer = Buffer.from('123');
+
+ const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64');
+
+ axios.get(dataURI).then(({data})=> {
+ assert.deepStrictEqual(data, buffer);
+ done();
+ }).catch(done);
+ });
+
+ it('should support requesting data URL as a Blob (if supported by the environment)', function (done) {
+
+ if (!isBlobSupported) {
+ this.skip();
+ return;
+ }
+
+ const buffer = Buffer.from('123');
+
+ const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64');
+
+ axios.get(dataURI, {responseType: 'blob'}).then(async ({data})=> {
+ assert.strictEqual(data.type, 'application/octet-stream');
+ assert.deepStrictEqual(await data.text(), '123');
+ done();
+ }).catch(done);
+ });
+
+ it('should support requesting data URL as a String (text)', function (done) {
+ const buffer = Buffer.from('123', 'utf-8');
+
+ const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64');
+
+ axios.get(dataURI, {responseType: "text"}).then(({data})=> {
+ assert.deepStrictEqual(data, '123');
+ done();
+ }).catch(done);
+ });
+
+ it('should support requesting data URL as a Stream', function (done) {
+ const buffer = Buffer.from('123', 'utf-8');
+
+ const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64');
+
+ axios.get(dataURI, {responseType: "stream"}).then(({data})=> {
+ var str = '';
+
+ data.on('data', function(response){
+ str += response.toString();
+ });
+
+ data.on('end', function(){
+ assert.strictEqual(str, '123');
+ done();
+ });
+ }).catch(done);
+ });
+ });
});
diff --git a/test/unit/helpers/fromDataURI.js b/test/unit/helpers/fromDataURI.js
new file mode 100644
index 0000000000..97ff84bf06
--- /dev/null
+++ b/test/unit/helpers/fromDataURI.js
@@ -0,0 +1,12 @@
+var assert = require('assert');
+var fromDataURI = require('../../../lib/helpers/fromDataURI');
+
+describe('helpers::fromDataURI', function () {
+ it('should return buffer from data uri', function () {
+ const buffer= Buffer.from('123');
+
+ const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64');
+
+ assert.deepStrictEqual(fromDataURI(dataURI, false), buffer);
+ });
+});