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

Fix parsing & compacting very deep objects #224

Merged
merged 1 commit into from Sep 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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