diff --git a/Backend/src/main/resources/static/download.js b/Backend/src/main/resources/static/download.js
index e93be04..2e23859 100644
--- a/Backend/src/main/resources/static/download.js
+++ b/Backend/src/main/resources/static/download.js
@@ -25,22 +25,7 @@ if (!fragment) {
setStatus('Decrypting\u2026');
const plaintext = await decryptFile(await response.arrayBuffer(), key);
- const url = URL.createObjectURL(new Blob([plaintext]));
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
-
- htmx.swap(htmx.find('#download-state'), `
-
🔒
- ${filename}
- `,
- { swapStyle: 'innerHTML' });
-
- htmx.on(htmx.find('#download-btn'), 'click', () => {
- a.click();
- URL.revokeObjectURL(url);
- showSuccess(filename);
- });
+ showPreview(filename, plaintext);
}
} catch (err) {
showError('Decryption failed: ' + err.message);
@@ -51,10 +36,89 @@ function setStatus(msg) {
if (el) el.textContent = msg;
}
+function escapeHtml(str) {
+ return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
+}
+
+function getMimeType(filename) {
+ const ext = filename.split('.').pop().toLowerCase();
+ const types = {
+ jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif',
+ webp: 'image/webp', svg: 'image/svg+xml', bmp: 'image/bmp', ico: 'image/x-icon',
+ mp4: 'video/mp4', webm: 'video/webm', ogv: 'video/ogg', mov: 'video/quicktime',
+ mp3: 'audio/mpeg', wav: 'audio/wav', ogg: 'audio/ogg', flac: 'audio/flac',
+ aac: 'audio/aac', m4a: 'audio/mp4',
+ txt: 'text/plain', md: 'text/plain', csv: 'text/csv', log: 'text/plain',
+ json: 'application/json', xml: 'text/xml',
+ js: 'text/plain', ts: 'text/plain', py: 'text/plain', java: 'text/plain',
+ c: 'text/plain', cpp: 'text/plain', h: 'text/plain', sh: 'text/plain',
+ yaml: 'text/plain', yml: 'text/plain', toml: 'text/plain', ini: 'text/plain',
+ pdf: 'application/pdf',
+ };
+ return types[ext] || 'application/octet-stream';
+}
+
+function showPreview(filename, plaintext) {
+ const mimeType = getMimeType(filename);
+ const blob = new Blob([plaintext], { type: mimeType });
+ const url = URL.createObjectURL(blob);
+ window.addEventListener('unload', () => URL.revokeObjectURL(url));
+
+ const name = escapeHtml(filename);
+ const downloadBtn = ``;
+ let previewHtml;
+
+ if (mimeType.startsWith('image/')) {
+ previewHtml = `
+
+ ${name}
+ ${downloadBtn}`;
+ } else if (mimeType.startsWith('video/')) {
+ previewHtml = `
+
+ ${name}
+ ${downloadBtn}`;
+ } else if (mimeType.startsWith('audio/')) {
+ previewHtml = `
+ 🎵
+ ${name}
+
+ ${downloadBtn}`;
+ } else if (mimeType === 'application/pdf') {
+ previewHtml = `
+ ${name}
+
+ ${downloadBtn}`;
+ } else if (mimeType.startsWith('text/') || mimeType === 'application/json') {
+ const text = new TextDecoder().decode(plaintext);
+ const preview = text.length > 10000 ? text.slice(0, 10000) + '\n\u2026' : text;
+ previewHtml = `
+ ${name}
+ ${escapeHtml(preview)}
+ ${downloadBtn}`;
+ } else {
+ previewHtml = `
+ 📄
+ ${name}
+ ${downloadBtn}`;
+ }
+
+ htmx.swap(htmx.find('#download-state'), previewHtml, { swapStyle: 'innerHTML' });
+
+ htmx.on(htmx.find('#download-btn'), 'click', () => {
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ showSuccess(filename);
+ });
+}
+
function showSuccess(filename) {
+ const name = escapeHtml(filename);
htmx.swap(htmx.find('#download-state'), `
✅
- ${filename}
+ ${name}
Your download has started.
Send a file`,
{ swapStyle: 'innerHTML' });
@@ -63,7 +127,7 @@ function showSuccess(filename) {
function showError(msg) {
htmx.swap(htmx.find('#download-state'), `
⚠
- ${msg}
+ ${escapeHtml(msg)}
Go home`,
{ swapStyle: 'innerHTML' });
}
diff --git a/Backend/src/main/resources/static/style.css b/Backend/src/main/resources/static/style.css
index 0ffbbc0..5557647 100644
--- a/Backend/src/main/resources/static/style.css
+++ b/Backend/src/main/resources/static/style.css
@@ -65,3 +65,37 @@ footer, footer a {
footer a:hover {
color: #8b949e;
}
+
+.preview-media {
+ max-width: 100%;
+ border-radius: 8px;
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.preview-audio {
+ width: 100%;
+}
+
+.preview-pdf {
+ width: 100%;
+ height: 420px;
+ border: none;
+ border-radius: 8px;
+ display: block;
+}
+
+.preview-text {
+ text-align: left;
+ max-height: 320px;
+ overflow: auto;
+ background: #0d1117;
+ padding: 1rem;
+ border-radius: 8px;
+ font-size: 0.78rem;
+ color: #e6edf3;
+ border: 1px solid #30363d;
+ white-space: pre-wrap;
+ word-break: break-word;
+}