1+ use std:: fmt:: Display ;
12use std:: path:: { Component , Path , PathBuf } ;
23use std:: pin:: Pin ;
34
@@ -9,8 +10,9 @@ use tokio_util::compat::{FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt};
910use tracing:: { debug, warn} ;
1011
1112use uv_distribution_filename:: SourceDistExtension ;
13+ use uv_warnings:: warn_user_once;
1214
13- use crate :: { Error , insecure_no_validate, validate_archive_member_name} ;
15+ use crate :: { CompressionMethod , Error , insecure_no_validate, validate_archive_member_name} ;
1416
1517const DEFAULT_BUF_SIZE : usize = 128 * 1024 ;
1618
@@ -43,7 +45,11 @@ struct ComputedEntry {
4345/// This is useful for unzipping files as they're being downloaded. If the archive
4446/// is already fully on disk, consider using `unzip_archive`, which can use multiple
4547/// threads to work faster in that case.
46- pub async fn unzip < R : tokio:: io:: AsyncRead + Unpin > (
48+ ///
49+ /// `source_hint` is used for warning messages, to identify the source of the ZIP archive
50+ /// beneath the reader. It might be a URL, a file path, or something else.
51+ pub async fn unzip < D : Display , R : tokio:: io:: AsyncRead + Unpin > (
52+ source_hint : D ,
4753 reader : R ,
4854 target : impl AsRef < Path > ,
4955) -> Result < ( ) , Error > {
@@ -79,8 +85,22 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
7985 let mut offset = 0 ;
8086
8187 while let Some ( mut entry) = zip. next_with_entry ( ) . await ? {
88+ let zip_entry = entry. reader ( ) . entry ( ) ;
89+
90+ // Check for unexpected compression methods.
91+ // A future version of uv will reject instead of warning about these.
92+ let compression = CompressionMethod :: from ( zip_entry. compression ( ) ) ;
93+ if !compression. is_well_known ( ) {
94+ warn_user_once ! (
95+ "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." ,
96+ stored = CompressionMethod :: Stored ,
97+ deflate = CompressionMethod :: Deflated ,
98+ zstd = CompressionMethod :: Zstd ,
99+ ) ;
100+ }
101+
82102 // Construct the (expected) path to the file on-disk.
83- let path = match entry . reader ( ) . entry ( ) . filename ( ) . as_str ( ) {
103+ let path = match zip_entry . filename ( ) . as_str ( ) {
84104 Ok ( path) => path,
85105 Err ( ZipError :: StringNotUtf8 ) => return Err ( Error :: LocalHeaderNotUtf8 { offset } ) ,
86106 Err ( err) => return Err ( err. into ( ) ) ,
@@ -107,14 +127,14 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
107127 continue ;
108128 } ;
109129
110- let file_offset = entry . reader ( ) . entry ( ) . file_offset ( ) ;
111- let expected_compressed_size = entry . reader ( ) . entry ( ) . compressed_size ( ) ;
112- let expected_uncompressed_size = entry . reader ( ) . entry ( ) . uncompressed_size ( ) ;
113- let expected_data_descriptor = entry . reader ( ) . entry ( ) . data_descriptor ( ) ;
130+ let file_offset = zip_entry . file_offset ( ) ;
131+ let expected_compressed_size = zip_entry . compressed_size ( ) ;
132+ let expected_uncompressed_size = zip_entry . uncompressed_size ( ) ;
133+ let expected_data_descriptor = zip_entry . data_descriptor ( ) ;
114134
115135 // Either create the directory or write the file to disk.
116136 let path = target. join ( & relpath) ;
117- let is_dir = entry . reader ( ) . entry ( ) . dir ( ) ?;
137+ let is_dir = zip_entry . dir ( ) ?;
118138 let computed = if is_dir {
119139 if directories. insert ( path. clone ( ) ) {
120140 fs_err:: tokio:: create_dir_all ( path)
@@ -123,23 +143,23 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
123143 }
124144
125145 // If this is a directory, we expect the CRC32 to be 0.
126- if entry . reader ( ) . entry ( ) . crc32 ( ) != 0 {
146+ if zip_entry . crc32 ( ) != 0 {
127147 if !skip_validation {
128148 return Err ( Error :: BadCrc32 {
129149 path : relpath. clone ( ) ,
130150 computed : 0 ,
131- expected : entry . reader ( ) . entry ( ) . crc32 ( ) ,
151+ expected : zip_entry . crc32 ( ) ,
132152 } ) ;
133153 }
134154 }
135155
136156 // If this is a directory, we expect the uncompressed size to be 0.
137- if entry . reader ( ) . entry ( ) . uncompressed_size ( ) != 0 {
157+ if zip_entry . uncompressed_size ( ) != 0 {
138158 if !skip_validation {
139159 return Err ( Error :: BadUncompressedSize {
140160 path : relpath. clone ( ) ,
141161 computed : 0 ,
142- expected : entry . reader ( ) . entry ( ) . uncompressed_size ( ) ,
162+ expected : zip_entry . uncompressed_size ( ) ,
143163 } ) ;
144164 }
145165 }
@@ -164,7 +184,7 @@ pub async fn unzip<R: tokio::io::AsyncRead + Unpin>(
164184 {
165185 Ok ( file) => {
166186 // Write the file to disk.
167- let size = entry . reader ( ) . entry ( ) . uncompressed_size ( ) ;
187+ let size = zip_entry . uncompressed_size ( ) ;
168188 let mut writer = if let Ok ( size) = usize:: try_from ( size) {
169189 tokio:: io:: BufWriter :: with_capacity ( std:: cmp:: min ( size, 1024 * 1024 ) , file)
170190 } else {
@@ -744,14 +764,18 @@ pub async fn untar<R: tokio::io::AsyncRead + Unpin>(
744764
745765/// Unpack a `.zip`, `.tar.gz`, `.tar.bz2`, `.tar.zst`, or `.tar.xz` archive into the target directory,
746766/// without requiring `Seek`.
747- pub async fn archive < R : tokio:: io:: AsyncRead + Unpin > (
767+ ///
768+ /// `source_hint` is used for warning messages, to identify the source of the archive
769+ /// beneath the reader. It might be a URL, a file path, or something else.
770+ pub async fn archive < D : Display , R : tokio:: io:: AsyncRead + Unpin > (
771+ source_hint : D ,
748772 reader : R ,
749773 ext : SourceDistExtension ,
750774 target : impl AsRef < Path > ,
751775) -> Result < ( ) , Error > {
752776 match ext {
753777 SourceDistExtension :: Zip => {
754- unzip ( reader, target) . await ?;
778+ unzip ( source_hint , reader, target) . await ?;
755779 }
756780 SourceDistExtension :: Tar => {
757781 untar ( reader, target) . await ?;
0 commit comments