fix: Adjust URL handling in CalibreOPDSClient to support base URLs without trailing slashes

This commit is contained in:
JB
2025-12-22 07:27:17 -08:00
parent d1e0e0a536
commit b2058ef3ee
2 changed files with 29 additions and 9 deletions
+15 -8
View File
@@ -157,9 +157,9 @@ class CalibreOPDSClient:
normalized = base_url.strip()
if not normalized:
raise ValueError("Calibre OPDS base URL is required")
if not normalized.endswith("/"):
normalized = f"{normalized}/"
self._base_url = normalized
# Store the original URL without forcing a trailing slash.
# Some servers (e.g., Booklore) return 404 for URLs with trailing slashes.
self._base_url = normalized.rstrip("/")
self._auth = None
if username:
self._auth = httpx.BasicAuth(username, password or "")
@@ -183,12 +183,19 @@ class CalibreOPDSClient:
href = href.strip()
if href.startswith("http://") or href.startswith("https://"):
return href
if href.startswith("/") or href.startswith("?") or href.startswith("#"):
return urljoin(self._base_url, href)
if href.startswith("/"):
# Absolute path - join with origin only
parsed = urlparse(self._base_url)
return f"{parsed.scheme}://{parsed.netloc}{href}"
if href.startswith("?") or href.startswith("#"):
return f"{self._base_url}{href}"
if href.startswith("./") or href.startswith("../"):
return urljoin(self._base_url, href)
# Ensure relative paths like "search" keep the catalog prefix
return urljoin(self._base_url, f"./{href}")
# For relative paths, we need a trailing slash for urljoin to work correctly
base_with_slash = self._base_url if self._base_url.endswith("/") else f"{self._base_url}/"
return urljoin(base_with_slash, href)
# Relative path like "search" or "catalog?page=1" - treat as sibling
base_with_slash = self._base_url if self._base_url.endswith("/") else f"{self._base_url}/"
return urljoin(base_with_slash, href)
def _open_client(self) -> httpx.Client:
return httpx.Client(
+14 -1
View File
@@ -163,7 +163,20 @@ def test_calibre_opds_relative_urls_keep_catalog_prefix() -> None:
assert client._make_url("search") == "http://example.com/opds/search"
assert client._make_url("books/sample.epub") == "http://example.com/opds/books/sample.epub"
assert client._make_url("/cover/1") == "http://example.com/cover/1"
assert client._make_url("?page=2") == "http://example.com/opds/?page=2"
assert client._make_url("?page=2") == "http://example.com/opds?page=2"
def test_calibre_opds_base_url_without_trailing_slash() -> None:
"""Ensure the client works with base URLs that don't have trailing slashes."""
client = CalibreOPDSClient("http://example.com/api/v1/opds")
# Base URL should be stored without trailing slash
assert client._base_url == "http://example.com/api/v1/opds"
# Relative paths should resolve as siblings to the base URL
assert client._make_url("catalog") == "http://example.com/api/v1/opds/catalog"
assert client._make_url("search?q=test") == "http://example.com/api/v1/opds/search?q=test"
assert client._make_url("/api/v1/opds/books") == "http://example.com/api/v1/opds/books"
assert client._make_url("?page=2") == "http://example.com/api/v1/opds?page=2"
def test_calibre_opds_filters_out_unsupported_formats() -> None: