From c48c4ec6f062769725d9c7bf1e0a16d8c1230f2f Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 01/13] Percent decode --- .../web.url-search-params.constructor.js | 141 +++++++++++++++--- tests/unit-global/web.url-search-params.js | 28 ++++ tests/unit-pure/web.url-search-params.js | 28 ++++ 3 files changed, 177 insertions(+), 20 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index e9e3af50d934..2ce425a0605b 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -1,9 +1,11 @@ 'use strict'; // TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env` require('../modules/es.array.iterator'); +require('../modules/es.string.from-code-point'); var $ = require('../internals/export'); var globalThis = require('../internals/global-this'); var safeGetBuiltIn = require('../internals/safe-get-built-in'); +var getBuiltIn = require('../internals/get-built-in'); var call = require('../internals/function-call'); var uncurryThis = require('../internals/function-uncurry-this'); var DESCRIPTORS = require('../internals/descriptors'); @@ -31,6 +33,8 @@ var validateArgumentsLength = require('../internals/validate-arguments-length'); var wellKnownSymbol = require('../internals/well-known-symbol'); var arraySort = require('../internals/array-sort'); +var $includes = require('../internals/array-includes').includes; + var ITERATOR = wellKnownSymbol('iterator'); var URL_SEARCH_PARAMS = 'URLSearchParams'; var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator'; @@ -43,9 +47,7 @@ var NativeRequest = safeGetBuiltIn('Request'); var Headers = safeGetBuiltIn('Headers'); var RequestPrototype = NativeRequest && NativeRequest.prototype; var HeadersPrototype = Headers && Headers.prototype; -var RegExp = globalThis.RegExp; var TypeError = globalThis.TypeError; -var decodeURIComponent = globalThis.decodeURIComponent; var encodeURIComponent = globalThis.encodeURIComponent; var charAt = uncurryThis(''.charAt); var join = uncurryThis([].join); @@ -56,32 +58,131 @@ var splice = uncurryThis([].splice); var split = uncurryThis(''.split); var stringSlice = uncurryThis(''.slice); -var plus = /\+/g; -var sequences = Array(4); +var FALLBACK_REPLACER = '\uFFFD'; +var charCodeAt = uncurryThis(''.charCodeAt); +var substring = uncurryThis(''.substring); +var indexOf = uncurryThis(''.indexOf); +var fromCharCode = String.fromCharCode; +var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); +var $parseInt = parseInt; + +var parseHexOctet = function (string, start) { + return $parseInt(stringSlice(string, start, start + 2), 16); +}; -var percentSequence = function (bytes) { - return sequences[bytes - 1] || (sequences[bytes - 1] = RegExp('((?:%[\\da-f]{2}){' + bytes + '})', 'gi')); +var getLeadingOnes = function (octet) { + var binString = $toString(octet, 2); + return indexOf(binString, '0') !== -1 ? indexOf(binString, '0') : binString.length; }; -var percentDecode = function (sequence) { - try { - return decodeURIComponent(sequence); - } catch (error) { - return sequence; +var utf8Decode = function (octets) { + var len = octets.length; + var codePoint = null; + + switch (len) { + case 1: + codePoint = octets[0]; + break; + case 2: + codePoint = (octets[0] & 0x1F) << 6 | (octets[1] & 0x3F); + break; + case 3: + codePoint = (octets[0] & 0x0F) << 12 | (octets[1] & 0x3F) << 6 | (octets[2] & 0x3F); + break; + case 4: + codePoint = (octets[0] & 0x07) << 18 | (octets[1] & 0x3F) << 12 | (octets[2] & 0x3F) << 6 | (octets[3] & 0x3F); + break; } + + return codePoint > 0x10FFFF ? null : codePoint; }; -var deserialize = function (it) { - var result = replace(it, plus, ' '); - var bytes = 4; - try { - return decodeURIComponent(result); - } catch (error) { - while (bytes) { - result = replace(result, percentSequence(bytes--), percentDecode); +/* eslint-disable max-statements -- TODO */ +var decode = function (input, preserveEscapeSet) { + var length = input.length; + var result = ''; + var i = 0; + + while (i < length) { + var charCode = charCodeAt(input, i); + var decodedChar = input[i]; + + if (charCode === 0x25) { + if (i + 3 > length) { + result += FALLBACK_REPLACER; + break; + } + + var escapeSequence = substring(input, i, i + 3); + var octet = parseHexOctet(input, i + 1); + + if (isNaN(octet)) { + result += FALLBACK_REPLACER; + i += 3; + continue; + } + + i += 2; + var byteSequenceLength = getLeadingOnes(octet); + + if (byteSequenceLength === 0) { + var asciiChar = fromCharCode(octet); + decodedChar = $includes(preserveEscapeSet, asciiChar, undefined) ? escapeSequence : asciiChar; + } else { + if (byteSequenceLength === 1 || byteSequenceLength > 4) { + result += FALLBACK_REPLACER; + i++; + continue; + } + + var octets = [octet]; + var sequenceIndex = 1; + + while (sequenceIndex < byteSequenceLength) { + i++; + if (i + 3 > length) { + result += FALLBACK_REPLACER; + break; + } + if (charCodeAt(input, i) !== 0x25) { + result += FALLBACK_REPLACER; + break; + } + var nextByte = parseHexOctet(input, i + 1); + if (isNaN(nextByte)) { + result += FALLBACK_REPLACER; + i += 3; + break; + } + octets.push(nextByte); + i += 2; + sequenceIndex++; + } + + if (octets.length !== byteSequenceLength) { + result += FALLBACK_REPLACER; + continue; + } + + var codePoint = utf8Decode(octets); + if (codePoint === null) { + result += FALLBACK_REPLACER; + } else { + decodedChar = fromCodePoint(codePoint); + } + } } - return result; + + result += decodedChar; + i++; } + + return result; +}; + +var deserialize = function (it) { + var preserveEscapeSet = ['%', ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#']; + return decode(it, preserveEscapeSet); }; var find = /[!'()~]|%20/g; diff --git a/tests/unit-global/web.url-search-params.js b/tests/unit-global/web.url-search-params.js index 2e74b74a4c07..f4aa62e33d4b 100644 --- a/tests/unit-global/web.url-search-params.js +++ b/tests/unit-global/web.url-search-params.js @@ -100,6 +100,34 @@ QUnit.test('URLSearchParams', assert => { params = new URLSearchParams(params.toString()); assert.same(params.get('query'), '+15555555555', 'parse encoded +'); + params = new URLSearchParams('b=%2sf%2a'); + assert.same(params.get('b'), '%2sf*', 'parse encoded %2sf%2a'); + params = new URLSearchParams('b=%%2a'); + assert.same(params.get('b'), '%*', 'parse encoded b=%%2a'); + + params = new URLSearchParams('a=b\u2384'); + assert.same(params.get('a'), 'b\u2384', 'parse \u2384'); + params = new URLSearchParams('a\u2384b=c'); + assert.same(params.get('a\u2384b'), 'c', 'parse \u2384'); + + params = new URLSearchParams('a=b%e2%8e%84'); + assert.same(params.get('a'), 'b\u2384', 'parse b%e2%8e%84'); + params = new URLSearchParams('a%e2%8e%84b=c'); + assert.same(params.get('a\u2384b'), 'c', 'parse b%e2%8e%84'); + + params = new URLSearchParams('a=b\uD83D\uDCA9c'); + assert.same(params.get('a'), 'b\uD83D\uDCA9c', 'parse \uD83D\uDCA9'); + params = new URLSearchParams('a\uD83D\uDCA9b=c'); + assert.same(params.get('a\uD83D\uDCA9b'), 'c', 'parse \uD83D\uDCA9'); + + params = new URLSearchParams('a=b%f0%9f%92%a9c'); + assert.same(params.get('a'), 'b\uD83D\uDCA9c', 'parse %f0%9f%92%a9'); + params = new URLSearchParams('a%f0%9f%92%a9b=c'); + assert.same(params.get('a\uD83D\uDCA9b'), 'c', 'parse %f0%9f%92%a9'); + + assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); + assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); + const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, { input: { '+': '%C2' }, output: [['+', '%C2']], name: 'object with +' }, diff --git a/tests/unit-pure/web.url-search-params.js b/tests/unit-pure/web.url-search-params.js index aa50dd9d3a3c..8d5dc59fd48a 100644 --- a/tests/unit-pure/web.url-search-params.js +++ b/tests/unit-pure/web.url-search-params.js @@ -102,6 +102,34 @@ QUnit.test('URLSearchParams', assert => { params = new URLSearchParams(params.toString()); assert.same(params.get('query'), '+15555555555', 'parse encoded +'); + params = new URLSearchParams('b=%2sf%2a'); + assert.same(params.get('b'), '%2sf*', 'parse encoded %2sf%2a'); + params = new URLSearchParams('b=%%2a'); + assert.same(params.get('b'), '%*', 'parse encoded b=%%2a'); + + params = new URLSearchParams('a=b\u2384'); + assert.same(params.get('a'), 'b\u2384', 'parse \u2384'); + params = new URLSearchParams('a\u2384b=c'); + assert.same(params.get('a\u2384b'), 'c', 'parse \u2384'); + + params = new URLSearchParams('a=b%e2%8e%84'); + assert.same(params.get('a'), 'b\u2384', 'parse b%e2%8e%84'); + params = new URLSearchParams('a%e2%8e%84b=c'); + assert.same(params.get('a\u2384b'), 'c', 'parse b%e2%8e%84'); + + params = new URLSearchParams('a=b\uD83D\uDCA9c'); + assert.same(params.get('a'), 'b\uD83D\uDCA9c', 'parse \uD83D\uDCA9'); + params = new URLSearchParams('a\uD83D\uDCA9b=c'); + assert.same(params.get('a\uD83D\uDCA9b'), 'c', 'parse \uD83D\uDCA9'); + + params = new URLSearchParams('a=b%f0%9f%92%a9c'); + assert.same(params.get('a'), 'b\uD83D\uDCA9c', 'parse %f0%9f%92%a9'); + params = new URLSearchParams('a%f0%9f%92%a9b=c'); + assert.same(params.get('a\uD83D\uDCA9b'), 'c', 'parse %f0%9f%92%a9'); + + assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); + assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); + const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, { input: { '+': '%C2' }, output: [['+', '%C2']], name: 'object with +' }, From 485c871e97c29a2e45a1bcb5b48f8a6f4e375577 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 02/13] Percent decode --- .../web.url-search-params.constructor.js | 18 ++++-------------- tests/unit-global/web.url-search-params.js | 1 + tests/unit-pure/web.url-search-params.js | 1 + 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 2ce425a0605b..443e695851bb 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -33,8 +33,6 @@ var validateArgumentsLength = require('../internals/validate-arguments-length'); var wellKnownSymbol = require('../internals/well-known-symbol'); var arraySort = require('../internals/array-sort'); -var $includes = require('../internals/array-includes').includes; - var ITERATOR = wellKnownSymbol('iterator'); var URL_SEARCH_PARAMS = 'URLSearchParams'; var URL_SEARCH_PARAMS_ITERATOR = URL_SEARCH_PARAMS + 'Iterator'; @@ -60,7 +58,6 @@ var stringSlice = uncurryThis(''.slice); var FALLBACK_REPLACER = '\uFFFD'; var charCodeAt = uncurryThis(''.charCodeAt); -var substring = uncurryThis(''.substring); var indexOf = uncurryThis(''.indexOf); var fromCharCode = String.fromCharCode; var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); @@ -98,7 +95,7 @@ var utf8Decode = function (octets) { }; /* eslint-disable max-statements -- TODO */ -var decode = function (input, preserveEscapeSet) { +var decode = function (input) { var length = input.length; var result = ''; var i = 0; @@ -113,7 +110,6 @@ var decode = function (input, preserveEscapeSet) { break; } - var escapeSequence = substring(input, i, i + 3); var octet = parseHexOctet(input, i + 1); if (isNaN(octet)) { @@ -126,8 +122,7 @@ var decode = function (input, preserveEscapeSet) { var byteSequenceLength = getLeadingOnes(octet); if (byteSequenceLength === 0) { - var asciiChar = fromCharCode(octet); - decodedChar = $includes(preserveEscapeSet, asciiChar, undefined) ? escapeSequence : asciiChar; + decodedChar = fromCharCode(octet); } else { if (byteSequenceLength === 1 || byteSequenceLength > 4) { result += FALLBACK_REPLACER; @@ -180,11 +175,6 @@ var decode = function (input, preserveEscapeSet) { return result; }; -var deserialize = function (it) { - var preserveEscapeSet = ['%', ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#']; - return decode(it, preserveEscapeSet); -}; - var find = /[!'()~]|%20/g; var replacements = { @@ -275,8 +265,8 @@ URLSearchParamsState.prototype = { if (attribute.length) { entry = split(attribute, '='); push(entries, { - key: deserialize(shift(entry)), - value: deserialize(join(entry, '=')) + key: decode(shift(entry)), + value: decode(join(entry, '=')) }); } } diff --git a/tests/unit-global/web.url-search-params.js b/tests/unit-global/web.url-search-params.js index f4aa62e33d4b..e38593d546c5 100644 --- a/tests/unit-global/web.url-search-params.js +++ b/tests/unit-global/web.url-search-params.js @@ -127,6 +127,7 @@ QUnit.test('URLSearchParams', assert => { assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); + assert.same(String(new URLSearchParams('%25')), '%25='); const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, diff --git a/tests/unit-pure/web.url-search-params.js b/tests/unit-pure/web.url-search-params.js index 8d5dc59fd48a..cd6f7345939c 100644 --- a/tests/unit-pure/web.url-search-params.js +++ b/tests/unit-pure/web.url-search-params.js @@ -129,6 +129,7 @@ QUnit.test('URLSearchParams', assert => { assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); + assert.same(String(new URLSearchParams('%25')), '%25='); const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, From 75fdae94898a36d01b36f54da09a3f0dbd3a2ccd Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 03/13] Percent decode --- .../core-js/modules/web.url-search-params.constructor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 443e695851bb..ab7eff3524bc 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -59,7 +59,8 @@ var stringSlice = uncurryThis(''.slice); var FALLBACK_REPLACER = '\uFFFD'; var charCodeAt = uncurryThis(''.charCodeAt); var indexOf = uncurryThis(''.indexOf); -var fromCharCode = String.fromCharCode; +var numberToString = uncurryThis(1.0.toString); +var fromCharCode = uncurryThis(''.fromCharCode); var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); var $parseInt = parseInt; @@ -68,7 +69,7 @@ var parseHexOctet = function (string, start) { }; var getLeadingOnes = function (octet) { - var binString = $toString(octet, 2); + var binString = numberToString(octet, 2); return indexOf(binString, '0') !== -1 ? indexOf(binString, '0') : binString.length; }; From 598d3de58937d409ead876140995e72f742fa938 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 04/13] Percent decode --- .../modules/web.url-search-params.constructor.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index ab7eff3524bc..644782c2cb55 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -56,11 +56,12 @@ var splice = uncurryThis([].splice); var split = uncurryThis(''.split); var stringSlice = uncurryThis(''.slice); +var plus = /\+/g; var FALLBACK_REPLACER = '\uFFFD'; var charCodeAt = uncurryThis(''.charCodeAt); var indexOf = uncurryThis(''.indexOf); var numberToString = uncurryThis(1.0.toString); -var fromCharCode = uncurryThis(''.fromCharCode); +var fromCharCode = String.fromCharCode; var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); var $parseInt = parseInt; @@ -176,6 +177,11 @@ var decode = function (input) { return result; }; +var deserialize = function (it) { + var result = replace(it, plus, ' '); + return decode(result); +} + var find = /[!'()~]|%20/g; var replacements = { @@ -266,8 +272,8 @@ URLSearchParamsState.prototype = { if (attribute.length) { entry = split(attribute, '='); push(entries, { - key: decode(shift(entry)), - value: decode(join(entry, '=')) + key: deserialize(shift(entry)), + value: deserialize(join(entry, '=')) }); } } From b2ddb51c163cd0dc3473a84dc5fe5d6de1455a43 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 05/13] Percent decode --- .../core-js/modules/web.url-search-params.constructor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 644782c2cb55..0614a68ec084 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -32,6 +32,7 @@ var createIterResultObject = require('../internals/create-iter-result-object'); var validateArgumentsLength = require('../internals/validate-arguments-length'); var wellKnownSymbol = require('../internals/well-known-symbol'); var arraySort = require('../internals/array-sort'); +var padStart = require('../internals/string-pad').start; var ITERATOR = wellKnownSymbol('iterator'); var URL_SEARCH_PARAMS = 'URLSearchParams'; @@ -70,7 +71,7 @@ var parseHexOctet = function (string, start) { }; var getLeadingOnes = function (octet) { - var binString = numberToString(octet, 2); + var binString = padStart(numberToString(octet, 2), 8, '0'); return indexOf(binString, '0') !== -1 ? indexOf(binString, '0') : binString.length; }; @@ -180,7 +181,7 @@ var decode = function (input) { var deserialize = function (it) { var result = replace(it, plus, ' '); return decode(result); -} +}; var find = /[!'()~]|%20/g; From 3f75274d8a441af0ef6c1570aecfedc0c5ff6c25 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 06/13] Percent decode --- .../modules/web.url-search-params.constructor.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 0614a68ec084..c86753c2c5f3 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -59,7 +59,6 @@ var stringSlice = uncurryThis(''.slice); var plus = /\+/g; var FALLBACK_REPLACER = '\uFFFD'; -var charCodeAt = uncurryThis(''.charCodeAt); var indexOf = uncurryThis(''.indexOf); var numberToString = uncurryThis(1.0.toString); var fromCharCode = String.fromCharCode; @@ -104,15 +103,20 @@ var decode = function (input) { var i = 0; while (i < length) { - var charCode = charCodeAt(input, i); var decodedChar = input[i]; - if (charCode === 0x25) { + if (decodedChar === '%') { if (i + 3 > length) { result += FALLBACK_REPLACER; break; } + if (input[i + 1] === '%') { + result += '%'; + i++; + continue; + } + var octet = parseHexOctet(input, i + 1); if (isNaN(octet)) { @@ -142,7 +146,7 @@ var decode = function (input) { result += FALLBACK_REPLACER; break; } - if (charCodeAt(input, i) !== 0x25) { + if (input[i] !== '%') { result += FALLBACK_REPLACER; break; } From f0120033ef367a25b38eab428fb51679eb7f71b4 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 07/13] Percent decode --- .../modules/web.url-search-params.constructor.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index c86753c2c5f3..fc0bd2d71b8f 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -106,12 +106,12 @@ var decode = function (input) { var decodedChar = input[i]; if (decodedChar === '%') { - if (i + 3 > length) { + if (i + 3 > length && i + 1 !== length) { result += FALLBACK_REPLACER; break; } - if (input[i + 1] === '%') { + if (input[i + 1] === '%' || i + 1 === length) { result += '%'; i++; continue; @@ -119,9 +119,9 @@ var decode = function (input) { var octet = parseHexOctet(input, i + 1); - if (isNaN(octet)) { - result += FALLBACK_REPLACER; - i += 3; + if (isNaN(octet) || octet < 32) { + result += decodedChar; + i++; continue; } From ba501bf436bdcfb28a3fce4ef0146d48105f6e40 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 08/13] Percent decode --- .../web.url-search-params.constructor.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index fc0bd2d71b8f..e5962b86c2df 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -59,6 +59,8 @@ var stringSlice = uncurryThis(''.slice); var plus = /\+/g; var FALLBACK_REPLACER = '\uFFFD'; +var VALID_HEX = /^[0-9a-f]+$/i; + var indexOf = uncurryThis(''.indexOf); var numberToString = uncurryThis(1.0.toString); var fromCharCode = String.fromCharCode; @@ -66,7 +68,10 @@ var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); var $parseInt = parseInt; var parseHexOctet = function (string, start) { - return $parseInt(stringSlice(string, start, start + 2), 16); + var substr = stringSlice(string, start, start + 2); + if (!VALID_HEX.test(substr)) return NaN; + + return $parseInt(substr, 16); }; var getLeadingOnes = function (octet) { @@ -107,7 +112,8 @@ var decode = function (input) { if (decodedChar === '%') { if (i + 3 > length && i + 1 !== length) { - result += FALLBACK_REPLACER; + /* eslint-disable no-useless-assignment -- TODO */ + decodedChar = FALLBACK_REPLACER; break; } @@ -119,7 +125,7 @@ var decode = function (input) { var octet = parseHexOctet(input, i + 1); - if (isNaN(octet) || octet < 32) { + if (isNaN(octet)) { result += decodedChar; i++; continue; @@ -142,17 +148,13 @@ var decode = function (input) { while (sequenceIndex < byteSequenceLength) { i++; - if (i + 3 > length) { - result += FALLBACK_REPLACER; - break; - } - if (input[i] !== '%') { - result += FALLBACK_REPLACER; + if (i + 3 > length || input[i] !== '%') { break; } + var nextByte = parseHexOctet(input, i + 1); + if (isNaN(nextByte)) { - result += FALLBACK_REPLACER; i += 3; break; } From b3e76ef21779b7aab4969260059780ab04e22ad4 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Tue, 13 Aug 2024 01:25:11 +0700 Subject: [PATCH 09/13] Percent decode --- packages/core-js/modules/web.url-search-params.constructor.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index e5962b86c2df..f55774051a37 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -154,6 +154,10 @@ var decode = function (input) { var nextByte = parseHexOctet(input, i + 1); + if (nextByte > 191 || nextByte < 128) { // incorrect next byte + break; + } + if (isNaN(nextByte)) { i += 3; break; From 5eaf9487693c63bc0b73720dcfe04b94acf947d1 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Wed, 14 Aug 2024 01:40:04 +0700 Subject: [PATCH 10/13] Percent decode --- .../web.url-search-params.constructor.js | 29 +++++++------------ tests/unit-global/web.url-search-params.js | 1 + tests/unit-pure/web.url-search-params.js | 1 + 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index f55774051a37..73bbc62da2df 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -65,6 +65,7 @@ var indexOf = uncurryThis(''.indexOf); var numberToString = uncurryThis(1.0.toString); var fromCharCode = String.fromCharCode; var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); +var $isNaN = isNaN; var $parseInt = parseInt; var parseHexOctet = function (string, start) { @@ -76,7 +77,8 @@ var parseHexOctet = function (string, start) { var getLeadingOnes = function (octet) { var binString = padStart(numberToString(octet, 2), 8, '0'); - return indexOf(binString, '0') !== -1 ? indexOf(binString, '0') : binString.length; + var firstZero = indexOf(binString, '0'); + return firstZero !== -1 ? firstZero : binString.length; }; var utf8Decode = function (octets) { @@ -101,23 +103,16 @@ var utf8Decode = function (octets) { return codePoint > 0x10FFFF ? null : codePoint; }; -/* eslint-disable max-statements -- TODO */ var decode = function (input) { var length = input.length; var result = ''; var i = 0; while (i < length) { - var decodedChar = input[i]; + var decodedChar = charAt(input, i); if (decodedChar === '%') { - if (i + 3 > length && i + 1 !== length) { - /* eslint-disable no-useless-assignment -- TODO */ - decodedChar = FALLBACK_REPLACER; - break; - } - - if (input[i + 1] === '%' || i + 1 === length) { + if (input[i + 1] === '%' || i + 1 === length || i + 3 > length) { result += '%'; i++; continue; @@ -125,7 +120,7 @@ var decode = function (input) { var octet = parseHexOctet(input, i + 1); - if (isNaN(octet)) { + if ($isNaN(octet)) { result += decodedChar; i++; continue; @@ -148,21 +143,17 @@ var decode = function (input) { while (sequenceIndex < byteSequenceLength) { i++; - if (i + 3 > length || input[i] !== '%') { - break; - } + if (i + 3 > length || input[i] !== '%') break; var nextByte = parseHexOctet(input, i + 1); - if (nextByte > 191 || nextByte < 128) { // incorrect next byte - break; - } + if (nextByte > 191 || nextByte < 128) break; - if (isNaN(nextByte)) { + if ($isNaN(nextByte)) { i += 3; break; } - octets.push(nextByte); + push(octets, nextByte); i += 2; sequenceIndex++; } diff --git a/tests/unit-global/web.url-search-params.js b/tests/unit-global/web.url-search-params.js index e38593d546c5..237a288dd9d9 100644 --- a/tests/unit-global/web.url-search-params.js +++ b/tests/unit-global/web.url-search-params.js @@ -128,6 +128,7 @@ QUnit.test('URLSearchParams', assert => { assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); assert.same(String(new URLSearchParams('%25')), '%25='); + assert.same(String(new URLSearchParams('%4')), '%254='); const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, diff --git a/tests/unit-pure/web.url-search-params.js b/tests/unit-pure/web.url-search-params.js index cd6f7345939c..c63428885e13 100644 --- a/tests/unit-pure/web.url-search-params.js +++ b/tests/unit-pure/web.url-search-params.js @@ -130,6 +130,7 @@ QUnit.test('URLSearchParams', assert => { assert.same(String(new URLSearchParams('%C2')), '%EF%BF%BD='); assert.same(String(new URLSearchParams('%F0%9F%D0%90')), '%EF%BF%BD%D0%90='); assert.same(String(new URLSearchParams('%25')), '%25='); + assert.same(String(new URLSearchParams('%4')), '%254='); const testData = [ { input: '?a=%', output: [['a', '%']], name: 'handling %' }, From a476cc5cf156720737d7f59d5573978e5444588d Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Wed, 14 Aug 2024 01:50:42 +0700 Subject: [PATCH 11/13] Percent decode --- .../core-js/modules/web.url-search-params.constructor.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 73bbc62da2df..f34dcb97fa81 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -112,7 +112,7 @@ var decode = function (input) { var decodedChar = charAt(input, i); if (decodedChar === '%') { - if (input[i + 1] === '%' || i + 1 === length || i + 3 > length) { + if (input[i + 1] === '%' || i + 3 > length) { result += '%'; i++; continue; @@ -147,12 +147,12 @@ var decode = function (input) { var nextByte = parseHexOctet(input, i + 1); - if (nextByte > 191 || nextByte < 128) break; - if ($isNaN(nextByte)) { i += 3; break; } + if (nextByte > 191 || nextByte < 128) break; + push(octets, nextByte); i += 2; sequenceIndex++; From 177312e1ffb1963ace8c5ff42b536534ff5e563d Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Wed, 14 Aug 2024 11:50:02 +0700 Subject: [PATCH 12/13] Optimization & fixes --- .../web.url-search-params.constructor.js | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index f34dcb97fa81..53acfe88a37f 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -32,7 +32,6 @@ var createIterResultObject = require('../internals/create-iter-result-object'); var validateArgumentsLength = require('../internals/validate-arguments-length'); var wellKnownSymbol = require('../internals/well-known-symbol'); var arraySort = require('../internals/array-sort'); -var padStart = require('../internals/string-pad').start; var ITERATOR = wellKnownSymbol('iterator'); var URL_SEARCH_PARAMS = 'URLSearchParams'; @@ -48,6 +47,10 @@ var RequestPrototype = NativeRequest && NativeRequest.prototype; var HeadersPrototype = Headers && Headers.prototype; var TypeError = globalThis.TypeError; var encodeURIComponent = globalThis.encodeURIComponent; +var fromCharCode = String.fromCharCode; +var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); +var $isNaN = isNaN; +var $parseInt = parseInt; var charAt = uncurryThis(''.charAt); var join = uncurryThis([].join); var push = uncurryThis([].push); @@ -56,36 +59,31 @@ var shift = uncurryThis([].shift); var splice = uncurryThis([].splice); var split = uncurryThis(''.split); var stringSlice = uncurryThis(''.slice); +var exec = uncurryThis(/./.exec); var plus = /\+/g; var FALLBACK_REPLACER = '\uFFFD'; var VALID_HEX = /^[0-9a-f]+$/i; -var indexOf = uncurryThis(''.indexOf); -var numberToString = uncurryThis(1.0.toString); -var fromCharCode = String.fromCharCode; -var fromCodePoint = getBuiltIn('String', 'fromCodePoint'); -var $isNaN = isNaN; -var $parseInt = parseInt; - var parseHexOctet = function (string, start) { var substr = stringSlice(string, start, start + 2); - if (!VALID_HEX.test(substr)) return NaN; + if (!exec(VALID_HEX, substr)) return NaN; return $parseInt(substr, 16); }; var getLeadingOnes = function (octet) { - var binString = padStart(numberToString(octet, 2), 8, '0'); - var firstZero = indexOf(binString, '0'); - return firstZero !== -1 ? firstZero : binString.length; + var count = 0; + for (var mask = 0x80; mask > 0 && (octet & mask) !== 0; mask >>= 1) { + count++; + } + return count; }; var utf8Decode = function (octets) { - var len = octets.length; var codePoint = null; - switch (len) { + switch (octets.length) { case 1: codePoint = octets[0]; break; @@ -112,7 +110,7 @@ var decode = function (input) { var decodedChar = charAt(input, i); if (decodedChar === '%') { - if (input[i + 1] === '%' || i + 3 > length) { + if (charAt(input, i + 1) === '%' || i + 3 > length) { result += '%'; i++; continue; @@ -143,7 +141,7 @@ var decode = function (input) { while (sequenceIndex < byteSequenceLength) { i++; - if (i + 3 > length || input[i] !== '%') break; + if (i + 3 > length || charAt(input, i) !== '%') break; var nextByte = parseHexOctet(input, i + 1); From db0ef53d92ae998f3fd8fa13786e7a2d0565ae87 Mon Sep 17 00:00:00 2001 From: Sergey Nechaev Date: Thu, 15 Aug 2024 21:31:16 +0700 Subject: [PATCH 13/13] Methods refactoring --- .../modules/web.url-search-params.constructor.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core-js/modules/web.url-search-params.constructor.js b/packages/core-js/modules/web.url-search-params.constructor.js index 53acfe88a37f..318402c58ae0 100644 --- a/packages/core-js/modules/web.url-search-params.constructor.js +++ b/packages/core-js/modules/web.url-search-params.constructor.js @@ -102,6 +102,7 @@ var utf8Decode = function (octets) { }; var decode = function (input) { + input = replace(input, plus, ' '); var length = input.length; var result = ''; var i = 0; @@ -177,11 +178,6 @@ var decode = function (input) { return result; }; -var deserialize = function (it) { - var result = replace(it, plus, ' '); - return decode(result); -}; - var find = /[!'()~]|%20/g; var replacements = { @@ -272,8 +268,8 @@ URLSearchParamsState.prototype = { if (attribute.length) { entry = split(attribute, '='); push(entries, { - key: deserialize(shift(entry)), - value: deserialize(join(entry, '=')) + key: decode(shift(entry)), + value: decode(join(entry, '=')) }); } }