diff options
Diffstat (limited to 'js/url.ts')
-rw-r--r-- | js/url.ts | 376 |
1 files changed, 0 insertions, 376 deletions
diff --git a/js/url.ts b/js/url.ts deleted file mode 100644 index f22198da4..000000000 --- a/js/url.ts +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. -import * as urlSearchParams from "./url_search_params.ts"; -import * as domTypes from "./dom_types.ts"; -import { getRandomValues } from "./get_random_values.ts"; -import { window } from "./window.ts"; - -interface URLParts { - protocol: string; - username: string; - password: string; - hostname: string; - port: string; - path: string; - query: string | null; - hash: string; -} - -const patterns = { - protocol: "(?:([^:/?#]+):)", - authority: "(?://([^/?#]*))", - path: "([^?#]*)", - query: "(\\?[^#]*)", - hash: "(#.*)", - - authentication: "(?:([^:]*)(?::([^@]*))?@)", - hostname: "([^:]+)", - port: "(?::(\\d+))" -}; - -const urlRegExp = new RegExp( - `^${patterns.protocol}?${patterns.authority}?${patterns.path}${ - patterns.query - }?${patterns.hash}?` -); - -const authorityRegExp = new RegExp( - `^${patterns.authentication}?${patterns.hostname}${patterns.port}?$` -); - -const searchParamsMethods: Array<keyof urlSearchParams.URLSearchParams> = [ - "append", - "delete", - "set" -]; - -function parse(url: string): URLParts | undefined { - const urlMatch = urlRegExp.exec(url); - if (urlMatch) { - const [, , authority] = urlMatch; - const authorityMatch = authority - ? authorityRegExp.exec(authority) - : [null, null, null, null, null]; - if (authorityMatch) { - return { - protocol: urlMatch[1] || "", - username: authorityMatch[1] || "", - password: authorityMatch[2] || "", - hostname: authorityMatch[3] || "", - port: authorityMatch[4] || "", - path: urlMatch[3] || "", - query: urlMatch[4] || "", - hash: urlMatch[5] || "" - }; - } - } - return undefined; -} - -// Based on https://github.com/kelektiv/node-uuid -// TODO(kevinkassimo): Use deno_std version once possible. -function generateUUID(): string { - return "00000000-0000-4000-8000-000000000000".replace( - /[0]/g, - (): string => - // random integer from 0 to 15 as a hex digit. - (getRandomValues(new Uint8Array(1))[0] % 16).toString(16) - ); -} - -// Keep it outside of URL to avoid any attempts of access. -export const blobURLMap = new Map<string, domTypes.Blob>(); - -function isAbsolutePath(path: string): boolean { - return path.startsWith("/"); -} - -// Resolves `.`s and `..`s where possible. -// Preserves repeating and trailing `/`s by design. -function normalizePath(path: string): string { - const isAbsolute = isAbsolutePath(path); - path = path.replace(/^\//, ""); - const pathSegments = path.split("/"); - - const newPathSegments: string[] = []; - for (let i = 0; i < pathSegments.length; i++) { - const previous = newPathSegments[newPathSegments.length - 1]; - if ( - pathSegments[i] == ".." && - previous != ".." && - (previous != undefined || isAbsolute) - ) { - newPathSegments.pop(); - } else if (pathSegments[i] != ".") { - newPathSegments.push(pathSegments[i]); - } - } - - let newPath = newPathSegments.join("/"); - if (!isAbsolute) { - if (newPathSegments.length == 0) { - newPath = "."; - } - } else { - newPath = `/${newPath}`; - } - return newPath; -} - -// Standard URL basing logic, applied to paths. -function resolvePathFromBase(path: string, basePath: string): string { - const normalizedPath = normalizePath(path); - if (isAbsolutePath(normalizedPath)) { - return normalizedPath; - } - const normalizedBasePath = normalizePath(basePath); - if (!isAbsolutePath(normalizedBasePath)) { - throw new TypeError("Base path must be absolute."); - } - - // Special case. - if (path == "") { - return normalizedBasePath; - } - - // Remove everything after the last `/` in `normalizedBasePath`. - const prefix = normalizedBasePath.replace(/[^\/]*$/, ""); - // If `normalizedPath` ends with `.` or `..`, add a trailing space. - const suffix = normalizedPath.replace(/(?<=(^|\/)(\.|\.\.))$/, "/"); - - return normalizePath(prefix + suffix); -} - -export class URL { - private _parts: URLParts; - private _searchParams!: urlSearchParams.URLSearchParams; - - private _updateSearchParams(): void { - const searchParams = new urlSearchParams.URLSearchParams(this.search); - - for (const methodName of searchParamsMethods) { - /* eslint-disable @typescript-eslint/no-explicit-any */ - const method: (...args: any[]) => any = searchParams[methodName]; - searchParams[methodName] = (...args: unknown[]): any => { - method.apply(searchParams, args); - this.search = searchParams.toString(); - }; - /* eslint-enable */ - } - this._searchParams = searchParams; - - // convert to `any` that has avoided the private limit - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (this._searchParams as any).url = this; - } - - get hash(): string { - return this._parts.hash; - } - - set hash(value: string) { - value = unescape(String(value)); - if (!value) { - this._parts.hash = ""; - } else { - if (value.charAt(0) !== "#") { - value = `#${value}`; - } - // hashes can contain % and # unescaped - this._parts.hash = escape(value) - .replace(/%25/g, "%") - .replace(/%23/g, "#"); - } - } - - get host(): string { - return `${this.hostname}${this.port ? `:${this.port}` : ""}`; - } - - set host(value: string) { - value = String(value); - const url = new URL(`http://${value}`); - this._parts.hostname = url.hostname; - this._parts.port = url.port; - } - - get hostname(): string { - return this._parts.hostname; - } - - set hostname(value: string) { - value = String(value); - this._parts.hostname = encodeURIComponent(value); - } - - get href(): string { - const authentication = - this.username || this.password - ? `${this.username}${this.password ? ":" + this.password : ""}@` - : ""; - - return `${this.protocol}//${authentication}${this.host}${this.pathname}${ - this.search - }${this.hash}`; - } - - set href(value: string) { - value = String(value); - if (value !== this.href) { - const url = new URL(value); - this._parts = { ...url._parts }; - this._updateSearchParams(); - } - } - - get origin(): string { - return `${this.protocol}//${this.host}`; - } - - get password(): string { - return this._parts.password; - } - - set password(value: string) { - value = String(value); - this._parts.password = encodeURIComponent(value); - } - - get pathname(): string { - return this._parts.path ? this._parts.path : "/"; - } - - set pathname(value: string) { - value = unescape(String(value)); - if (!value || value.charAt(0) !== "/") { - value = `/${value}`; - } - // paths can contain % unescaped - this._parts.path = escape(value).replace(/%25/g, "%"); - } - - get port(): string { - return this._parts.port; - } - - set port(value: string) { - const port = parseInt(String(value), 10); - this._parts.port = isNaN(port) - ? "" - : Math.max(0, port % 2 ** 16).toString(); - } - - get protocol(): string { - return `${this._parts.protocol}:`; - } - - set protocol(value: string) { - value = String(value); - if (value) { - if (value.charAt(value.length - 1) === ":") { - value = value.slice(0, -1); - } - this._parts.protocol = encodeURIComponent(value); - } - } - - get search(): string { - if (this._parts.query === null || this._parts.query === "") { - return ""; - } - - return this._parts.query; - } - - set search(value: string) { - value = String(value); - let query: string | null; - - if (value === "") { - query = null; - } else if (value.charAt(0) !== "?") { - query = `?${value}`; - } else { - query = value; - } - - this._parts.query = query; - this._updateSearchParams(); - } - - get username(): string { - return this._parts.username; - } - - set username(value: string) { - value = String(value); - this._parts.username = encodeURIComponent(value); - } - - get searchParams(): urlSearchParams.URLSearchParams { - return this._searchParams; - } - - constructor(url: string, base?: string | URL) { - let baseParts: URLParts | undefined; - if (base) { - baseParts = typeof base === "string" ? parse(base) : base._parts; - if (!baseParts || baseParts.protocol == "") { - throw new TypeError("Invalid base URL."); - } - } - - const urlParts = parse(url); - if (!urlParts) { - throw new TypeError("Invalid URL."); - } - - if (urlParts.protocol) { - this._parts = urlParts; - } else if (baseParts) { - this._parts = { - protocol: baseParts.protocol, - username: baseParts.username, - password: baseParts.password, - hostname: baseParts.hostname, - port: baseParts.port, - path: resolvePathFromBase(urlParts.path, baseParts.path || "/"), - query: urlParts.query, - hash: urlParts.hash - }; - } else { - throw new TypeError("URL requires a base URL."); - } - this._updateSearchParams(); - } - - toString(): string { - return this.href; - } - - toJSON(): string { - return this.href; - } - - // TODO(kevinkassimo): implement MediaSource version in the future. - static createObjectURL(b: domTypes.Blob): string { - const origin = window.location.origin || "http://deno-opaque-origin"; - const key = `blob:${origin}/${generateUUID()}`; - blobURLMap.set(key, b); - return key; - } - - static revokeObjectURL(url: string): void { - let urlObject; - try { - urlObject = new URL(url); - } catch { - throw new TypeError("Provided URL string is not valid"); - } - if (urlObject.protocol !== "blob:") { - return; - } - // Origin match check seems irrelevant for now, unless we implement - // persisten storage for per window.location.origin at some point. - blobURLMap.delete(url); - } -} |