Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,11 @@ These are the available config options for making requests. Only the `url` is re
firstName: 'Fred'
},

// `formDataHeaderPolicy` controls how node.js FormData#getHeaders() is copied.
// 'legacy' (default) copies all returned headers for v1 compatibility.
// 'content-only' copies only Content-Type and Content-Length.
formDataHeaderPolicy: 'legacy',

// syntax alternative to send data into the body
// method post
// only the value is sent, not the key
Expand Down Expand Up @@ -846,6 +851,10 @@ These are the available config options for making requests. Only the `url` is re
// `maxBodyLength` (Node only option) defines the max size of the http request content in bytes allowed
maxBodyLength: 2000,

// `redact` masks matching config keys when AxiosError#toJSON() is called.
// Matching is case-insensitive and recursive. It does not change the request.
redact: ['authorization', 'password'],

// `validateStatus` defines whether to resolve or reject the promise for a given
// HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
// or `undefined`), the promise will be resolved; otherwise, the promise will be
Expand Down Expand Up @@ -1360,6 +1369,17 @@ axios.get('/user/12345').catch(function (error) {
});
```

To avoid logging secrets from `error.config`, pass a `redact` array in the request config. Matching config keys are masked case-insensitively at any depth when `AxiosError#toJSON()` is called.

```js
axios.get('/user/12345', {
headers: { Authorization: 'Bearer token' },
redact: ['authorization']
}).catch(function (error) {
console.log(error.toJSON().config.headers.Authorization); // [REDACTED ****]
});
```

## Handling Timeouts

```js
Expand Down Expand Up @@ -1601,6 +1621,8 @@ form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
axios.post('https://example.com', form);
```

In node.js, when a `FormData` object provides `getHeaders()`, axios copies all returned headers by default for v1 compatibility. If the `FormData` object is custom or not fully trusted, set `formDataHeaderPolicy: 'content-only'` to copy only `Content-Type` and `Content-Length`, and set any other request headers explicitly with the request `headers` config.

### 🆕 Automatic serialization to FormData

Starting from `v0.27.0`, Axios supports automatic object serialization to a FormData object if the request `Content-Type`
Expand Down
11 changes: 11 additions & 0 deletions docs/pages/advanced/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,14 @@ axios.get("/user/12345").catch(function (error) {
console.log(error.toJSON());
});
```

To avoid logging secrets from `error.config`, pass a `redact` array in the request config. Matching config keys are masked case-insensitively at any depth when `AxiosError#toJSON()` is called.

```js
axios.get("/user/12345", {
headers: { Authorization: "Bearer token" },
redact: ["authorization"]
}).catch(function (error) {
console.log(error.toJSON().config.headers.Authorization); // [REDACTED ****]
});
```
24 changes: 24 additions & 0 deletions docs/pages/advanced/request-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ The `data` is the data to be sent as the request body. This can be a string, a p
- Browser only: FormData, File, Blob
- Node only: Stream, Buffer, FormData (form-data package)

For Node.js `FormData` objects that provide a `getHeaders()` method, axios copies all returned headers by default for v1 compatibility. If the `FormData` object is custom or not fully trusted, set `formDataHeaderPolicy: 'content-only'` to copy only `Content-Type` and `Content-Length`, and set any other request headers explicitly via the request `headers` config.

### `formDataHeaderPolicy` <Badge type="warning" text="Node.js only" />

Controls how axios copies headers returned by Node.js `FormData#getHeaders()`. The default is `'legacy'`, which copies all returned headers to preserve existing v1 behavior. Set `'content-only'` to copy only `Content-Type` and `Content-Length` from `getHeaders()`.

### `timeout`

The `timeout` is the number of milliseconds before the request times out. If the request takes longer than `timeout`, the request will be aborted.
Expand Down Expand Up @@ -206,6 +212,22 @@ The `maxContentLength` property defines the maximum number of bytes that the ser

The `maxBodyLength` property defines the maximum number of bytes that the server will accept in the request.

### `redact`

The `redact` property is an optional array of config key names to mask when an `AxiosError` is serialized with `toJSON()`. Matching is case-insensitive and recursive across the serialized request config. Matching values are replaced with `[REDACTED ****]`.

`redact` only affects error serialization. It does not change request data, headers, or the original config object.

```js
axios.get('/user/12345', {
headers: { Authorization: 'Bearer token' },
auth: { username: 'me', password: 'secret' },
redact: ['authorization', 'password']
}).catch((error) => {
console.log(error.toJSON().config);
});
```

