UNPKG

@nuxtjs/mdc

Version:
70 lines (69 loc) 1.87 kB
export const unsafeTags = [ "object" ]; export const unsafeAttributes = [ "srcdoc", "formaction" ]; export const unsafeLinkPrefix = [ "javascript:", "data:text/html", "vbscript:", "data:text/javascript", "data:text/vbscript", "data:text/css", "data:text/plain", "data:text/xml" ]; function isAnchorLinkAllowed(value) { const decodedUrl = decodeURIComponent(value); const urlSanitized = decodedUrl.replace(/&#x([0-9a-f]+);?/gi, "").replace(/&#(\d+);?/g, "").replace(/&[a-z]+;?/gi, ""); try { const url = new URL(urlSanitized, "http://example.com"); if (url.origin === "http://example.com") { return true; } if (unsafeLinkPrefix.some((prefix) => url.href.toLowerCase().startsWith(prefix))) { return false; } } catch { return false; } return true; } export const validateProp = (attribute, value) => { attribute = attribute.toLowerCase().replace(/^(:|v-bind:)/, "").replace(/^(@|v-on:)/, "on"); if (attribute.startsWith("on") || unsafeAttributes.includes(attribute)) { return false; } if (attribute === "href" || attribute === "src" || attribute === "xlinkhref") { return isAnchorLinkAllowed(value); } return true; }; export const validateProps = (type, props) => { if (unsafeTags.includes(type)) { return {}; } if (!props) { return {}; } props = Object.fromEntries( Object.entries(props).filter(([name, value]) => { if (props?.[`:${name}`]) { return false; } const isValid = validateProp(name, value); if (!isValid) { console.warn(`[@nuxtjs/mdc] removing unsafe attribute: ${name}="${value}"`); } return isValid; }) ); if (type === "pre") { if (typeof props.highlights === "string") { props.highlights = props.highlights.split(" ").map((i) => Number.parseInt(i)); } } return props; };