CalDAV: HTML-Entities in Terminfeldern dekodieren

Manche Kalender speichern SUMMARY/LOCATION/DESCRIPTION mit wörtlichen
HTML-Entities (z. B. "Vorstellungsgespräch – dot.haus"). Diese
wurden roh angezeigt. Beim Parsen werden sie jetzt in echte Zeichen
dekodiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 22:58:07 +02:00
co-authored by Claude Opus 4.8
parent 22f967c753
commit 7e0c27341a
+18 -3
View File
@@ -122,6 +122,21 @@ function unescapeText(s) {
.replace(/\\\\/g, '\\'); .replace(/\\\\/g, '\\');
} }
// Some clients store SUMMARY/LOCATION/DESCRIPTION with HTML entities baked in
// (e.g. "Vorstellungsgespr&#228;ch &#8211; dot.haus"). iCal text is plain, so
// decode them back to real characters before we hand the value on.
function decodeHtmlEntities(s) {
return String(s == null ? '' : s)
.replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(parseInt(n, 10)))
.replace(/&#x([0-9a-fA-F]+);/g, (_, n) => String.fromCodePoint(parseInt(n, 16)))
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&');
}
// Fold a content line to <=75 octets per RFC 5545 (CRLF + single space). // Fold a content line to <=75 octets per RFC 5545 (CRLF + single space).
function fold(line) { function fold(line) {
if (Buffer.byteLength(line, 'utf8') <= 75) return line; if (Buffer.byteLength(line, 'utf8') <= 75) return line;
@@ -204,9 +219,9 @@ function parseVEvent(ics) {
paramParts.forEach((p) => { const [k, v] = p.split('='); if (k) params[k.toUpperCase()] = v; }); paramParts.forEach((p) => { const [k, v] = p.split('='); if (k) params[k.toUpperCase()] = v; });
const key = name.toUpperCase(); const key = name.toUpperCase();
if (key === 'UID') ev.uid = value.trim(); if (key === 'UID') ev.uid = value.trim();
else if (key === 'SUMMARY') ev.summary = unescapeText(value); else if (key === 'SUMMARY') ev.summary = decodeHtmlEntities(unescapeText(value));
else if (key === 'LOCATION') ev.location = unescapeText(value); else if (key === 'LOCATION') ev.location = decodeHtmlEntities(unescapeText(value));
else if (key === 'DESCRIPTION') ev.description = unescapeText(value); else if (key === 'DESCRIPTION') ev.description = decodeHtmlEntities(unescapeText(value));
else if (key === 'DTSTART') { const r = icsToDate(value, params); ev.start = r.date; ev.allDay = r.allDay; } else if (key === 'DTSTART') { const r = icsToDate(value, params); ev.start = r.date; ev.allDay = r.allDay; }
else if (key === 'DTEND') { const r = icsToDate(value, params); ev.end = r.date; } else if (key === 'DTEND') { const r = icsToDate(value, params); ev.end = r.date; }
} }