diff --git a/src/specify_cli/presets/__init__.py b/src/specify_cli/presets/__init__.py index a5cea4f958..b8c8f390db 100644 --- a/src/specify_cli/presets/__init__.py +++ b/src/specify_cli/presets/__init__.py @@ -4165,6 +4165,13 @@ def _validate_catalog_url(self, url: str) -> None: try: parsed = urlparse(url) hostname = parsed.hostname + # Accessing ``port`` performs urllib's syntax/range validation; + # ``hostname`` alone does not, so a non-numeric or out-of-range + # port would otherwise pass validation here and only fail later, + # at fetch time, as a raw error this function does not translate + # into PresetValidationError. Mirrors specify_cli.catalogs and + # bundler/services/adapters.py's copy of this same guard. + _ = parsed.port except ValueError: raise PresetValidationError(f"Catalog URL is malformed: {url}") from None is_localhost = hostname in ("localhost", "127.0.0.1", "::1") diff --git a/tests/test_presets.py b/tests/test_presets.py index f30ab4909e..8f9a2e358f 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -2086,6 +2086,25 @@ def test_validate_catalog_url_malformed_rejected(self, project_dir): with pytest.raises(PresetValidationError, match="malformed"): catalog._validate_catalog_url("https://[::1") + def test_validate_catalog_url_out_of_range_port_rejected(self, project_dir): + """An out-of-range port raises ValueError lazily on ``.port`` access. + + ``urlparse(...).hostname`` alone does not validate the port, so + without a ``_ = parsed.port`` probe inside the try/except, a URL like + ``https://example.com:99999/catalog.json`` sails through this + validator and only fails later, at fetch time, with a raw + untranslated error instead of a clean ``PresetValidationError``. The + sibling ``preset add --from `` download-URL guard already + catches this shape (see + ``test_preset_add_from_url_out_of_range_port_exits_cleanly``); this + catalog-source-URL validator had drifted from it and from the + original guard in ``specify_cli.catalogs``/ + ``bundler/services/adapters.py``. + """ + catalog = PresetCatalog(project_dir) + with pytest.raises(PresetValidationError, match="malformed"): + catalog._validate_catalog_url("https://example.com:99999/catalog.json") + def test_env_var_catalog_url(self, project_dir, monkeypatch): """Test catalog URL from environment variable.""" monkeypatch.setenv("SPECKIT_PRESET_CATALOG_URL", "https://custom.example.com/catalog.json")