Skip to content

fix(extensions,presets): surface clean error on malformed download URL#3577

Open
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/download-url-malformed-valueerror
Open

fix(extensions,presets): surface clean error on malformed download URL#3577
Noor-ul-ain001 wants to merge 2 commits into
github:mainfrom
Noor-ul-ain001:fix/download-url-malformed-valueerror

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Summary

ExtensionCatalog.download_extension and PresetCatalog.download_pack read a download_url from catalog payload data and pass it through urlparse(...).hostname during their HTTPS validation. A malformed authority — e.g. an unterminated IPv6 bracket like https://[::1, or https://[not-an-ip]/x — makes urlparse/.hostname raise a raw ValueError.

That ValueError escapes the command handlers, which only catch ExtensionError / PresetError, so instead of a clean CLI message the user gets an uncaught traceback.

This wraps the parse in a try/except ValueError and re-raises as the module's domain error (ExtensionError / PresetError) with a clear "download URL is malformed" message.

Bug class

Same shape as the already-fixed unguarded-urlparse leaks in:

This closes the two remaining sibling code paths (extensions + presets) that had the same latent crash.

Changes

  • src/specify_cli/extensions/__init__.py — guard the download_url parse in download_extension.
  • src/specify_cli/presets/__init__.py — guard the download_url parse in download_pack.
  • tests/test_extensions.py — regression test asserting a malformed URL raises ExtensionError (not ValueError).
  • tests/test_presets.py — matching regression test for download_pack.

Testing

  • New tests fail on the pre-fix source (raw ValueError) and pass with the guard ("test-the-test" verified).
  • Full tests/test_extensions.py and tests/test_presets.py pass (717 passed, 5 skipped).

🤖 Generated with Claude Code

`ExtensionCatalog.download_extension` and `PresetCatalog.download_pack` read
`download_url` from catalog payload data and pass it to `urlparse(...).hostname`
during the HTTPS validation. A malformed authority (e.g. an unterminated IPv6
bracket like `https://[::1`) makes urlparse/hostname raise a raw `ValueError`,
which escapes past the command handlers — they only catch `ExtensionError` /
`PresetError` — and surfaces as an uncaught traceback.

Guard the parse in a try/except and re-raise as the domain error so the CLI
reports a clean "download URL is malformed" message. Mirrors the same fix in
catalogs (github#3435) and workflows/catalog.py (github#3484).

Adds regression coverage for both catalogs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Converts malformed extension and preset download URLs into domain-specific errors.

Changes:

  • Guards URL parsing and hostname access.
  • Adds regression coverage for malformed IPv6 authorities.
Show a summary per file
File Description
src/specify_cli/extensions/__init__.py Raises ExtensionError for malformed URLs.
src/specify_cli/presets/__init__.py Raises PresetError for malformed URLs.
tests/test_extensions.py Tests malformed extension URLs.
tests/test_presets.py Tests malformed preset URLs.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Medium

hostname = parsed.hostname
except ValueError:
raise PresetError(
f"Preset download URL is malformed: {download_url}"

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback

Copilot review on github#3577 flagged that the malformed-URL fix stopped short:
`download_pack` now raises a clean `PresetError`, but the `preset_add`
handler rendered `{e}` unescaped. A catalog `download_url` like
`https://[not-an-ip]/x` is embedded verbatim in the message, so Rich
interprets `[not-an-ip]` as a markup tag and can raise a style/markup
exception while rendering the error — the CLI still crashes instead of
exiting cleanly.

Escape `str(e)` in the preset command handlers, matching the extension
handler at `extensions/_commands.py:657`, and hoist the `rich.markup`
import to module scope (dropping the two inline imports). Adds CLI-level
regression tests: a bracketed-host `download_url` exits cleanly, and the
compatibility/validation/error handlers escape markup-bearing messages.
Both tests fail on the pre-fix handler (test-the-test verified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Noor-ul-ain001

Copy link
Copy Markdown
Contributor Author

@
Addressed the Copilot feedback in 07b5611.

The malformed-URL fix stopped short: download_pack now raises a clean PresetError, but the preset_add handler rendered {e} unescaped, so a catalog download_url like https://[not-an-ip]/x was passed through to Rich as markup and could crash the renderer instead of exiting cleanly.

Changes:

  • presets/_commands.py — escape str(e) in the preset command error handlers (matching the extension handler at extensions/_commands.py:657) and hoist the rich.markup import to module scope.
  • tests/test_presets.py — added CLI-level regression tests: a bracketed-host download_url exits cleanly, and the compatibility/validation/error handlers escape markup-bearing messages. Both fail on the pre-fix handler (test-the-test verified).

Full tests/test_presets.py (367 passed, 2 skipped) and tests/test_extensions.py (354 passed, 3 skipped) still pass.
@

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants