diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1475ae36a..eadc5ef2ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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/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/lib/helpers/fromDataURI.js b/lib/helpers/fromDataURI.js index 4f57c7cf46..3ee2ab925a 100644 --- a/lib/helpers/fromDataURI.js +++ b/lib/helpers/fromDataURI.js @@ -23,7 +23,7 @@ module.exports = function fromDataURI(uri, asBlob, options) { } if (protocol === 'data') { - uri = uri.slice(protocol.length); + uri = protocol.length ? uri.slice(protocol.length + 1) : uri; var match = DATA_URL_PATTERN.exec(uri); diff --git a/lib/helpers/toFormData.js b/lib/helpers/toFormData.js index c8668b2c2b..a2ab5697f8 100644 --- a/lib/helpers/toFormData.js +++ b/lib/helpers/toFormData.js @@ -1,6 +1,7 @@ 'use strict'; var utils = require('../utils'); +var AxiosError = require('../core/AxiosError'); var envFormData = require('../env/classes/FormData'); function isVisitable(thing) { @@ -20,20 +21,6 @@ function renderKey(path, key, dots) { }).join(dots ? '.' : ''); } -function convertValue(value) { - if (value === null) return ''; - - if (utils.isDate(value)) { - return value.toISOString(); - } - - if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) { - return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); - } - - return value; -} - function isFlatArray(arr) { return utils.isArray(arr) && !arr.some(isVisitable); } @@ -42,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 @@ -73,11 +64,31 @@ 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'); } + function convertValue(value) { + if (value === null) return ''; + + if (utils.isDate(value)) { + 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 useBlob && typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); + } + + return value; + } + /** * * @param {*} value diff --git a/package-lock.json b/package-lock.json index 48385e24f3..6d6401c048 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "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", @@ -3815,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", @@ -20215,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", diff --git a/package.json b/package.json index fe01da1c1e..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" @@ -39,6 +39,7 @@ "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", diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js index 40d7f0eaaa..8804bd2d0b 100644 --- a/test/unit/adapters/http.js +++ b/test/unit/adapters/http.js @@ -15,6 +15,9 @@ var formidable = require('formidable'); 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 () { @@ -572,27 +575,23 @@ describe('supports http with nodejs', function () { var data = Array(2 * followRedirectsMaxBodyDefaults).join('ж'); server = http.createServer(function (req, res) { - res.setHeader('Content-Type', 'text/html; charset=UTF-8'); - res.end(); - }).listen(4444, function () { - var success = false, failure = false, error; + // 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) { - success = true; - }).catch(function (err) { - error = err; - failure = true; - }); - - - setTimeout(function () { - assert.equal(success, true, 'request should have succeeded'); - assert.equal(failure, false, 'request should not fail'); - assert.equal(error, undefined, 'There should not be any error'); + assert.equal(res.data, 'OK', 'should handle response'); done(); - }, 100); + }).catch(done); }); }); @@ -1485,6 +1484,24 @@ describe('supports http with nodejs', function () { }).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'); diff --git a/test/unit/helpers/fromDataURI.js b/test/unit/helpers/fromDataURI.js index e4f6754535..97ff84bf06 100644 --- a/test/unit/helpers/fromDataURI.js +++ b/test/unit/helpers/fromDataURI.js @@ -7,6 +7,6 @@ describe('helpers::fromDataURI', function () { const dataURI = 'data:application/octet-stream;base64,' + buffer.toString('base64'); - assert.deepStrictEqual(fromDataURI(dataURI), buffer); + assert.deepStrictEqual(fromDataURI(dataURI, false), buffer); }); });