| 1 | // Alchemy modifications are licensed under Apache-2.0. |
| 2 | // This file includes third-party code; see /THIRD_PARTY_LICENSES.md. |
| 3 | export const extractPathname = ( |
| 4 | path = "/", |
| 5 | includeSearch: boolean, |
| 6 | includeHash: boolean, |
| 7 | ): string => { |
| 8 | if (!path.startsWith("/")) { |
| 9 | path = `/${path}`; |
| 10 | } |
| 11 | const url = new URL(`//${path}`, "relative://"); |
| 12 | return `${url.pathname}${includeSearch ? url.search : ""}${includeHash ? url.hash : ""}`; |
| 13 | }; |
| 14 | |
| 15 | const URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/; |
| 16 | const HOST_WITH_PORT_REGEX = /.*:\d+$/; |
| 17 | const PATH_REGEX = /^\//; |
| 18 | |
| 19 | export const validateUrl = ( |
| 20 | token: string, |
| 21 | onlyRelative = false, |
| 22 | disallowPorts = false, |
| 23 | includeSearch = false, |
| 24 | includeHash = false, |
| 25 | ): [undefined, string] | [string, undefined] => { |
| 26 | const host = URL_REGEX.exec(token); |
| 27 | if (host && host.groups && host.groups.host) { |
| 28 | if (onlyRelative) { |
| 29 | return [ |
| 30 | undefined, |
| 31 | `Only relative URLs are allowed. Skipping absolute URL ${token}.`, |
| 32 | ]; |
| 33 | } |
| 34 | |
| 35 | if (disallowPorts && host.groups.host.match(HOST_WITH_PORT_REGEX)) { |
| 36 | return [ |
| 37 | undefined, |
| 38 | `Specifying ports is not supported. Skipping absolute URL ${token}.`, |
| 39 | ]; |
| 40 | } |
| 41 | |
| 42 | return [ |
| 43 | `https://${host.groups.host}${extractPathname(host.groups.path, includeSearch, includeHash)}`, |
| 44 | undefined, |
| 45 | ]; |
| 46 | } else { |
| 47 | if (!token.startsWith("/") && onlyRelative) { |
| 48 | token = `/${token}`; |
| 49 | } |
| 50 | |
| 51 | const path = PATH_REGEX.exec(token); |
| 52 | if (path) { |
| 53 | try { |
| 54 | return [extractPathname(token, includeSearch, includeHash), undefined]; |
| 55 | } catch { |
| 56 | return [undefined, `Error parsing URL segment ${token}. Skipping.`]; |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | return [ |
| 62 | undefined, |
| 63 | onlyRelative |
| 64 | ? "URLs should begin with a forward-slash." |
| 65 | : 'URLs should either be relative (e.g. begin with a forward-slash), or use HTTPS (e.g. begin with "https://").', |
| 66 | ]; |
| 67 | }; |
| 68 | |
| 69 | export function urlHasHost(token: string): boolean { |
| 70 | const host = URL_REGEX.exec(token); |
| 71 | return Boolean(host && host.groups && host.groups.host); |
| 72 | } |