core-js
Version:
Standard library
172 lines (142 loc) • 4.97 kB
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
};