Skip to content

Commit

Permalink
v6.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Sep 17, 2018
1 parent 8a8028e commit 34af57e
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 38 deletions.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## **6.6.0**
- [New] Add support for iso-8859-1, utf8 "sentinel" and numeric entities (#268)
- [New] move two-value combine to a `utils` function (#189)
- [Fix] `stringify`: fix a crash with `strictNullHandling` and a custom `filter`/`serializeDate` (#279)
- [Fix] when `parseArrays` is false, properly handle keys ending in `[]` (#260)
- [Fix] `stringify`: do not crash in an obscure combo of `interpretNumericEntities`, a bad custom `decoder`, & `iso-8859-1`
- [Fix] `utils`: `merge`: fix crash when `source` is a truthy primitive & no options are provided
- [refactor] `stringify`: Avoid arr = arr.concat(...), push to the existing instance (#269)
- [Refactor] `parse`: only need to reassign the var once
- [Refactor] `parse`/`stringify`: clean up `charset` options checking; fix defaults
- [Refactor] add missing defaults
- [Refactor] `parse`: one less `concat` call
- [Refactor] `utils`: `compactQueue`: make it explicitly side-effecting
- [Dev Deps] update `browserify, `eslint`, `@ljharb/eslint-config`, `iconv-lite`, `safe-publish-latest`, `tape`
- [Tests] up to `node` `v10.10`, `v9.11`, `v8.12`, `v6.14`, `v4.9`; pin included builds to LTS

## **6.5.2**
- [Fix] use `safer-buffer` instead of `Buffer` constructor
- [Refactor] utils: `module.exports` one thing, instead of mutating `exports` (#230)
Expand Down
173 changes: 136 additions & 37 deletions dist/qs.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,36 +42,81 @@ var defaults = {
allowDots: false,
allowPrototypes: false,
arrayLimit: 20,
charset: 'utf-8',
charsetSentinel: false,
decoder: utils.decode,
delimiter: '&',
depth: 5,
ignoreQueryPrefix: false,
interpretNumericEntities: false,
parameterLimit: 1000,
parseArrays: true,
plainObjects: false,
strictNullHandling: false
};

var interpretNumericEntities = function (str) {
return str.replace(/&#(\d+);/g, function ($0, numberStr) {
return String.fromCharCode(parseInt(numberStr, 10));
});
};

// This is what browsers will submit when the ✓ character occurs in an
// application/x-www-form-urlencoded body and the encoding of the page containing
// the form is iso-8859-1, or when the submitted form has an accept-charset
// attribute of iso-8859-1. Presumably also with other charsets that do not contain
// the ✓ character, such as us-ascii.
var isoSentinel = 'utf8=%26%2310003%3B'; // encodeURIComponent('✓')

// These are the percent-encoded utf-8 octets representing a checkmark, indicating that the request actually is utf-8 encoded.
var charsetSentinel = 'utf8=%E2%9C%93'; // encodeURIComponent('✓')

var parseValues = function parseQueryStringValues(str, options) {
var obj = {};
var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
var parts = cleanStr.split(options.delimiter, limit);
var skipIndex = -1; // Keep track of where the utf8 sentinel was found
var i;

var charset = options.charset;
if (options.charsetSentinel) {
for (i = 0; i < parts.length; ++i) {
if (parts[i].indexOf('utf8=') === 0) {
if (parts[i] === charsetSentinel) {
charset = 'utf-8';
} else if (parts[i] === isoSentinel) {
charset = 'iso-8859-1';
}
skipIndex = i;
i = parts.length; // The eslint settings do not allow break;
}
}
}

for (var i = 0; i < parts.length; ++i) {
for (i = 0; i < parts.length; ++i) {
if (i === skipIndex) {
continue;
}
var part = parts[i];

var bracketEqualsPos = part.indexOf(']=');
var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;

var key, val;
if (pos === -1) {
key = options.decoder(part, defaults.decoder);
key = options.decoder(part, defaults.decoder, charset);
val = options.strictNullHandling ? null : '';
} else {
key = options.decoder(part.slice(0, pos), defaults.decoder);
val = options.decoder(part.slice(pos + 1), defaults.decoder);
key = options.decoder(part.slice(0, pos), defaults.decoder, charset);
val = options.decoder(part.slice(pos + 1), defaults.decoder, charset);
}

if (val && options.interpretNumericEntities && charset === 'iso-8859-1') {
val = interpretNumericEntities(val);
}
if (has.call(obj, key)) {
obj[key] = [].concat(obj[key]).concat(val);
obj[key] = utils.combine(obj[key], val);
} else {
obj[key] = val;
}
Expand All @@ -87,14 +132,15 @@ var parseObject = function (chain, val, options) {
var obj;
var root = chain[i];

if (root === '[]') {
obj = [];
obj = obj.concat(leaf);
if (root === '[]' && options.parseArrays) {
obj = [].concat(leaf);
} 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 (
if (!options.parseArrays && cleanRoot === '') {
obj = { 0: leaf };
} else if (
!isNaN(index)
&& root !== cleanRoot
&& String(index) === cleanRoot
Expand Down Expand Up @@ -136,8 +182,7 @@ var parseKeys = function parseQueryStringKeys(givenKey, val, options) {

var keys = [];
if (parent) {
// If we aren't using plain objects, optionally prefix keys
// that would overwrite object prototype properties
// If we aren't using plain objects, optionally prefix keys that would overwrite object prototype properties
if (!options.plainObjects && has.call(Object.prototype, parent)) {
if (!options.allowPrototypes) {
return;
Expand Down Expand Up @@ -182,12 +227,19 @@ module.exports = function (str, opts) {
options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
options.parseArrays = options.parseArrays !== false;
options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
options.allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots;
options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;

if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') {
throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined');
}
if (typeof options.charset === 'undefined') {
options.charset = defaults.charset;
}

if (str === '' || str === null || typeof str === 'undefined') {
return options.plainObjects ? Object.create(null) : {};
}
Expand Down Expand Up @@ -225,13 +277,25 @@ var arrayPrefixGenerators = {
}
};

var isArray = Array.isArray;
var push = Array.prototype.push;
var pushToArray = function (arr, valueOrArray) {
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
};

var toISO = Date.prototype.toISOString;

var defaults = {
addQueryPrefix: false,
allowDots: false,
charset: 'utf-8',
charsetSentinel: false,
delimiter: '&',
encode: true,
encoder: utils.encode,
encodeValuesOnly: false,
// deprecated
indices: false,
serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
return toISO.call(date);
},
Expand All @@ -251,25 +315,28 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
allowDots,
serializeDate,
formatter,
encodeValuesOnly
encodeValuesOnly,
charset
) {
var obj = object;
if (typeof filter === 'function') {
obj = filter(prefix, obj);
} else if (obj instanceof Date) {
obj = serializeDate(obj);
} else if (obj === null) {
}

if (obj === null) {
if (strictNullHandling) {
return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix;
return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder, charset) : prefix;
}

obj = '';
}

if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
if (encoder) {
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder);
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))];
var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder, charset);
return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder, charset))];
}
return [formatter(prefix) + '=' + formatter(String(obj))];
}
Expand All @@ -296,7 +363,7 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
}

if (Array.isArray(obj)) {
values = values.concat(stringify(
pushToArray(values, stringify(
obj[key],
generateArrayPrefix(prefix, key),
generateArrayPrefix,
Expand All @@ -308,10 +375,11 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
allowDots,
serializeDate,
formatter,
encodeValuesOnly
encodeValuesOnly,
charset
));
} else {
values = values.concat(stringify(
pushToArray(values, stringify(
obj[key],
prefix + (allowDots ? '.' + key : '[' + key + ']'),
generateArrayPrefix,
Expand All @@ -323,7 +391,8 @@ var stringify = function stringify( // eslint-disable-line func-name-matching
allowDots,
serializeDate,
formatter,
encodeValuesOnly
encodeValuesOnly,
charset
));
}
}
Expand All @@ -345,9 +414,14 @@ module.exports = function (object, opts) {
var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder;
var sort = typeof options.sort === 'function' ? options.sort : null;
var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
var allowDots = typeof options.allowDots === 'undefined' ? defaults.allowDots : !!options.allowDots;
var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly;
var charset = options.charset || defaults.charset;
if (typeof options.charset !== 'undefined' && options.charset !== 'utf-8' && options.charset !== 'iso-8859-1') {
throw new Error('The charset option must be either utf-8, iso-8859-1, or undefined');
}

if (typeof options.format === 'undefined') {
options.format = formats['default'];
} else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) {
Expand Down Expand Up @@ -396,8 +470,7 @@ module.exports = function (object, opts) {
if (skipNulls && obj[key] === null) {
continue;
}

keys = keys.concat(stringify(
pushToArray(keys, stringify(
obj[key],
key,
generateArrayPrefix,
Expand All @@ -409,13 +482,24 @@ module.exports = function (object, opts) {
allowDots,
serializeDate,
formatter,
encodeValuesOnly
encodeValuesOnly,
charset
));
}

var joined = keys.join(delimiter);
var prefix = options.addQueryPrefix === true ? '?' : '';

if (options.charsetSentinel) {
if (charset === 'iso-8859-1') {
// encodeURIComponent('&#10003;'), the "numeric entity" representation of a checkmark
prefix += 'utf8=%26%2310003%3B&';
} else {
// encodeURIComponent('✓')
prefix += 'utf8=%E2%9C%93&';
}
}

return joined.length > 0 ? prefix + joined : '';
};

Expand All @@ -434,11 +518,9 @@ var hexTable = (function () {
}());

var compactQueue = function compactQueue(queue) {
var obj;

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

if (Array.isArray(obj)) {
var compacted = [];
Expand All @@ -452,8 +534,6 @@ var compactQueue = function compactQueue(queue) {
item.obj[item.prop] = compacted;
}
}

return obj;
};

var arrayToObject = function arrayToObject(source, options) {
Expand All @@ -476,7 +556,7 @@ var merge = function merge(target, source, options) {
if (Array.isArray(target)) {
target.push(source);
} else if (typeof target === 'object') {
if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) {
if ((options && (options.plainObjects || options.allowPrototypes)) || !has.call(Object.prototype, source)) {
target[source] = true;
}
} else {
Expand Down Expand Up @@ -529,15 +609,21 @@ var assign = function assignSingleSource(target, source) {
}, target);
};

var decode = function (str) {
var decode = function (str, decoder, charset) {
var strWithoutPlus = str.replace(/\+/g, ' ');
if (charset === 'iso-8859-1') {
// unescape never throws, no try...catch needed:
return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
}
// utf-8
try {
return decodeURIComponent(str.replace(/\+/g, ' '));
return decodeURIComponent(strWithoutPlus);
} catch (e) {
return str;
return strWithoutPlus;
}
};

var encode = function encode(str) {
var encode = function encode(str, defaultEncoder, charset) {
// This code was originally written by Brian White (mscdex) for the io.js core querystring library.
// It has been adapted here for stricter adherence to RFC 3986
if (str.length === 0) {
Expand All @@ -546,6 +632,12 @@ var encode = function encode(str) {

var string = typeof str === 'string' ? str : String(str);

if (charset === 'iso-8859-1') {
return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
});
}

var out = '';
for (var i = 0; i < string.length; ++i) {
var c = string.charCodeAt(i);
Expand Down Expand Up @@ -608,7 +700,9 @@ var compact = function compact(value) {
}
}

return compactQueue(queue);
compactQueue(queue);

return value;
};

var isRegExp = function isRegExp(obj) {
Expand All @@ -623,9 +717,14 @@ var isBuffer = function isBuffer(obj) {
return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
};

var combine = function combine(a, b) {
return [].concat(a, b);
};

module.exports = {
arrayToObject: arrayToObject,
assign: assign,
combine: combine,
compact: compact,
decode: decode,
encode: encode,
Expand Down
Loading

0 comments on commit 34af57e

Please sign in to comment.