1use crate::version::ArchiveFamily;
2
3pub const RAR13_SIGNATURE: &[u8; 4] = b"RE~^";
4pub const RAR15_SIGNATURE: &[u8; 7] = b"Rar!\x1a\x07\x00";
5pub const RAR50_SIGNATURE: &[u8; 8] = b"Rar!\x1a\x07\x01\x00";
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub struct ArchiveSignature {
10 pub family: ArchiveFamily,
11 pub offset: usize,
12 pub length: usize,
13}
14
15pub fn detect_archive_family(input: &[u8]) -> Option<ArchiveSignature> {
16 detect_at(input, 0)
17}
18
19pub fn find_archive_start(input: &[u8], max_scan: usize) -> Option<ArchiveSignature> {
20 let limit = input.len().min(max_scan);
21 (0..=limit).find_map(|offset| detect_at(input, offset))
22}
23
24fn detect_at(input: &[u8], offset: usize) -> Option<ArchiveSignature> {
25 let tail = input.get(offset..)?;
26
27 if tail.starts_with(RAR50_SIGNATURE) {
28 Some(ArchiveSignature {
29 family: ArchiveFamily::Rar50Plus,
30 offset,
31 length: RAR50_SIGNATURE.len(),
32 })
33 } else if tail.starts_with(RAR15_SIGNATURE) {
34 Some(ArchiveSignature {
35 family: ArchiveFamily::Rar15To40,
36 offset,
37 length: RAR15_SIGNATURE.len(),
38 })
39 } else if tail.starts_with(RAR13_SIGNATURE) {
40 Some(ArchiveSignature {
41 family: ArchiveFamily::Rar13,
42 offset,
43 length: RAR13_SIGNATURE.len(),
44 })
45 } else {
46 None
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn detects_all_known_signatures() {
56 assert_eq!(
57 detect_archive_family(b"RE~^").unwrap().family,
58 ArchiveFamily::Rar13
59 );
60 assert_eq!(
61 detect_archive_family(b"Rar!\x1a\x07\x00").unwrap().family,
62 ArchiveFamily::Rar15To40
63 );
64 assert_eq!(
65 detect_archive_family(b"Rar!\x1a\x07\x01\x00")
66 .unwrap()
67 .family,
68 ArchiveFamily::Rar50Plus
69 );
70 }
71
72 #[test]
73 fn finds_sfx_prefixed_archive() {
74 let sig = find_archive_start(b"stub bytes RE~^payload", 128).unwrap();
75 assert_eq!(sig.family, ArchiveFamily::Rar13);
76 assert_eq!(sig.offset, 11);
77 }
78
79 #[test]
80 fn rejects_unknown_and_truncated_signatures() {
81 assert_eq!(detect_archive_family(b""), None);
82 assert_eq!(detect_archive_family(b"RAR!"), None);
83 assert_eq!(detect_archive_family(b"Rar!\x1a\x07"), None);
84 assert_eq!(find_archive_start(b"not an archive", 128), None);
85 }
86
87 #[test]
88 fn scan_limit_bounds_sfx_detection() {
89 let input = b"stub bytes RE~^payload";
90
91 assert_eq!(find_archive_start(input, 10), None);
92
93 let sig = find_archive_start(input, 11).unwrap();
94 assert_eq!(sig.family, ArchiveFamily::Rar13);
95 assert_eq!(sig.offset, 11);
96 assert_eq!(sig.length, RAR13_SIGNATURE.len());
97 }
98}