rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
58 lines (50 loc) • 1.87 kB
JavaScript
// One instance for every call: constructing it per description is measurable on large schemas
let nativeSanitizer;
export function sanitizeHTML(htmlString) {
if (typeof htmlString !== "string") {
return "";
}
// Use native Sanitizer API if supported
if (typeof window !== "undefined" && window.Sanitizer && Element.prototype.setHTML) {
try {
const el = document.createElement("div");
nativeSanitizer ??= new window.Sanitizer({ allowAttributes: { "*": ["*"] } });
el.setHTML(htmlString, { sanitizer: nativeSanitizer });
return el.innerHTML;
} catch {
// If Sanitizer with options throws, fall through to fallback shim
}
}
// Minimal shim for unsupported browsers to prevent basic XSS
let doc;
if (typeof DOMParser !== "undefined") {
doc = new DOMParser().parseFromString(htmlString, "text/html");
} else {
// Fallback for SSR/Node if needed, though this is primarily browser-side
return htmlString;
}
// Remove script tags
const scripts = doc.querySelectorAll("script");
for (let i = scripts.length - 1; i >= 0; i--) {
scripts[i].parentNode.removeChild(scripts[i]);
}
// Remove on* attributes and javascript: URIs
const allElements = doc.querySelectorAll("*");
for (let i = 0; i < allElements.length; i++) {
const el = allElements[i];
for (let j = el.attributes.length - 1; j >= 0; j--) {
const attr = el.attributes[j];
const attrName = attr.name.toLowerCase();
const attrValue = attr.value.toLowerCase();
if (attrName.startsWith("on")) {
el.removeAttribute(attr.name);
} else if (
(attrName === "href" || attrName === "src") &&
(attrValue.includes("javascript:") || attrValue.includes("data:text/html"))
) {
el.removeAttribute(attr.name);
}
}
}
return doc.body.innerHTML;
}