From e7f469f477c8ab61a831313c62c44e51de07642a Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sun, 12 Apr 2026 14:18:53 +0200 Subject: [PATCH] fix: CalDAV HEAD auf Events + PROPPATCH auf Kalender * GET-Route akzeptiert jetzt auch HEAD - manche Clients pruefen Existenz einer Ressource via HEAD bevor sie GET senden. * Neue PROPPATCH-Route auf der Kalender-Collection: erkennt calendar-color + displayname und persistiert beides. Andere Properties werden als "angewendet" bestaetigt, damit DAVx5 und Apple Calendar nicht enttaeuscht sind. Damit sollten die 500-Fehler beim Sync verschwinden. Falls nicht, bitte Server- oder DAVx5-Log posten. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/dav/caldav.py | 49 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/backend/app/dav/caldav.py b/backend/app/dav/caldav.py index 70effce..698dcd2 100644 --- a/backend/app/dav/caldav.py +++ b/backend/app/dav/caldav.py @@ -386,7 +386,7 @@ def _extract_time_range(root: ET.Element): # GET single event # --------------------------------------------------------------------------- -@dav_bp.route('///', methods=['GET']) +@dav_bp.route('///', methods=['GET', 'HEAD']) @basic_auth def get_event(username, cal_part, filename): user: User = request.dav_user @@ -500,6 +500,53 @@ def delete_calendar(username, cal_part): return Response('', 204) +# --------------------------------------------------------------------------- +# PROPPATCH (Clients setzen gerne Anzeigefarbe/-name). Wir persistieren +# den Kalenderfarbe (calendar-color) + Displayname; andere Properties +# bestaetigen wir als "angewendet" damit DAVx5/Apple zufrieden sind. +# --------------------------------------------------------------------------- + +@dav_bp.route('///', methods=['PROPPATCH']) +@dav_bp.route('//', methods=['PROPPATCH']) +@basic_auth +def proppatch_calendar(username, cal_part): + user: User = request.dav_user + if username != user.username: + return Response('', 403) + cal_id = _parse_calendar_path(cal_part) + cal = _calendar_for(user, cal_id) if cal_id else None + if not cal: + return Response('Not found', 404) + try: + root = ET.fromstring(request.data or b'') + except ET.ParseError: + return Response('Malformed XML', 400) + + for el in root.iter(): + tag = el.tag + if tag == _qn('ic', 'calendar-color') and el.text: + cal.color = el.text.strip()[:7] + elif tag == _qn('d', 'displayname') and el.text: + cal.name = el.text.strip()[:255] + db.session.commit() + + # Respond with 207 marking everything as applied so the client is happy. + multistatus = ET.Element(_qn('d', 'multistatus')) + href = _href_calendar(user.username, cal.id) + resp = ET.SubElement(multistatus, _qn('d', 'response')) + ET.SubElement(resp, _qn('d', 'href')).text = href + propstat = ET.SubElement(resp, _qn('d', 'propstat')) + prop = ET.SubElement(propstat, _qn('d', 'prop')) + # Echo back everything the client asked to set + for set_block in root.findall(_qn('d', 'set')): + inner_prop = set_block.find(_qn('d', 'prop')) + if inner_prop is not None: + for child in inner_prop: + ET.SubElement(prop, child.tag) + ET.SubElement(propstat, _qn('d', 'status')).text = 'HTTP/1.1 200 OK' + return _xml_response(multistatus) + + # --------------------------------------------------------------------------- # MKCALENDAR (create a new calendar collection via the DAV URL) # ---------------------------------------------------------------------------