- Created a .cursorignore file to manage local environment files. - Updated .env.example to reflect changes in the public app URL. - Modified the gmail workspace configuration to include the drive-suite path. - Enhanced email view components to support attachment handling and fallback for plain text bodies. - Improved user experience by updating attachment display logic and integrating inline attachment support.
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
/**
|
|
* Remove scripts and other executable markup before displaying mail HTML.
|
|
* DOM-only (no broad regex) so newsletter markup is not corrupted.
|
|
*/
|
|
|
|
const EXECUTABLE_SELECTOR =
|
|
"script, iframe, object, embed, frame, frameset, link[rel='import'], link[as='script']"
|
|
|
|
const EVENT_HANDLER_ATTR = /^on/i
|
|
const URI_ATTRS = /^(?:href|src|srcset|data|action|formaction|xlink:href)$/i
|
|
const JAVASCRIPT_URI = /^javascript:/i
|
|
|
|
function stripExecutableFromDocument(doc: Document): void {
|
|
doc.querySelectorAll(EXECUTABLE_SELECTOR).forEach((el) => el.remove())
|
|
doc.querySelectorAll("svg script").forEach((el) => el.remove())
|
|
|
|
// <noscript> content is visible when scripts are blocked — unwrap it
|
|
for (const ns of doc.querySelectorAll("noscript")) {
|
|
const fragment = doc.createDocumentFragment()
|
|
const tpl = doc.createElement("template")
|
|
tpl.innerHTML = ns.textContent ?? ""
|
|
fragment.append(...Array.from(tpl.content.childNodes))
|
|
ns.replaceWith(fragment)
|
|
}
|
|
|
|
for (const el of doc.querySelectorAll("*")) {
|
|
for (const attr of [...el.attributes]) {
|
|
if (EVENT_HANDLER_ATTR.test(attr.name)) {
|
|
el.removeAttribute(attr.name)
|
|
continue
|
|
}
|
|
if (URI_ATTRS.test(attr.name) && JAVASCRIPT_URI.test(attr.value.trim())) {
|
|
el.removeAttribute(attr.name)
|
|
}
|
|
}
|
|
const style = el.getAttribute("style")
|
|
if (style && JAVASCRIPT_URI.test(style)) {
|
|
el.setAttribute(
|
|
"style",
|
|
style.replace(/javascript:[^;'")]+/gi, "about:blank")
|
|
)
|
|
}
|
|
}
|
|
|
|
for (const meta of doc.querySelectorAll('meta[http-equiv]')) {
|
|
if (meta.getAttribute("http-equiv")?.toLowerCase() !== "refresh") continue
|
|
const content = meta.getAttribute("content") ?? ""
|
|
if (JAVASCRIPT_URI.test(content)) meta.remove()
|
|
}
|
|
}
|
|
|
|
/** Strip scripts, nested frames, and inline handlers from an HTML fragment. */
|
|
export function stripExecutableEmailHtml(html: string): string {
|
|
if (!html || !html.trim()) return html
|
|
|
|
if (typeof DOMParser === "undefined") {
|
|
return html
|
|
}
|
|
|
|
try {
|
|
const doc = new DOMParser().parseFromString(html, "text/html")
|
|
stripExecutableFromDocument(doc)
|
|
return doc.body?.innerHTML ?? html
|
|
} catch {
|
|
return html
|
|
}
|
|
}
|