Skip to content

Commit

Permalink
[Refactor] use __proto__ syntax instead of Object.create for null…
Browse files Browse the repository at this point in the history
… objects
  • Loading branch information
ljharb committed Aug 22, 2024
1 parent 96f4d93 commit 3c8a6f5
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ assert.deepEqual(qs.parse('foo[bar]=baz'), {
});
```

When using the `plainObjects` option the parsed value is returned as a null object, created via `Object.create(null)` and as such you should be aware that prototype methods will not exist on it and a user may set those names to whatever value they like:
When using the `plainObjects` option the parsed value is returned as a null object, created via `{ __proto__: null }` and as such you should be aware that prototype methods will not exist on it and a user may set those names to whatever value they like:

```javascript
var nullObject = qs.parse('a[hasOwnProperty]=b', { plainObjects: true });
Expand Down
6 changes: 3 additions & 3 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ var parseObject = function (chain, val, options, valuesParsed) {
? []
: [].concat(leaf);
} else {
obj = options.plainObjects ? Object.create(null) : {};
obj = options.plainObjects ? { __proto__: null } : {};
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
var decodedRoot = options.decodeDotInKeys ? cleanRoot.replace(/%2E/g, '.') : cleanRoot;
var index = parseInt(decodedRoot, 10);
Expand Down Expand Up @@ -274,11 +274,11 @@ module.exports = function (str, opts) {
var options = normalizeParseOptions(opts);

if (str === '' || str === null || typeof str === 'undefined') {
return options.plainObjects ? Object.create(null) : {};
return options.plainObjects ? { __proto__: null } : {};
}

var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
var obj = options.plainObjects ? Object.create(null) : {};
var obj = options.plainObjects ? { __proto__: null } : {};

// Iterate over the keys and setup the new object

Expand Down
2 changes: 1 addition & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var compactQueue = function compactQueue(queue) {
};

var arrayToObject = function arrayToObject(source, options) {
var obj = options && options.plainObjects ? Object.create(null) : {};
var obj = options && options.plainObjects ? { __proto__: null } : {};
for (var i = 0; i < source.length; ++i) {
if (typeof source[i] !== 'undefined') {
obj[i] = source[i];
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
"evalmd": "^0.0.19",
"for-each": "^0.3.3",
"glob": "=10.3.7",
"has-bigints": "^1.0.2",
"has-override-mistake": "^1.0.1",
"has-property-descriptors": "^1.0.2",
"has-proto": "^1.0.3",
"has-symbols": "^1.0.3",
"iconv-lite": "^0.5.1",
"in-publish": "^2.0.1",
Expand Down
34 changes: 21 additions & 13 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var SaferBuffer = require('safer-buffer').Buffer;
var v = require('es-value-fixtures');
var inspect = require('object-inspect');
var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
var hasProto = require('has-proto')();

var qs = require('../');
var utils = require('../lib/utils');
Expand Down Expand Up @@ -691,9 +692,8 @@ test('parse()', function (t) {
st.end();
});

t.test('parses null objects correctly', { skip: !Object.create }, function (st) {
var a = Object.create(null);
a.b = 'c';
t.test('parses null objects correctly', { skip: !hasProto }, function (st) {
var a = { __proto__: null, b: 'c' };

st.deepEqual(qs.parse(a), { b: 'c' });
var result = qs.parse({ a: a });
Expand Down Expand Up @@ -870,17 +870,25 @@ test('parse()', function (t) {
st.end();
});

t.test('can return null objects', { skip: !Object.create }, function (st) {
var expected = Object.create(null);
expected.a = Object.create(null);
expected.a.b = 'c';
expected.a.hasOwnProperty = 'd';
t.test('can return null objects', { skip: !hasProto }, function (st) {
var expected = {
__proto__: null,
a: {
__proto__: null,
b: 'c',
hasOwnProperty: 'd'
}
};
st.deepEqual(qs.parse('a[b]=c&a[hasOwnProperty]=d', { plainObjects: true }), expected);
st.deepEqual(qs.parse(null, { plainObjects: true }), Object.create(null));
var expectedArray = Object.create(null);
expectedArray.a = Object.create(null);
expectedArray.a[0] = 'b';
expectedArray.a.c = 'd';
st.deepEqual(qs.parse(null, { plainObjects: true }), { __proto__: null });
var expectedArray = {
__proto__: null,
a: {
__proto__: null,
0: 'b',
c: 'd'
}
};
st.deepEqual(qs.parse('a[]=b&a[c]=d', { plainObjects: true }), expectedArray);
st.end();
});
Expand Down
16 changes: 6 additions & 10 deletions test/stringify.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ var SaferBuffer = require('safer-buffer').Buffer;
var hasSymbols = require('has-symbols');
var mockProperty = require('mock-property');
var emptyTestCases = require('./empty-keys-cases').emptyTestCases;
var hasBigInt = typeof BigInt === 'function';
var hasProto = require('has-proto')();
var hasBigInt = require('has-bigints')();

test('stringify()', function (t) {
t.test('stringifies a querystring object', function (st) {
Expand Down Expand Up @@ -650,10 +651,8 @@ test('stringify()', function (t) {
st.end();
});

t.test('stringifies a null object', { skip: !Object.create }, function (st) {
var obj = Object.create(null);
obj.a = 'b';
st.equal(qs.stringify(obj), 'a=b');
t.test('stringifies a null object', { skip: !hasProto }, function (st) {
st.equal(qs.stringify({ __proto__: null, a: 'b' }), 'a=b');
st.end();
});

Expand All @@ -665,11 +664,8 @@ test('stringify()', function (t) {
st.end();
});

t.test('stringifies an object with a null object as a child', { skip: !Object.create }, function (st) {
var obj = { a: Object.create(null) };

obj.a.b = 'c';
st.equal(qs.stringify(obj), 'a%5Bb%5D=c');
t.test('stringifies an object with a null object as a child', { skip: !hasProto }, function (st) {
st.equal(qs.stringify({ a: { __proto__: null, b: 'c' } }), 'a%5Bb%5D=c');
st.end();
});

Expand Down

0 comments on commit 3c8a6f5

Please sign in to comment.