66from __future__ import annotations
77
88import asyncio
9- from urllib .parse import urlsplit
9+ from urllib .parse import SplitResult , urlsplit
1010
1111from langchain_core .tools import tool
1212from nc_py_api import AsyncNextcloudApp
3434MAX_PROCESSED_OCCURRENCES_PER_SEARCH = 250_000
3535# Nextcloud exposes cached WebCal subscriptions as calendars only when this request header is present.
3636WEBCAL_CACHING_HEADERS = {"X-NC-CalDAV-Webcal-Caching" : "On" }
37+ DEFAULT_ORIGIN_PORTS = {"http" : 80 , "https" : 443 }
3738
3839
3940class 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+
426442def _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