JSFiddle - React, Tailwind, and code Playground
by Jason Miller
HTML
<script src="https://unpkg.com/snarkdown@1.2.2/dist/snarkdown.umd.js"></script>
<label>source: <em>(compiles as you type)</em>
<textarea id="input">
# XSS Mitigation
Add scripts, frames, malformed attributes, etc.
<script>console.log('inline script')</script>
<img onload="console.log('img onload')">
</textarea>
</label>
<label>HTML:
<textarea readonly id="output"></textarea>
</label>
<label>Rendered:
<div readonly id="rendered"></div>
</label>
CSS
html, body {
background: #eee;
font-family: system-ui;
}
label {
display: block;
margin: 10px auto;
width: 90%;
font-weight: bold;
}
textarea {
display: block;
width: 100%;
height: 27vh;
white-space: pre-wrap;
font: 14px 'Source Code Pro', Monaco, 'Courier New', monospace;
}
#rendered {
font-weight: normal;
display: block;
width: 100%;
height: 27vh;
background: #FFF;
border: 1px solid #ccc;
}
em { font-weight: normal; color: #555; }
#error {
color: red;
}
#error:empty {
display: none;
}
JavaScript
(input.oninput = () => {
const html = safeMarkdown(input.value);
output.value = html;
rendered.innerHTML = html;
})();
function safeMarkdown(markdown) {
const html = snarkdown(markdown);
const doc = new DOMParser().parseFromString(
`<!DOCTYPE html>\n<html><body>${html}`,
'text/html'
);
doc.normalize();
sanitize(doc.body);
return doc.body.innerHTML;
}
function sanitize(node) {
if (node.nodeType === 3) return;
if (node.nodeType !== 1 || /^(script|iframe|object|embed|svg)$/i.test(node.tagName)) {
return node.remove();
}
for (let i=node.attributes.length; i--; ) {
const name = node.attributes[i].name;
if (!/^(class|id|name|href|src|alt|align|valign)$/i.test(name)) {
node.attributes.removeNamedItem(name);
}
}
for (let i=node.childNodes.length; i--; ) {
sanitize(node.childNodes[i]);
}
}