Skip to main content

rar_stream/formats/
mod.rs

1//! RAR format detection and signatures.
2//!
3//! Zero dependencies.
4
5/// RAR file signature detection.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Signature {
8    /// RAR 1.5 to 4.x
9    Rar15,
10    /// RAR 5.0+
11    Rar50,
12}
13
14impl Signature {
15    pub const RAR15: &[u8; 7] = b"Rar!\x1a\x07\x00";
16    pub const RAR50: &[u8; 8] = b"Rar!\x1a\x07\x01\x00";
17
18    pub fn size(&self) -> u64 {
19        match self {
20            Self::Rar15 => 7,
21            Self::Rar50 => 8,
22        }
23    }
24
25    pub fn from_bytes(data: &[u8]) -> Option<Self> {
26        if data.len() >= 8 && data.starts_with(Self::RAR50) {
27            Some(Self::Rar50)
28        } else if data.len() >= 7 && data.starts_with(Self::RAR15) {
29            Some(Self::Rar15)
30        } else {
31            None
32        }
33    }
34}
35
36/// Raw timestamp value (Unix nanoseconds).
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub struct RawTimestamp {
39    pub nanos: i64,
40}
41
42impl RawTimestamp {
43    pub fn from_unix_nanos(nanos: i64) -> Self {
44        Self { nanos }
45    }
46
47    pub fn from_dos(dos_time: u32) -> Self {
48        let second = ((dos_time & 0x1f) * 2) as i64;
49        let minute = ((dos_time >> 5) & 0x3f) as i64;
50        let hour = ((dos_time >> 11) & 0x1f) as i64;
51        let day = ((dos_time >> 16) & 0x1f) as i64;
52        let month = ((dos_time >> 21) & 0x0f) as i64;
53        let year = ((dos_time >> 25) + 1980) as i64;
54
55        // Count days from epoch (1970-01-01) to the given date
56        let mut days: i64 = 0;
57        for y in 1970..year {
58            days += if y % 4 == 0 && (y % 100 != 0 || y % 400 == 0) {
59                366
60            } else {
61                365
62            };
63        }
64        let is_leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
65        let month_days = [
66            31,
67            if is_leap { 29 } else { 28 },
68            31,
69            30,
70            31,
71            30,
72            31,
73            31,
74            30,
75            31,
76            30,
77            31,
78        ];
79        for m in 0..(month - 1).min(11) as usize {
80            days += month_days[m] as i64;
81        }
82        days += day - 1;
83
84        let secs = days * 86400 + hour * 3600 + minute * 60 + second;
85        Self {
86            nanos: secs * 1_000_000_000,
87        }
88    }
89
90    pub fn saturating_add(self, add_nanos: i64) -> Self {
91        Self {
92            nanos: self.nanos.saturating_add(add_nanos),
93        }
94    }
95}
96
97pub fn parse_dos_datetime(dos_time: u32) -> RawTimestamp {
98    RawTimestamp::from_dos(dos_time)
99}
100
101pub fn parse_windows_filetime(filetime: u64) -> RawTimestamp {
102    const WINDOWS_TICK_NS: i128 = 100;
103    const EPOCH_DIFF: i128 = 11_644_473_600_000_000_000;
104    let unix_ns = (filetime as i128) * WINDOWS_TICK_NS - EPOCH_DIFF;
105    RawTimestamp::from_unix_nanos(unix_ns as i64)
106}