rar_stream/error.rs
1//! Error types for RAR parsing and decompression.
2//!
3//! This module provides the [`RarError`] type which covers all possible errors
4//! that can occur when parsing, decompressing, or decrypting RAR archives.
5//!
6//! ## Error Categories
7//!
8//! | Category | Errors | Description |
9//! |----------|--------|-------------|
10//! | Format | [`InvalidSignature`], [`InvalidHeader`] | File is not a valid RAR archive |
11//! | Encryption | [`PasswordRequired`], [`DecryptionFailed`] | Encryption-related errors |
12//! | Decompression | [`DecompressionNotSupported`] | Unsupported compression method |
13//! | I/O | [`Io`], [`BufferTooSmall`] | Read/write errors |
14//!
15//! ## Example
16//!
17//! ```rust,ignore
18//! use rar_stream::{RarFilesPackage, RarError};
19//!
20//! match package.parse(opts).await {
21//! Ok(files) => println!("Found {} files", files.len()),
22//! Err(RarError::InvalidSignature) => eprintln!("Not a RAR file"),
23//! Err(RarError::PasswordRequired) => eprintln!("Archive is encrypted"),
24//! Err(e) => eprintln!("Error: {}", e),
25//! }
26//! ```
27//!
28//! [`InvalidSignature`]: RarError::InvalidSignature
29//! [`InvalidHeader`]: RarError::InvalidHeader
30//! [`PasswordRequired`]: RarError::PasswordRequired
31//! [`DecryptionFailed`]: RarError::DecryptionFailed
32//! [`DecompressionNotSupported`]: RarError::DecompressionNotSupported
33//! [`Io`]: RarError::Io
34//! [`BufferTooSmall`]: RarError::BufferTooSmall
35
36use std::fmt;
37use std::io;
38
39/// Error type for RAR operations.
40///
41/// This enum covers all possible errors that can occur when parsing,
42/// decompressing, or decrypting RAR archives. It implements [`std::error::Error`]
43/// for integration with the Rust error handling ecosystem.
44///
45/// # Example
46///
47/// ```rust
48/// use rar_stream::RarError;
49///
50/// fn handle_error(err: RarError) {
51/// match err {
52/// RarError::InvalidSignature => {
53/// // File doesn't start with RAR magic bytes
54/// }
55/// RarError::PasswordRequired => {
56/// // Need to provide password in ParseOptions
57/// }
58/// RarError::Io(io_err) => {
59/// // Underlying I/O error (file not found, permission denied, etc.)
60/// }
61/// _ => {}
62/// }
63/// }
64/// ```
65#[derive(Debug)]
66pub enum RarError {
67 /// The file does not have a valid RAR signature.
68 ///
69 /// RAR files must start with either:
70 /// - RAR4: `Rar!\x1a\x07\x00` (7 bytes)
71 /// - RAR5: `Rar!\x1a\x07\x01\x00` (8 bytes)
72 InvalidSignature,
73
74 /// A header in the archive is malformed or corrupt.
75 ///
76 /// This usually indicates file corruption or an incomplete download.
77 InvalidHeader,
78
79 /// An unknown or unsupported header type was encountered.
80 ///
81 /// The `u8` value is the header type byte. Standard types are:
82 /// - `0x72` (114): Marker header
83 /// - `0x73` (115): Archive header
84 /// - `0x74` (116): File header
85 /// - `0x7B` (123): End of archive
86 InvalidHeaderType(u8),
87
88 /// The compression method is not supported.
89 ///
90 /// The `u8` value is the method byte:
91 /// - `0x30`: Store (no compression) - always supported
92 /// - `0x31`-`0x35`: LZSS variants - supported
93 /// - `0x36`+: Future methods - may not be supported
94 DecompressionNotSupported(u8),
95
96 /// The archive is encrypted but the `crypto` feature is not enabled.
97 ///
98 /// Enable the `crypto` feature in Cargo.toml:
99 /// ```toml
100 /// rar-stream = { version = "4", features = ["async", "crypto"] }
101 /// ```
102 EncryptedNotSupported,
103
104 /// The archive is encrypted but no password was provided.
105 ///
106 /// Provide a password in [`ParseOptions`]:
107 /// ```rust,ignore
108 /// let opts = ParseOptions {
109 /// password: Some("secret".to_string()),
110 /// ..Default::default()
111 /// };
112 /// ```
113 ///
114 /// [`ParseOptions`]: crate::ParseOptions
115 PasswordRequired,
116
117 /// Decryption failed (wrong password or corrupt data).
118 ///
119 /// The `String` contains additional context about the failure.
120 /// Common causes:
121 /// - Incorrect password
122 /// - Corrupt encrypted data
123 /// - Truncated archive
124 DecryptionFailed(String),
125
126 /// The provided buffer is too small.
127 ///
128 /// This occurs when reading into a fixed-size buffer that cannot
129 /// hold the required data.
130 BufferTooSmall {
131 /// Number of bytes needed.
132 needed: usize,
133 /// Number of bytes available.
134 have: usize,
135 },
136
137 /// An invalid file offset was requested.
138 ///
139 /// This occurs when seeking beyond the end of a file or archive.
140 InvalidOffset {
141 /// The requested offset.
142 offset: u64,
143 /// The actual file length.
144 length: u64,
145 },
146
147 /// An I/O error occurred.
148 ///
149 /// Wraps [`std::io::Error`] for file system operations.
150 Io(io::Error),
151
152 /// A header CRC32 check failed, indicating corruption or tampering.
153 CrcMismatch {
154 /// Expected CRC from the header.
155 expected: u32,
156 /// Actual CRC computed from header data.
157 actual: u32,
158 },
159
160 /// No files were found in the archive.
161 ///
162 /// The archive may be empty, or all files may have been filtered out.
163 NoFilesFound,
164
165 /// RAR5 format detected but a specific feature is not supported.
166 ///
167 /// This is a legacy error that should rarely occur with current versions.
168 Rar5NotFullySupported,
169
170 /// The archive has encrypted headers and requires a password to list files.
171 ///
172 /// RAR5 archives created with `rar -hp` encrypt both file data and headers.
173 /// Without the correct password, even file names cannot be read.
174 #[cfg(feature = "crypto")]
175 EncryptedHeaders,
176}
177
178impl fmt::Display for RarError {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 match self {
181 Self::InvalidSignature => write!(f, "Invalid RAR signature"),
182 Self::InvalidHeader => write!(f, "Invalid or malformed header"),
183 Self::InvalidHeaderType(t) => write!(f, "Invalid header type: {}", t),
184 Self::DecompressionNotSupported(m) => {
185 write!(f, "Decompression not supported (method: 0x{:02x})", m)
186 }
187 Self::EncryptedNotSupported => write!(f, "Encrypted archives not supported"),
188 Self::PasswordRequired => write!(f, "Password required for encrypted file"),
189 Self::DecryptionFailed(msg) => write!(f, "Decryption failed: {}", msg),
190 Self::BufferTooSmall { needed, have } => {
191 write!(f, "Buffer too small: need {} bytes, have {}", needed, have)
192 }
193 Self::InvalidOffset { offset, length } => {
194 write!(f, "Invalid offset: {} (file length: {})", offset, length)
195 }
196 Self::Io(e) => write!(f, "IO error: {}", e),
197 Self::CrcMismatch { expected, actual } => {
198 write!(
199 f,
200 "CRC32 mismatch: expected 0x{:08x}, got 0x{:08x}",
201 expected, actual
202 )
203 }
204 Self::NoFilesFound => write!(f, "No files found in archive"),
205 Self::Rar5NotFullySupported => {
206 write!(
207 f,
208 "RAR5 format detected but decompression not yet supported"
209 )
210 }
211 #[cfg(feature = "crypto")]
212 Self::EncryptedHeaders => {
213 write!(
214 f,
215 "Archive has encrypted headers, password required to list files"
216 )
217 }
218 }
219 }
220}
221
222impl std::error::Error for RarError {
223 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
224 match self {
225 Self::Io(e) => Some(e),
226 _ => None,
227 }
228 }
229}
230
231impl From<io::Error> for RarError {
232 fn from(e: io::Error) -> Self {
233 Self::Io(e)
234 }
235}
236
237impl From<crate::decompress::DecompressError> for RarError {
238 fn from(e: crate::decompress::DecompressError) -> Self {
239 match e {
240 crate::decompress::DecompressError::UnsupportedMethod(m) => {
241 Self::DecompressionNotSupported(m)
242 }
243 _ => Self::Io(io::Error::new(io::ErrorKind::InvalidData, e.to_string())),
244 }
245 }
246}
247
248pub type Result<T> = std::result::Result<T, RarError>;