rar_stream/parsing/rar5/
file_header.rs1use super::{Rar5HeaderFlags, VintReader};
7use crate::error::{RarError, Result};
8
9#[inline]
11fn safe_usize(value: u64) -> Result<usize> {
12 usize::try_from(value).map_err(|_| RarError::InvalidHeader)
13}
14
15#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
17pub struct Rar5FileFlags {
18 pub is_directory: bool,
20 pub has_mtime: bool,
22 pub has_crc32: bool,
24 pub unpacked_size_unknown: bool,
26}
27
28impl From<u64> for Rar5FileFlags {
29 fn from(flags: u64) -> Self {
30 Self {
31 is_directory: flags & 0x0001 != 0,
32 has_mtime: flags & 0x0002 != 0,
33 has_crc32: flags & 0x0004 != 0,
34 unpacked_size_unknown: flags & 0x0008 != 0,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Rar5CompressionInfo {
42 pub version: u8,
44 pub is_solid: bool,
46 pub method: u8,
48 pub dict_size_log: u8,
50}
51
52impl From<u64> for Rar5CompressionInfo {
53 fn from(info: u64) -> Self {
54 Self {
55 version: (info & 0x3F) as u8,
56 is_solid: (info >> 6) & 1 != 0,
57 method: ((info >> 7) & 0x07) as u8,
58 dict_size_log: ((info >> 10) & 0x0F) as u8 + 17,
59 }
60 }
61}
62
63impl Rar5CompressionInfo {
64 pub fn dict_size(&self) -> u64 {
66 1u64 << self.dict_size_log
67 }
68
69 pub fn is_stored(&self) -> bool {
71 self.method == 0
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[repr(u8)]
78pub enum Rar5HostOs {
79 Windows = 0,
80 Unix = 1,
81}
82
83impl TryFrom<u64> for Rar5HostOs {
84 type Error = ();
85
86 fn try_from(value: u64) -> std::result::Result<Self, Self::Error> {
87 match value {
88 0 => Ok(Self::Windows),
89 1 => Ok(Self::Unix),
90 _ => Err(()),
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct Rar5FileHeader {
98 pub crc32: u32,
100 pub header_size: u64,
102 pub header_flags: Rar5HeaderFlags,
104 pub file_flags: Rar5FileFlags,
106 pub unpacked_size: u64,
108 pub attributes: u64,
110 pub mtime: Option<u32>,
112 pub file_crc32: Option<u32>,
114 pub compression: Rar5CompressionInfo,
116 pub host_os: Rar5HostOs,
118 pub name: String,
120 pub packed_size: u64,
122 pub extra_area: Option<Vec<u8>>,
124}
125
126impl Rar5FileHeader {
127 pub fn continues_from_previous(&self) -> bool {
129 self.header_flags.split_before
130 }
131
132 pub fn continues_in_next(&self) -> bool {
134 self.header_flags.split_after
135 }
136
137 pub fn is_stored(&self) -> bool {
139 self.compression.is_stored()
140 }
141
142 pub fn is_directory(&self) -> bool {
144 self.file_flags.is_directory
145 }
146
147 pub fn is_encrypted(&self) -> bool {
149 self.header_flags.has_extra_area && self.encryption_info().is_some()
150 }
151
152 pub fn encryption_info(&self) -> Option<&[u8]> {
155 let extra = self.extra_area.as_ref()?;
156 Self::find_extra_field(extra, 0x01) }
158
159 fn find_extra_field(extra: &[u8], field_type: u64) -> Option<&[u8]> {
161 let mut pos = 0;
162 while pos < extra.len() {
163 let mut reader = super::VintReader::new(&extra[pos..]);
166 let size = reader.read()?;
167 let size_vint_len = reader.position();
168 let ftype = reader.read()?;
169 let header_consumed = reader.position();
170
171 if ftype == field_type {
172 let data_start = pos + header_consumed;
174 let size_usize = size as usize;
175 if size_usize as u64 != size {
176 return None; }
178 let data_end = pos + size_vint_len + size_usize;
179 if data_end <= extra.len() {
180 return Some(&extra[data_start..data_end]);
181 }
182 }
183
184 let size_usize = size as usize;
185 if size_usize as u64 != size {
186 return None;
187 }
188 pos += size_vint_len + size_usize;
189 }
190 None
191 }
192}
193
194pub struct Rar5FileHeaderParser;
195
196impl Rar5FileHeaderParser {
197 pub fn parse(buffer: &[u8]) -> Result<(Rar5FileHeader, usize)> {
199 if buffer.len() < 12 {
200 return Err(RarError::BufferTooSmall {
201 needed: 12,
202 have: buffer.len(),
203 });
204 }
205
206 let mut reader = VintReader::new(buffer);
207
208 let crc32 = reader.read_u32_le().ok_or(RarError::InvalidHeader)?;
210
211 let header_size = reader.read().ok_or(RarError::InvalidHeader)?;
213
214 let header_content_start = reader.position();
216
217 let header_type = reader.read().ok_or(RarError::InvalidHeader)?;
219 if header_type != 2 {
220 return Err(RarError::InvalidHeader);
221 }
222
223 let header_flags_raw = reader.read().ok_or(RarError::InvalidHeader)?;
225 let header_flags = Rar5HeaderFlags::from(header_flags_raw);
226
227 let extra_area_size = if header_flags.has_extra_area {
229 reader.read().ok_or(RarError::InvalidHeader)?
230 } else {
231 0
232 };
233
234 let packed_size = if header_flags.has_data_area {
236 reader.read().ok_or(RarError::InvalidHeader)?
237 } else {
238 0
239 };
240
241 let file_flags_raw = reader.read().ok_or(RarError::InvalidHeader)?;
243 let file_flags = Rar5FileFlags::from(file_flags_raw);
244
245 let unpacked_size = reader.read().ok_or(RarError::InvalidHeader)?;
246 let attributes = reader.read().ok_or(RarError::InvalidHeader)?;
247
248 let mtime = if file_flags.has_mtime {
250 Some(reader.read_u32_le().ok_or(RarError::InvalidHeader)?)
251 } else {
252 None
253 };
254
255 let file_crc32 = if file_flags.has_crc32 {
257 Some(reader.read_u32_le().ok_or(RarError::InvalidHeader)?)
258 } else {
259 None
260 };
261
262 let compression_raw = reader.read().ok_or(RarError::InvalidHeader)?;
264 let compression = Rar5CompressionInfo::from(compression_raw);
265
266 let host_os_raw = reader.read().ok_or(RarError::InvalidHeader)?;
268 let host_os = Rar5HostOs::try_from(host_os_raw).map_err(|()| RarError::InvalidHeader)?;
269
270 let name_len = reader.read().ok_or(RarError::InvalidHeader)?;
272 let name_bytes = reader
273 .read_bytes(safe_usize(name_len)?)
274 .ok_or(RarError::InvalidHeader)?;
275 let name = String::from_utf8_lossy(name_bytes).into_owned();
276
277 let extra_area = if extra_area_size > 0 {
279 let extra_bytes = reader
280 .read_bytes(safe_usize(extra_area_size)?)
281 .ok_or(RarError::InvalidHeader)?;
282 Some(extra_bytes.to_vec())
283 } else {
284 None
285 };
286
287 let total_consumed = header_content_start + safe_usize(header_size)?;
290
291 Ok((
292 Rar5FileHeader {
293 crc32,
294 header_size,
295 header_flags,
296 file_flags,
297 unpacked_size,
298 attributes,
299 mtime,
300 file_crc32,
301 compression,
302 host_os,
303 name,
304 packed_size,
305 extra_area,
306 },
307 total_consumed,
308 ))
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_compression_info() {
318 let info = Rar5CompressionInfo::from(0);
321 assert_eq!(info.version, 0);
322 assert_eq!(info.method, 0);
323 assert_eq!(info.dict_size_log, 17);
324 assert!(!info.is_solid);
325 assert!(info.is_stored());
326 }
327
328 #[test]
329 fn test_compression_with_method() {
330 let info = Rar5CompressionInfo::from(0x180);
332 assert_eq!(info.method, 3);
333 }
334
335 #[test]
336 fn test_stored_file() {
337 let info = Rar5CompressionInfo::from(0);
338 assert!(info.is_stored());
339 }
340}