From 6abcbf3bb9d7bd0d45ca06dc0e31f9400300425f Mon Sep 17 00:00:00 2001 From: William Woodruff Date: Wed, 8 Apr 2026 13:32:11 -0400 Subject: [PATCH] Remove bz/lzma/xz support from ZIP handling Signed-off-by: William Woodruff Remove CompressionMethod APIs entirely Signed-off-by: William Woodruff Tear out the rest of the deprecation pathways Signed-off-by: William Woodruff Fix `cargo shear` Signed-off-by: William Woodruff Bump snapshots Signed-off-by: William Woodruff `cargo fmt` Signed-off-by: William Woodruff Specialize error messages for unsupported compression methods in zip files Signed-off-by: William Woodruff Simplify matches Signed-off-by: William Woodruff --- Cargo.toml | 3 -- crates/uv-bin-install/src/lib.rs | 11 ++--- crates/uv-dev/src/validate_zip.rs | 2 +- .../src/distribution_database.rs | 14 ++---- crates/uv-distribution/src/source/mod.rs | 13 ++---- crates/uv-extract/Cargo.toml | 1 - crates/uv-extract/src/error.rs | 17 ++++++- crates/uv-extract/src/lib.rs | 46 +------------------ crates/uv-extract/src/stream.rs | 30 ++---------- crates/uv-extract/src/sync.rs | 16 +------ crates/uv-metadata/src/lib.rs | 15 +++++- crates/uv-python/src/downloads.rs | 4 +- crates/uv/src/commands/build_frontend.rs | 5 +- crates/uv/tests/build/extract.rs | 2 +- crates/uv/tests/pip_install/pip_install.rs | 23 ++++------ 15 files changed, 64 insertions(+), 138 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e555db8d27..56f1bdfa6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,11 +110,8 @@ async-compression = { version = "0.4.12", features = [ async-trait = { version = "0.1.82" } async_http_range_reader = { version = "0.11.0", package = "astral_async_http_range_reader" } async_zip = { version = "0.0.18", package = "astral_async_zip", features = [ - "bzip2", "deflate", - "lzma", "tokio", - "xz", "zstd", ] } axoupdater = { version = "0.10.0", default-features = false } diff --git a/crates/uv-bin-install/src/lib.rs b/crates/uv-bin-install/src/lib.rs index 1766ed9f95..a97fef7d5f 100644 --- a/crates/uv-bin-install/src/lib.rs +++ b/crates/uv-bin-install/src/lib.rs @@ -841,14 +841,9 @@ async fn download_and_unpack( let id = reporter.on_download_start(binary.name(), version, size); let mut progress_reader = ProgressReader::new(reader, id, reporter); - stream::archive( - &download_url, - &mut progress_reader, - format.into(), - temp_dir.path(), - ) - .await - .map_err(|e| Error::Extract { source: e })?; + stream::archive(&mut progress_reader, format.into(), temp_dir.path()) + .await + .map_err(|e| Error::Extract { source: e })?; reporter.on_download_complete(id); // Find the binary in the extracted files diff --git a/crates/uv-dev/src/validate_zip.rs b/crates/uv-dev/src/validate_zip.rs index a199bbad2d..1614254274 100644 --- a/crates/uv-dev/src/validate_zip.rs +++ b/crates/uv-dev/src/validate_zip.rs @@ -47,7 +47,7 @@ pub(crate) async fn validate_zip( let target = tempfile::TempDir::new()?; - uv_extract::stream::unzip(args.url.to_url(), reader.compat(), target.path()).await?; + uv_extract::stream::unzip(reader.compat(), target.path()).await?; Ok(()) } diff --git a/crates/uv-distribution/src/distribution_database.rs b/crates/uv-distribution/src/distribution_database.rs index 5465605b93..9ce7cb78cd 100644 --- a/crates/uv-distribution/src/distribution_database.rs +++ b/crates/uv-distribution/src/distribution_database.rs @@ -714,8 +714,6 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Create an entry for the HTTP cache. let http_entry = wheel_entry.with_file(format!("{}.http", filename.cache_key())); - let query_url = &url.clone(); - let download = |response: reqwest::Response| { async { let size = size.or_else(|| content_length(&response)); @@ -744,7 +742,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { let mut reader = ProgressReader::new(&mut hasher, progress, &**reporter); match extension { WheelExtension::Whl => { - uv_extract::stream::unzip(query_url, &mut reader, temp_dir.path()) + uv_extract::stream::unzip(&mut reader, temp_dir.path()) .await .map_err(|err| Error::Extract(filename.to_string(), err))? } @@ -757,7 +755,7 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { } None => match extension { WheelExtension::Whl => { - uv_extract::stream::unzip(query_url, &mut hasher, temp_dir.path()) + uv_extract::stream::unzip(&mut hasher, temp_dir.path()) .await .map_err(|err| Error::Extract(filename.to_string(), err))? } @@ -1129,11 +1127,9 @@ impl<'a, Context: BuildContext> DistributionDatabase<'a, Context> { // Unzip the wheel to a temporary directory. let files = match extension { - WheelExtension::Whl => { - uv_extract::stream::unzip(path.display(), &mut hasher, temp_dir.path()) - .await - .map_err(|err| Error::Extract(filename.to_string(), err))? - } + WheelExtension::Whl => uv_extract::stream::unzip(&mut hasher, temp_dir.path()) + .await + .map_err(|err| Error::Extract(filename.to_string(), err))?, WheelExtension::WhlZst => { uv_extract::stream::untar_zst(&mut hasher, temp_dir.path()) .await diff --git a/crates/uv-distribution/src/source/mod.rs b/crates/uv-distribution/src/source/mod.rs index f651ddd821..2145b55912 100644 --- a/crates/uv-distribution/src/source/mod.rs +++ b/crates/uv-distribution/src/source/mod.rs @@ -971,8 +971,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }; let download = |response| { - let query_url = url.clone(); - async { // At this point, we're seeing a new or updated source distribution. Initialize a // new revision, to collect the source and built artifacts. @@ -983,7 +981,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let entry = cache_shard.shard(revision.id()).entry(SOURCE); let algorithms = http_hash_algorithms(hashes); let hashes = self - .download_archive(query_url, response, source, ext, entry.path(), &algorithms) + .download_archive(response, source, ext, entry.path(), &algorithms) .await?; Ok(revision.with_hashes(HashDigests::from(hashes))) @@ -2723,8 +2721,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }; let download = |response| { - let query_url = url.clone(); - async { // Take the union of the requested and existing hash algorithms. let algorithms = { @@ -2738,7 +2734,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { }; let hashes = self - .download_archive(query_url, response, source, ext, entry.path(), &algorithms) + .download_archive(response, source, ext, entry.path(), &algorithms) .await?; for existing in revision.hashes() { if !hashes.contains(existing) { @@ -2772,7 +2768,6 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { /// Download and unzip a source distribution into the cache from an HTTP response. async fn download_archive( &self, - query_url: DisplaySafeUrl, response: Response, source: &BuildableSource<'_>, ext: SourceDistExtension, @@ -2801,7 +2796,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { // Download and unzip the source distribution into a temporary directory. let span = info_span!("download_source_dist", source_dist = %source); - uv_extract::stream::archive(query_url, &mut hasher, ext, temp_dir.path()) + uv_extract::stream::archive(&mut hasher, ext, temp_dir.path()) .await .map_err(|err| Error::Extract(source.to_string(), err))?; drop(span); @@ -2870,7 +2865,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> { let mut hasher = uv_extract::hash::HashReader::new(reader, &mut hashers); // Unzip the archive into a temporary directory. - uv_extract::stream::archive(path.display(), &mut hasher, ext, &temp_dir.path()) + uv_extract::stream::archive(&mut hasher, ext, &temp_dir.path()) .await .map_err(|err| Error::Extract(temp_dir.path().to_string_lossy().into_owned(), err))?; diff --git a/crates/uv-extract/Cargo.toml b/crates/uv-extract/Cargo.toml index 3cee949ac4..9d1b8d468e 100644 --- a/crates/uv-extract/Cargo.toml +++ b/crates/uv-extract/Cargo.toml @@ -20,7 +20,6 @@ uv-configuration = { workspace = true } uv-distribution-filename = { workspace = true } uv-pypi-types = { workspace = true } uv-static = { workspace = true } -uv-warnings = { workspace = true } astral-tokio-tar = { workspace = true } async-compression = { workspace = true, features = ["bzip2", "gzip", "zstd", "xz"] } diff --git a/crates/uv-extract/src/error.rs b/crates/uv-extract/src/error.rs index 93a830ca7d..6dbe87ffd1 100644 --- a/crates/uv-extract/src/error.rs +++ b/crates/uv-extract/src/error.rs @@ -5,7 +5,7 @@ pub enum Error { #[error("I/O operation failed during extraction")] Io(#[source] std::io::Error), #[error("Invalid zip file structure")] - AsyncZip(#[from] async_zip::error::ZipError), + AsyncZip(#[source] async_zip::error::ZipError), #[error("Invalid tar file")] Tar(#[from] tokio_tar::TarError), #[error( @@ -91,6 +91,19 @@ pub enum Error { EmptyFilename, #[error("Archive contains unacceptable filename: {filename}")] UnacceptableFilename { filename: String }, + #[error( + "Archive contains a file with an unsupported compression method; files must be compressed with 'stored', 'DEFLATE', or 'zstd'" + )] + UnsupportedCompression, +} + +impl From for Error { + fn from(err: async_zip::error::ZipError) -> Self { + match err { + async_zip::error::ZipError::CompressionNotSupported(_) => Self::UnsupportedCompression, + o => Self::AsyncZip(o), + } + } } impl Error { @@ -108,7 +121,7 @@ impl Error { Err(err) => err, }; let err = match err.downcast::() { - Ok(zip_err) => return Self::AsyncZip(zip_err), + Ok(zip_err) => return Self::from(zip_err), Err(err) => err, }; Self::Io(err) diff --git a/crates/uv-extract/src/lib.rs b/crates/uv-extract/src/lib.rs index 20d64e2574..b8cde8bd28 100644 --- a/crates/uv-extract/src/lib.rs +++ b/crates/uv-extract/src/lib.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, sync::LazyLock}; +use std::sync::LazyLock; pub use error::Error; use regex::Regex; @@ -14,50 +14,6 @@ mod vendor; static CONTROL_CHARACTERS_RE: LazyLock = LazyLock::new(|| Regex::new(r"\p{C}").unwrap()); static REPLACEMENT_CHARACTER: &str = "\u{FFFD}"; -/// Compression methods that we consider supported. -/// -/// Our underlying ZIP dependencies may support more. -pub(crate) enum CompressionMethod { - Stored, - Deflated, - Zstd, - // NOTE: This will become `Unsupported(...)` in the future. - Deprecated(&'static str), -} - -impl CompressionMethod { - /// Returns `true` if this is a well-known compression method that we - /// expect other ZIP implementations to support. - fn is_well_known(&self) -> bool { - matches!(self, Self::Stored | Self::Deflated | Self::Zstd) - } -} - -impl Display for CompressionMethod { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Stored => write!(f, "stored"), - Self::Deflated => write!(f, "DEFLATE"), - Self::Zstd => write!(f, "zstd"), - Self::Deprecated(name) => write!(f, "{name}"), - } - } -} - -impl From for CompressionMethod { - fn from(value: async_zip::Compression) -> Self { - match value { - async_zip::Compression::Stored => Self::Stored, - async_zip::Compression::Deflate => Self::Deflated, - async_zip::Compression::Zstd => Self::Zstd, - async_zip::Compression::Bz => Self::Deprecated("bzip2"), - async_zip::Compression::Lzma => Self::Deprecated("lzma"), - async_zip::Compression::Xz => Self::Deprecated("xz"), - _ => Self::Deprecated("unknown"), - } - } -} - /// Validate that a given filename (e.g. reported by a ZIP archive's /// local file entries or central directory entries) is "safe" to use. /// diff --git a/crates/uv-extract/src/stream.rs b/crates/uv-extract/src/stream.rs index b38339493f..b177945d7a 100644 --- a/crates/uv-extract/src/stream.rs +++ b/crates/uv-extract/src/stream.rs @@ -1,4 +1,3 @@ -use std::fmt::Display; use std::path::{Component, Path, PathBuf}; use std::pin::Pin; @@ -10,9 +9,8 @@ use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; use tracing::{debug, warn}; use uv_distribution_filename::SourceDistExtension; -use uv_warnings::warn_user_once; -use crate::{CompressionMethod, Error, insecure_no_validate, validate_archive_member_name}; +use crate::{Error, insecure_no_validate, validate_archive_member_name}; const DEFAULT_BUF_SIZE: usize = 128 * 1024; @@ -66,12 +64,8 @@ struct ComputedEntry { /// is already fully on disk, consider using `unzip_archive`, which can use multiple /// threads to work faster in that case. /// -/// `source_hint` is used for warning messages, to identify the source of the ZIP archive -/// beneath the reader. It might be a URL, a file path, or something else. -/// /// Returns the list of unpacked files and their sizes. -pub async fn unzip( - source_hint: D, +pub async fn unzip( reader: R, target: impl AsRef, ) -> Result, Error> { @@ -90,18 +84,6 @@ pub async fn unzip( while let Some(mut entry) = zip.next_with_entry().await? { let zip_entry = entry.reader().entry(); - // Check for unexpected compression methods. - // A future version of uv will reject instead of warning about these. - let compression = CompressionMethod::from(zip_entry.compression()); - if !compression.is_well_known() { - warn_user_once!( - "One or more file entries in '{source_hint}' use the '{compression}' compression method, which is not widely supported. A future version of uv will reject ZIP archives containing entries compressed with this method. Entries must be compressed with the '{stored}', '{deflate}', or '{zstd}' compression methods.", - stored = CompressionMethod::Stored, - deflate = CompressionMethod::Deflated, - zstd = CompressionMethod::Zstd, - ); - } - // Construct the (expected) path to the file on-disk. let path = match zip_entry.filename().as_str() { Ok(path) => path, @@ -780,18 +762,14 @@ async fn untar( /// Unpack a `.zip`, `.tar.gz`, `.tar.bz2`, `.tar.zst`, or `.tar.xz` archive into the target directory, /// without requiring `Seek`. /// -/// `source_hint` is used for warning messages, to identify the source of the archive -/// beneath the reader. It might be a URL, a file path, or something else. -/// /// Returns the list of unpacked files and their sizes. -pub async fn archive( - source_hint: D, +pub async fn archive( reader: R, ext: SourceDistExtension, target: impl AsRef, ) -> Result, Error> { match ext { - SourceDistExtension::Zip => unzip(source_hint, reader, target).await, + SourceDistExtension::Zip => unzip(reader, target).await, SourceDistExtension::Tar => untar(reader, target).await, SourceDistExtension::Tgz | SourceDistExtension::TarGz => untar_gz(reader, target).await, SourceDistExtension::Tbz | SourceDistExtension::TarBz2 => untar_bz2(reader, target).await, diff --git a/crates/uv-extract/src/sync.rs b/crates/uv-extract/src/sync.rs index 795db51a82..8edb01d482 100644 --- a/crates/uv-extract/src/sync.rs +++ b/crates/uv-extract/src/sync.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use std::sync::Mutex; use crate::vendor::CloneableSeekableReader; -use crate::{CompressionMethod, Error, insecure_no_validate, validate_archive_member_name}; +use crate::{Error, insecure_no_validate, validate_archive_member_name}; use async_zip::base::read::seek::ZipFileReader; use async_zip::error::ZipError; use futures::executor::block_on; @@ -11,13 +11,12 @@ use rayon::prelude::*; use rustc_hash::FxHashSet; use tracing::warn; use uv_configuration::initialize_rayon_once; -use uv_warnings::warn_user_once; /// Unzip a `.zip` archive into the target directory. /// /// Returns the list of unpacked files and their sizes. pub fn unzip(reader: fs_err::File, target: &Path) -> Result, Error> { - let (reader, filename) = reader.into_parts(); + let (reader, _) = reader.into_parts(); // Parse the central directory once, then clone the archive reader per Rayon worker so // extraction stays parallel for already-downloaded wheels. @@ -43,17 +42,6 @@ pub fn unzip(reader: fs_err::File, target: &Path) -> Result, Err(err) => return Err(err.into()), }; - let compression = CompressionMethod::from(entry.compression()); - if !compression.is_well_known() { - warn_user_once!( - "One or more file entries in '{filename}' use the '{compression}' compression method, which is not widely supported. A future version of uv will reject ZIP archives containing entries compressed with this method. Entries must be compressed with the '{stored}', '{deflate}', or '{zstd}' compression methods.", - filename = filename.display(), - stored = CompressionMethod::Stored, - deflate = CompressionMethod::Deflated, - zstd = CompressionMethod::Zstd, - ); - } - if let Err(e) = validate_archive_member_name(file_name) { if !skip_validation { return Err(e); diff --git a/crates/uv-metadata/src/lib.rs b/crates/uv-metadata/src/lib.rs index a94765322c..0c651e1690 100644 --- a/crates/uv-metadata/src/lib.rs +++ b/crates/uv-metadata/src/lib.rs @@ -40,12 +40,25 @@ pub enum Error { expected: u32, }, #[error("Failed to read from zip file")] - AsyncZip(#[from] async_zip::error::ZipError), + AsyncZip(#[source] async_zip::error::ZipError), + #[error( + "Archive contains a file with an unsupported compression method; files must be compressed with 'stored', 'DEFLATE', or 'zstd'" + )] + UnsupportedCompression, // No `#[from]` to enforce manual review of `io::Error` sources. #[error(transparent)] Io(io::Error), } +impl From for Error { + fn from(err: async_zip::error::ZipError) -> Self { + match err { + async_zip::error::ZipError::CompressionNotSupported(_) => Self::UnsupportedCompression, + o => Self::AsyncZip(o), + } + } +} + /// Find the `.dist-info` directory in a zipped wheel. /// /// Returns the dist info dir prefix without the `.dist-info` extension. diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs index 89281c8d50..0cc04aeb48 100644 --- a/crates/uv-python/src/downloads.rs +++ b/crates/uv-python/src/downloads.rs @@ -1421,12 +1421,12 @@ impl ManagedPythonDownload { if let Some(reporter) = reporter { let progress_key = reporter.on_request_start(direction, &self.key, size); let mut reader = ProgressReader::new(&mut hasher, progress_key, reporter); - uv_extract::stream::archive(filename, &mut reader, ext, target) + uv_extract::stream::archive(&mut reader, ext, target) .await .map_err(|err| Error::ExtractError(filename.to_owned(), err))?; reporter.on_request_complete(direction, progress_key); } else { - uv_extract::stream::archive(filename, &mut hasher, ext, target) + uv_extract::stream::archive(&mut hasher, ext, target) .await .map_err(|err| Error::ExtractError(filename.to_owned(), err))?; } diff --git a/crates/uv/src/commands/build_frontend.rs b/crates/uv/src/commands/build_frontend.rs index 2c21b6fb49..f8b69549c6 100644 --- a/crates/uv/src/commands/build_frontend.rs +++ b/crates/uv/src/commands/build_frontend.rs @@ -759,7 +759,7 @@ async fn build_package( let ext = SourceDistExtension::from_path(path.as_path()) .map_err(|err| Error::InvalidSourceDistExt(path.user_display().to_string(), err))?; let temp_dir = tempfile::tempdir_in(cache.bucket(CacheBucket::SourceDistributions))?; - uv_extract::stream::archive(path.display(), reader, ext, temp_dir.path()).await?; + uv_extract::stream::archive(reader, ext, temp_dir.path()).await?; // Extract the top-level directory from the archive. let extracted = match uv_extract::strip_component(temp_dir.path()) { @@ -866,8 +866,7 @@ async fn build_package( Error::InvalidSourceDistExt(source.path().user_display().to_string(), err) })?; let temp_dir = tempfile::tempdir_in(&output_dir)?; - uv_extract::stream::archive(source.path().display(), reader, ext, temp_dir.path()) - .await?; + uv_extract::stream::archive(reader, ext, temp_dir.path()).await?; // If the source distribution has a version in its filename, check the version. let version = source diff --git a/crates/uv/tests/build/extract.rs b/crates/uv/tests/build/extract.rs index 4d8e7b92ca..8854bbe78f 100644 --- a/crates/uv/tests/build/extract.rs +++ b/crates/uv/tests/build/extract.rs @@ -23,7 +23,7 @@ async fn unzip(url: &str) -> anyhow::Result<(), uv_extract::Error> { .into_async_read(); let target = tempfile::TempDir::new().map_err(uv_extract::Error::Io)?; - uv_extract::stream::unzip(url, reader.compat(), target.path()).await?; + uv_extract::stream::unzip(reader.compat(), target.path()).await?; Ok(()) } diff --git a/crates/uv/tests/pip_install/pip_install.rs b/crates/uv/tests/pip_install/pip_install.rs index e65aa268a5..cde858e5f4 100644 --- a/crates/uv/tests/pip_install/pip_install.rs +++ b/crates/uv/tests/pip_install/pip_install.rs @@ -16124,7 +16124,7 @@ fn abi_compatibility_on_nondebug_python_with_debug_wheel() { } #[test] -fn warn_on_bz2_wheel() { +fn fail_on_bz2_wheel() { let context = uv_test::test_context!("3.14"); let vendor = FindLinksServer::vendor(); @@ -16133,22 +16133,21 @@ fn warn_on_bz2_wheel() { context.pip_install() .arg(format!("futzed_bz2 @ {}/futzed_bz2-0.1.0-py3-none-any.whl", vendor.url())), @" - success: true - exit_code: 0 + success: false + exit_code: 1 ----- stdout ----- ----- stderr ----- - Resolved 1 package in [TIME] - warning: One or more file entries in 'http://[LOCALHOST]/futzed_bz2-0.1.0-py3-none-any.whl' use the 'bzip2' compression method, which is not widely supported. A future version of uv will reject ZIP archives containing entries compressed with this method. Entries must be compressed with the 'stored', 'DEFLATE', or 'zstd' compression methods. - Prepared 1 package in [TIME] - Installed 1 package in [TIME] - + futzed-bz2==0.1.0 (from http://[LOCALHOST]/futzed_bz2-0.1.0-py3-none-any.whl) + WARN Range requests not supported for futzed_bz2-0.1.0-py3-none-any.whl; streaming wheel + × Failed to download `futzed-bz2 @ http://[LOCALHOST]/futzed_bz2-0.1.0-py3-none-any.whl` + ├─▶ Failed to read metadata: `http://[LOCALHOST]/futzed_bz2-0.1.0-py3-none-any.whl` + ╰─▶ Archive contains a file with an unsupported compression method; files must be compressed with 'stored', 'DEFLATE', or 'zstd' " ); } #[test] -fn warn_on_lzma_wheel() { +fn fail_on_lzma_wheel() { let context = uv_test::test_context!("3.14"); let vendor = FindLinksServer::vendor(); @@ -16162,12 +16161,10 @@ fn warn_on_lzma_wheel() { ----- stdout ----- ----- stderr ----- + WARN Range requests not supported for futzed_lzma-0.1.0-py3-none-any.whl; streaming wheel × Failed to download `futzed-lzma @ http://[LOCALHOST]/futzed_lzma-0.1.0-py3-none-any.whl` - ├─▶ Request failed after 3 retries in [TIME] ├─▶ Failed to read metadata: `http://[LOCALHOST]/futzed_lzma-0.1.0-py3-none-any.whl` - ├─▶ Failed to read from zip file - ├─▶ an upstream reader returned an error: stream/file format not recognized - ╰─▶ stream/file format not recognized + ╰─▶ Archive contains a file with an unsupported compression method; files must be compressed with 'stored', 'DEFLATE', or 'zstd' " ); }