Files
RenewCMS/web/src/lib/articleLinkUrl.ts
T

40 lines
1.3 KiB
TypeScript

const WEB_URL = /^https?:\/\/[^\s\\/?#]+(?:[/?#]\S*)?$/i
const MAIL_URL = /^mailto:[^\s@/?#]+@[^\s@]+$/i
const SITE_PATH = /^\/(?![/\\])\S*$/
const ANCHOR = /^#\S+$/
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/g
const HAS_CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/
const SCHEME = /^([a-z][a-z0-9+.-]*):/i
const STORED_SCHEMES = ['http', 'https', 'mailto', 'tel', 'ftp']
export function normalizeLinkUrl(raw: string): string | null {
const url = raw.trim()
if (HAS_CONTROL_CHARACTER.test(url)) {
return null
}
if (WEB_URL.test(url) || MAIL_URL.test(url) || SITE_PATH.test(url) || ANCHOR.test(url)) {
return url
}
return null
}
export function isStoredHrefSafe(raw: string): boolean {
const visible = raw.replace(CONTROL_CHARACTERS, '').trim().replace(/\s/g, '')
const scheme = SCHEME.exec(visible)
if (scheme === null) {
return visible !== ''
}
return STORED_SCHEMES.includes(scheme[1]!.toLowerCase())
}
export function neutralizeUnsafeLinks(html: string): string {
const document = new DOMParser().parseFromString(html, 'text/html')
for (const anchor of document.body.querySelectorAll('a')) {
if (isStoredHrefSafe(anchor.getAttribute('href') ?? '')) {
continue
}
anchor.replaceWith(...anchor.childNodes)
}
return document.body.innerHTML
}