37 lines
2.3 KiB
JavaScript
37 lines
2.3 KiB
JavaScript
// Inert parsing only; inscription markup is never attached to the parent DOM.
|
|
// The deliberately small allowlist also removes navigation (including meta refresh).
|
|
const tags = new Set('p div span h1 h2 h3 h4 h5 h6 br hr pre code b strong i em u s ul ol li table thead tbody tr td th blockquote svg g path rect circle ellipse line polyline polygon text tspan defs lineargradient radialgradient stop'.split(' '));
|
|
const attrs = new Set('style title width height viewbox fill stroke stroke-width d x y x1 y1 x2 y2 cx cy r rx ry points transform opacity offset stop-color font-size text-anchor'.split(' '));
|
|
export function preview(item) {
|
|
const bytes = new Uint8Array(item.bytes);
|
|
const type = item.contentType.split(';')[0];
|
|
const text = new TextDecoder().decode(bytes);
|
|
if (type === 'text/plain' || type === 'application/json') {
|
|
const node = document.createElement('pre'); node.textContent = text; return node;
|
|
}
|
|
let node, blob;
|
|
if (type === 'text/html' || type === 'image/svg+xml') {
|
|
const inert = document.implementation.createHTMLDocument('');
|
|
const template = inert.createElement('template'); template.innerHTML = text;
|
|
const clean = parent => {
|
|
for (const child of [...parent.children]) {
|
|
if (!tags.has(child.localName.toLowerCase())) { child.remove(); continue; }
|
|
for (const attr of [...child.attributes]) if (!attrs.has(attr.name.toLowerCase())) child.removeAttribute(attr.name);
|
|
clean(child);
|
|
}
|
|
};
|
|
clean(template.content);
|
|
const csp = "default-src 'none'; style-src 'unsafe-inline'; script-src 'none'; form-action 'none'; base-uri 'none'";
|
|
blob = new Blob([`<!doctype html><meta http-equiv="Content-Security-Policy" content="${csp}">`, template.innerHTML], { type: 'text/html' });
|
|
node = document.createElement('iframe'); node.setAttribute('sandbox', ''); node.title = `${type} safe preview`;
|
|
node.referrerPolicy = 'no-referrer';
|
|
} else {
|
|
blob = new Blob([bytes], { type }); node = document.createElement('img'); node.alt = `${type} inscription`;
|
|
}
|
|
const url = URL.createObjectURL(blob); node.src = url; node.dataset.previewUrl = url;
|
|
node.className = 'inscription-preview'; return node;
|
|
}
|
|
export function clearPreviews(root) {
|
|
root.querySelectorAll('[data-preview-url]').forEach(node => URL.revokeObjectURL(node.dataset.previewUrl));
|
|
}
|