### `validateStatus`

The `validateStatus` function allows you to override the default status code validation. By default, axios will reject the promise if the status code is not in the range of 200-299. You can override this behavior by providing a custom `validateStatus` function. The function should return `true` if the status code is within the range you want to accept.
Expand Down Expand Up @@ -362,6 +384,7 @@ The `maxRate` property defines the maximum **bandwidth** (in bytes per second) f
data: {
firstName: "Fred"
},
formDataHeaderPolicy: "legacy",
// Syntax alternative to send data into the body method post only the value is sent, not the key
data: "Country=Brasil&City=Belo Horizonte",
timeout: 1000,
Expand All @@ -387,6 +410,7 @@ The `maxRate` property defines the maximum **bandwidth** (in bytes per second) f
},
maxContentLength: 2000,
maxBodyLength: 2000,
redact: ['authorization', 'password'],
validateStatus: function (status) {
return status >= 200 && status < 300;
},
Expand Down
2 changes: 2 additions & 0 deletions index.d.cts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ declare namespace axios {
http2Options?: Record<string, any> & {
sessionTimeout?: number;
};
formDataHeaderPolicy?: 'legacy' | 'content-only';
redact?: string[];
}

// Alias
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ export interface AxiosRequestConfig<D = any> {
http2Options?: Record<string, any> & {
sessionTimeout?: number;
};
formDataHeaderPolicy?: 'legacy' | 'content-only';
redact?: string[];
}

// Alias
Expand Down
6 changes: 4 additions & 2 deletions lib/adapters/adapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ const knownAdapters = {
utils.forEach(knownAdapters, (fn, value) => {
if (fn) {
try {
Object.defineProperty(fn, 'name', { value });
// Null-proto descriptors so a polluted Object.prototype.get cannot turn
// these data descriptors into accessor descriptors on the way in.
Object.defineProperty(fn, 'name', { __proto__: null, value });
} catch (e) {
// eslint-disable-next-line no-empty
}
Object.defineProperty(fn, 'adapterName', { value });
Object.defineProperty(fn, 'adapterName', { __proto__: null, value });
}
});

Expand Down
98 changes: 68 additions & 30 deletions lib/adapters/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
const { http: httpFollow, https: httpsFollow } = followRedirects;

const isHttps = /https:?/;
const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];

function setFormDataHeaders(headers, formHeaders, policy) {
if (policy !== 'content-only') {
headers.set(formHeaders);
return;
}

Object.entries(formHeaders).forEach(([key, val]) => {
if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
headers.set(key, val);
}
});
}

