Skip to content

Commit 4d6e808

Browse files
committed
fix(calendar): normalize DAV origin comparison
Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Dick Tump <dick@tump.me>
1 parent 681ab92 commit 4d6e808

1 file changed

Lines changed: 19 additions & 3 deletions

File tree

ex_app/lib/all_tools/calendar_advanced_search.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from __future__ import annotations
77

88
import asyncio
9-
from urllib.parse import urlsplit
9+
from urllib.parse import SplitResult, urlsplit
1010

1111
from langchain_core.tools import tool
1212
from nc_py_api import AsyncNextcloudApp
@@ -34,6 +34,7 @@
3434
MAX_PROCESSED_OCCURRENCES_PER_SEARCH = 250_000
3535
# Nextcloud exposes cached WebCal subscriptions as calendars only when this request header is present.
3636
WEBCAL_CACHING_HEADERS = {"X-NC-CalDAV-Webcal-Caching": "On"}
37+
DEFAULT_ORIGIN_PORTS = {"http": 80, "https": 443}
3738

3839

3940
class CalendarRequestError(RuntimeError):
@@ -411,8 +412,11 @@ def _same_origin_dav_path(nc: AsyncNextcloudApp, href: str) -> str:
411412
# WebCal subscriptions are read from Nextcloud's cached DAV collection, never from their external URL.
412413
target = urlsplit(href)
413414
endpoint = urlsplit(nc._session.cfg.endpoint)
414-
if target.scheme and (target.scheme, target.netloc) != (endpoint.scheme, endpoint.netloc):
415-
raise ValueError("Calendar collection URL does not belong to this Nextcloud server")
415+
if target.scheme or target.netloc:
416+
target_origin = _normalized_origin(target, fallback_scheme=endpoint.scheme)
417+
endpoint_origin = _normalized_origin(endpoint)
418+
if target_origin is None or target_origin != endpoint_origin:
419+
raise ValueError("Calendar collection URL does not belong to this Nextcloud server")
416420
dav_path = urlsplit(nc._session.cfg.dav_endpoint).path.rstrip("/")
417421
if target.path == dav_path:
418422
relative_path = "/"
@@ -423,6 +427,18 @@ def _same_origin_dav_path(nc: AsyncNextcloudApp, href: str) -> str:
423427
return relative_path + (f"?{target.query}" if target.query else "")
424428

425429

430+
def _normalized_origin(url: SplitResult, fallback_scheme: str | None = None) -> tuple[str, str, int | None] | None:
431+
scheme = (url.scheme or fallback_scheme or "").casefold()
432+
hostname = url.hostname
433+
if not scheme or hostname is None:
434+
return None
435+
try:
436+
explicit_port = url.port
437+
except ValueError:
438+
return None
439+
return scheme, hostname.casefold(), explicit_port if explicit_port is not None else DEFAULT_ORIGIN_PORTS.get(scheme)
440+
441+
426442
def _require_success(response, allowed_statuses: set[int], request_stage: str) -> None:
427443
if response.status_code not in allowed_statuses:
428444
raise CalendarRequestError(response.status_code, request_stage)

0 commit comments

Comments
 (0)