Files
HomeAssistantVS/custom_components/maintenance_supporter/frontend-src/helpers/url.ts
T

63 lines
2.3 KiB
TypeScript

/** Shared URL-safety guard for rendering user-supplied links.
*
* A user-entered URL (object/task documentation, spare-part shopping link,
* document weblink) must never be linkified as `javascript:`/`data:`/
* protocol-relative, else it is a stored-XSS vector. The
* `X && /^https?:\/\//i.test(X)` idiom was inlined at ~8 render sites; one
* forgotten copy is a hole, so it lives here once.
*
* Since #207 a documentation link or a document's web link may also open an
* app (`obsidian://`, `onenote:`, `mailto:`): every scheme passes except the
* blocked ones, the twin of `helpers/url_safety.py` (a tripwire pins the two).
*/
/** Schemes a link may never use: they run code in the page, open the
* device's own files or the browser's internals, or start Windows handlers
* attackers have abused. */
export const BLOCKED_LINK_SCHEMES: ReadonlySet<string> = new Set([
"about",
"blob",
"data",
"file",
"filesystem",
"javascript",
"livescript",
"ms-appinstaller",
"ms-cxh",
"ms-cxh-full",
"ms-msdt",
"ms-officecmd",
"search",
"search-ms",
"view-source",
"vbscript",
]);
/** The link as a browser reads it before looking at the scheme: ASCII tabs
* and newlines anywhere dropped, control characters and spaces at either
* end trimmed ("java\tscript:" is javascript:). */
export function cleanLink(url: string): string {
// eslint-disable-next-line no-control-regex
return url.replace(/[\t\n\r]/g, "").replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "");
}
/** The lower-cased scheme of an absolute link, else null. Two characters at
* least: "C:\manual.pdf" is a drive path, not a link. */
export function linkScheme(url: string | null | undefined): string | null {
const match = /^([a-z][a-z0-9+.-]+):/i.exec(cleanLink(url || ""));
return match ? match[1].toLowerCase() : null;
}
/** An absolute link safe to open: http(s) or an app link — never one of the
* blocked schemes, never relative. */
export function isSafeLink(url: string | null | undefined): url is string {
const scheme = linkScheme(url);
return scheme !== null && !BLOCKED_LINK_SCHEMES.has(scheme);
}
/** Only an absolute http(s) URL — for links that must be web pages (a
* spare part's shop page). */
export function isSafeHttpUrl(url: string | null | undefined): url is string {
return !!url && /^https?:\/\//i.test(url);
}