// Symbols used to bind a single 'error' listener to a pooled socket and track
// the request currently owning that socket across keep-alive reuse (issue #10780).
Expand Down Expand Up @@ -232,30 +246,51 @@ function setProxy(options, configProxy, location, isRedirect) {
}
}
if (proxy) {
// Read proxy fields without traversing the prototype chain. URL instances expose
// username/password/hostname/host/port/protocol via getters on URL.prototype (so
// direct reads are shielded), but plain object proxies — and the `auth` field
// (which URL does not expose) — must be guarded so a polluted Object.prototype
// (e.g. Object.prototype.auth = { username, password }) cannot inject
// attacker-controlled credentials into the Proxy-Authorization header or
// redirect proxying to an attacker-controlled host.
const isProxyURL = proxy instanceof URL;
const readProxyField = (key) =>
isProxyURL || utils.hasOwnProp(proxy, key) ? proxy[key] : undefined;

const proxyUsername = readProxyField('username');
const proxyPassword = readProxyField('password');
let proxyAuth = utils.hasOwnProp(proxy, 'auth') ? proxy.auth : undefined;

// Basic proxy authorization
if (proxy.username) {
proxy.auth = (proxy.username || '') + ':' + (proxy.password || '');
if (proxyUsername) {
proxyAuth = (proxyUsername || '') + ':' + (proxyPassword || '');
}

if (proxy.auth) {
// Support proxy auth object form
const validProxyAuth = Boolean(proxy.auth.username || proxy.auth.password);
if (proxyAuth) {
// Support proxy auth object form. Read sub-fields via own-prop checks so a
// plain object inheriting from polluted Object.prototype cannot leak creds.
const authIsObject = typeof proxyAuth === 'object';
const authUsername =
authIsObject && utils.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined;
const authPassword =
authIsObject && utils.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined;
const validProxyAuth = Boolean(authUsername || authPassword);

if (validProxyAuth) {
proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || '');
} else if (typeof proxy.auth === 'object') {
proxyAuth = (authUsername || '') + ':' + (authPassword || '');
} else if (authIsObject) {
throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
}

const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64');
const base64 = Buffer.from(proxyAuth, 'utf8').toString('base64');

options.headers['Proxy-Authorization'] = 'Basic ' + base64;
}

// Preserve a user-supplied Host header (case-insensitive) so callers can override
// the value forwarded to the proxy; otherwise default to the request URL's host.
let hasUserHostHeader = false;
for (const name in options.headers) {
for (const name of Object.keys(options.headers)) {
if (name.toLowerCase() === 'host') {
hasUserHostHeader = true;
break;
Expand All @@ -264,14 +299,15 @@ function setProxy(options, configProxy, location, isRedirect) {
if (!hasUserHostHeader) {
options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
}
const proxyHost = proxy.hostname || proxy.host;
const proxyHost = readProxyField('hostname') || readProxyField('host');
options.hostname = proxyHost;
// Replace 'host' since options is not a URL object
options.host = proxyHost;
options.port = proxy.port;
options.port = readProxyField('port');
options.path = location;
if (proxy.protocol) {
options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`;
const proxyProtocol = readProxyField('protocol');
if (proxyProtocol) {
options.protocol = proxyProtocol.includes(':') ? proxyProtocol : `${proxyProtocol}:`;
}
}

Expand Down Expand Up @@ -598,9 +634,12 @@ export default isHttpAdapterSupported &&
}
);
// support for https://www.npmjs.com/package/form-data api
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders) &&
data.getHeaders !== Object.prototype.getHeaders) {
headers.set(data.getHeaders());
} else if (
utils.isFormData(data) &&
utils.isFunction(data.getHeaders) &&
data.getHeaders !== Object.prototype.getHeaders
) {
setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));

if (!headers.hasContentLength()) {
try {
Expand Down Expand Up @@ -724,7 +763,6 @@ export default isHttpAdapterSupported &&

// Null-prototype to block prototype pollution gadgets on properties read
// directly by Node's http.request (e.g. insecureHTTPParser, lookup).
// See GHSA-q8qp-cvcw-x6jj.
const options = Object.assign(Object.create(null), {
path,
method: method,
Expand All @@ -743,11 +781,9 @@ export default isHttpAdapterSupported &&

if (config.socketPath) {
if (typeof config.socketPath !== 'string') {
return reject(new AxiosError(
'socketPath must be a string',
AxiosError.ERR_BAD_OPTION_VALUE,
config
));
return reject(
new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)
);
}

if (config.allowedSocketPaths != null) {
Expand All @@ -761,11 +797,13 @@ export default isHttpAdapterSupported &&
);

if (!isAllowed) {
return reject(new AxiosError(
`socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`,
AxiosError.ERR_BAD_OPTION_VALUE,
config
));
return reject(
new AxiosError(
`socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`,
AxiosError.ERR_BAD_OPTION_VALUE,
config
)
);
}
}

Expand Down Expand Up @@ -816,7 +854,7 @@ export default isHttpAdapterSupported &&

// Always set an explicit own value so a polluted
// Object.prototype.insecureHTTPParser cannot enable the lenient parser
// through Node's internal options copy (GHSA-q8qp-cvcw-x6jj).
// through Node's internal options copy
options.insecureHTTPParser = Boolean(own('insecureHTTPParser'));

// Create the request
Expand Down Expand Up @@ -904,7 +942,7 @@ export default isHttpAdapterSupported &&

if (responseType === 'stream') {
// Enforce maxContentLength on streamed responses; previously this
// was applied only to buffered responses. See GHSA-vf2m-468p-8v99.
// was applied only to buffered responses.
if (config.maxContentLength > -1) {
const limit = config.maxContentLength;
const source = responseStream;
Expand Down Expand Up @@ -1121,7 +1159,7 @@ export default isHttpAdapterSupported &&

// Enforce maxBodyLength for streamed uploads on the native http/https
// transport (maxRedirects === 0); follow-redirects enforces it on the
// other path. See GHSA-5c9x-8gcm-mpgx.
// other path.
let uploadStream = data;
if (config.maxBodyLength > -1 && config.maxRedirects === 0) {
const limit = config.maxBodyLength;
Expand Down
Loading
Loading