1use std::{ffi::OsString, path::PathBuf};
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5 #[error("I/O operation failed during extraction")]
6 Io(#[source] std::io::Error),
7 #[error("Invalid zip file")]
8 Zip(#[from] zip::result::ZipError),
9 #[error("Invalid zip file structure")]
10 AsyncZip(#[from] async_zip::error::ZipError),
11 #[error("Invalid tar file")]
12 Tar(#[from] tokio_tar::TarError),
13 #[error(
14 "The top-level of the archive must only contain a list directory, but it contains: {0:?}"
15 )]
16 NonSingularArchive(Vec<OsString>),
17 #[error("The top-level of the archive must only contain a list directory, but it's empty")]
18 EmptyArchive,
19 #[error("ZIP local header filename at offset {offset} does not use UTF-8 encoding")]
20 LocalHeaderNotUtf8 { offset: u64 },
21 #[error("ZIP central directory entry filename at index {index} does not use UTF-8 encoding")]
22 CentralDirectoryEntryNotUtf8 { index: u64 },
23 #[error("Bad CRC (got {computed:08x}, expected {expected:08x}) for file: {}", path.display())]
24 BadCrc32 {
25 path: PathBuf,
26 computed: u32,
27 expected: u32,
28 },
29 #[error("Bad uncompressed size (got {computed:08x}, expected {expected:08x}) for file: {}", path.display())]
30 BadUncompressedSize {
31 path: PathBuf,
32 computed: u64,
33 expected: u64,
34 },
35 #[error("Bad compressed size (got {computed:08x}, expected {expected:08x}) for file: {}", path.display())]
36 BadCompressedSize {
37 path: PathBuf,
38 computed: u64,
39 expected: u64,
40 },
41 #[error("ZIP file contains multiple entries with different contents for: {}", path.display())]
42 DuplicateLocalFileHeader { path: PathBuf },
43 #[error("ZIP file contains a local file header without a corresponding central-directory record entry for: {} ({offset})", path.display())]
44 MissingCentralDirectoryEntry { path: PathBuf, offset: u64 },
45 #[error("ZIP file contains an end-of-central-directory record entry, but no local file header for: {} ({offset}", path.display())]
46 MissingLocalFileHeader { path: PathBuf, offset: u64 },
47 #[error("ZIP file uses conflicting paths for the local file header at {} (got {}, expected {})", offset, local_path.display(), central_directory_path.display())]
48 ConflictingPaths {
49 offset: u64,
50 local_path: PathBuf,
51 central_directory_path: PathBuf,
52 },
53 #[error("ZIP file uses conflicting checksums for the local file header and central-directory record (got {local_crc32}, expected {central_directory_crc32}) for: {} ({offset})", path.display())]
54 ConflictingChecksums {
55 path: PathBuf,
56 offset: u64,
57 local_crc32: u32,
58 central_directory_crc32: u32,
59 },
60 #[error("ZIP file uses conflicting compressed sizes for the local file header and central-directory record (got {local_compressed_size}, expected {central_directory_compressed_size}) for: {} ({offset})", path.display())]
61 ConflictingCompressedSizes {
62 path: PathBuf,
63 offset: u64,
64 local_compressed_size: u64,
65 central_directory_compressed_size: u64,
66 },
67 #[error("ZIP file uses conflicting uncompressed sizes for the local file header and central-directory record (got {local_uncompressed_size}, expected {central_directory_uncompressed_size}) for: {} ({offset})", path.display())]
68 ConflictingUncompressedSizes {
69 path: PathBuf,
70 offset: u64,
71 local_uncompressed_size: u64,
72 central_directory_uncompressed_size: u64,
73 },
74 #[error("ZIP file contains trailing contents after the end-of-central-directory record")]
75 TrailingContents,
76 #[error(
77 "ZIP file reports a number of entries in the central directory that conflicts with the actual number of entries (got {actual}, expected {expected})"
78 )]
79 ConflictingNumberOfEntries { actual: u64, expected: u64 },
80 #[error("Data descriptor is missing for file: {}", path.display())]
81 MissingDataDescriptor { path: PathBuf },
82 #[error("File contains an unexpected data descriptor: {}", path.display())]
83 UnexpectedDataDescriptor { path: PathBuf },
84 #[error(
85 "ZIP file end-of-central-directory record contains a comment that appears to be an embedded ZIP file"
86 )]
87 ZipInZip,
88 #[error("ZIP64 end-of-central-directory record contains unsupported extensible data")]
89 ExtensibleData,
90 #[error("ZIP file end-of-central-directory record contains multiple entries with the same path, but conflicting modes: {}", path.display())]
91 DuplicateExecutableFileHeader { path: PathBuf },
92 #[error("Archive contains a file with an empty filename")]
93 EmptyFilename,
94 #[error("Archive contains unacceptable filename: {filename}")]
95 UnacceptableFilename { filename: String },
96}
97
98impl Error {
99 pub(crate) fn io_or_compression(err: std::io::Error) -> Self {
104 if err.kind() != std::io::ErrorKind::Other {
105 return Self::Io(err);
106 }
107
108 let err = match err.downcast::<tokio_tar::TarError>() {
109 Ok(tar_err) => return Self::Tar(tar_err),
110 Err(err) => err,
111 };
112 let err = match err.downcast::<async_zip::error::ZipError>() {
113 Ok(zip_err) => return Self::AsyncZip(zip_err),
114 Err(err) => err,
115 };
116 let err = match err.downcast::<zip::result::ZipError>() {
117 Ok(zip_err) => return Self::Zip(zip_err),
118 Err(err) => err,
119 };
120
121 Self::Io(err)
122 }
123
124 pub fn is_http_streaming_unsupported(&self) -> bool {
128 matches!(
129 self,
130 Self::AsyncZip(async_zip::error::ZipError::FeatureNotSupported(_))
131 )
132 }
133
134 pub fn is_http_streaming_failed(&self) -> bool {
136 match self {
137 Self::AsyncZip(async_zip::error::ZipError::UpstreamReadError(_)) => true,
138 Self::Io(err) => {
139 if let Some(inner) = err.get_ref() {
140 inner.downcast_ref::<reqwest::Error>().is_some()
141 } else {
142 false
143 }
144 }
145 _ => false,
146 }
147 }
148}