Comparing version 6.5.2 to 6.6.0
@@ -0,1 +1,17 @@ | ||
## **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** | ||
@@ -2,0 +18,0 @@ - [Fix] use `safer-buffer` instead of `Buffer` constructor |
173
dist/qs.js
@@ -45,6 +45,11 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Qs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
arrayLimit: 20, | ||
charset: 'utf-8', | ||
charsetSentinel: false, | ||
decoder: utils.decode, | ||
delimiter: '&', | ||
depth: 5, | ||
ignoreQueryPrefix: false, | ||
interpretNumericEntities: false, | ||
parameterLimit: 1000, | ||
parseArrays: true, | ||
plainObjects: false, | ||
@@ -54,2 +59,18 @@ 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) { | ||
@@ -60,4 +81,24 @@ var obj = {}; | ||
var parts = cleanStr.split(options.delimiter, limit); | ||
var skipIndex = -1; // Keep track of where the utf8 sentinel was found | ||
var i; | ||
for (var i = 0; i < parts.length; ++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 (i = 0; i < parts.length; ++i) { | ||
if (i === skipIndex) { | ||
continue; | ||
} | ||
var part = parts[i]; | ||
@@ -70,10 +111,14 @@ | ||
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 { | ||
@@ -94,5 +139,4 @@ obj[key] = val; | ||
if (root === '[]') { | ||
obj = []; | ||
obj = obj.concat(leaf); | ||
if (root === '[]' && options.parseArrays) { | ||
obj = [].concat(leaf); | ||
} else { | ||
@@ -102,3 +146,5 @@ obj = options.plainObjects ? Object.create(null) : {}; | ||
var index = parseInt(cleanRoot, 10); | ||
if ( | ||
if (!options.parseArrays && cleanRoot === '') { | ||
obj = { 0: leaf }; | ||
} else if ( | ||
!isNaN(index) | ||
@@ -145,4 +191,3 @@ && root !== cleanRoot | ||
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)) { | ||
@@ -192,3 +237,3 @@ if (!options.allowPrototypes) { | ||
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; | ||
@@ -199,2 +244,9 @@ options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; | ||
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') { | ||
@@ -237,5 +289,15 @@ return options.plainObjects ? Object.create(null) : {}; | ||
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: '&', | ||
@@ -245,2 +307,4 @@ encode: true, | ||
encodeValuesOnly: false, | ||
// deprecated | ||
indices: false, | ||
serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching | ||
@@ -265,3 +329,4 @@ return toISO.call(date); | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
) { | ||
@@ -273,5 +338,7 @@ var obj = object; | ||
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; | ||
} | ||
@@ -284,4 +351,4 @@ | ||
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))]; | ||
} | ||
@@ -313,3 +380,3 @@ return [formatter(prefix) + '=' + formatter(String(obj))]; | ||
if (Array.isArray(obj)) { | ||
values = values.concat(stringify( | ||
pushToArray(values, stringify( | ||
obj[key], | ||
@@ -326,6 +393,7 @@ generateArrayPrefix(prefix, key), | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
} else { | ||
values = values.concat(stringify( | ||
pushToArray(values, stringify( | ||
obj[key], | ||
@@ -342,3 +410,4 @@ prefix + (allowDots ? '.' + key : '[' + key + ']'), | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
@@ -365,5 +434,10 @@ } | ||
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') { | ||
@@ -417,4 +491,3 @@ options.format = formats['default']; | ||
} | ||
keys = keys.concat(stringify( | ||
pushToArray(keys, stringify( | ||
obj[key], | ||
@@ -431,3 +504,4 @@ key, | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
@@ -439,2 +513,12 @@ } | ||
if (options.charsetSentinel) { | ||
if (charset === 'iso-8859-1') { | ||
// encodeURIComponent('✓'), 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 : ''; | ||
@@ -458,7 +542,5 @@ }; | ||
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]; | ||
@@ -477,4 +559,2 @@ if (Array.isArray(obj)) { | ||
} | ||
return obj; | ||
}; | ||
@@ -502,3 +582,3 @@ | ||
} 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; | ||
@@ -556,11 +636,17 @@ } | ||
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. | ||
@@ -574,2 +660,8 @@ // It has been adapted here for stricter adherence to RFC 3986 | ||
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 = ''; | ||
@@ -637,3 +729,5 @@ for (var i = 0; i < string.length; ++i) { | ||
return compactQueue(queue); | ||
compactQueue(queue); | ||
return value; | ||
}; | ||
@@ -653,5 +747,10 @@ | ||
var combine = function combine(a, b) { | ||
return [].concat(a, b); | ||
}; | ||
module.exports = { | ||
arrayToObject: arrayToObject, | ||
assign: assign, | ||
combine: combine, | ||
compact: compact, | ||
@@ -658,0 +757,0 @@ decode: decode, |
@@ -11,6 +11,11 @@ 'use strict'; | ||
arrayLimit: 20, | ||
charset: 'utf-8', | ||
charsetSentinel: false, | ||
decoder: utils.decode, | ||
delimiter: '&', | ||
depth: 5, | ||
ignoreQueryPrefix: false, | ||
interpretNumericEntities: false, | ||
parameterLimit: 1000, | ||
parseArrays: true, | ||
plainObjects: false, | ||
@@ -20,2 +25,18 @@ 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) { | ||
@@ -26,4 +47,24 @@ var obj = {}; | ||
var parts = cleanStr.split(options.delimiter, limit); | ||
var skipIndex = -1; // Keep track of where the utf8 sentinel was found | ||
var i; | ||
for (var i = 0; i < parts.length; ++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 (i = 0; i < parts.length; ++i) { | ||
if (i === skipIndex) { | ||
continue; | ||
} | ||
var part = parts[i]; | ||
@@ -36,10 +77,14 @@ | ||
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 { | ||
@@ -60,5 +105,4 @@ obj[key] = val; | ||
if (root === '[]') { | ||
obj = []; | ||
obj = obj.concat(leaf); | ||
if (root === '[]' && options.parseArrays) { | ||
obj = [].concat(leaf); | ||
} else { | ||
@@ -68,3 +112,5 @@ obj = options.plainObjects ? Object.create(null) : {}; | ||
var index = parseInt(cleanRoot, 10); | ||
if ( | ||
if (!options.parseArrays && cleanRoot === '') { | ||
obj = { 0: leaf }; | ||
} else if ( | ||
!isNaN(index) | ||
@@ -111,4 +157,3 @@ && root !== cleanRoot | ||
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)) { | ||
@@ -158,3 +203,3 @@ if (!options.allowPrototypes) { | ||
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; | ||
@@ -165,2 +210,9 @@ options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes; | ||
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') { | ||
@@ -167,0 +219,0 @@ return options.plainObjects ? Object.create(null) : {}; |
@@ -18,5 +18,15 @@ 'use strict'; | ||
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: '&', | ||
@@ -26,2 +36,4 @@ encode: true, | ||
encodeValuesOnly: false, | ||
// deprecated | ||
indices: false, | ||
serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching | ||
@@ -46,3 +58,4 @@ return toISO.call(date); | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
) { | ||
@@ -54,5 +67,7 @@ var obj = object; | ||
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; | ||
} | ||
@@ -65,4 +80,4 @@ | ||
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))]; | ||
} | ||
@@ -94,3 +109,3 @@ return [formatter(prefix) + '=' + formatter(String(obj))]; | ||
if (Array.isArray(obj)) { | ||
values = values.concat(stringify( | ||
pushToArray(values, stringify( | ||
obj[key], | ||
@@ -107,6 +122,7 @@ generateArrayPrefix(prefix, key), | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
} else { | ||
values = values.concat(stringify( | ||
pushToArray(values, stringify( | ||
obj[key], | ||
@@ -123,3 +139,4 @@ prefix + (allowDots ? '.' + key : '[' + key + ']'), | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
@@ -146,5 +163,10 @@ } | ||
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') { | ||
@@ -198,4 +220,3 @@ options.format = formats['default']; | ||
} | ||
keys = keys.concat(stringify( | ||
pushToArray(keys, stringify( | ||
obj[key], | ||
@@ -212,3 +233,4 @@ key, | ||
formatter, | ||
encodeValuesOnly | ||
encodeValuesOnly, | ||
charset | ||
)); | ||
@@ -220,3 +242,13 @@ } | ||
if (options.charsetSentinel) { | ||
if (charset === 'iso-8859-1') { | ||
// encodeURIComponent('✓'), 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 : ''; | ||
}; |
@@ -15,7 +15,5 @@ 'use strict'; | ||
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]; | ||
@@ -34,4 +32,2 @@ if (Array.isArray(obj)) { | ||
} | ||
return obj; | ||
}; | ||
@@ -59,3 +55,3 @@ | ||
} 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; | ||
@@ -113,11 +109,17 @@ } | ||
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. | ||
@@ -131,2 +133,8 @@ // It has been adapted here for stricter adherence to RFC 3986 | ||
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 = ''; | ||
@@ -194,3 +202,5 @@ for (var i = 0; i < string.length; ++i) { | ||
return compactQueue(queue); | ||
compactQueue(queue); | ||
return value; | ||
}; | ||
@@ -210,5 +220,10 @@ | ||
var combine = function combine(a, b) { | ||
return [].concat(a, b); | ||
}; | ||
module.exports = { | ||
arrayToObject: arrayToObject, | ||
assign: assign, | ||
combine: combine, | ||
compact: compact, | ||
@@ -215,0 +230,0 @@ decode: decode, |
@@ -5,3 +5,3 @@ { | ||
"homepage": "https://github.com/ljharb/qs", | ||
"version": "6.5.2", | ||
"version": "6.6.0", | ||
"repository": { | ||
@@ -28,14 +28,14 @@ "type": "git", | ||
"devDependencies": { | ||
"@ljharb/eslint-config": "^12.2.1", | ||
"browserify": "^16.2.0", | ||
"@ljharb/eslint-config": "^13.0.0", | ||
"browserify": "^16.2.3", | ||
"covert": "^1.1.0", | ||
"editorconfig-tools": "^0.1.1", | ||
"eslint": "^4.19.1", | ||
"eslint": "^5.9.0", | ||
"evalmd": "^0.0.17", | ||
"iconv-lite": "^0.4.21", | ||
"iconv-lite": "^0.4.24", | ||
"mkdirp": "^0.5.1", | ||
"qs-iconv": "^1.0.4", | ||
"safe-publish-latest": "^1.1.1", | ||
"safe-publish-latest": "^1.1.2", | ||
"safer-buffer": "^2.1.2", | ||
"tape": "^4.9.0" | ||
"tape": "^4.9.1" | ||
}, | ||
@@ -48,3 +48,3 @@ "scripts": { | ||
"readme": "evalmd README.md", | ||
"prelint": "editorconfig-tools check * lib/* test/*", | ||
"postlint": "editorconfig-tools check * lib/* test/*", | ||
"lint": "eslint lib/*.js test/*.js", | ||
@@ -51,0 +51,0 @@ "coverage": "covert test", |
@@ -149,2 +149,58 @@ # qs <sup>[![Version Badge][2]][1]</sup> | ||
If you have to deal with legacy browsers or services, there's | ||
also support for decoding percent-encoded octets as iso-8859-1: | ||
```javascript | ||
var oldCharset = qs.parse('a=%A7', { charset: 'iso-8859-1' }); | ||
assert.deepEqual(oldCharset, { a: '§' }); | ||
``` | ||
Some services add an initial `utf8=✓` value to forms so that old | ||
Internet Explorer versions are more likely to submit the form as | ||
utf-8. Additionally, the server can check the value against wrong | ||
encodings of the checkmark character and detect that a query string | ||
or `application/x-www-form-urlencoded` body was *not* sent as | ||
utf-8, eg. if the form had an `accept-charset` parameter or the | ||
containing page had a different character set. | ||
**qs** supports this mechanism via the `charsetSentinel` option. | ||
If specified, the `utf8` parameter will be omitted from the | ||
returned object. It will be used to switch to `iso-8859-1`/`utf-8` | ||
mode depending on how the checkmark is encoded. | ||
**Important**: When you specify both the `charset` option and the | ||
`charsetSentinel` option, the `charset` will be overridden when | ||
the request contains a `utf8` parameter from which the actual | ||
charset can be deduced. In that sense the `charset` will behave | ||
as the default charset rather than the authoritative charset. | ||
```javascript | ||
var detectedAsUtf8 = qs.parse('utf8=%E2%9C%93&a=%C3%B8', { | ||
charset: 'iso-8859-1', | ||
charsetSentinel: true | ||
}); | ||
assert.deepEqual(detectedAsUtf8, { a: 'ø' }); | ||
// Browsers encode the checkmark as ✓ when submitting as iso-8859-1: | ||
var detectedAsIso8859_1 = qs.parse('utf8=%26%2310003%3B&a=%F8', { | ||
charset: 'utf-8', | ||
charsetSentinel: true | ||
}); | ||
assert.deepEqual(detectedAsIso8859_1, { a: 'ø' }); | ||
``` | ||
If you want to decode the `&#...;` syntax to the actual character, | ||
you can specify the `interpretNumericEntities` option as well: | ||
```javascript | ||
var detectedAsIso8859_1 = qs.parse('a=%26%239786%3B', { | ||
charset: 'iso-8859-1', | ||
interpretNumericEntities: true | ||
}); | ||
assert.deepEqual(detectedAsIso8859_1, { a: '☺' }); | ||
``` | ||
It also works when the charset has been detected in `charsetSentinel` | ||
mode. | ||
### Parsing Arrays | ||
@@ -430,6 +486,36 @@ | ||
If you're communicating with legacy systems, you can switch to `iso-8859-1` | ||
using the `charset` option: | ||
```javascript | ||
var iso = qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }); | ||
assert.equal(iso, '%E6=%E6'); | ||
``` | ||
Characters that don't exist in `iso-8859-1` will be converted to numeric | ||
entities, similar to what browsers do: | ||
```javascript | ||
var numeric = qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }); | ||
assert.equal(numeric, 'a=%26%239786%3B'); | ||
``` | ||
You can use the `charsetSentinel` option to announce the character by | ||
including an `utf8=✓` parameter with the proper encoding if the checkmark, | ||
similar to what Ruby on Rails and others do when submitting forms. | ||
```javascript | ||
var sentinel = qs.stringify({ a: '☺' }, { charsetSentinel: true }); | ||
assert.equal(sentinel, 'utf8=%E2%9C%93&a=%E2%98%BA'); | ||
var isoSentinel = qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }); | ||
assert.equal(isoSentinel, 'utf8=%26%2310003%3B&a=%E6'); | ||
``` | ||
### Dealing with special character sets | ||
By default the encoding and decoding of characters is done in `utf-8`. If you | ||
wish to encode querystrings to a different character set (i.e. | ||
By default the encoding and decoding of characters is done in `utf-8`, | ||
and `iso-8859-1` support is also built in via the `charset` parameter. | ||
If you wish to encode querystrings to a different character set (i.e. | ||
[Shift JIS](https://en.wikipedia.org/wiki/Shift_JIS)) you can use the | ||
@@ -436,0 +522,0 @@ [`qs-iconv`](https://github.com/martinheidegger/qs-iconv) library: |
@@ -260,3 +260,3 @@ 'use strict'; | ||
t.test('should not throw when a native prototype has an enumerable property', { parallel: false }, function (st) { | ||
t.test('should not throw when a native prototype has an enumerable property', function (st) { | ||
Object.prototype.crash = ''; | ||
@@ -306,3 +306,10 @@ Array.prototype.crash = ''; | ||
t.test('allows disabling array parsing', function (st) { | ||
st.deepEqual(qs.parse('a[0]=b&a[1]=c', { parseArrays: false }), { a: { 0: 'b', 1: 'c' } }); | ||
var indices = qs.parse('a[0]=b&a[1]=c', { parseArrays: false }); | ||
st.deepEqual(indices, { a: { 0: 'b', 1: 'c' } }); | ||
st.equal(Array.isArray(indices.a), false, 'parseArrays:false, indices case is not an array'); | ||
var emptyBrackets = qs.parse('a[]=b', { parseArrays: false }); | ||
st.deepEqual(emptyBrackets, { a: { 0: 'b' } }); | ||
st.equal(Array.isArray(emptyBrackets.a), false, 'parseArrays:false, empty brackets case is not an array'); | ||
st.end(); | ||
@@ -575,3 +582,81 @@ }); | ||
t.test('throws if an invalid charset is specified', function (st) { | ||
st['throws'](function () { | ||
qs.parse('a=b', { charset: 'foobar' }); | ||
}, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined')); | ||
st.end(); | ||
}); | ||
t.test('parses an iso-8859-1 string if asked to', function (st) { | ||
st.deepEqual(qs.parse('%A2=%BD', { charset: 'iso-8859-1' }), { '¢': '½' }); | ||
st.end(); | ||
}); | ||
var urlEncodedCheckmarkInUtf8 = '%E2%9C%93'; | ||
var urlEncodedOSlashInUtf8 = '%C3%B8'; | ||
var urlEncodedNumCheckmark = '%26%2310003%3B'; | ||
var urlEncodedNumSmiley = '%26%239786%3B'; | ||
t.test('prefers an utf-8 charset specified by the utf8 sentinel to a default charset of iso-8859-1', function (st) { | ||
st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'iso-8859-1' }), { ø: 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('prefers an iso-8859-1 charset specified by the utf8 sentinel to a default charset of utf-8', function (st) { | ||
st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { 'ø': 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('does not require the utf8 sentinel to be defined before the parameters whose decoding it affects', function (st) { | ||
st.deepEqual(qs.parse('a=' + urlEncodedOSlashInUtf8 + '&utf8=' + urlEncodedNumCheckmark, { charsetSentinel: true, charset: 'utf-8' }), { a: 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('should ignore an utf8 sentinel with an unknown value', function (st) { | ||
st.deepEqual(qs.parse('utf8=foo&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true, charset: 'utf-8' }), { ø: 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('uses the utf8 sentinel to switch to utf-8 when no default charset is given', function (st) { | ||
st.deepEqual(qs.parse('utf8=' + urlEncodedCheckmarkInUtf8 + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { ø: 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('uses the utf8 sentinel to switch to iso-8859-1 when no default charset is given', function (st) { | ||
st.deepEqual(qs.parse('utf8=' + urlEncodedNumCheckmark + '&' + urlEncodedOSlashInUtf8 + '=' + urlEncodedOSlashInUtf8, { charsetSentinel: true }), { 'ø': 'ø' }); | ||
st.end(); | ||
}); | ||
t.test('interprets numeric entities in iso-8859-1 when `interpretNumericEntities`', function (st) { | ||
st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1', interpretNumericEntities: true }), { foo: '☺' }); | ||
st.end(); | ||
}); | ||
t.test('handles a custom decoder returning `null`, in the `iso-8859-1` charset, when `interpretNumericEntities`', function (st) { | ||
st.deepEqual(qs.parse('foo=&bar=' + urlEncodedNumSmiley, { | ||
charset: 'iso-8859-1', | ||
decoder: function (str, defaultDecoder, charset) { | ||
return str ? defaultDecoder(str, defaultDecoder, charset) : null; | ||
}, | ||
interpretNumericEntities: true | ||
}), { foo: null, bar: '☺' }); | ||
st.end(); | ||
}); | ||
t.test('does not interpret numeric entities in iso-8859-1 when `interpretNumericEntities` is absent', function (st) { | ||
st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'iso-8859-1' }), { foo: '☺' }); | ||
st.end(); | ||
}); | ||
t.test('does not interpret numeric entities when the charset is utf-8, even when `interpretNumericEntities`', function (st) { | ||
st.deepEqual(qs.parse('foo=' + urlEncodedNumSmiley, { charset: 'utf-8', interpretNumericEntities: true }), { foo: '☺' }); | ||
st.end(); | ||
}); | ||
t.test('does not interpret %uXXXX syntax in iso-8859-1 mode', function (st) { | ||
st.deepEqual(qs.parse('%u263A=%u263A', { charset: 'iso-8859-1' }), { '%u263A': '%u263A' }); | ||
st.end(); | ||
}); | ||
t.end(); | ||
}); |
@@ -589,2 +589,34 @@ 'use strict'; | ||
t.test('throws if an invalid charset is specified', function (st) { | ||
st['throws'](function () { | ||
qs.stringify({ a: 'b' }, { charset: 'foobar' }); | ||
}, new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined')); | ||
st.end(); | ||
}); | ||
t.test('respects a charset of iso-8859-1', function (st) { | ||
st.equal(qs.stringify({ æ: 'æ' }, { charset: 'iso-8859-1' }), '%E6=%E6'); | ||
st.end(); | ||
}); | ||
t.test('encodes unrepresentable chars as numeric entities in iso-8859-1 mode', function (st) { | ||
st.equal(qs.stringify({ a: '☺' }, { charset: 'iso-8859-1' }), 'a=%26%239786%3B'); | ||
st.end(); | ||
}); | ||
t.test('respects an explicit charset of utf-8 (the default)', function (st) { | ||
st.equal(qs.stringify({ a: 'æ' }, { charset: 'utf-8' }), 'a=%C3%A6'); | ||
st.end(); | ||
}); | ||
t.test('adds the right sentinel when instructed to and the charset is utf-8', function (st) { | ||
st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), 'utf8=%E2%9C%93&a=%C3%A6'); | ||
st.end(); | ||
}); | ||
t.test('adds the right sentinel when instructed to and the charset is iso-8859-1', function (st) { | ||
st.equal(qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), 'utf8=%26%2310003%3B&a=%E6'); | ||
st.end(); | ||
}); | ||
t.test('does not mutate the options argument', function (st) { | ||
@@ -597,3 +629,23 @@ var options = {}; | ||
t.test('strictNullHandling works with custom filter', function (st) { | ||
var filter = function (prefix, value) { | ||
return value; | ||
}; | ||
var options = { strictNullHandling: true, filter: filter }; | ||
st.equal(qs.stringify({ key: null }, options), 'key'); | ||
st.end(); | ||
}); | ||
t.test('strictNullHandling works with null serializeDate', function (st) { | ||
var serializeDate = function () { | ||
return null; | ||
}; | ||
var options = { strictNullHandling: true, serializeDate: serializeDate }; | ||
var date = new Date(); | ||
st.equal(qs.stringify({ key: date }, options), 'key'); | ||
st.end(); | ||
}); | ||
t.end(); | ||
}); |
@@ -21,2 +21,5 @@ 'use strict'; | ||
var noOptionsNonObjectSource = utils.merge({ foo: 'baz' }, 'bar'); | ||
t.deepEqual(noOptionsNonObjectSource, { foo: 'baz', bar: true }); | ||
t.end(); | ||
@@ -36,1 +39,53 @@ }); | ||
}); | ||
test('combine()', function (t) { | ||
t.test('both arrays', function (st) { | ||
var a = [1]; | ||
var b = [2]; | ||
var combined = utils.combine(a, b); | ||
st.deepEqual(a, [1], 'a is not mutated'); | ||
st.deepEqual(b, [2], 'b is not mutated'); | ||
st.notEqual(a, combined, 'a !== combined'); | ||
st.notEqual(b, combined, 'b !== combined'); | ||
st.deepEqual(combined, [1, 2], 'combined is a + b'); | ||
st.end(); | ||
}); | ||
t.test('one array, one non-array', function (st) { | ||
var aN = 1; | ||
var a = [aN]; | ||
var bN = 2; | ||
var b = [bN]; | ||
var combinedAnB = utils.combine(aN, b); | ||
st.deepEqual(b, [bN], 'b is not mutated'); | ||
st.notEqual(aN, combinedAnB, 'aN + b !== aN'); | ||
st.notEqual(a, combinedAnB, 'aN + b !== a'); | ||
st.notEqual(bN, combinedAnB, 'aN + b !== bN'); | ||
st.notEqual(b, combinedAnB, 'aN + b !== b'); | ||
st.deepEqual([1, 2], combinedAnB, 'first argument is array-wrapped when not an array'); | ||
var combinedABn = utils.combine(a, bN); | ||
st.deepEqual(a, [aN], 'a is not mutated'); | ||
st.notEqual(aN, combinedABn, 'a + bN !== aN'); | ||
st.notEqual(a, combinedABn, 'a + bN !== a'); | ||
st.notEqual(bN, combinedABn, 'a + bN !== bN'); | ||
st.notEqual(b, combinedABn, 'a + bN !== b'); | ||
st.deepEqual([1, 2], combinedABn, 'second argument is array-wrapped when not an array'); | ||
st.end(); | ||
}); | ||
t.test('neither is an array', function (st) { | ||
var combined = utils.combine(1, 2); | ||
st.notEqual(1, combined, '1 + 2 !== 1'); | ||
st.notEqual(2, combined, '1 + 2 !== 2'); | ||
st.deepEqual([1, 2], combined, 'both arguments are array-wrapped when not an array'); | ||
st.end(); | ||
}); | ||
t.end(); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
134583
2417
562