feat(calendar): make external mutations durable
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import posixpath
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
@@ -241,8 +242,23 @@ class CalDAVClient:
|
||||
return parse_multistatus(payload)
|
||||
|
||||
def fetch_object(self, href: str) -> str:
|
||||
payload = self.request("GET", self.object_url(href), body=None, depth=None, expected={200})
|
||||
return payload.decode("utf-8")
|
||||
return self.fetch_object_state(href).calendar_data or ""
|
||||
|
||||
def fetch_object_state(self, href: str) -> CalDAVObject:
|
||||
"""Fetch a resource together with its ETag for outbox reconciliation."""
|
||||
|
||||
_status, headers, payload = self.request_raw(
|
||||
"GET",
|
||||
self.object_url(href),
|
||||
body=None,
|
||||
depth=None,
|
||||
expected={200},
|
||||
)
|
||||
return CalDAVObject(
|
||||
href=href,
|
||||
etag=response_etag(headers),
|
||||
calendar_data=payload.decode("utf-8"),
|
||||
)
|
||||
|
||||
def put_object(self, href: str, ics: str, *, etag: str | None = None, create: bool = False, overwrite: bool = False) -> CalDAVWriteResult:
|
||||
headers = {"Content-Type": "text/calendar; charset=utf-8"}
|
||||
@@ -279,7 +295,32 @@ class CalDAVClient:
|
||||
return CalDAVWriteResult(href=href, etag=response_etag(response_headers), status=status)
|
||||
|
||||
def object_url(self, href: str) -> str:
|
||||
return urllib.parse.urljoin(self.collection_url, href)
|
||||
candidate = urllib.parse.urljoin(self.collection_url, href)
|
||||
collection_parts = urllib.parse.urlparse(self.collection_url)
|
||||
candidate_parts = urllib.parse.urlparse(candidate)
|
||||
if (
|
||||
candidate_parts.username
|
||||
or candidate_parts.password
|
||||
or (
|
||||
candidate_parts.scheme.lower(),
|
||||
candidate_parts.hostname,
|
||||
candidate_parts.port,
|
||||
)
|
||||
!= (
|
||||
collection_parts.scheme.lower(),
|
||||
collection_parts.hostname,
|
||||
collection_parts.port,
|
||||
)
|
||||
):
|
||||
raise CalDAVError("CalDAV object href must use the configured collection origin")
|
||||
collection_path = posixpath.normpath(urllib.parse.unquote(collection_parts.path))
|
||||
candidate_path = posixpath.normpath(urllib.parse.unquote(candidate_parts.path))
|
||||
collection_prefix = collection_path.rstrip("/") + "/"
|
||||
if not candidate_path.startswith(collection_prefix) or candidate_path == collection_path:
|
||||
raise CalDAVError("CalDAV object href must remain inside the configured collection path")
|
||||
if candidate_parts.query or candidate_parts.fragment:
|
||||
raise CalDAVError("CalDAV object href must not contain a query or fragment")
|
||||
return urllib.parse.urlunparse(candidate_parts)
|
||||
|
||||
def request(self, method: str, url: str, *, body: bytes | None, depth: str | None, expected: set[int]) -> bytes:
|
||||
_status, _headers, payload = self.request_raw(method, url, body=body, depth=depth, expected=expected)
|
||||
|
||||
Reference in New Issue
Block a user