Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bug #4727 : toFormData Blob issue on node>v17; #4728

Merged
merged 9 commits into from May 20, 2022
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions 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)
})
2 changes: 1 addition & 1 deletion lib/helpers/fromDataURI.js
Expand Up @@ -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);

Expand Down
39 changes: 25 additions & 14 deletions 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) {
Expand All @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
49 changes: 33 additions & 16 deletions test/unit/adapters/http.js
Expand Up @@ -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 () {

Expand Down Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -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');

Expand Down
2 changes: 1 addition & 1 deletion test/unit/helpers/fromDataURI.js
Expand Up @@ -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);
});
});