172 lines
4.9 KiB
JavaScript
172 lines
4.9 KiB
JavaScript
'use strict';
|
|
// TODO: in core-js@4, move /modules/ dependencies to public entries for better optimization by tools like `preset-env`
|
|
require('../modules/es.string.from-code-point');
|
|
var getBuiltIn = require('../internals/get-built-in');
|
|
var uncurryThis = require('../internals/function-uncurry-this');
|
|
|
|
var fromCharCode = String.fromCharCode;
|
|
var fromCodePoint = getBuiltIn('String', 'fromCodePoint');
|
|
var $encodeURIComponent = encodeURIComponent;
|
|
var $parseInt = parseInt;
|
|
var charAt = uncurryThis(''.charAt);
|
|
var push = uncurryThis([].push);
|
|
var replace = uncurryThis(''.replace);
|
|
var stringSlice = uncurryThis(''.slice);
|
|
var exec = uncurryThis(/./.exec);
|
|
|
|
var FALLBACK_REPLACER = '\uFFFD';
|
|
var VALID_HEX = /^[0-9a-f]+$/i;
|
|
// a surrogate pair is matched first, so a one-unit match is always a lone surrogate
|
|
var SURROGATE = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g;
|
|
|
|
var parseHexOctet = function (string, start) {
|
|
var substr = stringSlice(string, start, start + 2);
|
|
if (!exec(VALID_HEX, substr)) return NaN;
|
|
|
|
return $parseInt(substr, 16);
|
|
};
|
|
|
|
var getLeadingOnes = function (octet) {
|
|
var count = 0;
|
|
for (var mask = 0x80; mask > 0 && (octet & mask) !== 0; mask >>= 1) {
|
|
count++;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
var utf8Decode = function (octets) {
|
|
var codePoint = null;
|
|
var length = octets.length;
|
|
|
|
switch (length) {
|
|
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;
|
|
}
|
|
|
|
// reject surrogates, overlong encodings, and out-of-range codepoints
|
|
if (codePoint === null
|
|
|| codePoint > 0x10FFFF
|
|
|| (codePoint >= 0xD800 && codePoint <= 0xDFFF)
|
|
|| codePoint < (length > 3 ? 0x10000 : length > 2 ? 0x800 : length > 1 ? 0x80 : 0)
|
|
) return null;
|
|
|
|
return codePoint;
|
|
};
|
|
|
|
var replaceLoneSurrogate = function (chunk) {
|
|
return chunk.length === 2 ? chunk : FALLBACK_REPLACER;
|
|
};
|
|
|
|
// https://url.spec.whatwg.org/#percent-decode
|
|
/* eslint-disable max-depth -- ok */
|
|
var decode = function (input) {
|
|
var length = input.length;
|
|
var result = '';
|
|
var i = 0;
|
|
|
|
while (i < length) {
|
|
var decodedChar = charAt(input, i);
|
|
|
|
if (decodedChar === '%') {
|
|
if (charAt(input, i + 1) === '%' || i + 3 > length) {
|
|
result += '%';
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
var octet = parseHexOctet(input, i + 1);
|
|
|
|
// eslint-disable-next-line no-self-compare -- NaN check
|
|
if (octet !== octet) {
|
|
result += decodedChar;
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
i += 2;
|
|
var byteSequenceLength = getLeadingOnes(octet);
|
|
|
|
if (byteSequenceLength === 0) {
|
|
decodedChar = fromCharCode(octet);
|
|
} 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 || charAt(input, i) !== '%') break;
|
|
|
|
var nextByte = parseHexOctet(input, i + 1);
|
|
|
|
// eslint-disable-next-line no-self-compare -- NaN check
|
|
if (nextByte !== nextByte || nextByte > 191 || nextByte < 128) break;
|
|
|
|
// https://encoding.spec.whatwg.org/#utf-8-decoder - position-specific byte ranges
|
|
if (sequenceIndex === 1) {
|
|
if (octet === 0xE0 && nextByte < 0xA0) break;
|
|
if (octet === 0xED && nextByte > 0x9F) break;
|
|
if (octet === 0xF0 && nextByte < 0x90) break;
|
|
if (octet === 0xF4 && nextByte > 0x8F) break;
|
|
}
|
|
|
|
push(octets, nextByte);
|
|
i += 2;
|
|
sequenceIndex++;
|
|
}
|
|
|
|
if (octets.length !== byteSequenceLength) {
|
|
result += FALLBACK_REPLACER;
|
|
continue;
|
|
}
|
|
|
|
var codePoint = utf8Decode(octets);
|
|
if (codePoint === null) {
|
|
for (var replacement = 0; replacement < byteSequenceLength; replacement++) result += FALLBACK_REPLACER;
|
|
i++;
|
|
continue;
|
|
} else {
|
|
decodedChar = fromCodePoint(codePoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
result += decodedChar;
|
|
i++;
|
|
}
|
|
|
|
return result;
|
|
};
|
|
/* eslint-enable max-depth -- ok */
|
|
|
|
// https://url.spec.whatwg.org/#string-percent-encode-after-encoding
|
|
// a lone surrogate is the only input `encodeURIComponent` throws on, and the UTF-8
|
|
// encoder replaces it - so the throw selects the slow path instead of a per-call scan
|
|
var encode = function (input) {
|
|
try {
|
|
return $encodeURIComponent(input);
|
|
} catch (error) {
|
|
return $encodeURIComponent(replace(input, SURROGATE, replaceLoneSurrogate));
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
decode: decode,
|
|
encode: encode
|
|
};
|