feat(widget): enhance widget settings menu with improved styling and functionality
This commit is contained in:
@@ -535,7 +535,7 @@ async function selectMail(id) {
|
||||
const res = await fetch(`/api/mail.php?action=get_message&id=${encodeURIComponent(id)}`);
|
||||
const data = await res.json();
|
||||
if (data.success && data.message) {
|
||||
bodyEl.innerHTML = data.message.body_html || `<pre class="font-sans whitespace-pre-wrap">${escapeHtml(data.message.body_text || found.body_preview)}</pre>`;
|
||||
renderMailBodyContent(bodyEl, data.message.body_html, data.message.body_text || found.body_preview);
|
||||
found.is_read = true;
|
||||
updateUnreadCount();
|
||||
renderMailList(allMessages);
|
||||
@@ -543,10 +543,123 @@ async function selectMail(id) {
|
||||
bodyEl.innerHTML = `<div class="p-4 rounded-xl bg-red-50 text-red-700 text-xs">${data.error || 'Kon bericht niet inladen.'}</div>`;
|
||||
}
|
||||
} catch(err) {
|
||||
bodyEl.innerHTML = `<p class="whitespace-pre-wrap">${escapeHtml(found.body_preview)}</p>`;
|
||||
renderMailBodyContent(bodyEl, null, found.body_preview);
|
||||
}
|
||||
}
|
||||
|
||||
// Grondige HTML sanitization voor inkomende e-mails (XSS & exploit preventie)
|
||||
function sanitizeEmailHtml(html) {
|
||||
if (!html) return '';
|
||||
|
||||
// 1. Verwijder alle script tags en inhoud
|
||||
let cleaned = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
||||
|
||||
// 2. Verwijder potentieel gevaarlijke tags
|
||||
cleaned = cleaned.replace(/<\/?(object|embed|applet|meta|base|form|input|button|textarea|select)\b[^>]*>/gi, '');
|
||||
|
||||
// 3. Verwijder inline JavaScript event handlers (onload, onerror, onclick, onmouseover, etc.)
|
||||
cleaned = cleaned.replace(/\s+on[a-z0-9_-]+\s*=\s*(?:'[^']*'|"[^"]*"|[^\s>]+)/gi, '');
|
||||
|
||||
// 4. Neutraliseer javascript: en data:text/html pseudo-protocollen in links en afbeeldingen
|
||||
cleaned = cleaned.replace(/(href|src)\s*=\s*['"]\s*(?:javascript|data:text\/html):[^'"]*['"]/gi, '$1="#"');
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
// Veilig renderen van e-mail HTML via een geïsoleerde sandboxed iframe
|
||||
function renderMailBodyContent(container, htmlContent, plainContent) {
|
||||
container.innerHTML = '';
|
||||
|
||||
if (!htmlContent) {
|
||||
const pre = document.createElement('pre');
|
||||
pre.className = 'font-sans whitespace-pre-wrap text-sm text-slate-800 dark:text-slate-200 leading-relaxed p-2';
|
||||
pre.textContent = plainContent || '(Geen inhoud)';
|
||||
container.appendChild(pre);
|
||||
return;
|
||||
}
|
||||
|
||||
const safeHtml = sanitizeEmailHtml(htmlContent);
|
||||
const isDark = document.documentElement.classList.contains('dark');
|
||||
|
||||
// Sandboxed iframe: strikte isolatie (geen allow-scripts)
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'mailBodyIframe';
|
||||
iframe.setAttribute('sandbox', 'allow-same-origin allow-popups');
|
||||
iframe.className = 'w-full border-0 rounded-xl transition-all';
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.minHeight = '350px';
|
||||
iframe.style.border = 'none';
|
||||
iframe.style.background = isDark ? '#0c0f17' : '#ffffff';
|
||||
iframe.style.display = 'block';
|
||||
|
||||
container.appendChild(iframe);
|
||||
|
||||
const docHtml = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<base target="_blank">
|
||||
<style>
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
color-scheme: ${isDark ? 'dark' : 'light'};
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: ${isDark ? '#f1f5f9' : '#0f172a'};
|
||||
background-color: ${isDark ? '#0c0f17' : '#ffffff'};
|
||||
word-break: break-word;
|
||||
}
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
table {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
pre, code {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
a {
|
||||
color: #4f46e5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${safeHtml}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
iframe.srcdoc = docHtml;
|
||||
|
||||
iframe.onload = () => {
|
||||
try {
|
||||
const innerDoc = iframe.contentDocument || iframe.contentWindow.document;
|
||||
if (innerDoc) {
|
||||
const updateHeight = () => {
|
||||
const scrollH = innerDoc.documentElement.scrollHeight || innerDoc.body.scrollHeight;
|
||||
iframe.style.height = Math.max(scrollH + 35, 280) + 'px';
|
||||
};
|
||||
updateHeight();
|
||||
if (window.ResizeObserver) {
|
||||
const observer = new ResizeObserver(updateHeight);
|
||||
observer.observe(innerDoc.body);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
iframe.style.height = '600px';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateUnreadCount() {
|
||||
const unread = allMessages.filter(m => !m.is_read).length;
|
||||
const el = document.getElementById('countUnread');
|
||||
|
||||
Reference in New Issue
Block a user