axios 0.30.3 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,7 @@ var platform = require('../platform');
19
19
  var fromDataURI = require('../helpers/fromDataURI');
20
20
  var stream = require('stream');
21
21
  var estimateDataURLDecodedBytes = require('../helpers/estimateDataURLDecodedBytes.js');
22
+ var shouldBypassProxy = require('../helpers/shouldBypassProxy');
22
23
 
23
24
  var isHttps = /https:?/;
24
25
 
@@ -46,9 +47,11 @@ function setProxy(options, configProxy, location) {
46
47
  if (!proxy && proxy !== false) {
47
48
  var proxyUrl = getProxyForUrl(location);
48
49
  if (proxyUrl) {
49
- proxy = url.parse(proxyUrl);
50
- // replace 'host' since the proxy object is not a URL object
51
- proxy.host = proxy.hostname;
50
+ if (!shouldBypassProxy(location)) {
51
+ proxy = url.parse(proxyUrl);
52
+ // replace 'host' since the proxy object is not a URL object
53
+ proxy.host = proxy.hostname;
54
+ }
52
55
  }
53
56
  }
54
57
  if (proxy) {
@@ -142,8 +145,9 @@ module.exports = function httpAdapter(config) {
142
145
  }
143
146
 
144
147
  try {
148
+ var envOption = utils.hasOwnProperty(config, 'env') ? config.env : undefined;
145
149
  convertedData = fromDataURI(config.url, responseType === 'blob', {
146
- Blob: config.env && config.env.Blob
150
+ Blob: envOption && envOption.Blob
147
151
  });
148
152
  } catch (err) {
149
153
  throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
@@ -197,7 +201,8 @@ module.exports = function httpAdapter(config) {
197
201
  }
198
202
 
199
203
  // support for https://www.npmjs.com/package/form-data api
200
- if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
204
+ if (utils.isFormData(data) && utils.isFunction(data.getHeaders) &&
205
+ data.getHeaders !== Object.prototype.getHeaders) {
201
206
  Object.assign(headers, data.getHeaders());
202
207
  } else if (data && !utils.isStream(data)) {
203
208
  if (Buffer.isBuffer(data)) {
@@ -273,13 +278,13 @@ module.exports = function httpAdapter(config) {
273
278
  } else {
274
279
  options.hostname = parsed.hostname;
275
280
  options.port = parsed.port;
276
- setProxy(options, config.proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
281
+ setProxy(options, config.proxy, protocol + '//' + parsed.host + options.path);
277
282
  }
278
283
 
279
284
  var transport;
280
285
  var isHttpsRequest = isHttps.test(options.protocol);
281
286
  options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
282
- if (config.transport) {
287
+ if (utils.hasOwnProperty(config, 'transport') && config.transport) {
283
288
  transport = config.transport;
284
289
  } else if (config.maxRedirects === 0) {
285
290
  transport = isHttpsRequest ? https : http;
@@ -345,8 +350,47 @@ module.exports = function httpAdapter(config) {
345
350
  };
346
351
 
347
352
  if (responseType === 'stream') {
348
- response.data = responseStream;
349
- settle(resolve, reject, response);
353
+ // Enforce maxContentLength on streamed responses too (GHSA-vf2m-468p-8v99).
354
+ // Previously the stream path bypassed the size guard because the check only
355
+ // ran on the buffering branch.
356
+ if (config.maxContentLength > -1) {
357
+ var maxContentLength = config.maxContentLength;
358
+ var streamedBytes = 0;
359
+ var limiter = new stream.Transform({
360
+ transform: function transformChunk(chunk, encoding, callback) {
361
+ streamedBytes += chunk.length;
362
+ if (streamedBytes > maxContentLength) {
363
+ callback(new AxiosError(
364
+ 'maxContentLength size of ' + maxContentLength + ' exceeded',
365
+ AxiosError.ERR_BAD_RESPONSE,
366
+ config,
367
+ lastRequest
368
+ ));
369
+ return;
370
+ }
371
+ callback(null, chunk);
372
+ }
373
+ });
374
+ limiter.on('error', function handleLimiterError() {
375
+ rejected = true;
376
+ responseStream.destroy();
377
+ });
378
+ responseStream.on('error', function forwardError(err) {
379
+ limiter.destroy(err);
380
+ });
381
+ response.data = limiter;
382
+ settle(resolve, reject, response);
383
+ // Defer piping via setImmediate so the caller's `.then` (a microtask)
384
+ // has run and attached any `error`/`data` listeners before chunks flow
385
+ // through the transform. `process.nextTick` would drain before those
386
+ // microtasks and lose the error event.
387
+ setImmediate(function startPipe() {
388
+ responseStream.pipe(limiter);
389
+ });
390
+ } else {
391
+ response.data = responseStream;
392
+ settle(resolve, reject, response);
393
+ }
350
394
  } else {
351
395
  var responseBuffer = [];
352
396
  var totalResponseBytes = 0;
@@ -471,7 +515,42 @@ module.exports = function httpAdapter(config) {
471
515
  if (utils.isStream(data)) {
472
516
  data.on('error', function handleStreamError(err) {
473
517
  reject(AxiosError.from(err, config, null, req));
474
- }).pipe(req);
518
+ });
519
+
520
+ // follow-redirects enforces options.maxBodyLength for stream uploads, but the
521
+ // native http/https transport (used when maxRedirects === 0) does not.
522
+ // Count bytes ourselves so the limit is always honored (GHSA-5c9x-8gcm-mpgx).
523
+ var nativeTransport = transport === http || transport === https;
524
+ if (nativeTransport && config.maxBodyLength > -1) {
525
+ var maxBodyLength = config.maxBodyLength;
526
+ var uploadedBytes = 0;
527
+ var bodyLimiter = new stream.Transform({
528
+ transform: function transformChunk(chunk, encoding, callback) {
529
+ uploadedBytes += chunk.length;
530
+ if (uploadedBytes > maxBodyLength) {
531
+ callback(new AxiosError(
532
+ 'Request body larger than maxBodyLength limit',
533
+ AxiosError.ERR_BAD_REQUEST,
534
+ config,
535
+ req
536
+ ));
537
+ return;
538
+ }
539
+ callback(null, chunk);
540
+ }
541
+ });
542
+ bodyLimiter.on('error', function handleLimiterError(err) {
543
+ if (rejected) return;
544
+ rejected = true;
545
+ try { data.unpipe(bodyLimiter); } catch (e) { /* noop */ }
546
+ try { bodyLimiter.unpipe(req); } catch (e) { /* noop */ }
547
+ req.destroy();
548
+ reject(err);
549
+ });
550
+ data.pipe(bodyLimiter).pipe(req);
551
+ } else {
552
+ data.pipe(req);
553
+ }
475
554
  } else {
476
555
  req.end(data);
477
556
  }
@@ -18,7 +18,8 @@ module.exports = function xhrAdapter(config) {
18
18
  var requestData = config.data;
19
19
  var requestHeaders = config.headers;
20
20
  var responseType = config.responseType;
21
- var withXSRFToken = config.withXSRFToken;
21
+ // Guard against prototype pollution (GHSA-xx6v-rp6x-q39c): only honor own properties.
22
+ var withXSRFToken = utils.hasOwnProperty(config, 'withXSRFToken') ? config.withXSRFToken : undefined;
22
23
  var onCanceled;
23
24
  function done() {
24
25
  if (config.cancelToken) {
@@ -146,8 +147,11 @@ module.exports = function xhrAdapter(config) {
146
147
  // Specifically not if we're in a web worker, or react-native.
147
148
  if (utils.isStandardBrowserEnv()) {
148
149
  // Add xsrf header
149
- withXSRFToken && utils.isFunction(withXSRFToken) && (withXSRFToken = withXSRFToken(config));
150
- if (withXSRFToken || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
150
+ if (utils.isFunction(withXSRFToken)) {
151
+ withXSRFToken = withXSRFToken(config);
152
+ }
153
+ // Strict boolean check (GHSA-xx6v-rp6x-q39c): only `true` short-circuits the same-origin guard.
154
+ if (withXSRFToken === true || (withXSRFToken !== false && isURLSameOrigin(fullPath))) {
151
155
  // Add xsrf header
152
156
  var xsrfValue = config.xsrfHeaderName && config.xsrfCookieName && cookies.read(config.xsrfCookieName);
153
157
  if (xsrfValue) {
@@ -66,7 +66,8 @@ var descriptors = {};
66
66
  'ERR_BAD_REQUEST',
67
67
  'ERR_CANCELED',
68
68
  'ERR_NOT_SUPPORT',
69
- 'ERR_INVALID_URL'
69
+ 'ERR_INVALID_URL',
70
+ 'ERR_FORM_DATA_DEPTH_EXCEEDED'
70
71
  // eslint-disable-next-line func-names
71
72
  ].forEach(function(code) {
72
73
  descriptors[code] = {value: code};
@@ -6,6 +6,7 @@ var isCancel = require('../cancel/isCancel');
6
6
  var defaults = require('../defaults');
7
7
  var CanceledError = require('../cancel/CanceledError');
8
8
  var normalizeHeaderName = require('../helpers/normalizeHeaderName');
9
+ var sanitizeHeaderValue = require('../helpers/sanitizeHeaderValue');
9
10
 
10
11
  /**
11
12
  * Throws a `CanceledError` if cancellation has been requested.
@@ -58,6 +59,10 @@ module.exports = function dispatchRequest(config) {
58
59
  }
59
60
  );
60
61
 
62
+ utils.forEach(config.headers, function sanitizeHeaderConfigValue(value, header) {
63
+ config.headers[header] = sanitizeHeaderValue(value);
64
+ });
65
+
61
66
  var adapter = config.adapter || defaults.adapter;
62
67
 
63
68
  return adapter(config).then(function onAdapterResolution(response) {
@@ -13,7 +13,17 @@ var utils = require('../utils');
13
13
  module.exports = function mergeConfig(config1, config2) {
14
14
  // eslint-disable-next-line no-param-reassign
15
15
  config2 = config2 || {};
16
- var config = {};
16
+ // Use a null-prototype object so a polluted Object.prototype cannot leak
17
+ // values (e.g. transport, adapter) into the returned config via inheritance.
18
+ var config = Object.create(null);
19
+
20
+ function getOwn(source, prop) {
21
+ return utils.hasOwnProperty(source, prop) ? source[prop] : undefined;
22
+ }
23
+
24
+ function hasOwn(source, prop) {
25
+ return utils.hasOwnProperty(source, prop);
26
+ }
17
27
 
18
28
  function getMergedValue(target, source) {
19
29
  if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
@@ -30,34 +40,34 @@ module.exports = function mergeConfig(config1, config2) {
30
40
 
31
41
  // eslint-disable-next-line consistent-return
32
42
  function mergeDeepProperties(prop) {
33
- if (!utils.isUndefined(config2[prop])) {
34
- return getMergedValue(config1[prop], config2[prop]);
35
- } else if (!utils.isUndefined(config1[prop])) {
43
+ if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
44
+ return getMergedValue(getOwn(config1, prop), config2[prop]);
45
+ } else if (hasOwn(config1, prop) && !utils.isUndefined(config1[prop])) {
36
46
  return getMergedValue(undefined, config1[prop]);
37
47
  }
38
48
  }
39
49
 
40
50
  // eslint-disable-next-line consistent-return
41
51
  function valueFromConfig2(prop) {
42
- if (!utils.isUndefined(config2[prop])) {
52
+ if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
43
53
  return getMergedValue(undefined, config2[prop]);
44
54
  }
45
55
  }
46
56
 
47
57
  // eslint-disable-next-line consistent-return
48
58
  function defaultToConfig2(prop) {
49
- if (!utils.isUndefined(config2[prop])) {
59
+ if (hasOwn(config2, prop) && !utils.isUndefined(config2[prop])) {
50
60
  return getMergedValue(undefined, config2[prop]);
51
- } else if (!utils.isUndefined(config1[prop])) {
61
+ } else if (hasOwn(config1, prop) && !utils.isUndefined(config1[prop])) {
52
62
  return getMergedValue(undefined, config1[prop]);
53
63
  }
54
64
  }
55
65
 
56
66
  // eslint-disable-next-line consistent-return
57
67
  function mergeDirectKeys(prop) {
58
- if (prop in config2) {
59
- return getMergedValue(config1[prop], config2[prop]);
60
- } else if (prop in config1) {
68
+ if (hasOwn(config2, prop)) {
69
+ return getMergedValue(getOwn(config1, prop), config2[prop]);
70
+ } else if (hasOwn(config1, prop)) {
61
71
  return getMergedValue(undefined, config1[prop]);
62
72
  }
63
73
  }
@@ -89,17 +89,20 @@ var defaults = {
89
89
  var isFileList;
90
90
 
91
91
  if (isObjectPayload) {
92
+ var formSerializer = utils.hasOwnProperty(this, 'formSerializer') ? this.formSerializer : undefined;
93
+ var envOption = utils.hasOwnProperty(this, 'env') ? this.env : undefined;
94
+
92
95
  if (contentType.indexOf('application/x-www-form-urlencoded') !== -1) {
93
- return toURLEncodedForm(data, this.formSerializer).toString();
96
+ return toURLEncodedForm(data, formSerializer).toString();
94
97
  }
95
98
 
96
99
  if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
97
- var _FormData = this.env && this.env.FormData;
100
+ var _FormData = envOption && envOption.FormData;
98
101
 
99
102
  return toFormData(
100
103
  isFileList ? {'files[]': data} : data,
101
104
  _FormData && new _FormData(),
102
- this.formSerializer
105
+ formSerializer
103
106
  );
104
107
  }
105
108
  }
package/lib/env/data.js CHANGED
@@ -1,3 +1,3 @@
1
1
  module.exports = {
2
- version: "0.30.3",
3
- };
2
+ "version": "0.31.1"
3
+ };
@@ -3,16 +3,17 @@
3
3
  var toFormData = require('./toFormData');
4
4
 
5
5
  function encode(str) {
6
+ // Do not map `%00` back to a raw null byte (GHSA-xhjh-pmcv-23jw): that reversed
7
+ // the safe percent-encoding from encodeURIComponent and enabled null byte injection.
6
8
  var charMap = {
7
9
  '!': '%21',
8
10
  "'": '%27',
9
11
  '(': '%28',
10
12
  ')': '%29',
11
13
  '~': '%7E',
12
- '%20': '+',
13
- '%00': '\x00'
14
+ '%20': '+'
14
15
  };
15
- return encodeURIComponent(str).replace(/[!'\(\)~]|%20|%00/g, function replacer(match) {
16
+ return encodeURIComponent(str).replace(/[!'\(\)~]|%20/g, function replacer(match) {
16
17
  return charMap[match];
17
18
  });
18
19
  }
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ var utils = require('../utils');
4
+
5
+ var INVALID_HEADER_VALUE_RE = /[^\x09\x20-\x7E\x80-\xFF]/g;
6
+ var BOUNDARY_WHITESPACE_RE = /^[\x09\x20]+|[\x09\x20]+$/g;
7
+
8
+ function sanitizeHeaderValue(value) {
9
+ if (value === false || value == null) {
10
+ return value;
11
+ }
12
+
13
+ if (utils.isArray(value)) {
14
+ return value.map(sanitizeHeaderValue);
15
+ }
16
+
17
+ return String(value)
18
+ .replace(INVALID_HEADER_VALUE_RE, '')
19
+ .replace(BOUNDARY_WHITESPACE_RE, '');
20
+ }
21
+
22
+ module.exports = sanitizeHeaderValue;
@@ -0,0 +1,133 @@
1
+ 'use strict';
2
+
3
+ var URL = require('url').URL;
4
+
5
+ var DEFAULT_PORTS = {
6
+ http: 80,
7
+ https: 443,
8
+ ws: 80,
9
+ wss: 443,
10
+ ftp: 21
11
+ };
12
+
13
+ function parseNoProxyEntry(entry) {
14
+ var entryHost = entry;
15
+ var entryPort = 0;
16
+
17
+ if (entryHost.charAt(0) === '[') {
18
+ var bracketIndex = entryHost.indexOf(']');
19
+
20
+ if (bracketIndex !== -1) {
21
+ var host = entryHost.slice(1, bracketIndex);
22
+ var rest = entryHost.slice(bracketIndex + 1);
23
+
24
+ if (rest.charAt(0) === ':' && /^\d+$/.test(rest.slice(1))) {
25
+ entryPort = parseInt(rest.slice(1), 10);
26
+ }
27
+
28
+ return [host, entryPort];
29
+ }
30
+ }
31
+
32
+ var firstColon = entryHost.indexOf(':');
33
+ var lastColon = entryHost.lastIndexOf(':');
34
+
35
+ if (firstColon !== -1 && firstColon === lastColon && /^\d+$/.test(entryHost.slice(lastColon + 1))) {
36
+ entryPort = parseInt(entryHost.slice(lastColon + 1), 10);
37
+ entryHost = entryHost.slice(0, lastColon);
38
+ }
39
+
40
+ return [entryHost, entryPort];
41
+ }
42
+
43
+ function normalizeNoProxyHost(hostname) {
44
+ if (!hostname) {
45
+ return hostname;
46
+ }
47
+
48
+ if (hostname.charAt(0) === '[' && hostname.charAt(hostname.length - 1) === ']') {
49
+ hostname = hostname.slice(1, -1);
50
+ }
51
+
52
+ return hostname.replace(/\.+$/, '');
53
+ }
54
+
55
+ function isLoopbackIPv4(hostname) {
56
+ var octets = hostname.split('.');
57
+
58
+ if (octets.length !== 4) {
59
+ return false;
60
+ }
61
+
62
+ if (octets[0] !== '127') {
63
+ return false;
64
+ }
65
+
66
+ return octets.every(function testOctet(octet) {
67
+ return /^\d+$/.test(octet) && Number(octet) >= 0 && Number(octet) <= 255;
68
+ });
69
+ }
70
+
71
+ function isLoopbackHost(hostname) {
72
+ return hostname === 'localhost' || hostname === '::1' || isLoopbackIPv4(hostname);
73
+ }
74
+
75
+ module.exports = function shouldBypassProxy(location) {
76
+ var parsed;
77
+
78
+ try {
79
+ parsed = new URL(location);
80
+ } catch (err) {
81
+ return false;
82
+ }
83
+
84
+ var noProxy = (process.env.no_proxy || process.env.NO_PROXY || '').toLowerCase();
85
+
86
+ if (!noProxy) {
87
+ return false;
88
+ }
89
+
90
+ if (noProxy === '*') {
91
+ return true;
92
+ }
93
+
94
+ var protocol = parsed.protocol.split(':', 1)[0];
95
+ var port = parsed.port !== '' ? parseInt(parsed.port, 10) : (DEFAULT_PORTS[protocol] || 0);
96
+ var hostname = normalizeNoProxyHost(parsed.hostname.toLowerCase());
97
+
98
+ return noProxy.split(/[\s,]+/).some(function testNoProxyEntry(entry) {
99
+ if (!entry) {
100
+ return false;
101
+ }
102
+
103
+ var entryParts = parseNoProxyEntry(entry);
104
+ var entryHost = normalizeNoProxyHost(entryParts[0]);
105
+ var entryPort = entryParts[1];
106
+
107
+ if (entryHost === '*') {
108
+ return true;
109
+ }
110
+
111
+ if (!entryHost) {
112
+ return false;
113
+ }
114
+
115
+ if (entryPort && entryPort !== port) {
116
+ return false;
117
+ }
118
+
119
+ if (isLoopbackHost(hostname) && isLoopbackHost(entryHost)) {
120
+ return true;
121
+ }
122
+
123
+ if (entryHost.charAt(0) === '*') {
124
+ entryHost = entryHost.slice(1);
125
+ }
126
+
127
+ if (entryHost.charAt(0) === '.') {
128
+ return hostname.slice(-entryHost.length) === entryHost;
129
+ }
130
+
131
+ return hostname === entryHost;
132
+ });
133
+ };
@@ -69,6 +69,7 @@ function toFormData(obj, formData, options) {
69
69
  var dots = options.dots;
70
70
  var indexes = options.indexes;
71
71
  var _Blob = options.Blob || typeof Blob !== 'undefined' && Blob;
72
+ var maxDepth = options.maxDepth === undefined ? 100 : options.maxDepth;
72
73
  var useBlob = _Blob && isSpecCompliant(formData);
73
74
 
74
75
  if (!utils.isFunction(visitor)) {
@@ -145,9 +146,19 @@ function toFormData(obj, formData, options) {
145
146
  isVisitable: isVisitable
146
147
  });
147
148
 
148
- function build(value, path) {
149
+ function build(value, path, depth) {
149
150
  if (utils.isUndefined(value)) return;
150
151
 
152
+ // eslint-disable-next-line no-param-reassign
153
+ depth = depth || 0;
154
+
155
+ if (depth > maxDepth) {
156
+ throw new AxiosError(
157
+ 'Maximum object depth of ' + maxDepth + ' exceeded (got ' + depth + ' levels)',
158
+ AxiosError.ERR_FORM_DATA_DEPTH_EXCEEDED
159
+ );
160
+ }
161
+
151
162
  if (stack.indexOf(value) !== -1) {
152
163
  throw Error('Circular reference detected in ' + path.join('.'));
153
164
  }
@@ -160,7 +171,7 @@ function toFormData(obj, formData, options) {
160
171
  );
161
172
 
162
173
  if (result === true) {
163
- build(el, path ? path.concat(key) : [key]);
174
+ build(el, path ? path.concat(key) : [key], depth + 1);
164
175
  }
165
176
  });
166
177
 
@@ -171,7 +182,7 @@ function toFormData(obj, formData, options) {
171
182
  throw new TypeError('data must be an object');
172
183
  }
173
184
 
174
- build(obj);
185
+ build(obj, null, 0);
175
186
 
176
187
  return formData;
177
188
  }
package/lib/utils.js CHANGED
@@ -131,7 +131,15 @@ function isPlainObject(val) {
131
131
  * @return {boolean} True if value is a empty Object, otherwise false
132
132
  */
133
133
  function isEmptyObject(val) {
134
- return val && Object.keys(val).length === 0 && Object.getPrototypeOf(val) === Object.prototype;
134
+ if (!isPlainObject(val)) {
135
+ return false;
136
+ }
137
+ for (var key in val) {
138
+ if (Object.prototype.hasOwnProperty.call(val, key)) {
139
+ return false;
140
+ }
141
+ }
142
+ return true;
135
143
  }
136
144
 
137
145
  /**
@@ -198,11 +206,17 @@ function isStream(val) {
198
206
  */
199
207
  function isFormData(thing) {
200
208
  var pattern = '[object FormData]';
201
- return thing && (
202
- (typeof FormData === 'function' && thing instanceof FormData) ||
203
- toString.call(thing) === pattern ||
204
- (isFunction(thing.toString) && thing.toString() === pattern)
205
- );
209
+ if (!thing) return false;
210
+ if (typeof FormData === 'function' && thing instanceof FormData) return true;
211
+ // Reject non-objects (strings, numbers, booleans) up front — Object.getPrototypeOf
212
+ // throws a TypeError on primitives in ES5 environments.
213
+ if (!isObject(thing)) return false;
214
+ // Reject plain objects inheriting directly from Object.prototype so prototype-pollution gadgets can't spoof FormData (GHSA-6chq-wfr3-2hj9).
215
+ var proto = Object.getPrototypeOf(thing);
216
+ if (!proto || proto === Object.prototype) return false;
217
+ if (!isFunction(thing.append)) return false;
218
+ return toString.call(thing) === pattern ||
219
+ (isFunction(thing.toString) && thing.toString() === pattern);
206
220
  }
207
221
 
208
222
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axios",
3
- "version": "0.30.3",
3
+ "version": "0.31.1",
4
4
  "description": "Promise based HTTP client for the browser and node.js",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",