Skip to content

Commit c978faa

Browse files
fix(bundler): reject host-less catalog URLs in adapters (use hostname, not netloc) (#3333)
_validate_remote_url in bundler/services/adapters.py guarded on parsed.netloc, which is truthy for host-less URLs like "https://:8080" or "https://user@" even though they carry no host. so those passed the "must be a valid URL with a host" check. its docstring says it mirrors specify_cli.catalogs validation, but that site was already fixed to use hostname in #3210/#3227 and this twin was missed. switch to parsed.hostname (None for host-less URLs), matching catalogs.py. this guard runs before any network call, so it is a pre-flight safety check. add parametrized regression tests for the host-less forms plus a valid host+port sanity case.
1 parent b5f1194 commit c978faa

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

src/specify_cli/bundler/services/adapters.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ def _validate_remote_url(source_id: str, url: str) -> None:
7575
f"Catalog '{source_id}' URL must use HTTPS (got {parsed.scheme}://). "
7676
"HTTP is only allowed for localhost."
7777
)
78-
if not parsed.netloc:
78+
# Check hostname, not netloc: netloc is truthy for host-less URLs like
79+
# "https://:8080" or "https://user@...", so requiring netloc would let
80+
# those through even though they carry no host. hostname is None in those
81+
# cases. Mirrors the fix in ``specify_cli.catalogs`` (#3210).
82+
if not parsed.hostname:
7983
raise BundlerError(
8084
f"Catalog '{source_id}' URL must be a valid URL with a host: {url}"
8185
)

tests/unit/test_bundler_adapters.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,30 @@ def fake_open_url(url, timeout=10, extra_headers=None, redirect_validator=None):
6969
fetcher = adapters.make_catalog_fetcher(allow_network=True)
7070
with pytest.raises(BundlerError, match="must use HTTPS"):
7171
fetcher(_source("https://example.com/c.json"))
72+
73+
74+
@pytest.mark.parametrize(
75+
"url",
76+
[
77+
"https://:8080", # port only, no host
78+
"https://:0",
79+
"https://user@", # userinfo only, no host
80+
"https://user:pw@",
81+
"https://:8080/catalog.json",
82+
],
83+
)
84+
def test_validate_remote_url_rejects_host_less_urls(url):
85+
"""A URL with a truthy netloc but no host (``https://:8080``,
86+
``https://user@``) must be rejected.
87+
88+
``urlparse`` gives these a non-empty ``netloc`` but ``hostname is None``,
89+
so a ``netloc`` check would wrongly accept them. This mirrors the fix in
90+
``specify_cli.catalogs`` (#3210), which the docstring says this validator
91+
mirrors."""
92+
with pytest.raises(BundlerError, match="valid URL with a host"):
93+
adapters._validate_remote_url("team", url)
94+
95+
96+
def test_validate_remote_url_accepts_normal_https_url():
97+
# Sanity: a real host with a port still passes.
98+
adapters._validate_remote_url("team", "https://example.com:8080/c.json")

0 commit comments

Comments
 (0)