use data: URI for image preview: CSP img-src only allows self+data, blob: would be blocked (#171)

This commit is contained in:
fen
2026-09-10 11:19:01 -05:00
parent e23ca6e8d1
commit 1cd333e1e6
+17 -9
View File
@@ -252,40 +252,48 @@ function extOf(name) {
// content into the editor. Title auto-fills only if still blank (house // content into the editor. Title auto-fills only if still blank (house
// rule: never overwrite a typed title). // rule: never overwrite a typed title).
const IMAGE_RE = /^image\//; const IMAGE_RE = /^image\//;
let previewURL = null; let previewReader = null; // in-flight FileReader, aborted when the file changes
async function showFileInEditor(file) { // #171: images preview via a data: URI — the CSP is img-src 'self' data:
// and blob: object URLs would be blocked at render time.
function showFileInEditor(file) {
const wrap = document.querySelector('.editor-wrap'); const wrap = document.querySelector('.editor-wrap');
const img = $('file-preview'); const img = $('file-preview');
if (IMAGE_RE.test(file.type)) { if (IMAGE_RE.test(file.type)) {
if (previewURL) URL.revokeObjectURL(previewURL); if (previewReader) { try { previewReader.abort(); } catch(e) {} previewReader = null; }
previewURL = URL.createObjectURL(file); previewReader = new FileReader();
img.src = previewURL; previewReader.onload = () => {
if (attachedFile !== file) return; // a different file was attached meanwhile
img.src = previewReader.result; // data: URI, allowed by img-src 'self' data:
img.alt = file.name; img.alt = file.name;
wrap.classList.add('previewing'); wrap.classList.add('previewing');
img.classList.remove('hidden'); img.classList.remove('hidden');
previewReader = null;
};
previewReader.onerror = () => { previewReader = null; };
previewReader.readAsDataURL(file);
return; return;
} }
wrap.classList.remove('previewing'); wrap.classList.remove('previewing');
img.classList.add('hidden'); img.classList.add('hidden');
if (!TEXT_EXTS.has(extOf(file.name))) return; // unknown binary: leave editor alone if (!TEXT_EXTS.has(extOf(file.name))) return; // unknown binary: leave editor alone
try { file.text().then(text => {
const text = await file.text(); if (attachedFile !== file) return;
if (!content.value.trim()) { if (!content.value.trim()) {
content.value = text; content.value = text;
updateGutter(); updateGutter();
guessLang(); guessLang();
} }
} catch (e) {} }).catch(() => {});
} }
function clearPreview() { function clearPreview() {
if (previewReader) { try { previewReader.abort(); } catch(e) {} previewReader = null; }
const wrap = document.querySelector('.editor-wrap'); const wrap = document.querySelector('.editor-wrap');
const img = $('file-preview'); const img = $('file-preview');
wrap.classList.remove('previewing'); wrap.classList.remove('previewing');
img.classList.add('hidden'); img.classList.add('hidden');
img.removeAttribute('src'); img.removeAttribute('src');
if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; }
} }
function setAttachedFile(file) { function setAttachedFile(file) {