Skip to content

Commit 14fa3ad

Browse files
fix(catalogs): raise catalog error, not raw ValueError, on a malformed URL (#3435)
CatalogStackBase._validate_catalog_url read parsed.hostname, which raises ValueError on a malformed authority (e.g. an unclosed ipv6 bracket "https://[::1"). every other reject path raises the class catalog error, so the raw ValueError leaked to the caller. wrap the parse + hostname access and convert ValueError to the normal error via cls._error. twin of the bundler fix in adapters._validate_remote_url.
1 parent 1736f07 commit 14fa3ad

2 files changed

Lines changed: 21 additions & 3 deletions

File tree

src/specify_cli/catalogs.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@ def _validate_catalog_url(cls, url: str) -> None:
7171
"""Validate that a catalog URL uses HTTPS, except localhost HTTP."""
7272
from urllib.parse import urlparse
7373

74-
parsed = urlparse(url)
75-
is_localhost = parsed.hostname in ("localhost", "127.0.0.1", "::1")
74+
try:
75+
parsed = urlparse(url)
76+
hostname = parsed.hostname
77+
except ValueError:
78+
raise cls._error(f"Catalog URL is malformed: {url}") from None
79+
is_localhost = hostname in ("localhost", "127.0.0.1", "::1")
7680
if parsed.scheme != "https" and not (parsed.scheme == "http" and is_localhost):
7781
raise cls._error(
7882
f"Catalog URL must use HTTPS (got {parsed.scheme}://). "
@@ -81,7 +85,7 @@ def _validate_catalog_url(cls, url: str) -> None:
8185
# Check hostname, not netloc: netloc is truthy for host-less URLs like
8286
# "https://:8080" or "https://user@", so the host guarantee this error
8387
# promises would not actually hold. hostname is None in those cases (#3209).
84-
if not parsed.hostname:
88+
if not hostname:
8589
raise cls._error("Catalog URL must be a valid URL with a host.")
8690

8791
def _load_catalog_config(self, config_path: Path) -> list[CatalogEntry] | None:

tests/integrations/test_integration_catalog.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ def test_hostless_url_with_truthy_netloc_rejected(self, url):
8484
with pytest.raises(IntegrationCatalogError, match="valid URL"):
8585
IntegrationCatalog._validate_catalog_url(url)
8686

87+
@pytest.mark.parametrize(
88+
"url",
89+
[
90+
"https://[::1", # unclosed ipv6 bracket
91+
"https://[not-an-ip]/c.json", # bracketed non-ip host
92+
],
93+
)
94+
def test_malformed_url_rejected_cleanly(self, url):
95+
# A malformed authority makes urlparse/hostname raise ValueError. The
96+
# validator must turn that into its normal catalog error, not leak a
97+
# raw ValueError to the caller.
98+
with pytest.raises(IntegrationCatalogError, match="malformed"):
99+
IntegrationCatalog._validate_catalog_url(url)
100+
87101

88102
# ---------------------------------------------------------------------------
89103
# IntegrationCatalog — active catalogs

0 commit comments

Comments
 (0)