From 7895b947937d7ecb39ea3f1616de9dcea77d98de Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Sun, 7 May 2023 11:22:58 -0400 Subject: [PATCH] [Fix] `parse`: Fix parsing when the global Object prototype is frozen --- lib/parse.js | 3 ++- package.json | 3 +++ test/parse.js | 36 ++++++++++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/parse.js b/lib/parse.js index a4ac4fa0..ae194fb5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -49,7 +49,8 @@ var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓') var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓') var parseValues = function parseQueryStringValues(str, options) { - var obj = {}; + var obj = { __proto__: null }; + var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str; var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit; var parts = cleanStr.split(options.delimiter, limit); diff --git a/package.json b/package.json index 272575cb..26ee48bb 100644 --- a/package.json +++ b/package.json @@ -40,10 +40,13 @@ "eslint": "=8.8.0", "evalmd": "^0.0.19", "for-each": "^0.3.3", + "has-override-mistake": "^1.0.0", + "has-property-descriptors": "^1.0.0", "has-symbols": "^1.0.3", "iconv-lite": "^0.5.1", "in-publish": "^2.0.1", "mkdirp": "^0.5.5", + "mock-property": "^1.0.0", "npmignore": "^0.3.0", "nyc": "^10.3.2", "object-inspect": "^1.12.3", diff --git a/test/parse.js b/test/parse.js index 7d7b4dd8..ca9ca730 100644 --- a/test/parse.js +++ b/test/parse.js @@ -1,11 +1,15 @@ 'use strict'; var test = require('tape'); -var qs = require('../'); -var utils = require('../lib/utils'); +var hasPropertyDescriptors = require('has-property-descriptors')(); var iconv = require('iconv-lite'); +var mockProperty = require('mock-property'); +var hasOverrideMistake = require('has-override-mistake')(); var SaferBuffer = require('safer-buffer').Buffer; +var qs = require('../'); +var utils = require('../lib/utils'); + test('parse()', function (t) { t.test('parses a simple string', function (st) { st.deepEqual(qs.parse('0=foo'), { 0: 'foo' }); @@ -601,6 +605,34 @@ test('parse()', function (t) { st.end(); }); + t.test('does not crash when the global Object prototype is frozen', { skip: !hasPropertyDescriptors || !hasOverrideMistake }, function (st) { + // We can't actually freeze the global Object prototype as that will interfere with other tests, and once an object is frozen, it + // can't be unfrozen. Instead, we add a new non-writable property to simulate this. + st.teardown(mockProperty(Object.prototype, 'frozenProp', { value: 'foo', nonWritable: true, nonEnumerable: true })); + + st['throws']( + function () { + var obj = {}; + obj.frozenProp = 'bar'; + }, + // node < 6 has a different error message + /^TypeError: Cannot assign to read only property 'frozenProp' of (?:object '#'|#)/, + 'regular assignment of an inherited non-writable property throws' + ); + + var parsed; + st.doesNotThrow( + function () { + parsed = qs.parse('frozenProp', { allowPrototypes: false }); + }, + 'parsing a nonwritable Object.prototype property does not throw' + ); + + st.deepEqual(parsed, {}, 'bare "frozenProp" results in {}'); + + st.end(); + }); + t.test('params starting with a closing bracket', function (st) { st.deepEqual(qs.parse(']=toString'), { ']': 'toString' }); st.deepEqual(qs.parse(']]=toString'), { ']]': 'toString' });