Skip to main content

rar_stream/parsing/
marker_header.rs

1//! Marker header parser - RAR signature.
2//!
3//! The marker header is the first 7 bytes of a RAR file.
4//! RAR4: 0x52 0x61 0x72 0x21 0x1A 0x07 0x00
5//! RAR5: 0x52 0x61 0x72 0x21 0x1A 0x07 0x01 0x00
6
7use crate::error::{RarError, Result};
8
9/// RAR4 magic signature: `Rar!\x1a\x07\x00`.
10///
11/// ```
12/// use rar_stream::parsing::marker_header::RAR4_SIGNATURE;
13/// assert_eq!(&RAR4_SIGNATURE[..4], b"Rar!");
14/// ```
15pub const RAR4_SIGNATURE: [u8; 7] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00];
16
17/// RAR5 magic signature: `Rar!\x1a\x07\x01\x00`.
18///
19/// ```
20/// use rar_stream::parsing::marker_header::RAR5_SIGNATURE;
21/// assert_eq!(&RAR5_SIGNATURE[..4], b"Rar!");
22/// assert_eq!(RAR5_SIGNATURE[6], 0x01); // distinguishes RAR5 from RAR4
23/// ```
24pub const RAR5_SIGNATURE: [u8; 8] = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00];
25
26/// RAR archive version.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
28pub enum RarVersion {
29    /// RAR 4.x format (also known as RAR 2.9)
30    #[default]
31    Rar4,
32    /// RAR 5.x format
33    Rar5,
34}
35
36impl RarVersion {
37    /// Returns the signature size for this version.
38    pub fn signature_size(&self) -> usize {
39        match self {
40            Self::Rar4 => 7,
41            Self::Rar5 => 8,
42        }
43    }
44}
45
46/// Parsed RAR marker header (the first header in every RAR archive).
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct MarkerHeader {
49    /// Header CRC (RAR4 only; always 0x6152 for valid archives).
50    pub crc: u16,
51    /// Header type byte.
52    pub header_type: u8,
53    /// Header flags.
54    pub flags: u16,
55    /// Header size in bytes.
56    pub size: u32,
57    /// Detected RAR format version.
58    pub version: RarVersion,
59}
60
61/// Parser for the RAR archive marker (signature) header.
62pub struct MarkerHeaderParser;
63
64impl MarkerHeaderParser {
65    /// Size of the RAR4 marker header in bytes.
66    pub const HEADER_SIZE: usize = 7;
67
68    /// Detect RAR version from buffer without full parsing.
69    ///
70    /// Returns [`RarVersion::Rar4`] or [`RarVersion::Rar5`] based on the
71    /// signature bytes, or [`RarError::InvalidSignature`] if the buffer
72    /// doesn't start with a valid RAR signature.
73    ///
74    /// ```
75    /// use rar_stream::parsing::marker_header::MarkerHeaderParser;
76    /// use rar_stream::parsing::marker_header::RarVersion;
77    ///
78    /// let rar4 = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00];
79    /// assert_eq!(MarkerHeaderParser::detect_version(&rar4).unwrap(), RarVersion::Rar4);
80    ///
81    /// let rar5 = [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00];
82    /// assert_eq!(MarkerHeaderParser::detect_version(&rar5).unwrap(), RarVersion::Rar5);
83    ///
84    /// assert!(MarkerHeaderParser::detect_version(b"not rar").is_err());
85    /// ```
86    pub fn detect_version(buffer: &[u8]) -> Result<RarVersion> {
87        if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
88            return Ok(RarVersion::Rar5);
89        }
90        if buffer.len() >= 7 && buffer[..7] == RAR4_SIGNATURE {
91            return Ok(RarVersion::Rar4);
92        }
93        Err(RarError::InvalidSignature)
94    }
95
96    /// Parse marker header from buffer.
97    /// The marker header is actually parsed as a generic RAR header.
98    /// The "size" field tells us how many bytes this header consumes.
99    pub fn parse(buffer: &[u8]) -> Result<MarkerHeader> {
100        if buffer.len() < Self::HEADER_SIZE {
101            return Err(RarError::BufferTooSmall {
102                needed: Self::HEADER_SIZE,
103                have: buffer.len(),
104            });
105        }
106
107        // Check for RAR5 first (longer signature)
108        if buffer.len() >= 8 && buffer[..8] == RAR5_SIGNATURE {
109            return Ok(MarkerHeader {
110                crc: 0,
111                header_type: 0,
112                flags: 0,
113                size: 8,
114                version: RarVersion::Rar5,
115            });
116        }
117
118        // Verify RAR4 signature (first 7 bytes)
119        if buffer[..7] != RAR4_SIGNATURE {
120            return Err(RarError::InvalidSignature);
121        }
122
123        // Parse as generic header structure (RAR4)
124        let crc = u16::from_le_bytes([buffer[0], buffer[1]]);
125        let header_type = buffer[2];
126        let flags = u16::from_le_bytes([buffer[3], buffer[4]]);
127        let size = u16::from_le_bytes([buffer[5], buffer[6]]) as u32;
128
129        Ok(MarkerHeader {
130            crc,
131            header_type,
132            flags,
133            size,
134            version: RarVersion::Rar4,
135        })
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn test_parse_rar4_marker() {
145        // RAR4 signature + header
146        let buffer = [
147            0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00, // RAR4 signature
148            0x00, 0x00, 0x00, 0x00, // padding for HEADER_SIZE
149        ];
150        let header = MarkerHeaderParser::parse(&buffer).unwrap();
151        assert_eq!(header.header_type, b'r'); // 0x72
152    }
153
154    #[test]
155    fn test_invalid_signature() {
156        let buffer = [0x00; 11];
157        assert!(matches!(
158            MarkerHeaderParser::parse(&buffer),
159            Err(RarError::InvalidSignature)
160        ));
161    }
162
163    #[test]
164    fn test_buffer_too_small() {
165        let buffer = [0x52, 0x61, 0x72];
166        assert!(matches!(
167            MarkerHeaderParser::parse(&buffer),
168            Err(RarError::BufferTooSmall { .. })
169        ));
170    }
171}