Skip to main content

rar_stream/parsing/
file_header.rs

1//! File header parser.
2//!
3//! Each file in a RAR archive has a file header that describes
4//! the file's name, size, compression method, etc.
5
6use crate::error::{RarError, Result};
7
8/// File header type constant.
9pub const FILE_HEADER_TYPE: u8 = 0x74; // 116
10
11#[derive(Debug, Clone)]
12pub struct FileHeader {
13    pub crc: u16,
14    pub header_type: u8,
15    pub flags: u16,
16    pub head_size: u16,
17    pub packed_size: u64,
18    pub unpacked_size: u64,
19    pub host_os: u8,
20    pub file_crc: u32,
21    pub timestamp: u32,
22    pub version: u8,
23    pub method: u8,
24    pub name_size: u16,
25    pub attributes: u32,
26    pub name: String,
27    // Parsed flags
28    pub continues_from_previous: bool,
29    pub continues_in_next: bool,
30    pub is_encrypted: bool,
31    pub has_comment: bool,
32    pub has_info_from_previous: bool,
33    pub has_high_size: bool,
34    pub has_special_name: bool,
35    pub has_salt: bool,
36    pub is_old_version: bool,
37    pub has_extended_time: bool,
38}
39
40pub struct FileHeaderParser;
41
42impl FileHeaderParser {
43    /// Maximum header size to read (includes variable-length filename).
44    pub const HEADER_SIZE: usize = 280;
45    /// Minimum fixed header size before filename.
46    const MIN_HEADER_SIZE: usize = 32;
47
48    pub fn parse(buffer: &[u8]) -> Result<FileHeader> {
49        if buffer.len() < Self::MIN_HEADER_SIZE {
50            return Err(RarError::BufferTooSmall {
51                needed: Self::MIN_HEADER_SIZE,
52                have: buffer.len(),
53            });
54        }
55
56        let mut offset = 0;
57
58        let crc = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
59        offset += 2;
60
61        let header_type = buffer[offset];
62        offset += 1;
63
64        let flags = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
65        offset += 2;
66
67        let head_size = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
68        offset += 2;
69
70        let mut packed_size = u32::from_le_bytes([
71            buffer[offset],
72            buffer[offset + 1],
73            buffer[offset + 2],
74            buffer[offset + 3],
75        ]) as u64;
76        offset += 4;
77
78        let mut unpacked_size = u32::from_le_bytes([
79            buffer[offset],
80            buffer[offset + 1],
81            buffer[offset + 2],
82            buffer[offset + 3],
83        ]) as u64;
84        offset += 4;
85
86        let host_os = buffer[offset];
87        offset += 1;
88
89        let file_crc = u32::from_le_bytes([
90            buffer[offset],
91            buffer[offset + 1],
92            buffer[offset + 2],
93            buffer[offset + 3],
94        ]);
95        offset += 4;
96
97        let timestamp = u32::from_le_bytes([
98            buffer[offset],
99            buffer[offset + 1],
100            buffer[offset + 2],
101            buffer[offset + 3],
102        ]);
103        offset += 4;
104
105        let version = buffer[offset];
106        offset += 1;
107
108        let method = buffer[offset];
109        offset += 1;
110
111        let name_size = u16::from_le_bytes([buffer[offset], buffer[offset + 1]]);
112        offset += 2;
113
114        let attributes = u32::from_le_bytes([
115            buffer[offset],
116            buffer[offset + 1],
117            buffer[offset + 2],
118            buffer[offset + 3],
119        ]);
120        offset += 4;
121
122        // Parse flags
123        let continues_from_previous = (flags & 0x01) != 0;
124        let continues_in_next = (flags & 0x02) != 0;
125        let is_encrypted = (flags & 0x04) != 0;
126        let has_comment = (flags & 0x08) != 0;
127        let has_info_from_previous = (flags & 0x10) != 0;
128        let has_high_size = (flags & 0x100) != 0;
129        let has_special_name = (flags & 0x200) != 0;
130        let has_salt = (flags & 0x400) != 0;
131        let is_old_version = (flags & 0x800) != 0;
132        let has_extended_time = (flags & 0x1000) != 0;
133
134        // Handle 64-bit sizes
135        if has_high_size && buffer.len() >= offset + 8 {
136            let high_packed = u32::from_le_bytes([
137                buffer[offset],
138                buffer[offset + 1],
139                buffer[offset + 2],
140                buffer[offset + 3],
141            ]) as u64;
142            offset += 4;
143            let high_unpacked = u32::from_le_bytes([
144                buffer[offset],
145                buffer[offset + 1],
146                buffer[offset + 2],
147                buffer[offset + 3],
148            ]) as u64;
149            offset += 4;
150
151            packed_size |= high_packed << 32;
152            unpacked_size |= high_unpacked << 32;
153        }
154
155        // Parse filename
156        let name_end = offset + name_size as usize;
157        if buffer.len() < name_end {
158            return Err(RarError::BufferTooSmall {
159                needed: name_end,
160                have: buffer.len(),
161            });
162        }
163        let name = String::from_utf8_lossy(&buffer[offset..name_end]).to_string();
164
165        Ok(FileHeader {
166            crc,
167            header_type,
168            flags,
169            head_size,
170            packed_size,
171            unpacked_size,
172            host_os,
173            file_crc,
174            timestamp,
175            version,
176            method,
177            name_size,
178            attributes,
179            name,
180            continues_from_previous,
181            continues_in_next,
182            is_encrypted,
183            has_comment,
184            has_info_from_previous,
185            has_high_size,
186            has_special_name,
187            has_salt,
188            is_old_version,
189            has_extended_time,
190        })
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    #[test]
199    fn test_parse_file_header() {
200        // Minimal file header with a 4-byte filename "test"
201        let mut buffer = vec![0u8; 36];
202        buffer[2] = FILE_HEADER_TYPE; // type
203        buffer[5] = 36; // head_size low byte
204        buffer[26] = 4; // name_size = 4
205        buffer[32] = b't';
206        buffer[33] = b'e';
207        buffer[34] = b's';
208        buffer[35] = b't';
209
210        let header = FileHeaderParser::parse(&buffer).unwrap();
211        assert_eq!(header.header_type, FILE_HEADER_TYPE);
212        assert_eq!(header.name, "test");
213    }
214
215    #[test]
216    fn test_compression_method() {
217        let mut buffer = vec![0u8; 36];
218        buffer[2] = FILE_HEADER_TYPE;
219        buffer[5] = 36;
220        buffer[25] = 0x30; // method = Store (no compression) - at offset 25
221        buffer[26] = 4;    // name_size low byte
222        buffer[32..36].copy_from_slice(b"test");
223
224        let header = FileHeaderParser::parse(&buffer).unwrap();
225        assert_eq!(header.method, 0x30); // Store method
226    }
227}