1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3export 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
15const URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/;
16const HOST_WITH_PORT_REGEX = /.*:\d+$/;
17const PATH_REGEX = /^\//;
18
19export 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
69export function urlHasHost(token: string): boolean {
70 const host = URL_REGEX.exec(token);
71 return Boolean(host && host.groups && host.groups.host);
72}