Spaces:
Paused
Paused
| ; | |
| const { isASCIIHex } = require("./infra"); | |
| const { utf8Encode } = require("./encoding"); | |
| function p(char) { | |
| return char.codePointAt(0); | |
| } | |
| // https://url.spec.whatwg.org/#percent-encode | |
| function percentEncode(c) { | |
| let hex = c.toString(16).toUpperCase(); | |
| if (hex.length === 1) { | |
| hex = `0${hex}`; | |
| } | |
| return `%${hex}`; | |
| } | |
| // https://url.spec.whatwg.org/#percent-decode | |
| function percentDecodeBytes(input) { | |
| const output = new Uint8Array(input.byteLength); | |
| let outputIndex = 0; | |
| for (let i = 0; i < input.byteLength; ++i) { | |
| const byte = input[i]; | |
| if (byte !== 0x25) { | |
| output[outputIndex++] = byte; | |
| } else if (byte === 0x25 && (!isASCIIHex(input[i + 1]) || !isASCIIHex(input[i + 2]))) { | |
| output[outputIndex++] = byte; | |
| } else { | |
| const bytePoint = parseInt(String.fromCodePoint(input[i + 1], input[i + 2]), 16); | |
| output[outputIndex++] = bytePoint; | |
| i += 2; | |
| } | |
| } | |
| return output.slice(0, outputIndex); | |
| } | |
| // https://url.spec.whatwg.org/#string-percent-decode | |
| function percentDecodeString(input) { | |
| const bytes = utf8Encode(input); | |
| return percentDecodeBytes(bytes); | |
| } | |
| // https://url.spec.whatwg.org/#c0-control-percent-encode-set | |
| function isC0ControlPercentEncode(c) { | |
| return c <= 0x1F || c > 0x7E; | |
| } | |
| // https://url.spec.whatwg.org/#fragment-percent-encode-set | |
| const extraFragmentPercentEncodeSet = new Set([p(" "), p("\""), p("<"), p(">"), p("`")]); | |
| function isFragmentPercentEncode(c) { | |
| return isC0ControlPercentEncode(c) || extraFragmentPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#query-percent-encode-set | |
| const extraQueryPercentEncodeSet = new Set([p(" "), p("\""), p("#"), p("<"), p(">")]); | |
| const extraQueryPercentEncodeChars = String.fromCodePoint(...[...extraQueryPercentEncodeSet].sort()); | |
| function isQueryPercentEncode(c) { | |
| return isC0ControlPercentEncode(c) || extraQueryPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#special-query-percent-encode-set | |
| const extraSpecialQueryPercentEncodeSet = new Set([...extraQueryPercentEncodeSet, p("'")]); | |
| const extraSpecialQueryPercentEncodeChars = String.fromCodePoint(...[...extraSpecialQueryPercentEncodeSet].sort()); | |
| function isSpecialQueryPercentEncode(c) { | |
| return isC0ControlPercentEncode(c) || extraSpecialQueryPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#path-percent-encode-set | |
| const extraPathPercentEncodeSet = new Set([p("?"), p("`"), p("{"), p("}"), p("^")]); | |
| function isPathPercentEncode(c) { | |
| return isQueryPercentEncode(c) || extraPathPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#userinfo-percent-encode-set | |
| const extraUserinfoPercentEncodeSet = | |
| new Set([p("/"), p(":"), p(";"), p("="), p("@"), p("["), p("\\"), p("]"), p("|")]); | |
| function isUserinfoPercentEncode(c) { | |
| return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#component-percent-encode-set | |
| const extraComponentPercentEncodeSet = new Set([p("$"), p("%"), p("&"), p("+"), p(",")]); | |
| function isComponentPercentEncode(c) { | |
| return isUserinfoPercentEncode(c) || extraComponentPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#application-x-www-form-urlencoded-percent-encode-set | |
| const extraURLEncodedPercentEncodeSet = new Set([p("!"), p("'"), p("("), p(")"), p("~")]); | |
| function isURLEncodedPercentEncode(c) { | |
| return isComponentPercentEncode(c) || extraURLEncodedPercentEncodeSet.has(c); | |
| } | |
| // https://url.spec.whatwg.org/#code-point-percent-encode-after-encoding | |
| // https://url.spec.whatwg.org/#utf-8-percent-encode | |
| // Assuming encoding is always utf-8 allows us to trim one of the logic branches. TODO: support encoding. | |
| // The "-Internal" variant here has code points as JS strings. The external version used by other files has code points | |
| // as JS numbers, like the rest of the codebase. | |
| function utf8PercentEncodeCodePointInternal(codePoint, percentEncodePredicate) { | |
| const bytes = utf8Encode(codePoint); | |
| let output = ""; | |
| for (const byte of bytes) { | |
| // Our percentEncodePredicate operates on bytes, not code points, so this is slightly different from the spec. | |
| if (!percentEncodePredicate(byte)) { | |
| output += String.fromCharCode(byte); | |
| } else { | |
| output += percentEncode(byte); | |
| } | |
| } | |
| return output; | |
| } | |
| function utf8PercentEncodeCodePoint(codePoint, percentEncodePredicate) { | |
| return utf8PercentEncodeCodePointInternal(String.fromCodePoint(codePoint), percentEncodePredicate); | |
| } | |
| // https://url.spec.whatwg.org/#string-percent-encode-after-encoding | |
| // https://url.spec.whatwg.org/#string-utf-8-percent-encode | |
| function utf8PercentEncodeString(input, percentEncodePredicate, spaceAsPlus = false) { | |
| let output = ""; | |
| for (const codePoint of input) { | |
| if (spaceAsPlus && codePoint === " ") { | |
| output += "+"; | |
| } else { | |
| output += utf8PercentEncodeCodePointInternal(codePoint, percentEncodePredicate); | |
| } | |
| } | |
| return output; | |
| } | |
| module.exports = { | |
| isC0ControlPercentEncode, | |
| isFragmentPercentEncode, | |
| isQueryPercentEncode, | |
| isSpecialQueryPercentEncode, | |
| isPathPercentEncode, | |
| isUserinfoPercentEncode, | |
| isURLEncodedPercentEncode, | |
| extraQueryPercentEncodeChars, | |
| extraSpecialQueryPercentEncodeChars, | |
| percentDecodeString, | |
| percentDecodeBytes, | |
| utf8PercentEncodeString, | |
| utf8PercentEncodeCodePoint | |
| }; | |