Skip to main content

rars_format/
rar50.rs

1use crate::detect::{find_archive_start, ArchiveSignature, RAR50_SIGNATURE};
2use crate::error::{Error, Result};
3use crate::io_util::{align16 as checked_align16, read_exact_at, read_u32};
4pub(crate) use crate::source::ArchiveSource;
5use crate::version::ArchiveFamily;
6use rars_crc32::crc32;
7use rars_crypto::rar50::{Rar50Cipher, Rar50Keys};
8use std::fs::File;
9use std::io::{Read, Write};
10use std::ops::Range;
11use std::path::Path;
12use std::sync::Arc;
13
14mod blake2sp;
15mod extract;
16mod write;
17
18pub use extract::extract_volumes_to;
19pub use write::{
20    ArchiveMetadataEntry, CompressedEntry, EncryptedArchiveCommentEntry, EncryptedCompressedEntry,
21    EncryptedStoredEntry, EncryptedStoredEntryWithServices, EncryptedStoredServiceEntry,
22    FilterKind, FilterPolicy, Rar50VolumeWriter, Rar50Writer, StoredEntry, StoredEntryWithServices,
23    StoredServiceEntry, WriterOptions,
24};
25
26const HEAD_MAIN: u64 = 1;
27const HEAD_FILE: u64 = 2;
28const HEAD_SERVICE: u64 = 3;
29const HEAD_CRYPT: u64 = 4;
30const HEAD_END: u64 = 5;
31const REV5_SIGNATURE: &[u8] = b"Rar!\x1aRev";
32
33const HFL_EXTRA: u64 = 0x0001;
34const HFL_DATA: u64 = 0x0002;
35const HFL_SPLIT_BEFORE: u64 = 0x0008;
36const HFL_SPLIT_AFTER: u64 = 0x0010;
37
38const MHFL_VOLUME: u64 = 0x0001;
39const MHFL_VOLUME_NUMBER: u64 = 0x0002;
40const MHFL_SOLID: u64 = 0x0004;
41const MHFL_RECOVERY: u64 = 0x0008;
42const MHFL_LOCKED: u64 = 0x0010;
43
44const FHFL_DIRECTORY: u64 = 0x0001;
45const FHFL_MTIME: u64 = 0x0002;
46const FHFL_CRC32: u64 = 0x0004;
47
48const MHEXTRA_LOCATOR: u64 = 0x01;
49const MHEXTRA_LOCATOR_QUICK_OPEN: u64 = 0x0001;
50const MHEXTRA_LOCATOR_RECOVERY: u64 = 0x0002;
51
52const FHEXTRA_CRYPT: u64 = 0x01;
53const FHEXTRA_HASH: u64 = 0x02;
54const FHEXTRA_REDIR: u64 = 0x05;
55const FHEXTRA_SUBDATA: u64 = 0x07;
56const MHEXTRA_ARCHIVE_METADATA: u64 = 0x02;
57const MHEXTRA_ARCHIVE_METADATA_NAME: u64 = 0x0001;
58const MHEXTRA_ARCHIVE_METADATA_TIME: u64 = 0x0002;
59
60#[derive(Debug, Clone)]
61#[non_exhaustive]
62pub struct Archive {
63    pub sfx_offset: usize,
64    pub main: MainHeader,
65    pub blocks: Vec<Block>,
66    source: ArchiveSource,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[non_exhaustive]
71pub struct MainHeader {
72    pub block: BlockHeader,
73    pub archive_flags: u64,
74    pub volume_number: Option<u64>,
75    pub extras: Vec<MainExtraRecord>,
76}
77
78impl MainHeader {
79    pub fn is_volume(&self) -> bool {
80        self.archive_flags & MHFL_VOLUME != 0
81    }
82
83    pub fn is_solid(&self) -> bool {
84        self.archive_flags & MHFL_SOLID != 0
85    }
86
87    pub fn has_recovery_record(&self) -> bool {
88        self.archive_flags & MHFL_RECOVERY != 0
89    }
90
91    pub fn is_locked(&self) -> bool {
92        self.archive_flags & MHFL_LOCKED != 0
93    }
94
95    pub fn locator(&self) -> Option<&LocatorRecord> {
96        self.extras.iter().find_map(|record| match record {
97            MainExtraRecord::Locator(locator) => Some(locator),
98            _ => None,
99        })
100    }
101
102    pub fn archive_metadata(&self) -> Option<&ArchiveMetadataRecord> {
103        self.extras.iter().find_map(|record| match record {
104            MainExtraRecord::ArchiveMetadata(metadata) => Some(metadata),
105            _ => None,
106        })
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum MainExtraRecord {
113    Locator(LocatorRecord),
114    ArchiveMetadata(ArchiveMetadataRecord),
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118#[non_exhaustive]
119pub struct LocatorRecord {
120    pub flags: u64,
121    pub quick_open_offset: Option<u64>,
122    pub recovery_record_offset: Option<u64>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126#[non_exhaustive]
127pub struct ArchiveMetadataRecord {
128    pub flags: u64,
129    pub name: Option<Vec<u8>>,
130    pub creation_time: Option<u64>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum Block {
136    File(FileHeader),
137    Service(FileHeader),
138    End(BlockHeader),
139    Unknown(BlockHeader),
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143#[non_exhaustive]
144pub struct BlockHeader {
145    pub header_crc: u32,
146    pub header_size: u64,
147    pub header_type: u64,
148    pub flags: u64,
149    pub extra_area_size: Option<u64>,
150    pub data_size: Option<u64>,
151    pub offset: usize,
152    // Type-specific header bytes are archive-relative. Payload bytes are
153    // source-absolute so SFX-prefixed archives can be read directly.
154    pub header_range: Range<usize>,
155    pub data_range: Range<usize>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159#[non_exhaustive]
160pub struct FileHeader {
161    pub block: BlockHeader,
162    pub file_flags: u64,
163    pub unpacked_size: u64,
164    pub attributes: u64,
165    pub mtime: Option<u32>,
166    pub data_crc32: Option<u32>,
167    pub compression_info: u64,
168    pub host_os: u64,
169    pub name: Vec<u8>,
170    pub hash: Option<FileHash>,
171    pub redirection: Option<FileRedirection>,
172    pub service_data: Option<Vec<u8>>,
173    pub encrypted: bool,
174    pub encryption: Option<FileEncryption>,
175    crypto: Option<FileCryptoState>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179#[non_exhaustive]
180pub struct FileRedirection {
181    pub redirection_type: u64,
182    pub flags: u64,
183    pub target_name: Vec<u8>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187#[non_exhaustive]
188pub struct FileHash {
189    pub hash_type: u64,
190    pub data: Vec<u8>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194#[non_exhaustive]
195pub struct RecoveryRecord {
196    pub percent: u64,
197    pub payload_size: u64,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201#[non_exhaustive]
202pub struct FileEncryption {
203    pub version: u64,
204    pub flags: u64,
205    pub kdf_count: u8,
206    pub salt: [u8; 16],
207    pub iv: [u8; 16],
208    pub check_value: Option<[u8; 12]>,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212struct FileCryptoState {
213    keys: Rar50Keys,
214    iv: [u8; 16],
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218#[non_exhaustive]
219pub struct Rev5Volume {
220    pub version: u8,
221    pub data_count: u16,
222    pub recovery_count: u16,
223    pub recovery_number: u16,
224    pub payload_crc32: u32,
225    pub payload_size: u64,
226    pub payload: Vec<u8>,
227    pub data_volumes: Vec<Rev5DataVolume>,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231#[non_exhaustive]
232pub struct Rev5VolumeMeta {
233    pub version: u8,
234    pub data_count: u16,
235    pub recovery_count: u16,
236    pub recovery_number: u16,
237    pub payload_crc32: u32,
238    pub payload_size: u64,
239    pub data_volumes: Vec<Rev5DataVolume>,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243#[non_exhaustive]
244pub struct Rev5DataVolume {
245    pub file_size: u64,
246    pub crc32: u32,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250#[non_exhaustive]
251pub struct CompressionInfo {
252    pub algorithm_version: u8,
253    pub solid: bool,
254    pub method: u8,
255    pub dictionary_power: u8,
256    pub dictionary_fraction: u8,
257    pub rar5_compat: bool,
258    pub dictionary_size: u64,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
262#[non_exhaustive]
263pub struct ExtractedEntryMeta {
264    pub name: Vec<u8>,
265    pub file_time: u32,
266    pub attr: u64,
267    pub host_os: u64,
268    pub is_directory: bool,
269}
270
271impl FileHeader {
272    pub fn name_bytes(&self) -> &[u8] {
273        &self.name
274    }
275
276    /// Returns the file name with invalid UTF-8 replaced for display only.
277    ///
278    /// Use [`Self::name_bytes`] when exact archive bytes matter.
279    pub fn name_lossy(&self) -> String {
280        String::from_utf8_lossy(&self.name).into_owned()
281    }
282
283    pub fn is_split_before(&self) -> bool {
284        self.block.flags & HFL_SPLIT_BEFORE != 0
285    }
286
287    pub fn is_split_after(&self) -> bool {
288        self.block.flags & HFL_SPLIT_AFTER != 0
289    }
290
291    pub fn is_directory(&self) -> bool {
292        self.file_flags & FHFL_DIRECTORY != 0
293    }
294
295    pub fn is_stored(&self) -> bool {
296        compression_method(self.compression_info) == 0
297    }
298
299    pub fn is_redirection(&self) -> bool {
300        self.redirection.is_some()
301    }
302
303    pub fn decoded_compression_info(&self) -> Result<CompressionInfo> {
304        decode_compression_info(self.compression_info)
305    }
306
307    pub fn packed_size(&self) -> u64 {
308        self.block.data_size.unwrap_or(0)
309    }
310
311    pub fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
312        archive.read_range(self.block.data_range.clone())
313    }
314
315    pub fn verify_crc32(&self, data: &[u8]) -> Result<()> {
316        let Some(expected) = self.data_crc32 else {
317            return Ok(());
318        };
319        if self.uses_hash_mac() {
320            return Err(Error::InvalidHeader(
321                "RAR 5 encrypted CRC32 verification needs encryption keys",
322            ));
323        }
324        let actual = crc32(data);
325        if actual == expected {
326            Ok(())
327        } else {
328            Err(Error::Crc32Mismatch { expected, actual })
329        }
330    }
331
332    pub fn verify_hash(&self, data: &[u8]) -> Result<()> {
333        let Some(hash) = &self.hash else {
334            return Ok(());
335        };
336        if self.uses_hash_mac() {
337            return Err(Error::InvalidHeader(
338                "RAR 5 encrypted hash verification needs encryption keys",
339            ));
340        }
341        match hash.hash_type {
342            0 if hash.data.len() == 32 => {
343                let actual = blake2sp::hash(data);
344                if hash.data == actual {
345                    Ok(())
346                } else {
347                    Err(Error::HashMismatch { hash_type: 0 })
348                }
349            }
350            0 => Err(Error::InvalidHeader(
351                "RAR 5 BLAKE2sp hash record has invalid length",
352            )),
353            _ => Err(Error::UnsupportedFeature {
354                version: crate::version::ArchiveVersion::Rar50,
355                feature: "RAR 5 unknown file hash type",
356            }),
357        }
358    }
359
360    pub fn verify_integrity(&self, data: &[u8]) -> Result<()> {
361        self.verify_crc32(data)?;
362        self.verify_hash(data)
363    }
364
365    fn uses_hash_mac(&self) -> bool {
366        self.encryption
367            .as_ref()
368            .is_some_and(|encryption| encryption.flags & 0x0002 != 0)
369    }
370
371    pub fn recovery_record(&self) -> Result<Option<RecoveryRecord>> {
372        if self.name != b"RR" {
373            return Ok(None);
374        }
375        let Some(data) = &self.service_data else {
376            return Err(Error::InvalidHeader(
377                "RAR 5 recovery service is missing service data",
378            ));
379        };
380        let (percent, len) = read_vint_at(data, 0, data.len())?;
381        if len != data.len() {
382            return Err(Error::InvalidHeader(
383                "RAR 5 recovery service data has trailing bytes",
384            ));
385        }
386        Ok(Some(RecoveryRecord {
387            percent,
388            payload_size: self.packed_size(),
389        }))
390    }
391}
392
393impl Archive {
394    pub fn parse(input: &[u8]) -> Result<Self> {
395        Self::parse_with_options(input, crate::ArchiveReadOptions::default())
396    }
397
398    pub fn parse_owned(input: Vec<u8>) -> Result<Self> {
399        Self::parse_owned_with_options(input, crate::ArchiveReadOptions::default())
400    }
401
402    pub fn parse_with_options(
403        input: &[u8],
404        options: crate::ArchiveReadOptions<'_>,
405    ) -> Result<Self> {
406        let data: Arc<[u8]> = Arc::from(input.to_vec().into_boxed_slice());
407        Self::parse_shared(data, options.password)
408    }
409
410    pub fn parse_owned_with_options(
411        input: Vec<u8>,
412        options: crate::ArchiveReadOptions<'_>,
413    ) -> Result<Self> {
414        Self::parse_shared(Arc::from(input.into_boxed_slice()), options.password)
415    }
416
417    pub fn parse_with_password(input: &[u8], password: Option<&[u8]>) -> Result<Self> {
418        Self::parse_with_options(input, crate::ArchiveReadOptions { password })
419    }
420
421    pub fn parse_owned_with_password(input: Vec<u8>, password: Option<&[u8]>) -> Result<Self> {
422        Self::parse_owned_with_options(input, crate::ArchiveReadOptions { password })
423    }
424
425    pub fn parse_path(path: impl AsRef<Path>) -> Result<Self> {
426        Self::parse_path_with_options(path, crate::ArchiveReadOptions::default())
427    }
428
429    pub fn parse_path_with_options(
430        path: impl AsRef<Path>,
431        options: crate::ArchiveReadOptions<'_>,
432    ) -> Result<Self> {
433        Self::parse_path_with_password(path, options.password)
434    }
435
436    pub fn parse_path_with_password(
437        path: impl AsRef<Path>,
438        password: Option<&[u8]>,
439    ) -> Result<Self> {
440        let path = Arc::new(path.as_ref().to_path_buf());
441        let mut file = File::open(path.as_ref())?;
442        let len = file.metadata()?.len();
443        let scan_len = len.min(128 * 1024) as usize;
444        let mut scan = vec![0; scan_len];
445        file.read_exact(&mut scan)?;
446        let sig = find_archive_start(&scan, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
447        if sig.family != ArchiveFamily::Rar50Plus {
448            return Err(Error::UnsupportedSignature);
449        }
450        let archive_len = usize::try_from(len)
451            .map_err(|_| Error::InvalidHeader("RAR 5 archive size overflows usize"))?
452            .checked_sub(sig.offset)
453            .ok_or(Error::TooShort)?;
454        Self::parse_file_backed(
455            &mut file,
456            archive_len,
457            sig.offset,
458            ArchiveSource::File(path),
459            password,
460        )
461    }
462
463    pub fn parse_path_with_signature(
464        path: impl AsRef<Path>,
465        signature: ArchiveSignature,
466        options: crate::ArchiveReadOptions<'_>,
467    ) -> Result<Self> {
468        Self::parse_path_with_signature_and_password(path, signature, options.password)
469    }
470
471    pub fn parse_path_with_signature_and_password(
472        path: impl AsRef<Path>,
473        signature: ArchiveSignature,
474        password: Option<&[u8]>,
475    ) -> Result<Self> {
476        if signature.family != ArchiveFamily::Rar50Plus {
477            return Err(Error::UnsupportedSignature);
478        }
479        let path = Arc::new(path.as_ref().to_path_buf());
480        let mut file = File::open(path.as_ref())?;
481        let len = file.metadata()?.len();
482        let archive_len = usize::try_from(len)
483            .map_err(|_| Error::InvalidHeader("RAR 5 archive size overflows usize"))?
484            .checked_sub(signature.offset)
485            .ok_or(Error::TooShort)?;
486        Self::parse_file_backed(
487            &mut file,
488            archive_len,
489            signature.offset,
490            ArchiveSource::File(path),
491            password,
492        )
493    }
494
495    fn parse_shared(input: Arc<[u8]>, password: Option<&[u8]>) -> Result<Self> {
496        let sig = find_archive_start(&input, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
497        if sig.family != ArchiveFamily::Rar50Plus {
498            return Err(Error::UnsupportedSignature);
499        }
500        let archive = input.get(sig.offset..).ok_or(Error::TooShort)?;
501        let mut parsed = Self::parse_seekable(
502            archive,
503            sig.offset,
504            ArchiveSource::Memory(Arc::clone(&input)),
505            password,
506        )?;
507        parsed.sfx_offset = sig.offset;
508        Ok(parsed)
509    }
510
511    fn parse_seekable(
512        input: &[u8],
513        sfx_offset: usize,
514        source: ArchiveSource,
515        password: Option<&[u8]>,
516    ) -> Result<Self> {
517        if !input.starts_with(RAR50_SIGNATURE) {
518            return Err(Error::UnsupportedSignature);
519        }
520
521        let archive_len = input.len();
522        let (main, blocks) = parse_archive_blocks(
523            archive_len,
524            password,
525            |offset| parse_block_header_bytes(input, offset, archive_len, sfx_offset),
526            |offset, keys| {
527                parse_encrypted_block_header_bytes(input, offset, archive_len, sfx_offset, keys)
528            },
529        )?;
530
531        Ok(Self {
532            sfx_offset,
533            main,
534            blocks,
535            source,
536        })
537    }
538
539    fn parse_file_backed(
540        file: &mut File,
541        archive_len: usize,
542        sfx_offset: usize,
543        source: ArchiveSource,
544        password: Option<&[u8]>,
545    ) -> Result<Self> {
546        let signature = read_exact_at(file, sfx_offset, RAR50_SIGNATURE.len())?;
547        if signature != RAR50_SIGNATURE {
548            return Err(Error::UnsupportedSignature);
549        }
550
551        let file_cell = std::cell::RefCell::new(file);
552        let (main, blocks) = parse_archive_blocks(
553            archive_len,
554            password,
555            |offset| {
556                read_block_header_at(&mut file_cell.borrow_mut(), offset, archive_len, sfx_offset)
557            },
558            |offset, keys| {
559                read_encrypted_block_header_at(
560                    &mut file_cell.borrow_mut(),
561                    offset,
562                    archive_len,
563                    sfx_offset,
564                    keys,
565                )
566            },
567        )?;
568
569        Ok(Self {
570            sfx_offset,
571            main,
572            blocks,
573            source,
574        })
575    }
576
577    fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>> {
578        self.source.read_range(range)
579    }
580
581    fn source_len(&self) -> Result<usize> {
582        self.source.len()
583    }
584
585    fn range_reader(&self, range: Range<usize>) -> Result<Box<dyn Read + '_>> {
586        self.source.range_reader(range)
587    }
588
589    fn copy_range_to(&self, range: Range<usize>, writer: &mut dyn Write) -> Result<()> {
590        let source_len = self.source_len()?;
591        if range.start > range.end || range.end > source_len {
592            return Err(Error::InvalidHeader("RAR 5 repair range is out of bounds"));
593        }
594        let mut reader = self.range_reader(range)?;
595        std::io::copy(&mut reader, writer)?;
596        Ok(())
597    }
598
599    pub fn files(&self) -> impl Iterator<Item = &FileHeader> {
600        self.blocks.iter().filter_map(|block| match block {
601            Block::File(file) => Some(file),
602            _ => None,
603        })
604    }
605
606    pub fn services(&self) -> impl Iterator<Item = &FileHeader> {
607        self.blocks.iter().filter_map(|block| match block {
608            Block::Service(service) => Some(service),
609            _ => None,
610        })
611    }
612
613    /// Decodes the archive-level `CMT` service payload, if any.
614    ///
615    /// RAR 5 stores comments as `Service` blocks named `CMT`. Archive-level
616    /// comments appear before any `File` block; service blocks attached to a
617    /// specific file follow that file. This returns only the former.
618    pub fn archive_comment(&self) -> Result<Option<Vec<u8>>> {
619        self.archive_comment_with_password(None)
620    }
621
622    /// Same as [`Self::archive_comment`] but supplies a password for
623    /// individually-encrypted comment services.
624    pub fn archive_comment_with_password(
625        &self,
626        password: Option<&[u8]>,
627    ) -> Result<Option<Vec<u8>>> {
628        for block in &self.blocks {
629            match block {
630                Block::File(_) => return Ok(None),
631                Block::Service(service) if service.name == b"CMT" => {
632                    return service.decoded_data_unverified(self, password).map(Some);
633                }
634                _ => {}
635            }
636        }
637        Ok(None)
638    }
639
640    pub fn repair_recovery(&self) -> Result<Vec<u8>> {
641        let mut repaired = Vec::new();
642        self.repair_recovery_to(&mut repaired)?;
643        Ok(repaired)
644    }
645
646    pub fn repair_recovery_to(&self, writer: &mut dyn Write) -> Result<()> {
647        let recovery = self
648            .services()
649            .find(|service| matches!(service.recovery_record(), Ok(Some(_))))
650            .ok_or(Error::InvalidHeader(
651                "RAR 5 archive does not contain an inline recovery record",
652            ))?;
653        let prefix_start = self.sfx_offset;
654        let prefix_end = recovery
655            .block
656            .offset
657            .checked_sub(prefix_start)
658            .and_then(|relative| prefix_start.checked_add(relative))
659            .ok_or(Error::InvalidHeader(
660                "RAR 5 recovery prefix range overflows archive bounds",
661            ))?;
662        let source_len = self.source_len()?;
663        if prefix_end > source_len {
664            return Err(Error::InvalidHeader(
665                "RAR 5 recovery prefix is out of bounds",
666            ));
667        }
668        let recovery_data = recovery
669            .decoded_data_unverified(self, None)
670            .map_err(|error| error.at_entry(recovery.name.clone(), "reading recovery data"))?;
671        let prefix_len = prefix_end
672            .checked_sub(prefix_start)
673            .ok_or(Error::InvalidHeader(
674                "RAR 5 recovery prefix range overflows archive bounds",
675            ))?;
676        let repaired_shards = rars_recovery::rar5::repair_inline_recovery_prefix_shards(
677            prefix_len,
678            &recovery_data,
679            |range| {
680                let start = prefix_start
681                    .checked_add(range.start)
682                    .ok_or(rars_recovery::rar5::Error::PlanOverflow)?;
683                let end = prefix_start
684                    .checked_add(range.end)
685                    .ok_or(rars_recovery::rar5::Error::PlanOverflow)?;
686                self.read_range(start..end)
687                    .map_err(|_| rars_recovery::rar5::Error::BadRecoveryChunk)
688            },
689        )?;
690
691        self.copy_range_to(0..prefix_start, writer)?;
692        let mut cursor = 0usize;
693        for (range, data) in repaired_shards {
694            if range.start < cursor || range.end > prefix_len || range.len() != data.len() {
695                return Err(Error::InvalidHeader(
696                    "RAR 5 recovery shard range is invalid",
697                ));
698            }
699            self.copy_range_to(prefix_start + cursor..prefix_start + range.start, writer)?;
700            writer.write_all(&data)?;
701            cursor = range.end;
702        }
703        self.copy_range_to(prefix_start + cursor..prefix_end, writer)?;
704        self.copy_range_to(prefix_end..source_len, writer)?;
705        Ok(())
706    }
707}
708
709impl Rev5Volume {
710    pub fn parse(input: &[u8]) -> Result<Self> {
711        let (meta, payload_range) = Rev5VolumeMeta::parse_with_payload_range(input)?;
712        let payload = &input[payload_range];
713        let actual_payload_crc = crc32(payload);
714        if actual_payload_crc != meta.payload_crc32 {
715            return Err(Error::Crc32Mismatch {
716                expected: meta.payload_crc32,
717                actual: actual_payload_crc,
718            });
719        }
720
721        Ok(Self {
722            version: meta.version,
723            data_count: meta.data_count,
724            recovery_count: meta.recovery_count,
725            recovery_number: meta.recovery_number,
726            payload_crc32: meta.payload_crc32,
727            payload_size: meta.payload_size,
728            payload: payload.to_vec(),
729            data_volumes: meta.data_volumes,
730        })
731    }
732}
733
734impl Rev5VolumeMeta {
735    pub fn parse(input: &[u8]) -> Result<Self> {
736        Self::parse_with_payload_range(input).map(|(meta, _)| meta)
737    }
738
739    fn parse_with_payload_range(input: &[u8]) -> Result<(Self, Range<usize>)> {
740        if !input.starts_with(REV5_SIGNATURE) {
741            return Err(Error::UnsupportedSignature);
742        }
743        if input.len() < 16 {
744            return Err(Error::TooShort);
745        }
746        let header_crc = read_u32(input, 8)?;
747        let header_size = read_u32(input, 12)? as usize;
748        if header_size <= 5 || header_size > 0x100000 {
749            return Err(Error::InvalidHeader("RAR 5 REV header size is invalid"));
750        }
751        let header_end = 16usize
752            .checked_add(header_size)
753            .ok_or(Error::InvalidHeader("RAR 5 REV header size overflows"))?;
754        if header_end > input.len() {
755            return Err(Error::TooShort);
756        }
757        let actual_header_crc = crc32(&input[12..header_end]);
758        if actual_header_crc != header_crc {
759            return Err(Error::Crc32Mismatch {
760                expected: header_crc,
761                actual: actual_header_crc,
762            });
763        }
764
765        let body = &input[16..header_end];
766        if body.len() < 11 {
767            return Err(Error::TooShort);
768        }
769        let mut reader = SliceReader::new(body, 0, body.len());
770        let version = reader.read_byte()?;
771        if version != 1 {
772            return Err(Error::UnsupportedFeature {
773                version: crate::version::ArchiveVersion::Rar50,
774                feature: "RAR 5 REV version",
775            });
776        }
777        let data_count = reader.read_u16()?;
778        let recovery_count = reader.read_u16()?;
779        let recovery_number = reader.read_u16()?;
780        let payload_crc32 = reader.read_u32()?;
781        let first_recovery_number = u32::from(data_count);
782        let recovery_end = first_recovery_number + u32::from(recovery_count);
783        let recovery_number = u32::from(recovery_number);
784        if recovery_count == 0
785            || recovery_number < first_recovery_number
786            || recovery_number >= recovery_end
787        {
788            return Err(Error::InvalidHeader("RAR 5 REV volume number is invalid"));
789        }
790
791        let expected_table_len = data_count as usize * 12;
792        let expected_table_end =
793            11usize
794                .checked_add(expected_table_len)
795                .ok_or(Error::InvalidHeader(
796                    "RAR 5 REV metadata table size overflows",
797                ))?;
798        if body.len() < expected_table_end {
799            return Err(Error::InvalidHeader(
800                "RAR 5 REV metadata table size is invalid",
801            ));
802        }
803        let mut data_volumes = Vec::with_capacity(data_count as usize);
804        for _ in 0..data_count {
805            let file_size = reader.read_u64()?;
806            let crc = reader.read_u32()?;
807            data_volumes.push(Rev5DataVolume {
808                file_size,
809                crc32: crc,
810            });
811        }
812
813        Ok((
814            Self {
815                version,
816                data_count,
817                recovery_count,
818                recovery_number: recovery_number as u16,
819                payload_crc32,
820                payload_size: (input.len() - header_end) as u64,
821                data_volumes,
822            },
823            header_end..input.len(),
824        ))
825    }
826}
827
828impl From<&Rev5Volume> for Rev5VolumeMeta {
829    fn from(volume: &Rev5Volume) -> Self {
830        Self {
831            version: volume.version,
832            data_count: volume.data_count,
833            recovery_count: volume.recovery_count,
834            recovery_number: volume.recovery_number,
835            payload_crc32: volume.payload_crc32,
836            payload_size: volume.payload_size,
837            data_volumes: volume.data_volumes.clone(),
838        }
839    }
840}
841
842impl From<Rev5Volume> for Rev5VolumeMeta {
843    fn from(volume: Rev5Volume) -> Self {
844        Self {
845            version: volume.version,
846            data_count: volume.data_count,
847            recovery_count: volume.recovery_count,
848            recovery_number: volume.recovery_number,
849            payload_crc32: volume.payload_crc32,
850            payload_size: volume.payload_size,
851            data_volumes: volume.data_volumes,
852        }
853    }
854}
855
856pub fn repair_rev5_volumes_to<F>(
857    data_volumes: &[Option<&[u8]>],
858    recovery_volumes: &[Rev5Volume],
859    mut write: F,
860) -> Result<()>
861where
862    F: FnMut(usize, &[u8]) -> Result<()>,
863{
864    let first = recovery_volumes.first().ok_or(Error::InvalidHeader(
865        "RAR 5 REV recovery volume set is empty",
866    ))?;
867    let data_count = usize::from(first.data_count);
868    if data_volumes.len() != data_count {
869        return Err(Error::InvalidHeader(
870            "RAR 5 REV data volume count does not match metadata",
871        ));
872    }
873    if recovery_volumes.iter().any(|rev| {
874        rev.version != first.version
875            || rev.data_count != first.data_count
876            || rev.recovery_count != first.recovery_count
877            || rev.data_volumes != first.data_volumes
878            || rev.payload.len() != first.payload.len()
879    }) {
880        return Err(Error::InvalidHeader(
881            "RAR 5 REV recovery volume metadata differs across files",
882        ));
883    }
884
885    let mut shards = Vec::with_capacity(data_count);
886    for (index, data) in data_volumes.iter().enumerate() {
887        let Some(data) = data else {
888            shards.push(None);
889            continue;
890        };
891        let meta = &first.data_volumes[index];
892        if data.len() as u64 != meta.file_size || crc32(data) != meta.crc32 {
893            shards.push(None);
894        } else {
895            shards.push(Some(*data));
896        }
897    }
898
899    let recovery_rows: Vec<_> = recovery_volumes
900        .iter()
901        .map(|rev| {
902            let row = usize::from(rev.recovery_number)
903                .checked_sub(data_count)
904                .ok_or(Error::InvalidHeader("RAR 5 REV recovery number is invalid"))?;
905            Ok((row, rev.payload.as_slice()))
906        })
907        .collect::<Result<_>>()?;
908    let mut seen_recovery_rows = std::collections::HashSet::with_capacity(recovery_rows.len());
909    if recovery_rows
910        .iter()
911        .any(|(row, _)| !seen_recovery_rows.insert(*row))
912    {
913        return Err(Error::InvalidHeader(
914            "RAR 5 REV recovery volume set contains duplicate recovery rows",
915        ));
916    }
917    let repaired = rars_recovery::rar5::reconstruct_data_shards(&shards, &recovery_rows)?;
918
919    for (index, (mut shard, meta)) in repaired.into_iter().zip(&first.data_volumes).enumerate() {
920        let file_size = usize::try_from(meta.file_size)
921            .map_err(|_| Error::InvalidHeader("RAR 5 REV data volume size overflows usize"))?;
922        if shard.len() < file_size {
923            return Err(Error::InvalidHeader(
924                "RAR 5 REV repaired shard is shorter than data volume size",
925            ));
926        }
927        shard.truncate(file_size);
928        let actual = crc32(&shard);
929        if actual != meta.crc32 {
930            return Err(Error::Crc32Mismatch {
931                expected: meta.crc32,
932                actual,
933            });
934        }
935        write(index, &shard)?;
936    }
937    Ok(())
938}
939
940pub fn repair_inline_recovery_bytes(input: &[u8]) -> Result<Vec<u8>> {
941    if !input.starts_with(RAR50_SIGNATURE) {
942        return Err(Error::UnsupportedSignature);
943    }
944    let repaired =
945        rars_recovery::rar5::repair_inline_recovery_archive(input).map_err(Error::from)?;
946    let parse_target = if repaired == input { input } else { &repaired };
947    let _ = Archive::parse(parse_target)?;
948    Ok(repaired)
949}
950
951fn parse_main_header_bytes(parsed: &ParsedBlockHeader) -> Result<MainHeader> {
952    let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
953    let archive_flags = reader.read_vint()?;
954    let volume_number = if archive_flags & MHFL_VOLUME_NUMBER != 0 {
955        Some(reader.read_vint()?)
956    } else {
957        None
958    };
959    let extras = parse_main_extra_area(&parsed.header, parsed.extra_range.clone())?;
960    Ok(MainHeader {
961        block: parsed.block.clone(),
962        archive_flags,
963        volume_number,
964        extras,
965    })
966}
967
968fn parse_main_extra_area(input: &[u8], range: Range<usize>) -> Result<Vec<MainExtraRecord>> {
969    let mut records = Vec::new();
970    parse_extra_records(input, range, |record_type, data| match record_type {
971        MHEXTRA_LOCATOR => {
972            let mut reader = SliceReader::new(input, data.start, data.end);
973            let flags = reader.read_vint()?;
974            let quick_open_offset = if flags & MHEXTRA_LOCATOR_QUICK_OPEN != 0 {
975                Some(reader.read_vint()?)
976            } else {
977                None
978            };
979            let recovery_record_offset = if flags & MHEXTRA_LOCATOR_RECOVERY != 0 {
980                Some(reader.read_vint()?)
981            } else {
982                None
983            };
984            // LOCATOR records are intentionally forward-compatible: known
985            // offsets are parsed and any trailing bytes remain reserved for
986            // future flags.
987            records.push(MainExtraRecord::Locator(LocatorRecord {
988                flags,
989                quick_open_offset,
990                recovery_record_offset,
991            }));
992            Ok(())
993        }
994        MHEXTRA_ARCHIVE_METADATA => {
995            let mut reader = SliceReader::new(input, data.start, data.end);
996            let flags = reader.read_vint()?;
997            let name = if flags & MHEXTRA_ARCHIVE_METADATA_NAME != 0 {
998                let name_len = usize_from_u64(
999                    reader.read_vint()?,
1000                    "RAR 5 archive metadata name length overflows usize",
1001                )?;
1002                Some(reader.read_bytes(name_len)?.to_vec())
1003            } else {
1004                None
1005            };
1006            let creation_time = if flags & MHEXTRA_ARCHIVE_METADATA_TIME != 0 {
1007                Some(reader.read_u64()?)
1008            } else {
1009                None
1010            };
1011            if reader.pos != reader.end {
1012                return Err(Error::InvalidHeader(
1013                    "RAR 5 archive metadata record has trailing bytes",
1014                ));
1015            }
1016            records.push(MainExtraRecord::ArchiveMetadata(ArchiveMetadataRecord {
1017                flags,
1018                name,
1019                creation_time,
1020            }));
1021            Ok(())
1022        }
1023        _ => Ok(()),
1024    })?;
1025    Ok(records)
1026}
1027
1028fn parse_file_header_bytes(parsed: &ParsedBlockHeader) -> Result<FileHeader> {
1029    let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
1030    let file_flags = reader.read_vint()?;
1031    let unpacked_size = reader.read_vint()?;
1032    let attributes = reader.read_vint()?;
1033    let mtime = if file_flags & FHFL_MTIME != 0 {
1034        Some(reader.read_u32()?)
1035    } else {
1036        None
1037    };
1038    let data_crc32 = if file_flags & FHFL_CRC32 != 0 {
1039        Some(reader.read_u32()?)
1040    } else {
1041        None
1042    };
1043    let compression_info = reader.read_vint()?;
1044    let host_os = reader.read_vint()?;
1045    let name_len = usize_from_u64(
1046        reader.read_vint()?,
1047        "RAR 5 file name length overflows usize",
1048    )?;
1049    let name = reader.read_bytes(name_len)?.to_vec();
1050    let mut file = FileHeader {
1051        block: parsed.block.clone(),
1052        file_flags,
1053        unpacked_size,
1054        attributes,
1055        mtime,
1056        data_crc32,
1057        compression_info,
1058        host_os,
1059        name,
1060        hash: None,
1061        redirection: None,
1062        service_data: None,
1063        encrypted: false,
1064        encryption: None,
1065        crypto: None,
1066    };
1067    parse_file_extra_area(&parsed.header, parsed.extra_range.clone(), &mut file)?;
1068    Ok(file)
1069}
1070
1071fn parse_file_extra_area(input: &[u8], range: Range<usize>, file: &mut FileHeader) -> Result<()> {
1072    if file.block.extra_area_size.is_none() {
1073        return Ok(());
1074    }
1075    parse_extra_records(input, range, |record_type, data| {
1076        match record_type {
1077            FHEXTRA_CRYPT => {
1078                file.encrypted = true;
1079                file.encryption = Some(parse_file_encryption_record(input, data)?);
1080            }
1081            FHEXTRA_HASH => {
1082                let (hash_type, hash_type_len) = read_vint_at(input, data.start, data.end)?;
1083                file.hash = Some(FileHash {
1084                    hash_type,
1085                    data: input[data.start + hash_type_len..data.end].to_vec(),
1086                });
1087            }
1088            FHEXTRA_REDIR => {
1089                file.redirection = Some(parse_file_redirection_record(input, data)?);
1090            }
1091            FHEXTRA_SUBDATA => {
1092                file.service_data = Some(input[data].to_vec());
1093            }
1094            _ => {}
1095        }
1096        Ok(())
1097    })
1098}
1099
1100fn parse_file_redirection_record(input: &[u8], range: Range<usize>) -> Result<FileRedirection> {
1101    let (redirection_type, type_len) = read_vint_at(input, range.start, range.end)?;
1102    let flags_start = range.start + type_len;
1103    let (flags, flags_len) = read_vint_at(input, flags_start, range.end)?;
1104    let name_len_start = flags_start + flags_len;
1105    let (name_len, name_len_len) = read_vint_at(input, name_len_start, range.end)?;
1106    let name_start = name_len_start + name_len_len;
1107    let name_len = usize::try_from(name_len).map_err(|_| {
1108        Error::InvalidHeader("RAR 5 file redirection target length overflows host address size")
1109    })?;
1110    let name_end = name_start
1111        .checked_add(name_len)
1112        .ok_or(Error::InvalidHeader(
1113            "RAR 5 file redirection target length overflows",
1114        ))?;
1115    if name_end != range.end {
1116        return Err(Error::InvalidHeader(
1117            "RAR 5 file redirection record has trailing bytes",
1118        ));
1119    }
1120    Ok(FileRedirection {
1121        redirection_type,
1122        flags,
1123        target_name: input[name_start..name_end].to_vec(),
1124    })
1125}
1126
1127fn parse_file_encryption_record(input: &[u8], range: Range<usize>) -> Result<FileEncryption> {
1128    let (version, version_len) = read_vint_at(input, range.start, range.end)?;
1129    let flags_pos = range.start + version_len;
1130    let (flags, flags_len) = read_vint_at(input, flags_pos, range.end)?;
1131    let mut pos = flags_pos + flags_len;
1132    if pos >= range.end {
1133        return Err(Error::TooShort);
1134    }
1135    let kdf_count = input[pos];
1136    pos += 1;
1137    let salt = read_array_at::<16>(input, &mut pos, range.end)?;
1138    let iv = read_array_at::<16>(input, &mut pos, range.end)?;
1139    let check_value = if flags & 0x0001 != 0 {
1140        Some(read_array_at::<12>(input, &mut pos, range.end)?)
1141    } else {
1142        None
1143    };
1144    if pos != range.end {
1145        return Err(Error::InvalidHeader(
1146            "RAR 5 file encryption record has trailing bytes",
1147        ));
1148    }
1149    Ok(FileEncryption {
1150        version,
1151        flags,
1152        kdf_count,
1153        salt,
1154        iv,
1155        check_value,
1156    })
1157}
1158
1159fn parse_archive_encryption_header(
1160    parsed: &ParsedBlockHeader,
1161    password: Option<&[u8]>,
1162) -> Result<Rar50Keys> {
1163    let password = password.ok_or(Error::NeedPassword)?;
1164    let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
1165    let version = reader.read_vint()?;
1166    if version != 0 {
1167        return Err(Error::UnsupportedFeature {
1168            version: crate::version::ArchiveVersion::Rar50,
1169            feature: "RAR 5 unknown header encryption version",
1170        });
1171    }
1172    let flags = reader.read_vint()?;
1173    let kdf_count = reader.read_byte()?;
1174    let salt = reader.read_array::<16>()?;
1175    let check_value = if flags & 0x0001 != 0 {
1176        Some(reader.read_array::<12>()?)
1177    } else {
1178        None
1179    };
1180    if reader.pos != reader.range.end {
1181        return Err(Error::InvalidHeader(
1182            "RAR 5 archive encryption header has trailing bytes",
1183        ));
1184    }
1185    let keys = Rar50Keys::derive(password, salt, kdf_count).map_err(map_rar50_crypto_error)?;
1186    if let Some(check_value) = check_value {
1187        keys.check_password(&check_value)
1188            .map_err(map_rar50_crypto_error)?;
1189    }
1190    Ok(keys)
1191}
1192
1193fn attach_file_crypto(file: &mut FileHeader, password: Option<&[u8]>) -> Result<()> {
1194    if !file.encrypted || file.crypto.is_some() {
1195        return Ok(());
1196    }
1197    let Some(password) = password else {
1198        return Ok(());
1199    };
1200    let encryption = file.encryption.as_ref().ok_or(Error::InvalidHeader(
1201        "RAR 5 encrypted file is missing encryption record",
1202    ))?;
1203    if encryption.version != 0 {
1204        return Err(Error::UnsupportedFeature {
1205            version: crate::version::ArchiveVersion::Rar50,
1206            feature: "RAR 5 unknown file encryption version",
1207        });
1208    }
1209    let keys = Rar50Keys::derive(password, encryption.salt, encryption.kdf_count)
1210        .map_err(map_rar50_crypto_error)?;
1211    if let Some(check_value) = encryption.check_value {
1212        keys.check_password(&check_value)
1213            .map_err(map_rar50_crypto_error)?;
1214    }
1215    file.crypto = Some(FileCryptoState {
1216        keys,
1217        iv: encryption.iv,
1218    });
1219    Ok(())
1220}
1221
1222fn map_rar50_crypto_error(error: rars_crypto::rar50::Error) -> Error {
1223    match error {
1224        rars_crypto::rar50::Error::KdfCountTooLarge => Error::UnsupportedFeature {
1225            version: crate::version::ArchiveVersion::Rar50,
1226            feature: "RAR 5 KDF count",
1227        },
1228        rars_crypto::rar50::Error::BadPassword => Error::WrongPasswordOrCorruptData,
1229        rars_crypto::rar50::Error::UnalignedInput => {
1230            Error::InvalidHeader("RAR 5 AES input is not block aligned")
1231        }
1232        other => Error::Rar50Crypto(other),
1233    }
1234}
1235
1236fn read_array_at<const N: usize>(input: &[u8], pos: &mut usize, end: usize) -> Result<[u8; N]> {
1237    if pos.checked_add(N).is_none_or(|next| next > end) {
1238        return Err(Error::TooShort);
1239    }
1240    let mut out = [0; N];
1241    out.copy_from_slice(&input[*pos..*pos + N]);
1242    *pos += N;
1243    Ok(out)
1244}
1245
1246fn parse_archive_blocks<F, G>(
1247    archive_len: usize,
1248    password: Option<&[u8]>,
1249    mut read_block: F,
1250    mut read_encrypted_block: G,
1251) -> Result<(MainHeader, Vec<Block>)>
1252where
1253    F: FnMut(usize) -> Result<ParsedBlockHeader>,
1254    G: FnMut(usize, &Rar50Keys) -> Result<ParsedBlockHeader>,
1255{
1256    let mut pos = RAR50_SIGNATURE.len();
1257    let first = read_block(pos).map_err(|error| error.at_archive_offset(pos))?;
1258    let header_keys = if first.block.header_type == HEAD_CRYPT {
1259        pos = first.next_offset;
1260        Some(parse_archive_encryption_header(&first, password)?)
1261    } else {
1262        None
1263    };
1264
1265    let main_pos = pos;
1266    let main_block;
1267    let first = if let Some(keys) = &header_keys {
1268        main_block =
1269            read_encrypted_block(pos, keys).map_err(|error| error.at_archive_offset(pos))?;
1270        &main_block
1271    } else {
1272        &first
1273    };
1274    if first.block.header_type != HEAD_MAIN {
1275        return Err(Error::InvalidHeader("RAR 5 main header is missing"));
1276    }
1277    let main = parse_main_header_bytes(first).map_err(|error| error.at_archive_offset(main_pos))?;
1278    pos = first.next_offset;
1279
1280    let mut blocks = Vec::new();
1281    while pos < archive_len {
1282        let parsed = if let Some(keys) = &header_keys {
1283            read_encrypted_block(pos, keys).map_err(|error| error.at_archive_offset(pos))?
1284        } else {
1285            read_block(pos).map_err(|error| error.at_archive_offset(pos))?
1286        };
1287        let next = parsed.next_offset;
1288        match parsed.block.header_type {
1289            HEAD_FILE => {
1290                let mut file = parse_file_header_bytes(&parsed)
1291                    .map_err(|error| error.at_archive_offset(pos))?;
1292                attach_file_crypto(&mut file, password)
1293                    .map_err(|error| error.at_archive_offset(pos))?;
1294                blocks.push(Block::File(file));
1295            }
1296            HEAD_SERVICE => {
1297                let mut service = parse_file_header_bytes(&parsed)
1298                    .map_err(|error| error.at_archive_offset(pos))?;
1299                attach_file_crypto(&mut service, password)
1300                    .map_err(|error| error.at_archive_offset(pos))?;
1301                blocks.push(Block::Service(service));
1302            }
1303            HEAD_CRYPT => {
1304                return Err(Error::UnsupportedFeature {
1305                    version: crate::version::ArchiveVersion::Rar50,
1306                    feature: "RAR 5 encrypted headers",
1307                });
1308            }
1309            HEAD_END => {
1310                blocks.push(Block::End(parsed.block));
1311                break;
1312            }
1313            _ => blocks.push(Block::Unknown(parsed.block)),
1314        }
1315        pos = next;
1316    }
1317
1318    Ok((main, blocks))
1319}
1320
1321fn parse_extra_records<F>(input: &[u8], range: Range<usize>, mut handle: F) -> Result<()>
1322where
1323    F: FnMut(u64, Range<usize>) -> Result<()>,
1324{
1325    let mut pos = range.start;
1326    while pos < range.end {
1327        let record_start = pos;
1328        let (record_size, size_len) = read_vint_at(input, pos, range.end)?;
1329        pos += size_len;
1330        let record_payload_len =
1331            usize_from_u64(record_size, "RAR 5 extra record size overflows usize")?;
1332        let record_end = pos
1333            .checked_add(record_payload_len)
1334            .ok_or(Error::InvalidHeader(
1335                "RAR 5 extra record size overflows usize",
1336            ))?;
1337        if record_end > range.end {
1338            return Err(Error::TooShort);
1339        }
1340        let (record_type, type_len) = read_vint_at(input, pos, record_end)?;
1341        let data_start = pos + type_len;
1342        handle(record_type, data_start..record_end)?;
1343        if record_end <= record_start {
1344            return Err(Error::InvalidHeader("RAR 5 extra record does not advance"));
1345        }
1346        pos = record_end;
1347    }
1348    Ok(())
1349}
1350
1351struct ParsedBlockHeader {
1352    block: BlockHeader,
1353    header: Vec<u8>,
1354    type_specific_range: Range<usize>,
1355    extra_range: Range<usize>,
1356    next_offset: usize,
1357}
1358
1359fn parse_block_header_bytes(
1360    input: &[u8],
1361    offset: usize,
1362    archive_len: usize,
1363    sfx_offset: usize,
1364) -> Result<ParsedBlockHeader> {
1365    let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1366    if remaining < 5 {
1367        return Err(Error::TooShort);
1368    }
1369    let header_crc = read_u32(input, offset)?;
1370    let after_crc = offset
1371        .checked_add(4)
1372        .ok_or(Error::InvalidHeader("RAR 5 header offset overflows usize"))?;
1373    let (header_size, header_size_len) = read_vint_at(input, after_crc, archive_len)?;
1374    let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1375    let header_total = 4usize
1376        .checked_add(header_size_len)
1377        .and_then(|size| size.checked_add(header_body_len))
1378        .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1379    if header_total > remaining {
1380        return Err(Error::TooShort);
1381    }
1382    let header_end = offset
1383        .checked_add(header_total)
1384        .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1385    let header = input
1386        .get(offset..header_end)
1387        .ok_or(Error::TooShort)?
1388        .to_vec();
1389    parse_block_header_image(
1390        header,
1391        offset,
1392        archive_len,
1393        sfx_offset,
1394        header_crc,
1395        header_total,
1396    )
1397}
1398
1399fn parse_encrypted_block_header_bytes(
1400    input: &[u8],
1401    offset: usize,
1402    archive_len: usize,
1403    sfx_offset: usize,
1404    keys: &Rar50Keys,
1405) -> Result<ParsedBlockHeader> {
1406    let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1407    if remaining < 32 {
1408        return Err(Error::TooShort);
1409    }
1410    let first = input.get(offset..offset + 32).ok_or(Error::TooShort)?;
1411    let mut iv = [0; 16];
1412    iv.copy_from_slice(&first[..16]);
1413    let mut first_plain = first[16..32].to_vec();
1414    Rar50Cipher::new(keys.key, iv)
1415        .decrypt_in_place(&mut first_plain)
1416        .map_err(map_rar50_crypto_error)?;
1417    let header_crc = read_u32(&first_plain, 0)?;
1418    let (header_size, header_size_len) = read_vint_at(&first_plain, 4, first_plain.len())?;
1419    let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1420    let header_total = 4usize
1421        .checked_add(header_size_len)
1422        .and_then(|size| size.checked_add(header_body_len))
1423        .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1424    let encrypted_len = checked_align16(header_total, "RAR 5 encrypted header size overflows")?;
1425    let disk_header_len = 16usize
1426        .checked_add(encrypted_len)
1427        .ok_or(Error::InvalidHeader(
1428            "RAR 5 encrypted header size overflows",
1429        ))?;
1430    if disk_header_len > remaining {
1431        return Err(Error::TooShort);
1432    }
1433    let encrypted = input
1434        .get(offset + 16..offset + disk_header_len)
1435        .ok_or(Error::TooShort)?;
1436    let mut header = encrypted.to_vec();
1437    Rar50Cipher::new(keys.key, iv)
1438        .decrypt_in_place(&mut header)
1439        .map_err(map_rar50_crypto_error)?;
1440    header.truncate(header_total);
1441
1442    parse_block_header_image(
1443        header,
1444        offset,
1445        archive_len,
1446        sfx_offset,
1447        header_crc,
1448        disk_header_len,
1449    )
1450}
1451
1452fn read_block_header_at(
1453    file: &mut File,
1454    offset: usize,
1455    archive_len: usize,
1456    sfx_offset: usize,
1457) -> Result<ParsedBlockHeader> {
1458    let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1459    if remaining < 5 {
1460        return Err(Error::TooShort);
1461    }
1462    let prefix_len = remaining.min(14);
1463    let prefix = read_exact_at(file, sfx_offset + offset, prefix_len)?;
1464    let header_crc = read_u32(&prefix, 0)?;
1465    let (header_size, header_size_len) = read_vint_at(&prefix, 4, prefix.len())?;
1466    let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1467    let header_total = 4usize
1468        .checked_add(header_size_len)
1469        .and_then(|size| size.checked_add(header_body_len))
1470        .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1471    if header_total > remaining {
1472        return Err(Error::TooShort);
1473    }
1474
1475    let header = read_exact_at(file, sfx_offset + offset, header_total)?;
1476    parse_block_header_image(
1477        header,
1478        offset,
1479        archive_len,
1480        sfx_offset,
1481        header_crc,
1482        header_total,
1483    )
1484}
1485
1486fn read_encrypted_block_header_at(
1487    file: &mut File,
1488    offset: usize,
1489    archive_len: usize,
1490    sfx_offset: usize,
1491    keys: &Rar50Keys,
1492) -> Result<ParsedBlockHeader> {
1493    let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1494    if remaining < 32 {
1495        return Err(Error::TooShort);
1496    }
1497    let first = read_exact_at(file, sfx_offset + offset, 32)?;
1498    let mut iv = [0; 16];
1499    iv.copy_from_slice(&first[..16]);
1500    let mut first_plain = first[16..32].to_vec();
1501    Rar50Cipher::new(keys.key, iv)
1502        .decrypt_in_place(&mut first_plain)
1503        .map_err(map_rar50_crypto_error)?;
1504    let header_crc = read_u32(&first_plain, 0)?;
1505    let (header_size, header_size_len) = read_vint_at(&first_plain, 4, first_plain.len())?;
1506    let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1507    let header_total = 4usize
1508        .checked_add(header_size_len)
1509        .and_then(|size| size.checked_add(header_body_len))
1510        .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1511    let encrypted_len = checked_align16(header_total, "RAR 5 encrypted header size overflows")?;
1512    let disk_header_len = 16usize
1513        .checked_add(encrypted_len)
1514        .ok_or(Error::InvalidHeader(
1515            "RAR 5 encrypted header size overflows",
1516        ))?;
1517    if disk_header_len > remaining {
1518        return Err(Error::TooShort);
1519    }
1520    let encrypted = read_exact_at(file, sfx_offset + offset + 16, encrypted_len)?;
1521    let mut header = encrypted;
1522    Rar50Cipher::new(keys.key, iv)
1523        .decrypt_in_place(&mut header)
1524        .map_err(map_rar50_crypto_error)?;
1525    header.truncate(header_total);
1526
1527    parse_block_header_image(
1528        header,
1529        offset,
1530        archive_len,
1531        sfx_offset,
1532        header_crc,
1533        disk_header_len,
1534    )
1535}
1536
1537fn parse_block_header_image(
1538    header: Vec<u8>,
1539    offset: usize,
1540    archive_len: usize,
1541    sfx_offset: usize,
1542    header_crc: u32,
1543    disk_header_len: usize,
1544) -> Result<ParsedBlockHeader> {
1545    let header_total = header.len();
1546    let (decoded_header_size, header_size_len) = read_vint_at(&header, 4, header_total)?;
1547    validate_block_header_crc(&header, header_crc)?;
1548    let type_start = 4 + header_size_len;
1549    let mut reader = SliceReader::new(&header, type_start, header_total);
1550    let header_type = reader.read_vint()?;
1551    let flags = reader.read_vint()?;
1552    let extra_area_size = if flags & HFL_EXTRA != 0 {
1553        Some(reader.read_vint()?)
1554    } else {
1555        None
1556    };
1557    let data_size = if flags & HFL_DATA != 0 {
1558        Some(reader.read_vint()?)
1559    } else {
1560        None
1561    };
1562    let extra_len = extra_area_size
1563        .map(|size| usize_from_u64(size, "RAR 5 extra area size overflows usize"))
1564        .transpose()?
1565        .unwrap_or(0);
1566    if extra_len > header_total.saturating_sub(reader.pos) {
1567        return Err(Error::TooShort);
1568    }
1569    let type_specific_end = header_total - extra_len;
1570    let data_len = data_size
1571        .map(|size| usize_from_u64(size, "RAR 5 data size overflows usize"))
1572        .transpose()?
1573        .unwrap_or(0);
1574    let next_offset = offset
1575        .checked_add(disk_header_len)
1576        .and_then(|pos| pos.checked_add(data_len))
1577        .ok_or(Error::InvalidHeader("RAR 5 data size overflows usize"))?;
1578    if next_offset > archive_len {
1579        return Err(Error::TooShort);
1580    }
1581    let type_specific_start = reader.pos;
1582    let data_start = sfx_offset
1583        .checked_add(offset)
1584        .and_then(|pos| pos.checked_add(disk_header_len))
1585        .ok_or(Error::InvalidHeader("RAR 5 data offset overflows usize"))?;
1586    let data_end = data_start
1587        .checked_add(data_len)
1588        .ok_or(Error::InvalidHeader("RAR 5 data size overflows usize"))?;
1589
1590    Ok(ParsedBlockHeader {
1591        block: BlockHeader {
1592            header_crc,
1593            header_size: decoded_header_size,
1594            header_type,
1595            flags,
1596            extra_area_size,
1597            data_size,
1598            offset: sfx_offset + offset,
1599            header_range: (offset + type_specific_start)..(offset + type_specific_end),
1600            data_range: data_start..data_end,
1601        },
1602        header,
1603        type_specific_range: type_specific_start..type_specific_end,
1604        extra_range: type_specific_end..header_total,
1605        next_offset,
1606    })
1607}
1608
1609fn validate_block_header_crc(header: &[u8], expected: u32) -> Result<()> {
1610    let actual = crc32(header.get(4..).ok_or(Error::TooShort)?);
1611    if actual != expected {
1612        return Err(Error::Crc32Mismatch { expected, actual });
1613    }
1614    Ok(())
1615}
1616
1617struct HeaderReader<'a> {
1618    input: &'a [u8],
1619    range: Range<usize>,
1620    pos: usize,
1621}
1622
1623impl<'a> HeaderReader<'a> {
1624    fn new(input: &'a [u8], range: Range<usize>) -> Result<Self> {
1625        if range.end > input.len() {
1626            return Err(Error::TooShort);
1627        }
1628        Ok(Self {
1629            input,
1630            pos: range.start,
1631            range,
1632        })
1633    }
1634
1635    fn read_vint(&mut self) -> Result<u64> {
1636        let (value, len) = read_vint_at(self.input, self.pos, self.range.end)?;
1637        self.pos += len;
1638        Ok(value)
1639    }
1640
1641    fn read_u32(&mut self) -> Result<u32> {
1642        let value = read_u32(self.input, self.pos)?;
1643        self.pos += 4;
1644        Ok(value)
1645    }
1646
1647    fn read_byte(&mut self) -> Result<u8> {
1648        if self.pos >= self.range.end {
1649            return Err(Error::TooShort);
1650        }
1651        let value = self.input[self.pos];
1652        self.pos += 1;
1653        Ok(value)
1654    }
1655
1656    fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
1657        read_array_at::<N>(self.input, &mut self.pos, self.range.end)
1658    }
1659
1660    fn read_bytes(&mut self, len: usize) -> Result<&'a [u8]> {
1661        let end = self
1662            .pos
1663            .checked_add(len)
1664            .ok_or(Error::InvalidHeader("RAR 5 field size overflows usize"))?;
1665        if end > self.range.end {
1666            return Err(Error::TooShort);
1667        }
1668        let bytes = &self.input[self.pos..end];
1669        self.pos = end;
1670        Ok(bytes)
1671    }
1672}
1673
1674struct SliceReader<'a> {
1675    input: &'a [u8],
1676    end: usize,
1677    pos: usize,
1678}
1679
1680impl<'a> SliceReader<'a> {
1681    fn new(input: &'a [u8], pos: usize, end: usize) -> Self {
1682        Self { input, pos, end }
1683    }
1684
1685    fn read_vint(&mut self) -> Result<u64> {
1686        let (value, len) = read_vint_at(self.input, self.pos, self.end)?;
1687        self.pos += len;
1688        Ok(value)
1689    }
1690
1691    fn read_byte(&mut self) -> Result<u8> {
1692        let bytes = self.read_bytes(1)?;
1693        Ok(bytes[0])
1694    }
1695
1696    fn read_u16(&mut self) -> Result<u16> {
1697        let bytes = self.read_bytes(2)?;
1698        Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
1699    }
1700
1701    fn read_u32(&mut self) -> Result<u32> {
1702        let bytes = self.read_bytes(4)?;
1703        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
1704    }
1705
1706    fn read_u64(&mut self) -> Result<u64> {
1707        let bytes = self.read_bytes(8)?;
1708        Ok(u64::from_le_bytes([
1709            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
1710        ]))
1711    }
1712
1713    fn read_bytes(&mut self, len: usize) -> Result<&'a [u8]> {
1714        let end = self
1715            .pos
1716            .checked_add(len)
1717            .ok_or(Error::InvalidHeader("RAR 5 field size overflows usize"))?;
1718        if end > self.end {
1719            return Err(Error::TooShort);
1720        }
1721        let bytes = &self.input[self.pos..end];
1722        self.pos = end;
1723        Ok(bytes)
1724    }
1725}
1726
1727fn read_vint_at(input: &[u8], offset: usize, end: usize) -> Result<(u64, usize)> {
1728    let mut value = 0u64;
1729    let mut shift = 0u32;
1730    for i in 0..10 {
1731        let pos = offset.checked_add(i).ok_or(Error::TooShort)?;
1732        if pos >= end {
1733            return Err(Error::TooShort);
1734        }
1735        let byte = *input.get(pos).ok_or(Error::TooShort)?;
1736        if shift == 63 && byte & 0x7e != 0 {
1737            return Err(Error::InvalidHeader("RAR 5 vint overflows u64"));
1738        }
1739        value = value
1740            .checked_add(((byte & 0x7f) as u64) << shift)
1741            .ok_or(Error::InvalidHeader("RAR 5 vint overflows u64"))?;
1742        if byte & 0x80 == 0 {
1743            return Ok((value, i + 1));
1744        }
1745        shift += 7;
1746    }
1747    Err(Error::InvalidHeader("RAR 5 vint is too long"))
1748}
1749
1750fn usize_from_u64(value: u64, message: &'static str) -> Result<usize> {
1751    usize::try_from(value).map_err(|_| Error::InvalidHeader(message))
1752}
1753
1754fn compression_method(compression_info: u64) -> u64 {
1755    (compression_info >> 7) & 0x07
1756}
1757
1758fn decode_compression_info(raw: u64) -> Result<CompressionInfo> {
1759    let algorithm_version = (raw & 0x3f) as u8;
1760    if algorithm_version > 1 {
1761        return Err(Error::UnsupportedFeature {
1762            version: crate::version::ArchiveVersion::Rar50,
1763            feature: "RAR 5 unknown compression algorithm version",
1764        });
1765    }
1766
1767    let dictionary_power = ((raw >> 10) & 0x1f) as u8;
1768    let dictionary_fraction = ((raw >> 15) & 0x1f) as u8;
1769    let rar5_compat = raw & 0x100000 != 0;
1770    if algorithm_version == 0 && (dictionary_fraction != 0 || rar5_compat) {
1771        return Err(Error::InvalidHeader(
1772            "RAR 5 v0 compression info uses v1 dictionary fields",
1773        ));
1774    }
1775    if algorithm_version == 0 && dictionary_power > 15 {
1776        return Err(Error::InvalidHeader(
1777            "RAR 5 v0 dictionary power exceeds 4 GiB limit",
1778        ));
1779    }
1780
1781    let dictionary_size = if algorithm_version == 1 {
1782        u64::from(dictionary_fraction + 32)
1783            .checked_shl(u32::from(dictionary_power) + 12)
1784            .ok_or(Error::InvalidHeader("RAR 5 dictionary size overflows u64"))?
1785    } else {
1786        (128 * 1024_u64)
1787            .checked_shl(u32::from(dictionary_power))
1788            .ok_or(Error::InvalidHeader("RAR 5 dictionary size overflows u64"))?
1789    };
1790
1791    Ok(CompressionInfo {
1792        algorithm_version,
1793        solid: raw & 0x40 != 0,
1794        method: ((raw >> 7) & 0x07) as u8,
1795        dictionary_power,
1796        dictionary_fraction,
1797        rar5_compat,
1798        dictionary_size,
1799    })
1800}
1801
1802#[cfg(test)]
1803mod tests {
1804    use super::*;
1805
1806    #[test]
1807    fn read_vint_at_honors_logical_end_before_decoding() {
1808        assert_eq!(read_vint_at(&[0x01], 0, 0), Err(Error::TooShort));
1809        assert_eq!(read_vint_at(&[0x81, 0x01], 0, 1), Err(Error::TooShort));
1810        assert_eq!(read_vint_at(&[0x81, 0x01], 0, 2).unwrap(), (129, 2));
1811    }
1812
1813    #[test]
1814    fn read_vint_at_rejects_values_wider_than_u64() {
1815        let max = [0xff; 9].into_iter().chain([0x01]).collect::<Vec<_>>();
1816        assert_eq!(read_vint_at(&max, 0, max.len()).unwrap(), (u64::MAX, 10));
1817
1818        let overflow = [0xff; 9].into_iter().chain([0x02]).collect::<Vec<_>>();
1819        assert_eq!(
1820            read_vint_at(&overflow, 0, overflow.len()),
1821            Err(Error::InvalidHeader("RAR 5 vint overflows u64"))
1822        );
1823    }
1824
1825    #[test]
1826    fn parses_file_redirection_extra_record() {
1827        let input = [1, 1, 6, b't', b'a', b'r', b'g', b'e', b't'];
1828        let record = parse_file_redirection_record(&input, 0..input.len()).unwrap();
1829
1830        assert_eq!(record.redirection_type, 1);
1831        assert_eq!(record.flags, 1);
1832        assert_eq!(record.target_name, b"target");
1833    }
1834
1835    #[test]
1836    fn rejects_file_redirection_record_with_trailing_bytes() {
1837        let input = [1, 0, 3, b'f', b'o', b'o', 0];
1838
1839        assert!(matches!(
1840            parse_file_redirection_record(&input, 0..input.len()),
1841            Err(Error::InvalidHeader(
1842                "RAR 5 file redirection record has trailing bytes"
1843            ))
1844        ));
1845    }
1846
1847    #[test]
1848    fn file_header_name_bytes_preserve_non_utf8_names() {
1849        let file = FileHeader {
1850            block: BlockHeader {
1851                header_crc: 0,
1852                header_size: 0,
1853                header_type: HEAD_FILE,
1854                flags: 0,
1855                extra_area_size: None,
1856                data_size: Some(0),
1857                offset: 0,
1858                header_range: 0..0,
1859                data_range: 0..0,
1860            },
1861            file_flags: 0,
1862            unpacked_size: 0,
1863            attributes: 0,
1864            mtime: None,
1865            data_crc32: None,
1866            compression_info: 0,
1867            host_os: 0,
1868            name: vec![0xff, b'.', b'b', b'i', b'n'],
1869            hash: None,
1870            redirection: None,
1871            service_data: None,
1872            encrypted: false,
1873            encryption: None,
1874            crypto: None,
1875        };
1876
1877        assert_eq!(file.name_bytes(), [0xff, b'.', b'b', b'i', b'n']);
1878        assert_eq!(file.name_lossy(), "\u{fffd}.bin");
1879    }
1880
1881    fn build_archive_with_optional_comment(comment: Option<&[u8]>) -> Archive {
1882        use crate::FeatureSet;
1883        let mut features = FeatureSet::store_only();
1884        features.archive_comment = comment.is_some();
1885        let entries = [crate::rar50::StoredEntry {
1886            name: b"payload.txt",
1887            data: b"payload bytes",
1888            mtime: None,
1889            attributes: 0x20,
1890            host_os: 3,
1891        }];
1892        let bytes = crate::rar50::Rar50Writer::new(crate::rar50::WriterOptions::new(
1893            crate::version::ArchiveVersion::Rar50,
1894            features,
1895        ))
1896        .stored_entries(&entries)
1897        .archive_comment(comment)
1898        .finish()
1899        .unwrap();
1900        Archive::parse(&bytes).unwrap()
1901    }
1902
1903    #[test]
1904    fn archive_comment_returns_none_for_archive_without_a_cmt_service() {
1905        let archive = build_archive_with_optional_comment(None);
1906        assert!(archive.archive_comment().unwrap().is_none());
1907    }
1908
1909    #[test]
1910    fn archive_comment_decodes_the_cmt_service_payload_text() {
1911        let comment_text = b"archive comment from rars unit test\n";
1912        let archive = build_archive_with_optional_comment(Some(comment_text));
1913        let comment = archive.archive_comment().unwrap();
1914        assert_eq!(comment.as_deref(), Some(&comment_text[..]));
1915    }
1916
1917    #[test]
1918    fn archive_comment_ignores_cmt_services_attached_to_files() {
1919        // Service blocks that follow a File block belong to that file, not the
1920        // archive — archive_comment should not surface them.
1921        use crate::FeatureSet;
1922        let services = [crate::rar50::StoredServiceEntry {
1923            name: b"CMT",
1924            data: b"per-file comment",
1925        }];
1926        let entry = crate::rar50::StoredEntryWithServices {
1927            entry: crate::rar50::StoredEntry {
1928                name: b"payload.txt",
1929                data: b"payload bytes",
1930                mtime: None,
1931                attributes: 0x20,
1932                host_os: 3,
1933            },
1934            services: &services,
1935        };
1936        let mut features = FeatureSet::store_only();
1937        features.file_comment = true;
1938        let bytes = crate::rar50::Rar50Writer::new(crate::rar50::WriterOptions::new(
1939            crate::version::ArchiveVersion::Rar50,
1940            features,
1941        ))
1942        .stored_entries_with_services(std::slice::from_ref(&entry))
1943        .finish()
1944        .unwrap();
1945        let archive = Archive::parse(&bytes).unwrap();
1946
1947        assert!(archive.archive_comment().unwrap().is_none());
1948    }
1949}