fix: Separate CalDAV/CardDAV Home-Sets + UI-URLs ohne /dav/
Kalender und Adressbuecher teilten sich den gleichen Home-Set (/dav/<user>/). DAVx5 hat bei Depth-1-PROPFIND beide Collection- Typen angezeigt und mangels bekanntem Resourcetype als "DEFAULT_TASK_CALENDAR_NAME"-Kacheln gelistet. Loesung: * calendar-home-set zeigt auf /dav/<user>/calendars/ * addressbook-home-set zeigt auf /dav/<user>/addressbooks/ * Beide Pfade sind eigene Container-Collections - PROPFIND Depth 1 liefert nur den jeweils passenden Typ * /dav/<user>/ selbst gibt bei Depth 1 keine Kinder mehr zurueck, Clients folgen den Home-Sets * Die konkreten URLs cal-<id> / ab-<id> liegen weiterhin unter /dav/<user>/ (keine Breaking Change fuer existierende Clients; nur die Discovery-URL aendert sich) Frontend: CalendarView + ContactsView zeigen als Auto-Discovery-URL nur noch den Hostname - PROPFIND auf / funktioniert ja jetzt. Die Direkt-URL bleibt vollstaendig mit /dav/<user>/cal-<id> bzw. ab-<id> fuer Clients die das brauchen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9c102823e4
commit
24a6015841
|
|
@ -178,12 +178,12 @@ def _principal_response(user: User) -> ET.Element:
|
|||
ET.SubElement(cup, _qn('d', 'href')).text = href
|
||||
pu = ET.SubElement(prop, _qn('d', 'principal-URL'))
|
||||
ET.SubElement(pu, _qn('d', 'href')).text = href
|
||||
home = ET.SubElement(prop, _qn('c', 'calendar-home-set'))
|
||||
ET.SubElement(home, _qn('d', 'href')).text = href
|
||||
# CardDAV address-book home set - same principal URL, addressbook
|
||||
# collections live next to calendars under /dav/<username>/
|
||||
# Separate home-sets so clients (DAVx5!) don't mix calendars and
|
||||
# addressbooks in the same listing.
|
||||
cal_home = ET.SubElement(prop, _qn('c', 'calendar-home-set'))
|
||||
ET.SubElement(cal_home, _qn('d', 'href')).text = f'/dav/{user.username}/calendars/'
|
||||
ab_home = ET.SubElement(prop, '{urn:ietf:params:xml:ns:carddav}addressbook-home-set')
|
||||
ET.SubElement(ab_home, _qn('d', 'href')).text = href
|
||||
ET.SubElement(ab_home, _qn('d', 'href')).text = f'/dav/{user.username}/addressbooks/'
|
||||
return _make_response(href, populate)
|
||||
|
||||
|
||||
|
|
@ -269,17 +269,49 @@ def propfind(subpath=''):
|
|||
multistatus.append(_principal_response(user))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/ : principal + list calendars AND addressbooks
|
||||
# /dav/<username>/ : principal only (no child collections in this listing
|
||||
# so clients don't mix calendars and addressbooks). Clients follow
|
||||
# calendar-home-set / addressbook-home-set for the actual lists.
|
||||
if len(parts) == 1:
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
multistatus.append(_principal_response(user))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/calendars/ : only calendar collections
|
||||
if len(parts) == 2 and parts[1] == 'calendars':
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
# A plain collection container
|
||||
container = ET.Element(_qn('d', 'response'))
|
||||
ET.SubElement(container, _qn('d', 'href')).text = f'/dav/{user.username}/calendars/'
|
||||
propstat = ET.SubElement(container, _qn('d', 'propstat'))
|
||||
prop = ET.SubElement(propstat, _qn('d', 'prop'))
|
||||
rt = ET.SubElement(prop, _qn('d', 'resourcetype'))
|
||||
ET.SubElement(rt, _qn('d', 'collection'))
|
||||
ET.SubElement(prop, _qn('d', 'displayname')).text = 'Kalender'
|
||||
ET.SubElement(propstat, _qn('d', 'status')).text = 'HTTP/1.1 200 OK'
|
||||
multistatus.append(container)
|
||||
if depth != '0':
|
||||
for cal in _user_calendars(user):
|
||||
multistatus.append(_calendar_response(user, cal))
|
||||
# Addressbooks live next to calendars. Import here to avoid a
|
||||
# circular import at module load time.
|
||||
from .carddav import _addressbook_response, _user_addressbooks
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/addressbooks/ : only addressbook collections
|
||||
if len(parts) == 2 and parts[1] == 'addressbooks':
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
from .carddav import _addressbook_response, _user_addressbooks
|
||||
container = ET.Element(_qn('d', 'response'))
|
||||
ET.SubElement(container, _qn('d', 'href')).text = f'/dav/{user.username}/addressbooks/'
|
||||
propstat = ET.SubElement(container, _qn('d', 'propstat'))
|
||||
prop = ET.SubElement(propstat, _qn('d', 'prop'))
|
||||
rt = ET.SubElement(prop, _qn('d', 'resourcetype'))
|
||||
ET.SubElement(rt, _qn('d', 'collection'))
|
||||
ET.SubElement(prop, _qn('d', 'displayname')).text = 'Adressbücher'
|
||||
ET.SubElement(propstat, _qn('d', 'status')).text = 'HTTP/1.1 200 OK'
|
||||
multistatus.append(container)
|
||||
if depth != '0':
|
||||
for ab in _user_addressbooks(user):
|
||||
multistatus.append(_addressbook_response(user, ab))
|
||||
return _xml_response(multistatus)
|
||||
|
|
|
|||
|
|
@ -223,9 +223,9 @@
|
|||
und Passwort sind deine normalen Mini-Cloud-Zugangsdaten.
|
||||
</p>
|
||||
<div class="url-row">
|
||||
<strong>Auto-Discovery</strong>
|
||||
<code>{{ origin }}/dav/</code>
|
||||
<Button icon="pi pi-copy" text size="small" title="Kopieren" @click="copyText(origin + '/dav/')" />
|
||||
<strong>Server (Auto-Discovery)</strong>
|
||||
<code>{{ origin }}</code>
|
||||
<Button icon="pi pi-copy" text size="small" title="Kopieren" @click="copyText(origin)" />
|
||||
</div>
|
||||
<div class="url-row">
|
||||
<strong>Dieser Kalender (direkt)</strong>
|
||||
|
|
@ -234,8 +234,8 @@
|
|||
@click="copyText(`${origin}/dav/${username}/cal-${selectedCal.id}/`)" />
|
||||
</div>
|
||||
<div class="caldav-clients">
|
||||
<div><strong>Thunderbird / DAVx5 / Apple</strong>: Auto-Discovery-URL benutzen</div>
|
||||
<div><strong>Outlook (CalDAV-Synchronizer)</strong>: Direkt-URL</div>
|
||||
<div><strong>Thunderbird / DAVx5 / Apple</strong>: Server-URL reicht</div>
|
||||
<div><strong>Outlook (CalDAV-Synchronizer)</strong>: Direkt-URL des Kalenders</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -129,16 +129,19 @@
|
|||
<div class="field carddav-block">
|
||||
<label><i class="pi pi-info-circle"></i> CardDAV-Zugang</label>
|
||||
<div class="url-row">
|
||||
<strong>Auto-Discovery</strong>
|
||||
<code>{{ origin }}/dav/</code>
|
||||
<Button icon="pi pi-copy" text size="small" @click="copyText(origin + '/dav/')" />
|
||||
<strong>Server (Auto-Discovery)</strong>
|
||||
<code>{{ origin }}</code>
|
||||
<Button icon="pi pi-copy" text size="small" @click="copyText(origin)" />
|
||||
</div>
|
||||
<div class="url-row">
|
||||
<strong>Dieses Adressbuch</strong>
|
||||
<strong>Dieses Adressbuch (direkt)</strong>
|
||||
<code>{{ origin }}/dav/{{ username }}/ab-{{ menuBook.id }}/</code>
|
||||
<Button icon="pi pi-copy" text size="small"
|
||||
@click="copyText(`${origin}/dav/${username}/ab-${menuBook.id}/`)" />
|
||||
</div>
|
||||
<div class="caldav-hint">
|
||||
Bei DAVx5/Apple/Thunderbird reicht die Auto-Discovery-URL. Benutzername + Passwort wie im Web.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button v-if="menuBook.permission === 'owner'" label="Adressbuch löschen"
|
||||
|
|
|
|||
Loading…
Reference in New Issue