Skip to content

Commit

Permalink
Merge pull request #224 from dougwilson/non-recursive-compact
Browse files Browse the repository at this point in the history
Fix parsing & compacting very deep objects
  • Loading branch information
ljharb committed Sep 9, 2017
2 parents 841b933 + 2ed6ea4 commit 461a04d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 53 deletions.
50 changes: 26 additions & 24 deletions lib/parse.js
Expand Up @@ -46,36 +46,38 @@ var parseValues = function parseQueryStringValues(str, options) {
return obj;
};

var parseObject = function parseObjectRecursive(chain, val, options) {
if (!chain.length) {
return val;
}
var parseObject = function (chain, val, options) {
var leaf = val;

for (var i = chain.length - 1; i >= 0; --i) {
var obj;
var root = chain[i];

var root = chain.shift();

var obj;
if (root === '[]') {
obj = [];
obj = obj.concat(parseObject(chain, val, options));
} else {
obj = options.plainObjects ? Object.create(null) : {};
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
var index = parseInt(cleanRoot, 10);
if (
!isNaN(index)
&& root !== cleanRoot
&& String(index) === cleanRoot
&& index >= 0
&& (options.parseArrays && index <= options.arrayLimit)
) {
if (root === '[]') {
obj = [];
obj[index] = parseObject(chain, val, options);
obj = obj.concat(leaf);
} else {
obj[cleanRoot] = parseObject(chain, val, options);
obj = options.plainObjects ? Object.create(null) : {};
var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
var index = parseInt(cleanRoot, 10);
if (
!isNaN(index)
&& root !== cleanRoot
&& String(index) === cleanRoot
&& index >= 0
&& (options.parseArrays && index <= options.arrayLimit)
) {
obj = [];
obj[index] = leaf;
} else {
obj[cleanRoot] = leaf;
}
}

leaf = obj;
}

return obj;
return leaf;
};

var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
Expand Down
68 changes: 39 additions & 29 deletions lib/utils.js
Expand Up @@ -11,6 +11,29 @@ var hexTable = (function () {
return array;
}());

var compactQueue = function (queue) {
var obj;

while (queue.length) {
var item = queue.pop();
obj = item.obj[item.prop];

if (Array.isArray(obj)) {
var compacted = [];

for (var j = 0; j < obj.length; ++j) {
if (typeof obj[j] !== 'undefined') {
compacted.push(obj[j]);
}
}

item.obj[item.prop] = compacted;
}
}

return obj;
};

exports.arrayToObject = function (source, options) {
var obj = options && options.plainObjects ? Object.create(null) : {};
for (var i = 0; i < source.length; ++i) {
Expand Down Expand Up @@ -144,39 +167,26 @@ exports.encode = function (str) {
return out;
};

exports.compact = function (obj, references) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

var refs = references || [];
var lookup = refs.indexOf(obj);
if (lookup !== -1) {
return refs[lookup];
}

refs.push(obj);

if (Array.isArray(obj)) {
var compacted = [];

for (var i = 0; i < obj.length; ++i) {
if (obj[i] && typeof obj[i] === 'object') {
compacted.push(exports.compact(obj[i], refs));
} else if (typeof obj[i] !== 'undefined') {
compacted.push(obj[i]);
exports.compact = function (value) {
var queue = [{ obj: { o: value }, prop: 'o' }];
var refs = [];

for (var i = 0; i < queue.length; ++i) {
var item = queue[i];
var obj = item.obj[item.prop];

var keys = Object.keys(obj);
for (var j = 0; j < keys.length; ++j) {
var key = keys[j];
var val = obj[key];
if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
queue.push({ obj: obj, prop: key });
refs.push(val);
}
}

return compacted;
}

var keys = Object.keys(obj);
keys.forEach(function (key) {
obj[key] = exports.compact(obj[key], refs);
});

return obj;
return compactQueue(queue);
};

exports.isRegExp = function (obj) {
Expand Down
27 changes: 27 additions & 0 deletions test/parse.js
Expand Up @@ -396,6 +396,33 @@ test('parse()', function (t) {
st.end();
});

t.test('does not crash when parsing deep objects', function (st) {
var parsed;
var str = 'foo';

for (var i = 0; i < 5000; i++) {
str += '[p]';
}

str += '=bar';

st.doesNotThrow(function () {
parsed = qs.parse(str, { depth: 5000 });
});

st.equal('foo' in parsed, true, 'parsed has "foo" property');

var depth = 0;
var ref = parsed.foo;
while ((ref = ref.p)) {
depth += 1;
}

st.equal(depth, 5000, 'parsed is 5000 properties deep');

st.end();
});

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

0 comments on commit 461a04d

Please sign in to comment.