""" NextcloudBackend — Nextcloud-specific WebDAV StorageBackend implementation. Extends WebDAVBackend with Nextcloud path conventions. Inheritance: NextcloudBackend → WebDAVBackend → StorageBackend All 7 StorageBackend abstract methods are inherited from WebDAVBackend. list_folder is inherited from WebDAVBackend — NextcloudBackend does NOT override it so that both providers share exactly one canonical adapter implementation (Phase 12.1 P0 fix). Only health_check is overridden to use the Nextcloud root (empty string is a valid check target for Nextcloud's WebDAV endpoint). Nextcloud WebDAV path convention: The server_url must be the normalized Nextcloud WebDAV root for a user: https://nc.example.com/remote.php/dav/files/{username}/ Use normalize_nextcloud_url(base_url, username) at construction time. Object keys are relative to this root (inherited from WebDAVBackend). SSRF prevention: Inherited from WebDAVBackend.__init__ — validate_cloud_url(server_url) is called before Client construction and before every outbound request (D-17). Credentials dict shape (same as WebDAVBackend): {"server_url": str, "username": str, "password": str} """ from __future__ import annotations import asyncio from storage.cloud_utils import validate_cloud_url from storage.webdav_backend import WebDAVBackend class NextcloudBackend(WebDAVBackend): """Nextcloud storage backend — extends WebDAVBackend. The server_url must be the full normalized Nextcloud WebDAV root: https://nc.example.com/remote.php/dav/files/{username}/ All 7 StorageBackend methods and list_folder (CloudResourceAdapter) are inherited from WebDAVBackend — no public override. Nextcloud-specific URL normalization is handled by normalize_nextcloud_url() in cloud_utils before construction. The SSRF guard is fully inherited — any private/localhost URL raises ValueError at construction time (and before every outbound request). """ def __init__(self, server_url: str, username: str, password: str) -> None: """Construct a NextcloudBackend. Args: server_url: Nextcloud WebDAV root URL. Should be pre-normalized via normalize_nextcloud_url(). E.g. "https://nc.example.com/remote.php/dav/files/alice/" username: Nextcloud username (stored for path convention context). password: Nextcloud account password or app-specific password (D-07). Raises: ValueError: If server_url targets a private/internal address (inherited SSRF guard). """ super().__init__(server_url, username, password) self._username = username # list_folder is intentionally NOT overridden. # NextcloudBackend inherits WebDAVBackend.list_folder which implements the # canonical (connection_id, user_id, parent_ref=None, page_token=None) -> CloudListing # contract. A legacy list_folder(folder_path="") -> list[dict] override was # the root cause of the Phase 12.1 P0 defect (TypeError on canonical invocation). async def health_check(self) -> bool: """Return True if the Nextcloud WebDAV endpoint is reachable. Uses client.check("") to probe the WebDAV root path rather than "/" because some Nextcloud configurations expect the base path check on the root without a trailing slash component. Returns: True if the server is reachable, False otherwise. """ try: # Re-validate before every outbound request (D-17) validate_cloud_url(self._server_url) result = await asyncio.to_thread(self._client.check, "") return bool(result) except Exception: return False