diff options
| author | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
|---|---|---|
| committer | rxliuli <rxliuli@gmail.com> | 2025-11-04 05:03:50 +0800 |
| commit | bce557cc2dc767628bed6aac87301a1be7c5431b (patch) | |
| tree | b51a051228d01fe3306cd7626d4a96768aadb944 /node_modules/@jet/environment/util/urls.js | |
init commit
Diffstat (limited to 'node_modules/@jet/environment/util/urls.js')
| -rw-r--r-- | node_modules/@jet/environment/util/urls.js | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/node_modules/@jet/environment/util/urls.js b/node_modules/@jet/environment/util/urls.js new file mode 100644 index 0000000..7e3aafc --- /dev/null +++ b/node_modules/@jet/environment/util/urls.js @@ -0,0 +1,430 @@ +"use strict"; +// MARK: - Parsing Regular Expressions +Object.defineProperty(exports, "__esModule", { value: true }); +exports.URL = exports.QueryHandling = void 0; +const optional_1 = require("../types/optional"); +const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i; +const queryParamRegex = /([^=?&]+)=?([^&]*)/g; +const componentOrder = ["hash", "query", "pathname", "host"]; +/** + * Defines how query parameters should be parsed and encoded. + */ +var QueryHandling; +(function (QueryHandling) { + /** + * Handle according to `application/x-www-form-urlencoded` rules (HTML forms). + * + * This is the **default decoding mode** for backward compatibility. + * + * **Example:** + * ```typescript + * // Input: "?search=hello+world&category=news+articles" + * // Parsed: { search: "hello world", category: "news articles" } + * // Output: "?search=hello+world&category=news+articles" + * ``` + * + * @see {@link https://url.spec.whatwg.org/#concept-urlencoded-parser WHATWG URL Standard} + */ + QueryHandling["FORM_ENCODED"] = "form-encoded"; + /** + * Handle according to RFC 3986 URI specification rules. + * + * This is the **default encoding mode** for backward compatibility. + * + * **Example:** + * ```typescript + * // Input: "?search=hello+world&math=2+2%3D4" + * // Parsed: { search: "hello+world", math: "2+2=4" } + * // Output: "?search=hello+world&math=2+2%3D4" + * ``` + * + * @see {@link https://tools.ietf.org/html/rfc3986#section-3.4 RFC 3986 Section 3.4} + */ + QueryHandling["RFC3986"] = "rfc3986"; +})(QueryHandling = exports.QueryHandling || (exports.QueryHandling = {})); +class URL { + constructor(url, options) { + var _a; + this.query = {}; + this.queryHandling = options === null || options === void 0 ? void 0 : options.queryHandling; + if ((0, optional_1.isNothing)(url)) { + return; + } + // Split the protocol from the rest of the urls + let remainder = url; + const match = protocolRegex.exec(url); + if ((0, optional_1.isSome)(match)) { + // Pull out the protocol + let protocol = match[1]; + if (protocol !== null && protocol !== undefined) { + protocol = protocol.split(":")[0]; + } + this.protocol = protocol !== null && protocol !== void 0 ? protocol : undefined; + // Save the remainder + remainder = (_a = match[3]) !== null && _a !== void 0 ? _a : undefined; + } + // Then match each component in a specific order + let parse = { remainder: remainder, result: undefined }; + for (const component of componentOrder) { + if (parse === undefined || parse.remainder === undefined) { + break; + } + switch (component) { + case "hash": { + parse = splitUrlComponent(parse.remainder, "#", "suffix"); + this.hash = parse === null || parse === void 0 ? void 0 : parse.result; + break; + } + case "query": { + parse = splitUrlComponent(parse.remainder, "?", "suffix"); + if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) { + this.query = URL.queryFromString(parse.result, this.queryHandling); + } + break; + } + case "pathname": { + parse = splitUrlComponent(parse.remainder, "/", "suffix"); + if ((parse === null || parse === void 0 ? void 0 : parse.result) !== undefined) { + // Replace the initial /, since paths require it + this.pathname = "/" + parse.result; + } + break; + } + case "host": { + const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix"); + const userInfo = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.result; + const hostPort = authorityParse === null || authorityParse === void 0 ? void 0 : authorityParse.remainder; + if (userInfo !== undefined) { + const userInfoSplit = userInfo.split(":"); + this.username = decodeURIComponent(userInfoSplit[0]); + this.password = decodeURIComponent(userInfoSplit[1]); + } + if (hostPort !== undefined) { + const hostPortSplit = hostPort.split(":"); + this.host = hostPortSplit[0]; + this.port = hostPortSplit[1]; + } + break; + } + default: { + throw new Error("Unhandled case!"); + } + } + } + } + get(component) { + switch (component) { + // Exhaustive match to make sure TS property minifiers and other + // transformer plugins do not break this code. + case "protocol": + return this.protocol; + case "username": + return this.username; + case "password": + return this.password; + case "port": + return this.port; + case "pathname": + return this.pathname; + case "query": + return this.query; + case "hash": + return this.hash; + default: + // The fallback for component which is not a property of URL object. + return this[component]; + } + } + set(component, value) { + if (value === undefined) { + return this; + } + if (component === "query") { + if (typeof value === "string") { + value = URL.queryFromString(value, this.queryHandling); + } + } + switch (component) { + // Exhaustive match to make sure TS property minifiers and other + // transformer plugins do not break this code. + case "protocol": + this.protocol = value; + break; + case "username": + this.username = value; + break; + case "password": + this.password = value; + break; + case "port": + this.port = value; + break; + case "pathname": + this.pathname = value; + break; + case "query": + this.query = value; + break; + case "hash": + this.hash = value; + break; + default: + // The fallback for component which is not a property of URL object. + this[component] = value; + break; + } + return this; + } + append(component, value) { + let existingValue = this.get(component); + let newValue; + if (component === "query") { + if (existingValue === undefined) { + existingValue = {}; + } + if (typeof value === "string") { + value = URL.queryFromString(value, this.queryHandling); + } + if (typeof existingValue === "string") { + newValue = { existingValue, ...value }; + } + else { + newValue = { ...existingValue, ...value }; + } + } + else { + if (existingValue === undefined) { + existingValue = ""; + } + let existingValueString = existingValue; + if (existingValueString === undefined) { + existingValueString = ""; + } + let newValueString = existingValueString; + if (component === "pathname") { + const pathLength = existingValueString.length; + if (pathLength === 0 || existingValueString[pathLength - 1] !== "/") { + newValueString += "/"; + } + } + // The component is not "query" so we treat value as string. + // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-plus-operands + newValueString += value; + newValue = newValueString; + } + return this.set(component, newValue); + } + param(key, value) { + if (key === null) { + return this; + } + if (this.query === undefined) { + this.query = {}; + } + if (value === undefined) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.query[key]; + } + else { + this.query[key] = value; + } + return this; + } + removeParam(key) { + if (key === undefined || this.query === undefined) { + return this; + } + if (key in this.query) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.query[key]; + } + return this; + } + path(value) { + return this.append("pathname", value); + } + pathExtension() { + var _a, _b; + // Extract path extension if one exists + if (this.pathname === undefined) { + return undefined; + } + const lastFilenameComponents = (_b = (_a = this.pathname + .split("/") + .filter((item) => item.length > 0) // Remove any double or trailing slashes + .pop()) === null || _a === void 0 ? void 0 : _a.split(".")) !== null && _b !== void 0 ? _b : []; + if (lastFilenameComponents.filter(function (part) { + return part !== ""; + }).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"]) + ) { + return undefined; + } + return lastFilenameComponents.pop(); + } + /** + * Returns the path components of the URL + * @returns An array of non-empty path components from `urls`. + */ + pathComponents() { + if (this.pathname === undefined) { + return []; + } + return this.pathname.split("/").filter((component) => component.length > 0); + } + /** + * Same as toString + * + * @returns A string representation of the URL + */ + build() { + return this.toString(); + } + /** + * Converts the URL to a string + * + * @returns A string representation of the URL + */ + toString() { + let url = ""; + if (this.protocol !== undefined) { + url += this.protocol + "://"; + } + if (this.username !== undefined) { + url += encodeURIComponent(this.username); + if (this.password !== undefined) { + url += ":" + encodeURIComponent(this.password); + } + url += "@"; + } + if (this.host !== undefined) { + url += this.host; + if (this.port !== undefined) { + url += ":" + this.port; + } + } + if (this.pathname !== undefined) { + url += this.pathname; + } + if (this.query !== undefined && Object.keys(this.query).length !== 0) { + url += "?" + URL.toQueryString(this.query, this.queryHandling); + } + if (this.hash !== undefined) { + url += "#" + this.hash; + } + return url; + } + // ---------------- + // Static API + // ---------------- + /** + * Converts a string into a query dictionary + * @param query - The string to parse + * @returns The query dictionary containing the key-value pairs in the query string + */ + static queryFromString(query, queryHandling = QueryHandling.FORM_ENCODED) { + const result = {}; + let parseResult = queryParamRegex.exec(query); + while (parseResult !== null && parseResult.length >= 3) { + let key = parseResult[1]; + let value = parseResult[2]; + // We support the legacy query format for "application/x-www-form-urlencoded" which can represent spaces as "+" symbols. + // https://url.spec.whatwg.org/#concept-urlencoded-parser + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#decoding_query_parameters_from_a_url + // + // For RFC3986 mode, plus signs remain as literal plus signs + if (queryHandling === QueryHandling.FORM_ENCODED) { + key = key.replace(/\+/g, " "); + value = value.replace(/\+/g, " "); + } + const decodedKey = decodeURIComponent(key); + const decodedValue = decodeURIComponent(value); + result[decodedKey] = decodedValue; + parseResult = queryParamRegex.exec(query); + } + return result; + } + /** + * Converts a query dictionary into a query string + * + * @param query - The query dictionary + * @returns The string representation of the query dictionary + */ + static toQueryString(query, queryHandling = QueryHandling.RFC3986) { + let queryString = ""; + let first = true; + for (const key of Object.keys(query)) { + if (!first) { + queryString += "&"; + } + first = false; + queryString += URL.encodeQueryComponent(key, queryHandling); + const value = query[key]; + if (value !== null && value.length > 0) { + queryString += "=" + URL.encodeQueryComponent(value, queryHandling); + } + } + return queryString; + } + /** + * Encode a query parameter key or value according to the specified mode. + * @param component - The key or value to encode + * @param queryHandling - The encoding mode + * @returns The encoded component + */ + static encodeQueryComponent(component, queryHandling) { + if (queryHandling === QueryHandling.FORM_ENCODED) { + // For form-encoded: encode with encodeURIComponent, then convert %20 back to + + return encodeURIComponent(component).replace(/%20/g, "+"); + } + else { + // For RFC 3986: standard percent-encoding (spaces become %20) + return encodeURIComponent(component); + } + } + static from(url) { + return new URL(url); + } + /** + * Convenience method to instantiate a URL from numerous (optional) components + * @param protocol - The protocol type + * @param host - The host name + * @param path - The path + * @param query - The query + * @param hash - The hash + * @param options - Configuration options for URL construction + * @returns The new URL object representing the URL + */ + static fromComponents(protocol, host, path, query, hash, options) { + const url = new URL(undefined, options); + url.protocol = protocol; + url.host = host; + url.pathname = path; + url.query = query !== null && query !== void 0 ? query : {}; + url.hash = hash; + return url; + } +} +exports.URL = URL; +// MARK: - Helpers +function splitUrlComponent(input, marker, style) { + const index = input.indexOf(marker); + let result; + let remainder = input; + if (index !== -1) { + const prefix = input.slice(0, index); + const suffix = input.slice(index + marker.length, input.length); + if (style === "prefix") { + result = prefix; + remainder = suffix; + } + else { + result = suffix; + remainder = prefix; + } + } + return { + result: result, + remainder: remainder, + }; +} +//# sourceMappingURL=urls.js.map
\ No newline at end of file |
