From 2c8f94844f0348bc595dced6da0952fdfa66cbac Mon Sep 17 00:00:00 2001 From: Alessio Gravili Date: Mon, 30 Mar 2026 23:07:53 -0700 Subject: [PATCH] commit --- README.md | 128 ++++++++---------------------- lib/index.d.ts | 13 +--- lib/parse.js | 64 +-------------- lib/stringify.js | 35 +-------- lib/utils.js | 15 +--- test/parse.cjs | 188 ++------------------------------------------- test/stringify.cjs | 50 ++---------- 7 files changed, 56 insertions(+), 437 deletions(-) diff --git a/README.md b/README.md index 4bec8e7e..077ea5ca 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -This is a modernized, esm-only fork of [qs](https://github.com/ljharb/qs) without unnecessary polyfills. This fork is not endorsed by [Jordan Harband](https://github.com/ljharb), the lead maintainer of qs. +This is a modernized, ESM-only fork of [qs](https://github.com/ljharb/qs) without unnecessary polyfills. This fork is not endorsed by [Jordan Harband](https://github.com/ljharb), the lead maintainer of qs. # qs-esm @@ -8,9 +8,33 @@ Original Maintainer of qs (not of this fork): [Jordan Harband](https://github.co The **qs** module was originally created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring). +## Changes from qs + +**Forked from [qs v6.12.1](https://github.com/ljharb/qs/tree/v6.12.1).** + +Changes made in this fork: + +- **ESM-only** — native ES modules; no CommonJS support +- **Node 18+ required** — removed polyfills for older Node.js versions +- **Zero dependencies** — replaced the `side-channel` npm package with a native `WeakMap` for cyclic reference detection in `stringify` +- **UTF-8 only** — dropped `charset`, `charsetSentinel`, and `interpretNumericEntities`. These were for legacy browser quirks (old IE iso-8859-1 form submissions, Rails charset sentinel params) that don't apply to modern apps. Dropping them removes branching on every encode/decode call and reduces overall complexity. Custom encoder/decoder callbacks no longer receive a `charset` argument +- **Security backport** — `arrayLimit` now applies to `[]` (bracket-only) notation, closing the same gap addressed by [GHSA-w7fw-mjwx-w883](https://github.com/ljharb/qs/security/advisories/GHSA-w7fw-mjwx-w883) in upstream qs + ## Migrate from qs -Install `qs-esm`, uninstall `qs` and `@types/qs`. Then, replace `import qs from 'qs';` with `import * as qs from 'qs-esm';`. +Install `qs-esm`, uninstall `qs` and `@types/qs`. Then replace: + +```js +import qs from 'qs' +// or +const qs = require('qs') +``` + +with: + +```js +import * as qs from 'qs-esm' +``` ## Usage @@ -171,50 +195,6 @@ assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'first' }), { foo: 'b assert.deepEqual(qs.parse('foo=bar&foo=baz', { duplicates: 'last' }), { foo: 'baz' }) ``` -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 **qs** can also parse arrays using a similar `[]` notation: @@ -346,6 +326,8 @@ var encodedValues = qs.stringify( assert.equal(encodedValues, 'a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h') ``` +### Encoding + This encoding can also be replaced by a custom encoding method set as `encoder` option: ```javascript @@ -373,13 +355,13 @@ var decoded = qs.parse('x=z', { }) ``` -You can encode keys and values using different logic by using the type argument provided to the encoder: +You can encode keys and values using different logic by using the `type` argument provided to the encoder: ```javascript var encoded = qs.stringify( { a: { b: 'c' } }, { - encoder: function (str, defaultEncoder, charset, type) { + encoder: function (str, defaultEncoder, type) { if (type === 'key') { return // Encoded key } else if (type === 'value') { @@ -390,11 +372,11 @@ var encoded = qs.stringify( ) ``` -The type argument is also provided to the decoder: +The `type` argument is also provided to the decoder: ```javascript var decoded = qs.parse('x=z', { - decoder: function (str, defaultDecoder, charset, type) { + decoder: function (str, defaultDecoder, type) { if (type === 'key') { return // Decoded key } else if (type === 'value') { @@ -633,52 +615,6 @@ var nullsSkipped = qs.stringify({ a: 'b', c: null }, { skipNulls: true }) assert.equal(nullsSkipped, 'a=b') ``` -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`, 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 -[`qs-iconv`](https://github.com/martinheidegger/qs-iconv) library: - -```javascript -var encoder = require('qs-iconv/encoder')('shift_jis') -var shiftJISEncoded = qs.stringify({ a: 'こんにちは!' }, { encoder: encoder }) -assert.equal(shiftJISEncoded, 'a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I') -``` - -This also works for decoding of query strings: - -```javascript -var decoder = require('qs-iconv/decoder')('shift_jis') -var obj = qs.parse('a=%82%B1%82%F1%82%C9%82%BF%82%CD%81I', { decoder: decoder }) -assert.deepEqual(obj, { a: 'こんにちは!' }) -``` - ### RFC 3986 and RFC 1738 space encoding RFC3986 used as default option and encodes ' ' to _%20_ which is backward compatible. diff --git a/lib/index.d.ts b/lib/index.d.ts index 7cc33b8e..a9b8f53e 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,7 +1,7 @@ export as namespace QSESM -export type defaultEncoder = (str: any, defaultEncoder?: any, charset?: string) => string -export type defaultDecoder = (str: string, decoder?: any, charset?: string) => string +export type defaultEncoder = (str: any, defaultEncoder?: any) => string +export type defaultDecoder = (str: string, decoder?: any) => string export type BooleanOptional = boolean | undefined @@ -11,7 +11,7 @@ export interface IStringifyBaseOptions { skipNulls?: boolean | undefined encode?: boolean | undefined encoder?: - | ((str: any, defaultEncoder: defaultEncoder, charset: string, type: 'key' | 'value') => string) + | ((str: any, defaultEncoder: defaultEncoder, type: 'key' | 'value') => string) | undefined filter?: Array | ((prefix: string, value: any) => any) | undefined arrayFormat?: 'indices' | 'brackets' | 'repeat' | 'comma' | undefined @@ -21,8 +21,6 @@ export interface IStringifyBaseOptions { format?: 'RFC1738' | 'RFC3986' | undefined encodeValuesOnly?: boolean | undefined addQueryPrefix?: boolean | undefined - charset?: 'utf-8' | 'iso-8859-1' | undefined - charsetSentinel?: boolean | undefined } export type IStringifyDynamicOptions = AllowDots extends true @@ -37,7 +35,7 @@ export interface IParseBaseOptions { delimiter?: string | RegExp | undefined depth?: number | false | undefined decoder?: - | ((str: string, defaultDecoder: defaultDecoder, charset: string, type: 'key' | 'value') => any) + | ((str: string, defaultDecoder: defaultDecoder, type: 'key' | 'value') => any) | undefined arrayLimit?: number | undefined parseArrays?: boolean | undefined @@ -47,9 +45,6 @@ export interface IParseBaseOptions { parameterLimit?: number | undefined strictNullHandling?: boolean | undefined ignoreQueryPrefix?: boolean | undefined - charset?: 'utf-8' | 'iso-8859-1' | undefined - charsetSentinel?: boolean | undefined - interpretNumericEntities?: boolean | undefined allowEmptyArrays?: boolean | undefined duplicates?: 'combine' | 'first' | 'last' | undefined } diff --git a/lib/parse.js b/lib/parse.js index a0960071..0021b54c 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -11,8 +11,6 @@ const defaults = { allowPrototypes: false, allowSparse: false, arrayLimit: 20, - charset: 'utf-8', - charsetSentinel: false, comma: false, decodeDotInKeys: false, decoder: utils.decode, @@ -20,19 +18,12 @@ const defaults = { depth: 5, duplicates: 'combine', ignoreQueryPrefix: false, - interpretNumericEntities: false, parameterLimit: 1000, parseArrays: true, plainObjects: false, strictNullHandling: false, } -const interpretNumericEntities = function (str) { - return str.replace(/&#(\d+);/g, function ($0, numberStr) { - return String.fromCharCode(parseInt(numberStr, 10)) - }) -} - const parseArrayValue = function (val, options) { if (val && typeof val === 'string' && options.comma && val.indexOf(',') > -1) { return val.split(',') @@ -41,44 +32,15 @@ const parseArrayValue = function (val, options) { return val } -// 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. -const 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. -const charsetSentinel = 'utf8=%E2%9C%93' // encodeURIComponent('✓') - const parseValues = function parseQueryStringValues(str, options) { const obj = { __proto__: null } const cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str const limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit const parts = cleanStr.split(options.delimiter, limit) - let skipIndex = -1 // Keep track of where the utf8 sentinel was found let i - let 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 - } const part = parts[i] const bracketEqualsPos = part.indexOf(']=') @@ -86,19 +48,15 @@ const parseValues = function parseQueryStringValues(str, options) { let key, val if (pos === -1) { - key = options.decoder(part, defaults.decoder, charset, 'key') + key = options.decoder(part, defaults.decoder, 'key') val = options.strictNullHandling ? null : '' } else { - key = options.decoder(part.slice(0, pos), defaults.decoder, charset, 'key') + key = options.decoder(part.slice(0, pos), defaults.decoder, 'key') val = utils.maybeMap(parseArrayValue(part.slice(pos + 1), options), function (encodedVal) { - return options.decoder(encodedVal, defaults.decoder, charset, 'value') + return options.decoder(encodedVal, defaults.decoder, 'value') }) } - if (val && options.interpretNumericEntities && charset === 'iso-8859-1') { - val = interpretNumericEntities(val) - } - if (part.indexOf('[]=') > -1) { val = isArray(val) ? [val] : val } @@ -235,15 +193,6 @@ const normalizeParseOptions = function normalizeParseOptions(opts) { throw new TypeError('Decoder has to be a function.') } - if ( - typeof opts.charset !== 'undefined' && - opts.charset !== 'utf-8' && - opts.charset !== 'iso-8859-1' - ) { - throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined') - } - const charset = typeof opts.charset === 'undefined' ? defaults.charset : opts.charset - const duplicates = typeof opts.duplicates === 'undefined' ? defaults.duplicates : opts.duplicates if (duplicates !== 'combine' && duplicates !== 'first' && duplicates !== 'last') { @@ -267,9 +216,6 @@ const normalizeParseOptions = function normalizeParseOptions(opts) { typeof opts.allowPrototypes === 'boolean' ? opts.allowPrototypes : defaults.allowPrototypes, allowSparse: typeof opts.allowSparse === 'boolean' ? opts.allowSparse : defaults.allowSparse, arrayLimit: typeof opts.arrayLimit === 'number' ? opts.arrayLimit : defaults.arrayLimit, - charset: charset, - charsetSentinel: - typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, comma: typeof opts.comma === 'boolean' ? opts.comma : defaults.comma, decodeDotInKeys: typeof opts.decodeDotInKeys === 'boolean' ? opts.decodeDotInKeys : defaults.decodeDotInKeys, @@ -282,10 +228,6 @@ const normalizeParseOptions = function normalizeParseOptions(opts) { depth: typeof opts.depth === 'number' || opts.depth === false ? +opts.depth : defaults.depth, duplicates: duplicates, ignoreQueryPrefix: opts.ignoreQueryPrefix === true, - interpretNumericEntities: - typeof opts.interpretNumericEntities === 'boolean' - ? opts.interpretNumericEntities - : defaults.interpretNumericEntities, parameterLimit: typeof opts.parameterLimit === 'number' ? opts.parameterLimit : defaults.parameterLimit, parseArrays: opts.parseArrays !== false, diff --git a/lib/stringify.js b/lib/stringify.js index 03397875..53c3f713 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -32,8 +32,6 @@ const defaults = { allowDots: false, allowEmptyArrays: false, arrayFormat: 'indices', - charset: 'utf-8', - charsetSentinel: false, delimiter: '&', encode: true, encodeDotInKeys: false, @@ -79,7 +77,6 @@ const _stringify = function stringify( format, formatter, encodeValuesOnly, - charset, sideChannel, ) { let obj = object @@ -119,7 +116,7 @@ const _stringify = function stringify( if (obj === null) { if (strictNullHandling) { return encoder && !encodeValuesOnly - ? encoder(prefix, defaults.encoder, charset, 'key', format) + ? encoder(prefix, defaults.encoder, 'key', format) : prefix } @@ -130,11 +127,11 @@ const _stringify = function stringify( if (encoder) { const keyValue = encodeValuesOnly ? prefix - : encoder(prefix, defaults.encoder, charset, 'key', format) + : encoder(prefix, defaults.encoder, 'key', format) return [ formatter(keyValue) + '=' + - formatter(encoder(obj, defaults.encoder, charset, 'value', format)), + formatter(encoder(obj, defaults.encoder, 'value', format)), ] } return [formatter(prefix) + '=' + formatter(String(obj))] @@ -206,7 +203,6 @@ const _stringify = function stringify( format, formatter, encodeValuesOnly, - charset, valueSideChannel, ), ) @@ -236,15 +232,6 @@ const normalizeStringifyOptions = function normalizeStringifyOptions(opts) { throw new TypeError('Encoder has to be a function.') } - const charset = opts.charset || defaults.charset - if ( - typeof opts.charset !== 'undefined' && - opts.charset !== 'utf-8' && - opts.charset !== 'iso-8859-1' - ) { - throw new TypeError('The charset option must be either utf-8, iso-8859-1, or undefined') - } - let format = formats['default'] if (typeof opts.format !== 'undefined') { if (!has.call(formats.formatters, opts.format)) { @@ -288,9 +275,6 @@ const normalizeStringifyOptions = function normalizeStringifyOptions(opts) { ? !!opts.allowEmptyArrays : defaults.allowEmptyArrays, arrayFormat: arrayFormat, - charset: charset, - charsetSentinel: - typeof opts.charsetSentinel === 'boolean' ? opts.charsetSentinel : defaults.charsetSentinel, commaRoundTrip: opts.commaRoundTrip, delimiter: typeof opts.delimiter === 'undefined' ? defaults.delimiter : opts.delimiter, encode: typeof opts.encode === 'boolean' ? opts.encode : defaults.encode, @@ -373,24 +357,13 @@ export function stringify(object, opts) { options.format, options.formatter, options.encodeValuesOnly, - options.charset, sideChannel, ), ) } const joined = keys.join(options.delimiter) - let prefix = options.addQueryPrefix === true ? '?' : '' - - if (options.charsetSentinel) { - if (options.charset === 'iso-8859-1') { - // encodeURIComponent('✓'), the "numeric entity" representation of a checkmark - prefix += 'utf8=%26%2310003%3B&' - } else { - // encodeURIComponent('✓') - prefix += 'utf8=%E2%9C%93&' - } - } + const prefix = options.addQueryPrefix === true ? '?' : '' return joined.length > 0 ? prefix + joined : '' } diff --git a/lib/utils.js b/lib/utils.js index 6baf27d7..48dc150f 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -145,13 +145,8 @@ export const assign = function assignSingleSource(target, source) { }, target) } -export const decode = function (str, decoder, charset) { +export const decode = function (str) { const 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(strWithoutPlus) } catch (e) { @@ -161,7 +156,7 @@ export const decode = function (str, decoder, charset) { const limit = 1024 -export const encode = function encode(str, defaultEncoder, charset, kind, format) { +export const encode = function encode(str, _defaultEncoder, _kind, format) { // 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) { @@ -175,12 +170,6 @@ export const encode = function encode(str, defaultEncoder, charset, kind, format string = 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' - }) - } - let out = '' for (let j = 0; j < string.length; j += limit) { const segment = string.length >= limit ? string.slice(j, j + limit) : string diff --git a/test/parse.cjs b/test/parse.cjs index f09c6065..7643398e 100644 --- a/test/parse.cjs +++ b/test/parse.cjs @@ -624,11 +624,11 @@ test('parse()', async function (t) { t.test( 'use number decoder, parses string that has one number with comma option enabled', function (st) { - var decoder = function (str, defaultDecoder, charset, type) { + var decoder = function (str, defaultDecoder, type) { if (!isNaN(Number(str))) { return parseFloat(str) } - return defaultDecoder(str, defaultDecoder, charset, type) + return defaultDecoder(str, defaultDecoder, type) } st.deepEqual(qs.parse('foo=1', { comma: true, decoder: decoder }), { foo: 1 }) @@ -1015,191 +1015,13 @@ test('parse()', async function (t) { st.end() }) - 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.test('allows for decoding keys and values differently', function (st) { - var decoder = function (str, defaultDecoder, charset, type) { + var decoder = function (str, defaultDecoder, type) { if (type === 'key') { - return defaultDecoder(str, defaultDecoder, charset, type).toLowerCase() + return defaultDecoder(str, defaultDecoder, type).toLowerCase() } if (type === 'value') { - return defaultDecoder(str, defaultDecoder, charset, type).toUpperCase() + return defaultDecoder(str, defaultDecoder, type).toUpperCase() } throw 'this should never happen! type: ' + type } diff --git a/test/stringify.cjs b/test/stringify.cjs index 5e08f596..868fc736 100644 --- a/test/stringify.cjs +++ b/test/stringify.cjs @@ -46,8 +46,8 @@ test('stringify()', async function (t) { t.test('stringifies bigints', { skip: !hasBigInt }, function (st) { var three = BigInt(3) - var encodeWithN = function (value, defaultEncoder, charset) { - var result = defaultEncoder(value, defaultEncoder, charset) + var encodeWithN = function (value, defaultEncoder) { + var result = defaultEncoder(value, defaultEncoder) return typeof value === 'bigint' ? result + 'n' : result } st.equal(qs.stringify(three), '') @@ -1413,44 +1413,6 @@ test('stringify()', async function (t) { st.end() }) - 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('`charsetSentinel` option', function (st) { - st.equal( - qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'utf-8' }), - 'utf8=%E2%9C%93&a=%C3%A6', - 'adds the right sentinel when instructed to and the charset is utf-8', - ) - - st.equal( - qs.stringify({ a: 'æ' }, { charsetSentinel: true, charset: 'iso-8859-1' }), - 'utf8=%26%2310003%3B&a=%E6', - 'adds the right sentinel when instructed to and the charset is iso-8859-1', - ) - - st.end() - }) - t.test('does not mutate the options argument', function (st) { var options = {} qs.stringify({}, options) @@ -1479,12 +1441,12 @@ test('stringify()', async function (t) { }) t.test('allows for encoding keys and values differently', function (st) { - var encoder = function (str, defaultEncoder, charset, type) { + var encoder = function (str, defaultEncoder, type) { if (type === 'key') { - return defaultEncoder(str, defaultEncoder, charset, type).toLowerCase() + return defaultEncoder(str, defaultEncoder, type).toLowerCase() } if (type === 'value') { - return defaultEncoder(str, defaultEncoder, charset, type).toUpperCase() + return defaultEncoder(str, defaultEncoder, type).toUpperCase() } throw 'this should never happen! type: ' + type } @@ -1651,7 +1613,7 @@ test('stringify()', async function (t) { } st.equal( - qs.stringify(obj, { arrayFormat: 'bracket', charset: 'utf-8' }), + qs.stringify(obj, { arrayFormat: 'bracket' }), 'foo=' + expected.join(''), )