Calendar + date cards: render webhook HTML (sanitize, links open in new tab)

This commit is contained in:
Kato
2026-09-05 19:26:50 +00:00
parent 853d768e21
commit f6eb226918
2 changed files with 32 additions and 2 deletions
+2
View File
@@ -1497,6 +1497,8 @@ TELEGRAM BANNER
white-space: nowrap;
}
.calendar-event-headline { flex: 1; color: #1c1e21; }
.calendar-event-headline a { color: #1877f2; text-decoration: none; font-weight: 600; }
.calendar-event-headline a:hover { text-decoration: underline; }
.calendar-event-time { color: #65676b; white-space: nowrap; }
/* ========================================
+30 -2
View File
@@ -981,7 +981,7 @@ function renderDates(events) {
card.innerHTML = '<div class="date-header">' +
'<div class="date-icon">📅</div>' +
'<h3 class="date-headline">' + escapeHtml(event.headline || 'Event') + '</h3>' +
'<h3 class="date-headline">' + sanitizeHtml(event.headline || 'Event') + '</h3>' +
'</div><div class="date-info">' +
'<div class="date-row">' +
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>' +
@@ -1004,6 +1004,34 @@ function renderDates(events) {
// ========================================
function pad2(n) { return String(n).padStart(2, '0'); }
// Webhook text may contain simple HTML (e.g. links). Sanitize before injecting:
// strip script/iframe/... tags, on* handlers and javascript: URLs; force
// links to open in a new tab.
function sanitizeHtml(html) {
const div = document.createElement('div');
div.innerHTML = String(html || '');
div.querySelectorAll('script, style, iframe, object, embed, link, meta').forEach(n => n.remove());
const walk = (node) => {
Array.from(node.childNodes || []).forEach(child => {
if (child.nodeType !== 1) return;
Array.from(child.attributes || []).forEach(attr => {
const name = attr.name.toLowerCase();
const val = (attr.value || '').replace(/\s+/g, '').toLowerCase();
if (name.startsWith('on') || ((name === 'href' || name === 'src') && val.startsWith('javascript:'))) {
child.removeAttribute(attr.name);
}
});
if (child.tagName === 'A') {
child.setAttribute('target', '_blank');
child.setAttribute('rel', 'noopener');
}
walk(child);
});
};
walk(div);
return div.innerHTML;
}
function initCalendar() {
renderCalendar();
}
@@ -1028,7 +1056,7 @@ function renderCalendar() {
const timeStr = pad2(sd.getHours()) + ':' + pad2(sd.getMinutes()) +
(ed ? '' + pad2(ed.getHours()) + ':' + pad2(ed.getMinutes()) : '');
list += '<li><span class="calendar-event-date">' + dateStr + '</span>' +
'<span class="calendar-event-headline">' + escapeHtml(ev.headline || 'Event') + '</span>' +
'<span class="calendar-event-headline">' + sanitizeHtml(ev.headline || 'Event') + '</span>' +
'<span class="calendar-event-time">' + timeStr + '</span></li>';
});
list += '</ul>';