})
describe('query params', () => {
- const safePerSpec = "!$'*+,:;@[]_|?/{}^()`"
- const toEncodeForKey = ' "<>#&='
- const toEncodeForValue = ' "<>#&'
+ const safePerSpec = "!$'*,:;@[]_|?/{}^()`"
+ const toEncodeForKey = '"<>#&='
+ const toEncodeForValue = '"<>#&'
const encodedToEncodeForKey = toEncodeForKey
.split('')
.map(c => {
expect(encodeQueryKey(toEncodeForKey)).toBe(encodedToEncodeForKey)
expect(encodeQueryValue(toEncodeForValue)).toBe(encodedToEncodeForValue)
})
+
+ it('encodes space as +', () => {
+ expect(encodeQueryKey(' ')).toBe('+')
+ expect(encodeQueryValue(' ')).toBe('+')
+ })
+
+ it('encodes +', () => {
+ expect(encodeQueryKey('+')).toBe('%2B')
+ expect(encodeQueryValue('+')).toBe('%2B')
+ })
})
describe('hash', () => {
})
})
+ it('decodes the + as space', () => {
+ expect(parseQuery('a+b=c+d')).toEqual({
+ 'a b': 'c d',
+ })
+ })
+
+ it('decodes the encoded + as +', () => {
+ expect(parseQuery('a%2Bb=c%2Bd')).toEqual({
+ 'a+b': 'c+d',
+ })
+ })
+
// this is for browsers like IE that allow invalid characters
it('keep invalid values as is', () => {
expect(parseQuery('e=%&e=%25')).toEqual({
const SLASH_RE = /\//g // %2F
const EQUAL_RE = /=/g // %3D
const IM_RE = /\?/g // %3F
+const PLUS_RE = /\+/g // %2B
/**
* NOTE: It's not clear to me if we should encode the + symbol in queries, it
* seems to be less flexible than not doing so and I can't find out the legacy
* - https://url.spec.whatwg.org/#urlencoded-parsing
* - https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
*/
-// const PLUS_RE = /\+/g // %3F
const ENC_BRACKET_OPEN_RE = /%5B/g // [
const ENC_BRACKET_CLOSE_RE = /%5D/g // ]
const ENC_CURLY_OPEN_RE = /%7B/g // {
const ENC_PIPE_RE = /%7C/g // |
const ENC_CURLY_CLOSE_RE = /%7D/g // }
+const ENC_SPACE_RE = /%20/g // }
/**
* Encode characters that need to be encoded on the path, search and hash
* @returns encoded string
*/
export function encodeQueryValue(text: string | number): string {
- return commonEncode(text)
- .replace(HASH_RE, '%23')
- .replace(AMPERSAND_RE, '%26')
- .replace(ENC_BACKTICK_RE, '`')
- .replace(ENC_CURLY_OPEN_RE, '{')
- .replace(ENC_CURLY_CLOSE_RE, '}')
- .replace(ENC_CARET_RE, '^')
+ return (
+ commonEncode(text)
+ // Encode the space as +, encode the + to differentiate it from the space
+ .replace(PLUS_RE, '%2B')
+ .replace(ENC_SPACE_RE, '+')
+ .replace(HASH_RE, '%23')
+ .replace(AMPERSAND_RE, '%26')
+ .replace(ENC_BACKTICK_RE, '`')
+ .replace(ENC_CURLY_OPEN_RE, '{')
+ .replace(ENC_CURLY_CLOSE_RE, '}')
+ .replace(ENC_CARET_RE, '^')
+ )
}
/**
const hasLeadingIM = search[0] === '?'
const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&')
for (let i = 0; i < searchParams.length; ++i) {
- const searchParam = searchParams[i]
+ // pre decode the + into space
+ // FIXME: can't import PLUS_RE because it becomes a different regex ???
+ const searchParam = searchParams[i].replace(/\+/g, ' ')
// allow the = character
let eqPos = searchParam.indexOf('=')
let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos))