Skip to main content

rars_format/
rar50.rs

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