"use strict"; 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(">")]); function isQueryPercentEncode(c) { return isC0ControlPercentEncode(c) || extraQueryPercentEncodeSet.has(c); } // https://url.spec.whatwg.org/#special-query-percent-encode-set function isSpecialQueryPercentEncode(c) { return isQueryPercentEncode(c) || c === p("'"); } // https://url.spec.whatwg.org/#path-percent-encode-set const extraPathPercentEncodeSet = new Set([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("^"), 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, percentDecodeString, percentDecodeBytes, utf8PercentEncodeString, utf8PercentEncodeCodePoint };