From c44cc245ed47a21043ee1d804134c6c6b4cb47fd Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Wed, 6 May 2026 07:03:57 -0500 Subject: [PATCH] Address Copilot PR review: reject unknown archive formats, fix case-sensitive check - Add explanatory comment to empty except KeyError block in _extract_workflow_yml - Use case-insensitive extension matching for local archive detection in workflow add - Reject unknown archive formats with clear error messages instead of silently defaulting to ZIP in preset add --from, extension add --from, download_extension(), and download_pack() --- src/specify_cli/__init__.py | 14 ++++++++++++-- src/specify_cli/extensions.py | 5 +++++ src/specify_cli/presets.py | 5 +++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index 99d313e12..843c60db3 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -2643,6 +2643,11 @@ def preset_add( console.print(f"[red]Error:[/red] Failed to download: {e}") raise typer.Exit(1) + if not archive_fmt: + console.print("[red]Error:[/red] Could not determine archive format from URL or Content-Type.") + console.print("Ensure the URL points to a .zip or .tar.gz file.") + raise typer.Exit(1) + suffix = ".tar.gz" if archive_fmt == "tar.gz" else ".zip" archive_path = Path(tmpdir) / f"preset{suffix}" archive_path.write_bytes(archive_data) @@ -3652,6 +3657,11 @@ def extension_add( archive_fmt = _detect_archive_format(from_url, content_type) archive_data = response.read() + if not archive_fmt: + console.print("[red]Error:[/red] Could not determine archive format from URL or Content-Type.") + console.print("Ensure the URL points to a .zip or .tar.gz file.") + raise typer.Exit(1) + suffix = ".tar.gz" if archive_fmt == "tar.gz" else ".zip" archive_path = download_dir / f"{extension}-url-download{suffix}" archive_path.write_bytes(archive_data) @@ -4936,7 +4946,7 @@ def _extract_workflow_yml(archive_path: Path, archive_fmt: str) -> bytes: if f is not None: return f.read() except KeyError: - pass + pass # Root-level workflow.yml not found; fall through to subdirectory search below. # Look in a single top-level subdirectory. candidates = [ m for m in tf.getmembers() @@ -5099,7 +5109,7 @@ def workflow_add( _validate_and_install_local(source_path, str(source_path)) return elif source_path.is_file() and ( - source.endswith(".tar.gz") or source.endswith(".tgz") or source.endswith(".zip") + source.lower().endswith(".tar.gz") or source.lower().endswith(".tgz") or source.lower().endswith(".zip") ): # Local archive file containing workflow.yml from .extensions import _detect_archive_format diff --git a/src/specify_cli/extensions.py b/src/specify_cli/extensions.py index f28c02e9b..27e78925d 100644 --- a/src/specify_cli/extensions.py +++ b/src/specify_cli/extensions.py @@ -2151,6 +2151,11 @@ class ExtensionCatalog: raise ExtensionError(f"Failed to save extension archive: {e}") # Choose file extension based on detected format. + if not archive_fmt: + raise ExtensionError( + f"Could not determine archive format for {download_url}. " + "Ensure the URL points to a .zip or .tar.gz file." + ) if archive_fmt == "tar.gz": archive_filename = f"{extension_id}-{version}.tar.gz" else: diff --git a/src/specify_cli/presets.py b/src/specify_cli/presets.py index 0d597423e..f8cd3bbba 100644 --- a/src/specify_cli/presets.py +++ b/src/specify_cli/presets.py @@ -2331,6 +2331,11 @@ class PresetCatalog: raise PresetError(f"Failed to save preset archive: {e}") # Choose file extension based on detected format. + if not archive_fmt: + raise PresetError( + f"Could not determine archive format for {download_url}. " + "Ensure the URL points to a .zip or .tar.gz file." + ) if archive_fmt == "tar.gz": archive_filename = f"{pack_id}-{version}.tar.gz" else: