Skip to main content

rars_format/
rar15_40.rs

1use crate::detect::{find_archive_start, ArchiveSignature, RAR15_SIGNATURE, SFX_SCAN_LIMIT};
2use crate::error::{Error, Result};
3use crate::features::FeatureSet;
4use crate::io_util::{align16 as checked_align16, read_exact_at, read_u16, read_u32};
5pub(crate) use crate::source::ArchiveSource;
6use crate::version::ArchiveFamily;
7use crate::ArchiveVersion;
8use rars_codec::rar13::Unpack15;
9use rars_codec::rar20::Unpack20;
10use rars_codec::rar29::Unpack29;
11use rars_crc32::{crc32, Crc32};
12use rars_crypto::rar15::Rar15Cipher;
13use rars_crypto::rar20::Rar20Cipher;
14use rars_crypto::rar30::{Error as Rar30Error, Rar30Cipher};
15use std::fs::File;
16use std::io::{Read, Write};
17use std::ops::Range;
18use std::path::Path;
19use std::sync::Arc;
20
21mod extract;
22mod write;
23pub use extract::extract_volumes_to;
24use extract::{DecoderSession, DecryptingReader};
25pub use write::{
26    write_compressed_archive, write_compressed_archive_with_comment, write_compressed_volumes,
27    write_rar29_compressed_archive_with_filter_policy, write_stored_archive,
28    write_stored_archive_with_comment, write_stored_volumes, FilterKind, FilterPolicy, FilterSpec,
29};
30
31const MARK_HEAD: u8 = 0x72;
32const MAIN_HEAD: u8 = 0x73;
33const FILE_HEAD: u8 = 0x74;
34const COMM_HEAD: u8 = 0x75;
35const PROTECT_HEAD: u8 = 0x78;
36const NEWSUB_HEAD: u8 = 0x7a;
37const ENDARC_HEAD: u8 = 0x7b;
38
39const LONG_BLOCK: u16 = 0x8000;
40const MHD_VOLUME: u16 = 0x0001;
41const MHD_COMMENT: u16 = 0x0002;
42const MHD_SOLID: u16 = 0x0008;
43const MHD_NEWNUMBERING: u16 = 0x0010;
44const MHD_PROTECT: u16 = 0x0040;
45const MHD_PASSWORD: u16 = 0x0080;
46const MHD_FIRSTVOLUME: u16 = 0x0100;
47const MHD_ENCRYPTVER: u16 = 0x0200;
48
49const FHD_SPLIT_BEFORE: u16 = 0x0001;
50const FHD_SPLIT_AFTER: u16 = 0x0002;
51const FHD_PASSWORD: u16 = 0x0004;
52const FHD_COMMENT: u16 = 0x0008;
53const FHD_SOLID: u16 = 0x0010;
54const FHD_LARGE: u16 = 0x0100;
55const FHD_UNICODE: u16 = 0x0200;
56const FHD_SALT: u16 = 0x0400;
57const FHD_EXTTIME: u16 = 0x1000;
58const FHD_DIRECTORY_MASK: u16 = 0x00e0;
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 head_crc: u16,
73    pub flags: u16,
74    pub head_size: u16,
75    pub reserved1: u16,
76    pub reserved2: u32,
77    pub encrypt_version: Option<u8>,
78}
79
80impl MainHeader {
81    pub fn has_archive_comment(&self) -> bool {
82        self.flags & MHD_COMMENT != 0
83    }
84
85    pub fn is_volume(&self) -> bool {
86        self.flags & MHD_VOLUME != 0
87    }
88
89    pub fn is_solid(&self) -> bool {
90        self.flags & MHD_SOLID != 0
91    }
92
93    pub fn uses_new_numbering(&self) -> bool {
94        self.flags & MHD_NEWNUMBERING != 0
95    }
96
97    pub fn has_recovery_record(&self) -> bool {
98        self.flags & MHD_PROTECT != 0
99    }
100
101    pub fn has_encrypted_headers(&self) -> bool {
102        self.flags & MHD_PASSWORD != 0
103    }
104
105    pub fn is_first_volume(&self) -> bool {
106        self.flags & MHD_FIRSTVOLUME != 0
107    }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum Block {
113    File(FileHeader),
114    Comment(CommentHeader),
115    Protect(ProtectHeader),
116    NewSub(NewSubHeader),
117    End(BlockHeader),
118    Unknown(BlockHeader),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122#[non_exhaustive]
123pub struct BlockHeader {
124    pub head_crc: u16,
125    pub head_type: u8,
126    pub flags: u16,
127    pub head_size: u16,
128    pub add_size: Option<u64>,
129    pub offset: usize,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133#[non_exhaustive]
134pub struct FileHeader {
135    pub block: BlockHeader,
136    pub pack_size: u64,
137    pub unp_size: u64,
138    pub host_os: u8,
139    pub file_crc: u32,
140    pub file_time: u32,
141    pub unp_ver: u8,
142    pub method: u8,
143    pub name: Vec<u8>,
144    pub attr: u32,
145    pub salt: Option<[u8; 8]>,
146    pub file_comment: Vec<u8>,
147    pub ext_time: Vec<u8>,
148    pub packed_range: Range<usize>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152#[non_exhaustive]
153pub struct NewSubHeader {
154    pub file: FileHeader,
155    pub kind: NewSubKind,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159#[non_exhaustive]
160pub struct CommentHeader {
161    pub block: BlockHeader,
162    pub unp_size: u16,
163    pub unp_ver: u8,
164    pub method: u8,
165    pub comment_crc: u16,
166    pub packed_range: Range<usize>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170#[non_exhaustive]
171pub struct ProtectHeader {
172    pub block: BlockHeader,
173    pub version: u8,
174    pub rec_sectors: u16,
175    pub total_blocks: u32,
176    pub mark: [u8; 8],
177    pub data_range: Range<usize>,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub enum NewSubKind {
183    ArchiveComment,
184    RecoveryRecord,
185    Unknown(Vec<u8>),
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189#[non_exhaustive]
190pub struct WriterOptions {
191    pub target: ArchiveVersion,
192    pub features: FeatureSet,
193    pub compression_level: Option<u8>,
194    pub dictionary_size: Option<usize>,
195}
196
197impl WriterOptions {
198    pub const fn new(target: ArchiveVersion, features: FeatureSet) -> Self {
199        Self {
200            target,
201            features,
202            compression_level: None,
203            dictionary_size: None,
204        }
205    }
206
207    pub const fn with_compression_level(mut self, level: u8) -> Self {
208        self.compression_level = Some(level);
209        self
210    }
211
212    pub const fn with_dictionary_size(mut self, size: usize) -> Self {
213        self.dictionary_size = Some(size);
214        self
215    }
216}
217
218impl Default for WriterOptions {
219    fn default() -> Self {
220        Self {
221            target: ArchiveVersion::Rar15,
222            features: FeatureSet::store_only(),
223            compression_level: None,
224            dictionary_size: None,
225        }
226    }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct StoredEntry<'a> {
231    pub name: &'a [u8],
232    pub data: &'a [u8],
233    pub file_time: u32,
234    pub file_attr: u32,
235    pub host_os: u8,
236    pub password: Option<&'a [u8]>,
237    pub file_comment: Option<&'a [u8]>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub struct FileEntry<'a> {
242    pub name: &'a [u8],
243    pub data: &'a [u8],
244    pub file_time: u32,
245    pub file_attr: u32,
246    pub host_os: u8,
247    pub password: Option<&'a [u8]>,
248    pub file_comment: Option<&'a [u8]>,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252#[non_exhaustive]
253pub struct ExtractedEntryMeta {
254    pub name: Vec<u8>,
255    pub file_time: u32,
256    pub attr: u32,
257    pub host_os: u8,
258    pub is_directory: bool,
259}
260
261impl FileHeader {
262    pub fn name_bytes(&self) -> &[u8] {
263        &self.name
264    }
265
266    /// Returns the file name with invalid UTF-8 replaced for display only.
267    ///
268    /// Use [`Self::name_bytes`] when exact archive bytes matter.
269    pub fn name_lossy(&self) -> String {
270        String::from_utf8_lossy(&self.name).into_owned()
271    }
272
273    pub fn is_split_before(&self) -> bool {
274        self.block.flags & FHD_SPLIT_BEFORE != 0
275    }
276
277    pub fn is_split_after(&self) -> bool {
278        self.block.flags & FHD_SPLIT_AFTER != 0
279    }
280
281    pub fn is_encrypted(&self) -> bool {
282        self.block.flags & FHD_PASSWORD != 0
283    }
284
285    pub fn is_solid(&self) -> bool {
286        self.block.flags & FHD_SOLID != 0
287    }
288
289    pub fn is_directory(&self) -> bool {
290        self.block.flags & FHD_DIRECTORY_MASK == FHD_DIRECTORY_MASK
291    }
292
293    pub fn has_ext_time(&self) -> bool {
294        self.block.flags & FHD_EXTTIME != 0
295    }
296
297    pub fn has_file_comment(&self) -> bool {
298        self.block.flags & FHD_COMMENT != 0 && !self.file_comment.is_empty()
299    }
300
301    pub fn file_comment(&self) -> Result<Option<Vec<u8>>> {
302        if !self.has_file_comment() {
303            return Ok(None);
304        }
305        let size = read_u16(&self.file_comment, 0)? as usize;
306        let start = 2usize;
307        let end = start
308            .checked_add(size)
309            .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
310        let comment = self.file_comment.get(start..end).ok_or(Error::TooShort)?;
311        Ok(Some(comment.to_vec()))
312    }
313
314    pub fn is_stored(&self) -> bool {
315        self.method == 0x30
316    }
317
318    pub fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
319        archive.read_range(self.packed_range.clone())
320    }
321
322    pub fn write_packed_data(&self, archive: &Archive, out: &mut impl Write) -> Result<()> {
323        archive.copy_range_to(self.packed_range.clone(), out)
324    }
325
326    pub(crate) fn stored_data(&self, archive: &Archive) -> Result<Vec<u8>> {
327        self.stored_data_with_password(archive, None)
328    }
329
330    pub(crate) fn stored_data_with_password(
331        &self,
332        archive: &Archive,
333        password: Option<&[u8]>,
334    ) -> Result<Vec<u8>> {
335        if !self.is_stored() {
336            return Err(self.unsupported_compression());
337        }
338        if !self.is_encrypted() && self.pack_size != self.unp_size {
339            return Err(Error::InvalidHeader(
340                "RAR 1.5 stored file has mismatched packed and unpacked sizes",
341            ));
342        }
343        let mut data = self.packed_data_for_decode(archive, password)?;
344        if self.is_encrypted() {
345            data.truncate(
346                usize::try_from(self.unp_size)
347                    .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
348            );
349        }
350        Ok(data)
351    }
352
353    pub(crate) fn unpacked_data(&self, archive: &Archive) -> Result<Vec<u8>> {
354        if self.is_stored() {
355            return self.stored_data(archive);
356        }
357        let mut session = DecoderSession::new(false);
358        session.decode_file_data(archive, self)
359    }
360
361    pub(crate) fn unpacked_data_with_rar29(
362        &self,
363        archive: &Archive,
364        decoder: &mut Unpack29,
365        solid: bool,
366    ) -> Result<Vec<u8>> {
367        if self.is_stored() {
368            return self.stored_data(archive);
369        }
370        if self.is_encrypted() {
371            return Err(self.unsupported_encryption());
372        }
373        if self.unp_ver < 29 {
374            return Err(self.unsupported_compression());
375        }
376        let packed = self.packed_data(archive)?;
377        let target = usize::try_from(self.unp_size)
378            .map_err(|_| Error::InvalidHeader("RAR 2.9 unpacked size overflows usize"))?;
379        if solid {
380            decoder.decode_member(&packed, target)
381        } else {
382            decoder.decode_non_solid_member(&packed, target)
383        }
384        .map_err(Into::into)
385    }
386
387    pub(crate) fn unpacked_data_with_unpack15(
388        &self,
389        archive: &Archive,
390        decoder: &mut Unpack15,
391        solid: bool,
392    ) -> Result<Vec<u8>> {
393        if self.is_stored() {
394            return self.stored_data(archive);
395        }
396        if self.is_encrypted() {
397            return Err(self.unsupported_encryption());
398        }
399        if self.unp_ver != 15 {
400            return Err(self.unsupported_compression());
401        }
402        decoder
403            .decode_member(
404                &self.packed_data(archive)?,
405                usize::try_from(self.unp_size)
406                    .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
407                solid,
408            )
409            .map_err(Into::into)
410    }
411
412    pub(crate) fn unpacked_data_with_unpack20(
413        &self,
414        archive: &Archive,
415        decoder: &mut Unpack20,
416        password: Option<&[u8]>,
417    ) -> Result<Vec<u8>> {
418        if self.is_stored() {
419            return self.stored_data_with_password(archive, password);
420        }
421        if self.unp_ver != 20 && self.unp_ver != 26 {
422            return Err(self.unsupported_compression());
423        }
424        let mut packed = self.packed_reader_for_decode(archive, password)?;
425        let mut out = Vec::new();
426        decoder
427            .decode_member_from_reader(
428                &mut packed,
429                usize::try_from(self.unp_size)
430                    .map_err(|_| Error::InvalidHeader("RAR 2.0 unpacked size overflows usize"))?,
431                &mut out,
432            )
433            .map(|_| out)
434            .map_err(Into::into)
435    }
436
437    fn packed_data_for_decode(
438        &self,
439        archive: &Archive,
440        password: Option<&[u8]>,
441    ) -> Result<Vec<u8>> {
442        let mut reader = self.packed_reader_for_decode(archive, password)?;
443        let mut data = Vec::new();
444        reader.read_to_end(&mut data)?;
445        Ok(data)
446    }
447
448    pub(super) fn packed_reader_for_decode<'a>(
449        &self,
450        archive: &'a Archive,
451        password: Option<&[u8]>,
452    ) -> Result<Box<dyn Read + 'a>> {
453        let reader = archive.range_reader(self.packed_range.clone())?;
454        if !self.is_encrypted() {
455            return Ok(reader);
456        }
457        let Some(password) = password else {
458            return Err(Error::NeedPassword);
459        };
460        if (self.unp_ver == 20 || self.unp_ver == 26 || self.unp_ver >= 29)
461            && !self.packed_range.len().is_multiple_of(16)
462        {
463            return Err(Error::InvalidHeader(
464                "RAR encrypted payload is not block aligned",
465            ));
466        }
467        Ok(Box::new(DecryptingReader::new(
468            reader,
469            self.unp_ver,
470            password,
471            self.salt,
472        )?))
473    }
474
475    pub fn verify_crc32(&self, data: &[u8]) -> Result<()> {
476        let actual = crc32(data);
477        if actual == self.file_crc {
478            Ok(())
479        } else {
480            Err(Error::Crc32Mismatch {
481                expected: self.file_crc,
482                actual,
483            })
484        }
485    }
486
487    pub fn metadata(&self) -> ExtractedEntryMeta {
488        ExtractedEntryMeta {
489            name: self.name.clone(),
490            file_time: self.file_time,
491            attr: self.attr,
492            host_os: self.host_os,
493            is_directory: self.is_directory(),
494        }
495    }
496
497    pub fn write_to(
498        &self,
499        archive: &Archive,
500        password: Option<&[u8]>,
501        out: &mut impl Write,
502    ) -> Result<()> {
503        if self.is_directory() {
504            return Ok(());
505        }
506        let mut session = DecoderSession::new_with_password(false, password);
507        session.write_file_to(archive, self, out)
508    }
509
510    fn write_stored_to(
511        &self,
512        archive: &Archive,
513        password: Option<&[u8]>,
514        out: &mut impl Write,
515    ) -> Result<()> {
516        if !self.is_stored() {
517            return Err(self.unsupported_compression());
518        }
519        if !self.is_encrypted() && self.pack_size != self.unp_size {
520            return Err(Error::InvalidHeader(
521                "RAR 1.5 stored file has mismatched packed and unpacked sizes",
522            ));
523        }
524        let mut reader = self
525            .packed_reader_for_decode(archive, password)
526            .map_err(|error| self.map_encrypted_payload_error(password, error))?;
527        let expected_len = usize::try_from(self.unp_size)
528            .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?;
529        let mut crc = Crc32::new();
530        let mut crc_writer = CrcWriter {
531            inner: out,
532            crc: &mut crc,
533        };
534        let copied = std::io::copy(
535            &mut reader.by_ref().take(expected_len as u64),
536            &mut crc_writer,
537        )?;
538        if copied != expected_len as u64 {
539            return Err(self.map_encrypted_payload_error(
540                password,
541                Error::InvalidHeader("RAR 1.5 stored file ended before unpacked size"),
542            ));
543        }
544        let actual = crc.finish();
545        if actual == self.file_crc {
546            Ok(())
547        } else {
548            Err(self.map_encrypted_payload_error(
549                password,
550                Error::Crc32Mismatch {
551                    expected: self.file_crc,
552                    actual,
553                },
554            ))
555        }
556    }
557
558    fn map_encrypted_payload_error(&self, password: Option<&[u8]>, error: Error) -> Error {
559        if !self.is_encrypted() || password.is_none() {
560            return error;
561        }
562        match error {
563            Error::NeedPassword => Error::NeedPassword,
564            Error::UnsupportedSignature
565            | Error::UnsupportedVersion(_)
566            | Error::UnsupportedFeature { .. }
567            | Error::Rar50BufferedDecodeLimitExceeded { .. }
568            | Error::UnsupportedFamilyFeature { .. }
569            | Error::UnsupportedCompression { .. }
570            | Error::UnsupportedEncryption { .. }
571            | Error::TooShort
572            | Error::Io(_)
573            | Error::AtArchiveOffset { .. }
574            | Error::AtEntry { .. } => error,
575            Error::InvalidHeader(_)
576            | Error::Codec(_)
577            | Error::Rar3Recovery(_)
578            | Error::Rar5Recovery(_)
579            | Error::Rar20Crypto(_)
580            | Error::Rar30Crypto(_)
581            | Error::Rar50Crypto(_)
582            | Error::CrcMismatch { .. }
583            | Error::Crc32Mismatch { .. }
584            | Error::HashMismatch { .. }
585            | Error::WrongPasswordOrCorruptData => Error::WrongPasswordOrCorruptData,
586        }
587    }
588
589    fn entry_error(&self, operation: &'static str, error: Error) -> Error {
590        if matches!(
591            error,
592            Error::NeedPassword | Error::WrongPasswordOrCorruptData
593        ) {
594            return error;
595        }
596        error.at_entry(self.name.clone(), operation)
597    }
598
599    fn crc_result(&self, actual: u32, password: Option<&[u8]>) -> Result<()> {
600        if actual == self.file_crc {
601            Ok(())
602        } else {
603            Err(self.map_encrypted_payload_error(
604                password,
605                Error::Crc32Mismatch {
606                    expected: self.file_crc,
607                    actual,
608                },
609            ))
610        }
611    }
612
613    fn write_rar29_to(
614        &self,
615        archive: &Archive,
616        decoder: &mut Unpack29,
617        out: &mut impl Write,
618    ) -> Result<()> {
619        if self.is_stored() {
620            return self.write_stored_to(archive, None, out);
621        }
622        if self.is_encrypted() {
623            return Err(self.unsupported_encryption());
624        }
625        if self.unp_ver < 29 {
626            return Err(self.unsupported_compression());
627        }
628
629        let mut packed = archive.range_reader(self.packed_range.clone())?;
630        let mut crc = Crc32::new();
631        let mut crc_writer = CrcWriter {
632            inner: out,
633            crc: &mut crc,
634        };
635        decoder
636            .decode_member_from_reader(
637                &mut packed,
638                usize::try_from(self.unp_size)
639                    .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
640                &mut crc_writer,
641            )
642            .map_err(Error::from)?;
643        let actual = crc.finish();
644        if actual == self.file_crc {
645            Ok(())
646        } else {
647            Err(Error::Crc32Mismatch {
648                expected: self.file_crc,
649                actual,
650            })
651        }
652    }
653
654    fn write_unpack15_to(
655        &self,
656        archive: &Archive,
657        decoder: &mut Unpack15,
658        solid: bool,
659        password: Option<&[u8]>,
660        out: &mut impl Write,
661    ) -> Result<()> {
662        if self.is_stored() {
663            return self.write_stored_to(archive, password, out);
664        }
665        if self.unp_ver != 15 {
666            return Err(self.unsupported_compression());
667        }
668
669        let mut input = self
670            .packed_reader_for_decode(archive, password)
671            .map_err(|error| self.map_encrypted_payload_error(password, error))?;
672        self.write_unpack15_decoded(decoder, solid, &mut input, out, password)
673            .map_err(|error| self.map_encrypted_payload_error(password, error))
674    }
675
676    fn write_unpack15_decoded(
677        &self,
678        decoder: &mut Unpack15,
679        solid: bool,
680        input: &mut impl Read,
681        out: &mut impl Write,
682        password: Option<&[u8]>,
683    ) -> Result<()> {
684        let mut crc = Crc32::new();
685        let mut crc_writer = CrcWriter {
686            inner: out,
687            crc: &mut crc,
688        };
689        decoder
690            .decode_member_from_reader(
691                input,
692                usize::try_from(self.unp_size)
693                    .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
694                solid,
695                &mut crc_writer,
696            )
697            .map_err(Error::from)?;
698        let actual = crc.finish();
699        self.crc_result(actual, password)
700    }
701
702    fn write_unpack20_to(
703        &self,
704        archive: &Archive,
705        decoder: &mut Unpack20,
706        password: Option<&[u8]>,
707        out: &mut impl Write,
708    ) -> Result<()> {
709        if self.is_stored() {
710            return self.write_stored_to(archive, password, out);
711        }
712        if self.unp_ver != 20 && self.unp_ver != 26 {
713            return Err(self.unsupported_compression());
714        }
715
716        let mut crc = Crc32::new();
717        let mut crc_writer = CrcWriter {
718            inner: out,
719            crc: &mut crc,
720        };
721        let target = usize::try_from(self.unp_size)
722            .map_err(|_| Error::InvalidHeader("RAR 2.0 unpacked size overflows usize"))?;
723        let mut packed = self
724            .packed_reader_for_decode(archive, password)
725            .map_err(|error| self.map_encrypted_payload_error(password, error))?;
726        decoder
727            .decode_member_from_reader(&mut packed, target, &mut crc_writer)
728            .map_err(Error::from)
729            .map_err(|error| self.map_encrypted_payload_error(password, error))?;
730        let actual = crc.finish();
731        self.crc_result(actual, password)
732    }
733
734    fn unsupported_compression(&self) -> Error {
735        Error::UnsupportedCompression {
736            family: "RAR 1.5-4.x",
737            unpack_version: self.unp_ver,
738            method: self.method,
739        }
740    }
741
742    fn unsupported_encryption(&self) -> Error {
743        Error::UnsupportedEncryption {
744            family: "RAR 1.5-4.x",
745            unpack_version: self.unp_ver,
746        }
747    }
748}
749
750impl NewSubHeader {
751    pub fn name_bytes(&self) -> &[u8] {
752        self.file.name_bytes()
753    }
754
755    /// Returns the service-block name with invalid UTF-8 replaced for display only.
756    ///
757    /// Use [`Self::name_bytes`] when exact archive bytes matter.
758    pub fn name_lossy(&self) -> String {
759        self.file.name_lossy()
760    }
761}
762
763impl CommentHeader {
764    fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
765        archive.read_range(self.packed_range.clone())
766    }
767
768    fn unpacked_data(&self, archive: &Archive) -> Result<Vec<u8>> {
769        let target = usize::from(self.unp_size);
770        let data = if self.method == 0x30 {
771            let data = self.packed_data(archive)?;
772            if data.len() != target {
773                return Err(Error::InvalidHeader(
774                    "RAR 1.5 stored comment has mismatched packed and unpacked sizes",
775                ));
776            }
777            data
778        } else if self.unp_ver == 15 {
779            Unpack15::default().decode_member(&self.packed_data(archive)?, target, false)?
780        } else {
781            return Err(Error::UnsupportedCompression {
782                family: "RAR 1.5 comment",
783                unpack_version: self.unp_ver,
784                method: self.method,
785            });
786        };
787        let actual = (crc32(&data) & 0xffff) as u16;
788        if actual == self.comment_crc {
789            Ok(data)
790        } else {
791            Err(Error::CrcMismatch {
792                expected: self.comment_crc,
793                actual,
794            })
795        }
796    }
797}
798
799impl Archive {
800    pub fn parse(input: &[u8]) -> Result<Self> {
801        Self::parse_with_options(input, crate::ArchiveReadOptions::default())
802    }
803
804    pub fn parse_owned(input: Vec<u8>) -> Result<Self> {
805        Self::parse_owned_with_options(input, crate::ArchiveReadOptions::default())
806    }
807
808    pub fn parse_with_options(
809        input: &[u8],
810        options: crate::ArchiveReadOptions<'_>,
811    ) -> Result<Self> {
812        let data: Arc<[u8]> = Arc::from(input.to_vec().into_boxed_slice());
813        Self::parse_shared(data, options.password)
814    }
815
816    pub fn parse_owned_with_options(
817        input: Vec<u8>,
818        options: crate::ArchiveReadOptions<'_>,
819    ) -> Result<Self> {
820        Self::parse_shared(Arc::from(input.into_boxed_slice()), options.password)
821    }
822
823    pub fn parse_with_password(input: &[u8], password: Option<&[u8]>) -> Result<Self> {
824        Self::parse_with_options(
825            input,
826            crate::ArchiveReadOptions::with_optional_password(password),
827        )
828    }
829
830    pub fn parse_owned_with_password(input: Vec<u8>, password: Option<&[u8]>) -> Result<Self> {
831        Self::parse_owned_with_options(
832            input,
833            crate::ArchiveReadOptions::with_optional_password(password),
834        )
835    }
836
837    pub fn parse_path(path: impl AsRef<Path>) -> Result<Self> {
838        Self::parse_path_with_options(path, crate::ArchiveReadOptions::default())
839    }
840
841    pub fn parse_path_with_options(
842        path: impl AsRef<Path>,
843        options: crate::ArchiveReadOptions<'_>,
844    ) -> Result<Self> {
845        Self::parse_path_with_password(path, options.password)
846    }
847
848    pub fn parse_path_with_password(
849        path: impl AsRef<Path>,
850        password: Option<&[u8]>,
851    ) -> Result<Self> {
852        let path = Arc::new(path.as_ref().to_path_buf());
853        let mut file = File::open(path.as_ref())?;
854        let len = file.metadata()?.len();
855        let scan_len = len.min(SFX_SCAN_LIMIT as u64) as usize;
856        let mut scan = vec![0; scan_len];
857        file.read_exact(&mut scan)?;
858        let sig = find_archive_start(&scan, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
859        if sig.family != ArchiveFamily::Rar15To40 {
860            return Err(Error::UnsupportedSignature);
861        }
862        Self::parse_seekable(file, len, sig.offset, ArchiveSource::File(path), password)
863    }
864
865    pub fn parse_path_with_signature(
866        path: impl AsRef<Path>,
867        signature: ArchiveSignature,
868        options: crate::ArchiveReadOptions<'_>,
869    ) -> Result<Self> {
870        Self::parse_path_with_signature_and_password(path, signature, options.password)
871    }
872
873    pub fn parse_path_with_signature_and_password(
874        path: impl AsRef<Path>,
875        signature: ArchiveSignature,
876        password: Option<&[u8]>,
877    ) -> Result<Self> {
878        if signature.family != ArchiveFamily::Rar15To40 {
879            return Err(Error::UnsupportedSignature);
880        }
881        let path = Arc::new(path.as_ref().to_path_buf());
882        let file = File::open(path.as_ref())?;
883        let len = file.metadata()?.len();
884        Self::parse_seekable(
885            file,
886            len,
887            signature.offset,
888            ArchiveSource::File(path),
889            password,
890        )
891    }
892
893    fn parse_shared(input: Arc<[u8]>, password: Option<&[u8]>) -> Result<Self> {
894        let sig = find_archive_start(&input, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
895        if sig.family != ArchiveFamily::Rar15To40 {
896            return Err(Error::UnsupportedSignature);
897        }
898
899        let archive = &input[sig.offset..];
900        if !archive.starts_with(RAR15_SIGNATURE) {
901            return Err(Error::UnsupportedSignature);
902        }
903
904        let marker = parse_block_header(archive, 0)?;
905        if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
906            return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
907        }
908
909        let main_block = parse_block_header(archive, marker.head_size as usize)?;
910        if main_block.head_type != MAIN_HEAD {
911            return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
912        }
913        let main = parse_main_header(archive, &main_block)?;
914        let mut pos = main_block.offset + main_block.head_size as usize;
915        let mut blocks = Vec::new();
916        let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
917
918        while pos < archive.len() {
919            if archive.len() - pos < 7 {
920                break;
921            }
922            let (block, header, total) = if main.has_encrypted_headers() {
923                let password = password.ok_or(Error::NeedPassword)?;
924                let encrypted = decrypt_encrypted_header_at(
925                    archive,
926                    pos,
927                    password,
928                    &mut encrypted_header_ciphers,
929                )?;
930                (encrypted.block, encrypted.header, encrypted.total_size)
931            } else {
932                let block = parse_block_header(archive, pos)?;
933                let total = block_total_size(&block)?;
934                let header = archive[pos..pos + block.head_size as usize].to_vec();
935                (block, header, total)
936            };
937            match block.head_type {
938                FILE_HEAD => {
939                    let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
940                    let total = file_block_total_size(&block, total, file.pack_size)?;
941                    let next = checked_block_next(&block, total, archive.len())?;
942                    file.block.offset = block.offset;
943                    file.packed_range =
944                        packed_range(sig.offset, block.offset, total, file.pack_size)?;
945                    blocks.push(Block::File(file));
946                    pos = next;
947                }
948                NEWSUB_HEAD => {
949                    let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
950                    let total = file_block_total_size(&block, total, file.pack_size)?;
951                    let next = checked_block_next(&block, total, archive.len())?;
952                    file.block.offset = block.offset;
953                    file.packed_range =
954                        packed_range(sig.offset, block.offset, total, file.pack_size)?;
955                    let kind = classify_new_sub(&file.name);
956                    blocks.push(Block::NewSub(NewSubHeader { file, kind }));
957                    pos = next;
958                }
959                COMM_HEAD => {
960                    let next = checked_block_next(&block, total, archive.len())?;
961                    let mut comment = parse_comment_header(&header, relative_block(&block))?;
962                    comment.block.offset = block.offset;
963                    comment.packed_range =
964                        sig.offset + block.offset + 13..sig.offset + block.offset + total;
965                    blocks.push(Block::Comment(comment));
966                    pos = next;
967                }
968                PROTECT_HEAD => {
969                    let next = checked_block_next(&block, total, archive.len())?;
970                    let protect = parse_protect_header(&header, &block, sig.offset, total)?;
971                    blocks.push(Block::Protect(protect));
972                    pos = next;
973                }
974                ENDARC_HEAD => {
975                    let _next = checked_block_next(&block, total, archive.len())?;
976                    blocks.push(Block::End(block));
977                    break;
978                }
979                _ => {
980                    let next = checked_block_next(&block, total, archive.len())?;
981                    blocks.push(Block::Unknown(block));
982                    pos = next;
983                }
984            }
985        }
986
987        Ok(Self {
988            sfx_offset: sig.offset,
989            main,
990            blocks,
991            source: ArchiveSource::Memory(input),
992        })
993    }
994
995    fn parse_seekable(
996        mut file: File,
997        file_len: u64,
998        sfx_offset: usize,
999        source: ArchiveSource,
1000        password: Option<&[u8]>,
1001    ) -> Result<Self> {
1002        let marker = read_block_header_at(&mut file, file_len, sfx_offset, 0)?;
1003        if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
1004            return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
1005        }
1006
1007        let main_block =
1008            read_block_header_at(&mut file, file_len, sfx_offset, marker.head_size as usize)?;
1009        if main_block.head_type != MAIN_HEAD {
1010            return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
1011        }
1012        let main_header = read_exact_at(
1013            &mut file,
1014            sfx_offset + main_block.offset,
1015            main_block.head_size as usize,
1016        )?;
1017        let main = parse_main_header(&main_header, &relative_block(&main_block))?;
1018        let mut pos = main_block.offset + main_block.head_size as usize;
1019        let mut blocks = Vec::new();
1020        let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
1021
1022        while (sfx_offset + pos) as u64 + 7 <= file_len {
1023            let (block, header, total) = if main.has_encrypted_headers() {
1024                let password = password.ok_or(Error::NeedPassword)?;
1025                let encrypted = read_encrypted_header_at(
1026                    &mut file,
1027                    file_len,
1028                    sfx_offset,
1029                    pos,
1030                    password,
1031                    &mut encrypted_header_ciphers,
1032                )?;
1033                (encrypted.block, encrypted.header, encrypted.total_size)
1034            } else {
1035                let block = read_block_header_at(&mut file, file_len, sfx_offset, pos)?;
1036                let total = block_total_size(&block)?;
1037                let header = read_exact_at(&mut file, sfx_offset + pos, block.head_size as usize)?;
1038                (block, header, total)
1039            };
1040            match block.head_type {
1041                FILE_HEAD => {
1042                    let mut file_header =
1043                        parse_file_like_header(&header, relative_block(&block), 0)?;
1044                    let total = file_block_total_size(&block, total, file_header.pack_size)?;
1045                    let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1046                    file_header.block.offset = block.offset;
1047                    file_header.packed_range =
1048                        packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1049                    blocks.push(Block::File(file_header));
1050                    pos = next;
1051                }
1052                NEWSUB_HEAD => {
1053                    let mut file_header =
1054                        parse_file_like_header(&header, relative_block(&block), 0)?;
1055                    let total = file_block_total_size(&block, total, file_header.pack_size)?;
1056                    let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1057                    file_header.block.offset = block.offset;
1058                    file_header.packed_range =
1059                        packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1060                    let kind = classify_new_sub(&file_header.name);
1061                    blocks.push(Block::NewSub(NewSubHeader {
1062                        file: file_header,
1063                        kind,
1064                    }));
1065                    pos = next;
1066                }
1067                COMM_HEAD => {
1068                    let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1069                    let mut comment = parse_comment_header(&header, relative_block(&block))?;
1070                    comment.block.offset = block.offset;
1071                    comment.packed_range =
1072                        sfx_offset + block.offset + 13..sfx_offset + block.offset + total;
1073                    blocks.push(Block::Comment(comment));
1074                    pos = next;
1075                }
1076                PROTECT_HEAD => {
1077                    let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1078                    let protect = parse_protect_header(&header, &block, sfx_offset, total)?;
1079                    blocks.push(Block::Protect(protect));
1080                    pos = next;
1081                }
1082                ENDARC_HEAD => {
1083                    let _next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1084                    blocks.push(Block::End(block));
1085                    break;
1086                }
1087                _ => {
1088                    let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1089                    blocks.push(Block::Unknown(block));
1090                    pos = next;
1091                }
1092            }
1093        }
1094
1095        Ok(Self {
1096            sfx_offset,
1097            main,
1098            blocks,
1099            source,
1100        })
1101    }
1102
1103    fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>> {
1104        self.source.read_range(range)
1105    }
1106
1107    fn copy_range_to(&self, range: Range<usize>, out: &mut impl Write) -> Result<()> {
1108        self.source.copy_range_to(range, out)
1109    }
1110
1111    fn range_reader(&self, range: Range<usize>) -> Result<Box<dyn Read + '_>> {
1112        self.source.range_reader(range)
1113    }
1114
1115    pub fn files(&self) -> impl Iterator<Item = &FileHeader> {
1116        self.blocks.iter().filter_map(|block| match block {
1117            Block::File(file) => Some(file),
1118            _ => None,
1119        })
1120    }
1121
1122    pub fn new_subs(&self) -> impl Iterator<Item = &NewSubHeader> {
1123        self.blocks.iter().filter_map(|block| match block {
1124            Block::NewSub(sub) => Some(sub),
1125            _ => None,
1126        })
1127    }
1128
1129    pub fn protect_records(&self) -> impl Iterator<Item = &ProtectHeader> {
1130        self.blocks.iter().filter_map(|block| match block {
1131            Block::Protect(protect) => Some(protect),
1132            _ => None,
1133        })
1134    }
1135
1136    fn source_bytes(&self) -> Result<Vec<u8>> {
1137        self.source.bytes()
1138    }
1139
1140    pub fn repair_protect_head(&self) -> Result<Vec<u8>> {
1141        if let Some(recovery) = self
1142            .new_subs()
1143            .find(|sub| sub.kind == NewSubKind::RecoveryRecord)
1144        {
1145            return repair_newsub_recovery_bytes(
1146                &self.source_bytes()?,
1147                self.sfx_offset,
1148                self,
1149                recovery,
1150            );
1151        }
1152        let protect = self.protect_records().next().ok_or(Error::InvalidHeader(
1153            "RAR 2.x archive does not contain a PROTECT_HEAD recovery record",
1154        ))?;
1155        repair_protect_head_bytes(&self.source_bytes()?, self.sfx_offset, protect)
1156    }
1157
1158    /// Streams extracted entries to caller-provided writers.
1159    pub fn extract_to<F>(&self, options: crate::ArchiveReadOptions<'_>, mut open: F) -> Result<()>
1160    where
1161        F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1162    {
1163        let password = options.password;
1164        let mut session = DecoderSession::new_with_password(self.main.is_solid(), password);
1165        for file in self.files() {
1166            if file.is_split_before() || file.is_split_after() {
1167                return Err(Error::InvalidHeader(
1168                    "RAR 1.5 split entry requires multivolume extraction",
1169                ));
1170            }
1171            let meta = file.metadata();
1172            if meta.is_directory {
1173                let _ = open(&meta)?;
1174                continue;
1175            }
1176            let mut writer = open(&meta)?;
1177            if file.is_stored() {
1178                file.write_stored_to(self, password, &mut writer)
1179                    .map_err(|error| file.entry_error("extracting", error))?;
1180            } else {
1181                session
1182                    .write_file_to(self, file, &mut writer)
1183                    .map_err(|error| file.entry_error("extracting", error))?;
1184            }
1185        }
1186        Ok(())
1187    }
1188
1189    #[cfg(feature = "parallel")]
1190    pub fn extract_to_parallel_buffered<F>(
1191        &self,
1192        options: crate::ArchiveReadOptions<'_>,
1193        mut open: F,
1194    ) -> Result<()>
1195    where
1196        F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1197    {
1198        if self.main.is_solid()
1199            || self
1200                .files()
1201                .any(|file| file.is_solid() || file.is_split_before() || file.is_split_after())
1202        {
1203            return self.extract_to(options, open);
1204        }
1205
1206        let password = options.password;
1207        let files: Vec<_> = self.files().collect();
1208        if files.len() < 2 {
1209            return self.extract_to(options, open);
1210        }
1211        let entries = crate::parallel::map_collect(files, |file| {
1212            decode_parallel_entry(self, file, password)
1213        })?;
1214        for entry in entries {
1215            write_parallel_entry(entry, &mut open)?;
1216        }
1217        Ok(())
1218    }
1219
1220    pub fn archive_comment(&self) -> Result<Option<Vec<u8>>> {
1221        if let Some(comment) = self.blocks.iter().find_map(|block| match block {
1222            Block::Comment(comment) => Some(comment),
1223            _ => None,
1224        }) {
1225            return comment.unpacked_data(self).map(Some);
1226        }
1227
1228        let Some(comment) = self
1229            .new_subs()
1230            .find(|sub| sub.kind == NewSubKind::ArchiveComment)
1231        else {
1232            return Ok(None);
1233        };
1234        let data = comment.file.unpacked_data(self)?;
1235        comment.file.verify_crc32(&data)?;
1236        Ok(Some(data))
1237    }
1238}
1239
1240#[cfg(feature = "parallel")]
1241enum ParallelExtractedEntry {
1242    Directory(ExtractedEntryMeta),
1243    File {
1244        meta: ExtractedEntryMeta,
1245        data: Vec<u8>,
1246    },
1247}
1248
1249#[cfg(feature = "parallel")]
1250fn decode_parallel_entry(
1251    archive: &Archive,
1252    file: &FileHeader,
1253    password: Option<&[u8]>,
1254) -> Result<ParallelExtractedEntry> {
1255    if file.is_split_before() || file.is_split_after() {
1256        return Err(Error::InvalidHeader(
1257            "RAR 1.5 split entry requires multivolume extraction",
1258        ));
1259    }
1260    let meta = file.metadata();
1261    if meta.is_directory {
1262        return Ok(ParallelExtractedEntry::Directory(meta));
1263    }
1264    let mut data = Vec::new();
1265    if file.is_stored() {
1266        file.write_stored_to(archive, password, &mut data)
1267            .map_err(|error| file.entry_error("extracting", error))?;
1268    } else {
1269        let mut session = DecoderSession::new_with_password(false, password);
1270        session
1271            .write_file_to(archive, file, &mut data)
1272            .map_err(|error| file.entry_error("extracting", error))?;
1273    }
1274    Ok(ParallelExtractedEntry::File { meta, data })
1275}
1276
1277#[cfg(feature = "parallel")]
1278fn write_parallel_entry<F>(entry: ParallelExtractedEntry, open: &mut F) -> Result<()>
1279where
1280    F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1281{
1282    match entry {
1283        ParallelExtractedEntry::Directory(meta) => {
1284            let _ = open(&meta)?;
1285        }
1286        ParallelExtractedEntry::File { meta, data } => {
1287            let mut writer = open(&meta)?;
1288            writer.write_all(&data)?;
1289        }
1290    }
1291    Ok(())
1292}
1293
1294fn classify_new_sub(name: &[u8]) -> NewSubKind {
1295    match name {
1296        b"CMT" => NewSubKind::ArchiveComment,
1297        b"RR" => NewSubKind::RecoveryRecord,
1298        _ => NewSubKind::Unknown(name.to_vec()),
1299    }
1300}
1301
1302fn parse_main_header(input: &[u8], block: &BlockHeader) -> Result<MainHeader> {
1303    if block.head_size < 13 {
1304        return Err(Error::InvalidHeader("RAR 1.5 main header is too short"));
1305    }
1306    let start = block.offset;
1307    let head_end = start + block.head_size as usize;
1308    if head_end > input.len() {
1309        return Err(Error::TooShort);
1310    }
1311
1312    let encrypt_version = if block.flags & MHD_ENCRYPTVER != 0 {
1313        Some(*input.get(start + 13).ok_or(Error::TooShort)?)
1314    } else {
1315        None
1316    };
1317
1318    Ok(MainHeader {
1319        head_crc: block.head_crc,
1320        flags: block.flags,
1321        head_size: block.head_size,
1322        reserved1: read_u16(input, start + 7)?,
1323        reserved2: read_u32(input, start + 9)?,
1324        encrypt_version,
1325    })
1326}
1327
1328fn parse_comment_header(input: &[u8], block: BlockHeader) -> Result<CommentHeader> {
1329    if block.head_size < 13 {
1330        return Err(Error::InvalidHeader("RAR 1.5 comment header is too short"));
1331    }
1332    let start = block.offset;
1333    Ok(CommentHeader {
1334        block,
1335        unp_size: read_u16(input, start + 7)?,
1336        unp_ver: *input.get(start + 9).ok_or(Error::TooShort)?,
1337        method: *input.get(start + 10).ok_or(Error::TooShort)?,
1338        comment_crc: read_u16(input, start + 11)?,
1339        packed_range: 0..0,
1340    })
1341}
1342
1343fn parse_protect_header(
1344    input: &[u8],
1345    block: &BlockHeader,
1346    archive_offset: usize,
1347    total_size: usize,
1348) -> Result<ProtectHeader> {
1349    if block.head_size != 26 {
1350        return Err(Error::InvalidHeader(
1351            "RAR 2.x recovery header size is invalid",
1352        ));
1353    }
1354    let add_size = block.add_size.ok_or(Error::InvalidHeader(
1355        "RAR 2.x recovery header is missing data size",
1356    ))?;
1357    let rec_sectors = read_u16(input, 12)?;
1358    let total_blocks = read_u32(input, 14)?;
1359    let expected_add_size = u64::from(total_blocks)
1360        .checked_mul(2)
1361        .and_then(|size| size.checked_add(u64::from(rec_sectors) * 512))
1362        .ok_or(Error::InvalidHeader("RAR 2.x recovery data size overflows"))?;
1363    if add_size != expected_add_size {
1364        return Err(Error::InvalidHeader(
1365            "RAR 2.x recovery data size does not match header",
1366        ));
1367    }
1368    let mark: [u8; 8] = input
1369        .get(18..26)
1370        .ok_or(Error::TooShort)?
1371        .try_into()
1372        .expect("RAR protect mark size");
1373    let data_start = archive_offset
1374        .checked_add(block.offset)
1375        .and_then(|offset| offset.checked_add(block.head_size as usize))
1376        .ok_or(Error::InvalidHeader(
1377            "RAR 2.x recovery data range overflows",
1378        ))?;
1379    let data_end = archive_offset
1380        .checked_add(block.offset)
1381        .and_then(|offset| offset.checked_add(total_size))
1382        .ok_or(Error::InvalidHeader(
1383            "RAR 2.x recovery data range overflows",
1384        ))?;
1385    Ok(ProtectHeader {
1386        block: block.clone(),
1387        version: *input.get(11).ok_or(Error::TooShort)?,
1388        rec_sectors,
1389        total_blocks,
1390        mark,
1391        data_range: data_start..data_end,
1392    })
1393}
1394
1395fn repair_protect_head_bytes(
1396    source: &[u8],
1397    sfx_offset: usize,
1398    protect: &ProtectHeader,
1399) -> Result<Vec<u8>> {
1400    if protect.rec_sectors == 0 {
1401        return Err(Error::InvalidHeader(
1402            "RAR 2.x recovery record has no parity sectors",
1403        ));
1404    }
1405    if protect.mark != *b"Protect!" {
1406        return Err(Error::InvalidHeader("RAR 2.x recovery mark is invalid"));
1407    }
1408    let protected_start = sfx_offset;
1409    let protected_len = usize::try_from(protect.total_blocks)
1410        .ok()
1411        .and_then(|blocks| blocks.checked_mul(512))
1412        .ok_or(Error::InvalidHeader(
1413            "RAR 2.x protected sector size overflows",
1414        ))?;
1415    let protected_end = protected_start
1416        .checked_add(protected_len)
1417        .ok_or(Error::InvalidHeader(
1418            "RAR 2.x protected sector range overflows",
1419        ))?;
1420    if protected_end > source.len() {
1421        return Err(Error::InvalidHeader(
1422            "RAR 2.x protected sector range is invalid",
1423        ));
1424    }
1425    let recovery_data = source
1426        .get(protect.data_range.clone())
1427        .ok_or(Error::TooShort)?;
1428    let declared_blocks = usize::try_from(protect.total_blocks).map_err(|_| {
1429        Error::InvalidHeader("RAR 2.x recovery protected sector count overflows usize")
1430    })?;
1431    let tag_len = declared_blocks
1432        .checked_mul(2)
1433        .ok_or(Error::InvalidHeader("RAR 2.x recovery tag size overflows"))?;
1434    let parity_len =
1435        usize::from(protect.rec_sectors)
1436            .checked_mul(512)
1437            .ok_or(Error::InvalidHeader(
1438                "RAR 2.x recovery parity size overflows",
1439            ))?;
1440    if recovery_data.len() != tag_len + parity_len {
1441        return Err(Error::InvalidHeader(
1442            "RAR 2.x recovery data size is invalid",
1443        ));
1444    }
1445    let tags = &recovery_data[..tag_len];
1446    let parity = &recovery_data[tag_len..];
1447    // RAR 2.50 records may declare a final sector that starts before
1448    // PROTECT_HEAD but overlaps the recovery block. Only complete sectors
1449    // before the recovery block are safely repairable.
1450    let repairable_blocks = declared_blocks.min(protect.block.offset / 512);
1451
1452    let mut damaged = Vec::new();
1453    for index in 0..repairable_blocks {
1454        let sector_start = protected_start + index * 512;
1455        let sector = &source[sector_start..sector_start + 512];
1456        let actual = (!crc32(sector) & 0xffff) as u16;
1457        let expected = read_u16(tags, index * 2)?;
1458        if actual != expected {
1459            damaged.push(index);
1460        }
1461    }
1462    if damaged.is_empty() {
1463        return Ok(source.to_vec());
1464    }
1465    if damaged.len() > usize::from(protect.rec_sectors) {
1466        return Err(Error::InvalidHeader(
1467            "RAR 2.x recovery damage exceeds parity sector count",
1468        ));
1469    }
1470
1471    let mut used_slots = vec![false; usize::from(protect.rec_sectors)];
1472    for &index in &damaged {
1473        let slot = index % usize::from(protect.rec_sectors);
1474        if used_slots[slot] {
1475            return Err(Error::InvalidHeader(
1476                "RAR 2.x recovery cannot repair multiple sectors in the same parity group",
1477            ));
1478        }
1479        used_slots[slot] = true;
1480    }
1481
1482    let mut repaired = source.to_vec();
1483    for &missing_index in &damaged {
1484        let slot = missing_index % usize::from(protect.rec_sectors);
1485        let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1486        for index in (slot..repairable_blocks).step_by(usize::from(protect.rec_sectors)) {
1487            if index == missing_index {
1488                continue;
1489            }
1490            let sector_start = protected_start + index * 512;
1491            for (out, byte) in sector
1492                .iter_mut()
1493                .zip(&repaired[sector_start..sector_start + 512])
1494            {
1495                *out ^= *byte;
1496            }
1497        }
1498        let sector_start = protected_start + missing_index * 512;
1499        repaired[sector_start..sector_start + 512].copy_from_slice(&sector);
1500        let actual = (!crc32(&sector) & 0xffff) as u16;
1501        let expected = u16::from_le_bytes(
1502            tags[missing_index * 2..missing_index * 2 + 2]
1503                .try_into()
1504                .unwrap(),
1505        );
1506        if actual != expected {
1507            return Err(Error::CrcMismatch { expected, actual });
1508        }
1509    }
1510    Ok(repaired)
1511}
1512
1513fn repair_newsub_recovery_bytes(
1514    source: &[u8],
1515    sfx_offset: usize,
1516    archive: &Archive,
1517    recovery: &NewSubHeader,
1518) -> Result<Vec<u8>> {
1519    let recovery_data = newsub_recovery_data(archive, recovery)?;
1520    let expected_unpacked = usize::try_from(recovery.file.unp_size)
1521        .map_err(|_| Error::InvalidHeader("RAR 3.x recovery unpacked size overflows usize"))?;
1522    if recovery_data.len() != expected_unpacked {
1523        return Err(Error::InvalidHeader(
1524            "RAR 3.x recovery data size does not match unpacked size",
1525        ));
1526    }
1527    let protected_start = sfx_offset;
1528    let protected_end =
1529        sfx_offset
1530            .checked_add(recovery.file.block.offset)
1531            .ok_or(Error::InvalidHeader(
1532                "RAR 3.x recovery protected range overflows",
1533            ))?;
1534    if protected_end > source.len() || protected_start > protected_end {
1535        return Err(Error::InvalidHeader(
1536            "RAR 3.x recovery protected range is invalid",
1537        ));
1538    }
1539    let protected_len = protected_end - protected_start;
1540    let protected_sectors = protected_len.div_ceil(512);
1541    if protected_sectors == 0 {
1542        return Err(Error::InvalidHeader(
1543            "RAR 3.x recovery record has no protected sectors",
1544        ));
1545    }
1546    let tag_len = protected_sectors
1547        .checked_mul(2)
1548        .ok_or(Error::InvalidHeader("RAR 3.x recovery tag size overflows"))?;
1549    if recovery_data.len() <= tag_len || !(recovery_data.len() - tag_len).is_multiple_of(512) {
1550        return Err(Error::InvalidHeader(
1551            "RAR 3.x recovery data size is invalid",
1552        ));
1553    }
1554    let parity_sectors = (recovery_data.len() - tag_len) / 512;
1555    if parity_sectors == 0 {
1556        return Err(Error::InvalidHeader(
1557            "RAR 3.x recovery record has no parity sectors",
1558        ));
1559    }
1560    let tags = &recovery_data[..tag_len];
1561    let parity = &recovery_data[tag_len..];
1562
1563    let mut damaged = Vec::new();
1564    for index in 0..protected_sectors {
1565        let sector = protected_sector(source, protected_start, protected_len, index)?;
1566        let actual = (!crc32(&sector) & 0xffff) as u16;
1567        let expected = read_u16(tags, index * 2)?;
1568        if actual != expected {
1569            damaged.push(index);
1570        }
1571    }
1572    if damaged.is_empty() {
1573        return Ok(source.to_vec());
1574    }
1575    if damaged.len() > parity_sectors {
1576        return Err(Error::InvalidHeader(
1577            "RAR 3.x recovery damage exceeds parity sector count",
1578        ));
1579    }
1580
1581    let mut used_slots = vec![false; parity_sectors];
1582    for &index in &damaged {
1583        let slot = index % parity_sectors;
1584        if used_slots[slot] {
1585            return Err(Error::InvalidHeader(
1586                "RAR 3.x recovery cannot repair multiple sectors in the same parity group",
1587            ));
1588        }
1589        used_slots[slot] = true;
1590    }
1591
1592    let mut repaired = source.to_vec();
1593    for &missing_index in &damaged {
1594        let slot = missing_index % parity_sectors;
1595        let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1596        for index in (slot..protected_sectors).step_by(parity_sectors) {
1597            if index == missing_index {
1598                continue;
1599            }
1600            let other = protected_sector(&repaired, protected_start, protected_len, index)?;
1601            for (out, byte) in sector.iter_mut().zip(other) {
1602                *out ^= byte;
1603            }
1604        }
1605        let actual = (!crc32(&sector) & 0xffff) as u16;
1606        let expected = u16::from_le_bytes(
1607            tags[missing_index * 2..missing_index * 2 + 2]
1608                .try_into()
1609                .unwrap(),
1610        );
1611        if actual != expected {
1612            return Err(Error::CrcMismatch { expected, actual });
1613        }
1614        let sector_start = protected_start + missing_index * 512;
1615        let write_len = 512.min(protected_end - sector_start);
1616        repaired[sector_start..sector_start + write_len].copy_from_slice(&sector[..write_len]);
1617    }
1618
1619    Ok(repaired)
1620}
1621
1622fn newsub_recovery_data(archive: &Archive, recovery: &NewSubHeader) -> Result<Vec<u8>> {
1623    if recovery.file.is_encrypted() {
1624        return Err(Error::UnsupportedFeature {
1625            version: ArchiveVersion::Rar30,
1626            feature: "encrypted RAR 3.x NEWSUB recovery record",
1627        });
1628    }
1629    if recovery.file.method == 0x30 {
1630        if recovery.file.pack_size != recovery.file.unp_size {
1631            return Err(Error::InvalidHeader(
1632                "RAR 3.x recovery record packed size does not match unpacked size",
1633            ));
1634        }
1635        return recovery.file.stored_data(archive);
1636    }
1637    let mut session = DecoderSession::new(false);
1638    session.decode_file_data(archive, &recovery.file)
1639}
1640
1641fn protected_sector(
1642    source: &[u8],
1643    protected_start: usize,
1644    protected_len: usize,
1645    index: usize,
1646) -> Result<[u8; 512]> {
1647    let sector_offset = index.checked_mul(512).ok_or(Error::InvalidHeader(
1648        "RAR 3.x recovery sector offset overflows",
1649    ))?;
1650    if sector_offset >= protected_len {
1651        return Err(Error::InvalidHeader(
1652            "RAR 3.x recovery sector offset is invalid",
1653        ));
1654    }
1655    let sector_start = protected_start
1656        .checked_add(sector_offset)
1657        .ok_or(Error::InvalidHeader(
1658            "RAR 3.x recovery sector range overflows",
1659        ))?;
1660    let available = 512.min(protected_len - sector_offset);
1661    let mut sector = [0u8; 512];
1662    sector[..available].copy_from_slice(
1663        source
1664            .get(sector_start..sector_start + available)
1665            .ok_or(Error::TooShort)?,
1666    );
1667    Ok(sector)
1668}
1669
1670pub fn repair_rev3_volumes_to<F>(
1671    data_volumes: &[Option<&[u8]>],
1672    recovery_count: usize,
1673    recovery_volumes: &[(usize, &[u8])],
1674    mut write: F,
1675) -> Result<()>
1676where
1677    F: FnMut(usize, &[u8]) -> Result<()>,
1678{
1679    for (index, bytes) in rars_recovery::rar3::reconstruct_data_volumes(
1680        data_volumes,
1681        recovery_count,
1682        recovery_volumes,
1683    )
1684    .map_err(Error::from)?
1685    .into_iter()
1686    .enumerate()
1687    {
1688        let bytes = truncate_repaired_rev3_volume(bytes)?;
1689        write(index, &bytes)?;
1690    }
1691    Ok(())
1692}
1693
1694fn truncate_repaired_rev3_volume(mut bytes: Vec<u8>) -> Result<Vec<u8>> {
1695    let Ok(archive) = Archive::parse(&bytes) else {
1696        return Ok(bytes);
1697    };
1698    let Some(end) = archive.blocks.iter().find_map(|block| match block {
1699        Block::End(end) => Some(end),
1700        _ => None,
1701    }) else {
1702        return Ok(bytes);
1703    };
1704    let end_pos = archive
1705        .sfx_offset
1706        .checked_add(end.offset)
1707        .and_then(|offset| offset.checked_add(block_total_size(end).ok()?))
1708        .ok_or(Error::InvalidHeader(
1709            "RAR 3 repaired volume end offset overflows",
1710        ))?;
1711    if end_pos < bytes.len() && bytes[end_pos..].iter().all(|&byte| byte == 0) {
1712        bytes.truncate(end_pos);
1713    }
1714    Ok(bytes)
1715}
1716
1717struct EncryptedHeader {
1718    block: BlockHeader,
1719    header: Vec<u8>,
1720    total_size: usize,
1721}
1722
1723#[derive(Default)]
1724struct EncryptedHeaderCipherCache {
1725    salt: Option<[u8; 8]>,
1726    cipher: Option<Rar30Cipher>,
1727}
1728
1729impl EncryptedHeaderCipherCache {
1730    fn cipher(&mut self, password: &[u8], salt: [u8; 8]) -> Result<Rar30Cipher> {
1731        if self.salt != Some(salt) {
1732            self.salt = Some(salt);
1733            self.cipher =
1734                Some(Rar30Cipher::new(password, Some(salt)).map_err(map_rar30_crypto_error)?);
1735        }
1736        Ok(self
1737            .cipher
1738            .as_ref()
1739            .expect("RAR 3 encrypted header cipher cache initialized")
1740            .clone())
1741    }
1742}
1743
1744fn map_rar30_crypto_error(error: Rar30Error) -> Error {
1745    Error::from(error)
1746}
1747
1748fn decrypt_encrypted_header_at(
1749    archive: &[u8],
1750    offset: usize,
1751    password: &[u8],
1752    cipher_cache: &mut EncryptedHeaderCipherCache,
1753) -> Result<EncryptedHeader> {
1754    let salt = read_header_salt(archive, offset)?;
1755    let first_ciphertext = archive
1756        .get(offset + 8..offset + 24)
1757        .ok_or(Error::TooShort)?;
1758    let mut cipher = cipher_cache.cipher(password, salt)?;
1759    let mut first_block = [0u8; 16];
1760    first_block.copy_from_slice(first_ciphertext);
1761    cipher
1762        .decrypt_in_place(&mut first_block)
1763        .map_err(map_rar30_crypto_error)?;
1764    let head_size = read_u16(&first_block, 5)? as usize;
1765    if head_size < 7 {
1766        return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1767    }
1768    let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1769    let encrypted_start = offset
1770        .checked_add(8)
1771        .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1772    let encrypted_end = encrypted_start
1773        .checked_add(encrypted_header_size)
1774        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1775    let encrypted_rest = archive
1776        .get(offset + 24..encrypted_end)
1777        .ok_or(Error::TooShort)?;
1778    let mut header = Vec::with_capacity(encrypted_header_size);
1779    header.extend_from_slice(&first_block);
1780    header.extend_from_slice(encrypted_rest);
1781    cipher
1782        .decrypt_in_place(&mut header[16..])
1783        .map_err(map_rar30_crypto_error)?;
1784    header.truncate(head_size);
1785
1786    let mut block = parse_block_header(&header, 0)?;
1787    block.offset = offset;
1788    let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1789        .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1790    let total_size = 8usize
1791        .checked_add(encrypted_header_size)
1792        .and_then(|size| size.checked_add(payload_size))
1793        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1794    Ok(EncryptedHeader {
1795        block,
1796        header,
1797        total_size,
1798    })
1799}
1800
1801fn read_encrypted_header_at(
1802    file: &mut File,
1803    file_len: u64,
1804    archive_offset: usize,
1805    offset: usize,
1806    password: &[u8],
1807    cipher_cache: &mut EncryptedHeaderCipherCache,
1808) -> Result<EncryptedHeader> {
1809    let absolute = archive_offset
1810        .checked_add(offset)
1811        .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1812    if absolute as u64 + 24 > file_len {
1813        return Err(Error::TooShort);
1814    }
1815    let first = read_exact_at(file, absolute, 24)?;
1816    let salt = read_header_salt(&first, 0)?;
1817    let mut cipher = cipher_cache.cipher(password, salt)?;
1818    let mut first_block = [0u8; 16];
1819    first_block.copy_from_slice(&first[8..24]);
1820    cipher
1821        .decrypt_in_place(&mut first_block)
1822        .map_err(map_rar30_crypto_error)?;
1823    let head_size = read_u16(&first_block, 5)? as usize;
1824    if head_size < 7 {
1825        return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1826    }
1827    let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1828    let encrypted_start = absolute
1829        .checked_add(8)
1830        .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1831    if encrypted_start as u64 + encrypted_header_size as u64 > file_len {
1832        return Err(Error::TooShort);
1833    }
1834    let encrypted_rest = read_exact_at(file, encrypted_start + 16, encrypted_header_size - 16)?;
1835    let mut header = Vec::with_capacity(encrypted_header_size);
1836    header.extend_from_slice(&first_block);
1837    header.extend_from_slice(&encrypted_rest);
1838    cipher
1839        .decrypt_in_place(&mut header[16..])
1840        .map_err(map_rar30_crypto_error)?;
1841    header.truncate(head_size);
1842
1843    let mut block = parse_block_header(&header, 0)?;
1844    block.offset = offset;
1845    let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1846        .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1847    let total_size = 8usize
1848        .checked_add(encrypted_header_size)
1849        .and_then(|size| size.checked_add(payload_size))
1850        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1851    Ok(EncryptedHeader {
1852        block,
1853        header,
1854        total_size,
1855    })
1856}
1857
1858fn read_header_salt(input: &[u8], offset: usize) -> Result<[u8; 8]> {
1859    input
1860        .get(offset..offset + 8)
1861        .ok_or(Error::TooShort)
1862        .map(|salt| salt.try_into().expect("RAR 3 salt size"))
1863}
1864
1865fn parse_file_like_header(
1866    input: &[u8],
1867    block: BlockHeader,
1868    archive_offset: usize,
1869) -> Result<FileHeader> {
1870    if block.head_size < 32 {
1871        return Err(Error::InvalidHeader("RAR 1.5 file header is too short"));
1872    }
1873    if block.flags & LONG_BLOCK == 0 {
1874        return Err(Error::InvalidHeader(
1875            "RAR 1.5 file header is missing packed data size",
1876        ));
1877    }
1878
1879    let start = block.offset;
1880    let head_end = start + block.head_size as usize;
1881    if head_end > input.len() {
1882        return Err(Error::TooShort);
1883    }
1884
1885    let pack_low = read_u32(input, start + 7)? as u64;
1886    let unp_low = read_u32(input, start + 11)? as u64;
1887    let host_os = input[start + 15];
1888    let file_crc = read_u32(input, start + 16)?;
1889    let file_time = read_u32(input, start + 20)?;
1890    let unp_ver = input[start + 24];
1891    let method = input[start + 25];
1892    let name_size = read_u16(input, start + 26)? as usize;
1893    let attr = read_u32(input, start + 28)?;
1894    let mut pos = start + 32;
1895
1896    let (pack_size, unp_size) = if block.flags & FHD_LARGE != 0 {
1897        let high_pack = read_u32(input, pos)? as u64;
1898        let high_unp = read_u32(input, pos + 4)? as u64;
1899        pos += 8;
1900        ((high_pack << 32) | pack_low, (high_unp << 32) | unp_low)
1901    } else {
1902        (pack_low, unp_low)
1903    };
1904
1905    let name_end = pos
1906        .checked_add(name_size)
1907        .ok_or(Error::InvalidHeader("RAR 1.5 file name size overflows"))?;
1908    if name_end > head_end {
1909        return Err(Error::InvalidHeader(
1910            "RAR 1.5 file name extends beyond header",
1911        ));
1912    }
1913    let name = decode_file_name(&input[pos..name_end], block.flags);
1914    pos = name_end;
1915
1916    let salt = if block.flags & FHD_SALT != 0 {
1917        let salt_end = pos
1918            .checked_add(8)
1919            .ok_or(Error::InvalidHeader("RAR 1.5 salt size overflows"))?;
1920        if salt_end > head_end {
1921            return Err(Error::InvalidHeader(
1922                "RAR 1.5 salt extends beyond file header",
1923            ));
1924        }
1925        let salt_bytes = input.get(pos..salt_end).ok_or(Error::TooShort)?;
1926        pos = salt_end;
1927        Some(
1928            salt_bytes
1929                .try_into()
1930                .expect("RAR 1.5 salt slice has fixed length"),
1931        )
1932    } else {
1933        None
1934    };
1935
1936    let file_comment = if block.flags & FHD_COMMENT != 0 {
1937        if pos + 2 <= head_end {
1938            let comment_len = read_u16(input, pos)? as usize;
1939            let comment_total = comment_len
1940                .checked_add(2)
1941                .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1942            let comment_end = pos
1943                .checked_add(comment_total)
1944                .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1945            if comment_end <= head_end {
1946                let comment = input[pos..comment_end].to_vec();
1947                pos = comment_end;
1948                comment
1949            } else {
1950                Vec::new()
1951            }
1952        } else {
1953            Vec::new()
1954        }
1955    } else {
1956        Vec::new()
1957    };
1958
1959    let ext_time = if block.flags & FHD_EXTTIME != 0 {
1960        input[pos..head_end].to_vec()
1961    } else {
1962        Vec::new()
1963    };
1964    let data_start = head_end;
1965    let data_len = usize::try_from(pack_size)
1966        .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
1967    let data_end = data_start
1968        .checked_add(data_len)
1969        .ok_or(Error::InvalidHeader(
1970            "RAR 1.5 packed file size overflows usize",
1971        ))?;
1972    Ok(FileHeader {
1973        block,
1974        pack_size,
1975        unp_size,
1976        host_os,
1977        file_crc,
1978        file_time,
1979        unp_ver,
1980        method,
1981        name,
1982        attr,
1983        salt,
1984        file_comment,
1985        ext_time,
1986        packed_range: archive_offset + data_start..archive_offset + data_end,
1987    })
1988}
1989
1990fn decode_file_name(raw: &[u8], flags: u16) -> Vec<u8> {
1991    if flags & FHD_UNICODE == 0 {
1992        return raw.to_vec();
1993    }
1994
1995    let Some(zero_pos) = raw.iter().position(|byte| *byte == 0) else {
1996        return raw.to_vec();
1997    };
1998    if zero_pos + 1 >= raw.len() {
1999        return raw[..zero_pos].to_vec();
2000    }
2001
2002    let fallback = &raw[..zero_pos];
2003    let high_byte = raw[zero_pos + 1];
2004    let encoded = &raw[zero_pos + 2..];
2005    let mut pos = 0usize;
2006    let mut flag_byte = 0u8;
2007    let mut flag_bits = 0u8;
2008    let mut dst_pos = 0usize;
2009    let mut units = Vec::new();
2010
2011    while pos < encoded.len() {
2012        if flag_bits == 0 {
2013            flag_byte = encoded[pos];
2014            pos += 1;
2015            flag_bits = 8;
2016        }
2017        let mode = flag_byte >> 6;
2018        flag_byte <<= 2;
2019        flag_bits -= 2;
2020
2021        match mode {
2022            0 => {
2023                let Some(&low) = encoded.get(pos) else {
2024                    return raw.to_vec();
2025                };
2026                pos += 1;
2027                units.push(u16::from(low));
2028                dst_pos += 1;
2029            }
2030            1 => {
2031                let Some(&low) = encoded.get(pos) else {
2032                    return raw.to_vec();
2033                };
2034                pos += 1;
2035                units.push((u16::from(high_byte) << 8) | u16::from(low));
2036                dst_pos += 1;
2037            }
2038            2 => {
2039                let Some((&low, &high)) = encoded.get(pos).zip(encoded.get(pos + 1)) else {
2040                    return raw.to_vec();
2041                };
2042                pos += 2;
2043                units.push((u16::from(high) << 8) | u16::from(low));
2044                dst_pos += 1;
2045            }
2046            3 => {
2047                let Some(&length_byte) = encoded.get(pos) else {
2048                    return raw.to_vec();
2049                };
2050                pos += 1;
2051                let (count, correction, high) = if length_byte & 0x80 != 0 {
2052                    let Some(&correction) = encoded.get(pos) else {
2053                        return raw.to_vec();
2054                    };
2055                    pos += 1;
2056                    ((length_byte & 0x7f) as usize + 2, correction, high_byte)
2057                } else {
2058                    (length_byte as usize + 2, 0, 0)
2059                };
2060                for _ in 0..count {
2061                    let low = fallback
2062                        .get(dst_pos)
2063                        .copied()
2064                        .unwrap_or(b'?')
2065                        .wrapping_add(correction);
2066                    units.push((u16::from(high) << 8) | u16::from(low));
2067                    dst_pos += 1;
2068                }
2069            }
2070            _ => unreachable!("2-bit filename mode"),
2071        }
2072    }
2073
2074    char::decode_utf16(units)
2075        .map(|unit| unit.unwrap_or(char::REPLACEMENT_CHARACTER))
2076        .collect::<String>()
2077        .into_bytes()
2078}
2079
2080fn read_block_header_at(
2081    file: &mut File,
2082    file_len: u64,
2083    archive_offset: usize,
2084    offset: usize,
2085) -> Result<BlockHeader> {
2086    let absolute = archive_offset
2087        .checked_add(offset)
2088        .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
2089    if absolute as u64 + 7 > file_len {
2090        return Err(Error::TooShort);
2091    }
2092    let base = read_exact_at(file, absolute, 7)?;
2093    let head_size = read_u16(&base, 5)? as usize;
2094    if head_size < 7 {
2095        return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2096    }
2097    if absolute as u64 + head_size as u64 > file_len {
2098        return Err(Error::TooShort);
2099    }
2100    let header = if head_size == 7 {
2101        base
2102    } else {
2103        read_exact_at(file, absolute, head_size)?
2104    };
2105    let mut block = parse_block_header(&header, 0)?;
2106    block.offset = offset;
2107    Ok(block)
2108}
2109
2110fn relative_block(block: &BlockHeader) -> BlockHeader {
2111    let mut relative = block.clone();
2112    relative.offset = 0;
2113    relative
2114}
2115
2116struct CrcWriter<'a, W: Write + ?Sized> {
2117    inner: &'a mut W,
2118    crc: &'a mut Crc32,
2119}
2120
2121impl<W: Write + ?Sized> Write for CrcWriter<'_, W> {
2122    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2123        let written = self.inner.write(buf)?;
2124        self.crc.update(&buf[..written]);
2125        Ok(written)
2126    }
2127
2128    fn flush(&mut self) -> std::io::Result<()> {
2129        self.inner.flush()
2130    }
2131}
2132
2133fn parse_block_header(input: &[u8], offset: usize) -> Result<BlockHeader> {
2134    if input.len() < offset + 7 {
2135        return Err(Error::TooShort);
2136    }
2137    let head_crc = read_u16(input, offset)?;
2138    let head_type = input[offset + 2];
2139    let flags = read_u16(input, offset + 3)?;
2140    let head_size = read_u16(input, offset + 5)?;
2141    if head_size < 7 {
2142        return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2143    }
2144    let add_size = if flags & LONG_BLOCK != 0 {
2145        Some(read_u32(input, offset + 7)? as u64)
2146    } else {
2147        None
2148    };
2149    if offset + head_size as usize > input.len() {
2150        return Err(Error::TooShort);
2151    }
2152    if head_type != MARK_HEAD && should_validate_header_crc(head_type) {
2153        let header_end = header_crc_end(input, offset, head_type, flags, head_size)?;
2154        let actual = (crc32(&input[offset + 2..header_end]) & 0xffff) as u16;
2155        if actual != head_crc {
2156            return Err(Error::CrcMismatch {
2157                expected: head_crc,
2158                actual,
2159            });
2160        }
2161    }
2162    validate_legacy_auth_block_size(head_type, head_size)?;
2163
2164    Ok(BlockHeader {
2165        head_crc,
2166        head_type,
2167        flags,
2168        head_size,
2169        add_size,
2170        offset,
2171    })
2172}
2173
2174fn header_crc_end(
2175    input: &[u8],
2176    offset: usize,
2177    head_type: u8,
2178    flags: u16,
2179    head_size: u16,
2180) -> Result<usize> {
2181    let full_end = offset + head_size as usize;
2182    let fixed_end = match head_type {
2183        MAIN_HEAD if flags & MHD_COMMENT != 0 => Some(offset + 13),
2184        COMM_HEAD => Some(offset + 13),
2185        FILE_HEAD if flags & FHD_COMMENT != 0 => Some(file_header_comment_crc_end(input, offset)?),
2186        _ => None,
2187    };
2188    Ok(fixed_end.unwrap_or(full_end).min(full_end))
2189}
2190
2191fn file_header_comment_crc_end(input: &[u8], offset: usize) -> Result<usize> {
2192    if input.len() < offset + 32 {
2193        return Err(Error::TooShort);
2194    }
2195    let flags = read_u16(input, offset + 3)?;
2196    let name_size = read_u16(input, offset + 26)? as usize;
2197    let mut end = offset + 32;
2198    if flags & FHD_LARGE != 0 {
2199        end = end
2200            .checked_add(8)
2201            .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2202    }
2203    end = end
2204        .checked_add(name_size)
2205        .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2206    if flags & FHD_SALT != 0 {
2207        end = end
2208            .checked_add(8)
2209            .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2210    }
2211    Ok(end)
2212}
2213
2214fn should_validate_header_crc(head_type: u8) -> bool {
2215    // Historical AV/SIGN blocks are documented with inconsistent CRC fields in
2216    // real archives, so readers must not reject them solely on HEAD_CRC.
2217    !matches!(head_type, 0x76 | 0x79)
2218}
2219
2220fn validate_legacy_auth_block_size(head_type: u8, head_size: u16) -> Result<()> {
2221    let minimum = match head_type {
2222        0x76 => 21,
2223        0x79 => 182,
2224        _ => return Ok(()),
2225    };
2226    if head_size < minimum {
2227        return Err(Error::InvalidHeader(
2228            "RAR legacy authenticity block is too short",
2229        ));
2230    }
2231    Ok(())
2232}
2233
2234fn block_total_size(block: &BlockHeader) -> Result<usize> {
2235    let total = block.head_size as u64 + block.add_size.unwrap_or(0);
2236    usize::try_from(total).map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2237}
2238
2239fn file_block_total_size(
2240    block: &BlockHeader,
2241    default_total: usize,
2242    pack_size: u64,
2243) -> Result<usize> {
2244    let low_payload_size = usize::try_from(block.add_size.unwrap_or(0))
2245        .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2246    let header_prefix = default_total
2247        .checked_sub(low_payload_size)
2248        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2249    let pack_size = usize::try_from(pack_size)
2250        .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2251    header_prefix
2252        .checked_add(pack_size)
2253        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2254}
2255
2256fn checked_block_next(block: &BlockHeader, total: usize, archive_len: usize) -> Result<usize> {
2257    let next = block
2258        .offset
2259        .checked_add(total)
2260        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2261    if next > archive_len {
2262        return Err(Error::TooShort);
2263    }
2264    Ok(next)
2265}
2266
2267fn checked_file_block_next(
2268    sfx_offset: usize,
2269    block: &BlockHeader,
2270    total: usize,
2271    file_len: u64,
2272) -> Result<usize> {
2273    let next = block
2274        .offset
2275        .checked_add(total)
2276        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2277    let absolute_next = sfx_offset
2278        .checked_add(next)
2279        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2280    if absolute_next as u64 > file_len {
2281        return Err(Error::TooShort);
2282    }
2283    Ok(next)
2284}
2285
2286fn packed_range(
2287    archive_offset: usize,
2288    block_offset: usize,
2289    total: usize,
2290    pack_size: u64,
2291) -> Result<Range<usize>> {
2292    let pack_size = usize::try_from(pack_size)
2293        .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2294    let block_end = archive_offset
2295        .checked_add(block_offset)
2296        .and_then(|start| start.checked_add(total))
2297        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2298    let block_start = block_end
2299        .checked_sub(pack_size)
2300        .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2301    Ok(block_start..block_end)
2302}
2303
2304#[cfg(test)]
2305mod tests {
2306    use super::*;
2307
2308    fn test_write_main_header(out: &mut Vec<u8>, flags: u16) {
2309        let start = out.len();
2310        out.extend_from_slice(&0u16.to_le_bytes());
2311        out.push(MAIN_HEAD);
2312        out.extend_from_slice(&flags.to_le_bytes());
2313        out.extend_from_slice(&13u16.to_le_bytes());
2314        out.extend_from_slice(&0u16.to_le_bytes());
2315        out.extend_from_slice(&0u32.to_le_bytes());
2316        test_write_header_crc(out, start);
2317    }
2318
2319    fn test_write_header_crc(out: &mut [u8], start: usize) {
2320        let crc = (crc32(&out[start + 2..]) & 0xffff) as u16;
2321        out[start..start + 2].copy_from_slice(&crc.to_le_bytes());
2322    }
2323
2324    fn legacy_auth_block(head_type: u8, head_size: u16) -> Vec<u8> {
2325        let mut block = vec![0; head_size as usize];
2326        block[2] = head_type;
2327        block[5..7].copy_from_slice(&head_size.to_le_bytes());
2328        block
2329    }
2330
2331    #[test]
2332    fn rejects_too_short_legacy_auth_blocks_even_without_crc_check() {
2333        assert!(matches!(
2334            parse_block_header(&legacy_auth_block(0x76, 20), 0),
2335            Err(Error::InvalidHeader(
2336                "RAR legacy authenticity block is too short"
2337            ))
2338        ));
2339        assert!(matches!(
2340            parse_block_header(&legacy_auth_block(0x79, 181), 0),
2341            Err(Error::InvalidHeader(
2342                "RAR legacy authenticity block is too short"
2343            ))
2344        ));
2345    }
2346
2347    #[test]
2348    fn accepts_minimum_legacy_auth_block_sizes_with_bad_crc() {
2349        assert_eq!(
2350            parse_block_header(&legacy_auth_block(0x76, 21), 0)
2351                .unwrap()
2352                .head_size,
2353            21
2354        );
2355        assert_eq!(
2356            parse_block_header(&legacy_auth_block(0x79, 182), 0)
2357                .unwrap()
2358                .head_size,
2359            182
2360        );
2361    }
2362
2363    #[test]
2364    fn parses_fhd_large_high_size_fields_from_file_header() {
2365        let name = b"large.bin";
2366        let head_size = 32 + 8 + name.len();
2367        let mut header = Vec::new();
2368        header.extend_from_slice(&0u16.to_le_bytes());
2369        header.push(FILE_HEAD);
2370        header.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2371        header.extend_from_slice(&(head_size as u16).to_le_bytes());
2372        header.extend_from_slice(&0x89ab_cdefu32.to_le_bytes());
2373        header.extend_from_slice(&0x7654_3210u32.to_le_bytes());
2374        header.push(3);
2375        header.extend_from_slice(&0x1234_5678u32.to_le_bytes());
2376        header.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2377        header.push(29);
2378        header.push(0x35);
2379        header.extend_from_slice(&(name.len() as u16).to_le_bytes());
2380        header.extend_from_slice(&0x20u32.to_le_bytes());
2381        header.extend_from_slice(&1u32.to_le_bytes());
2382        header.extend_from_slice(&2u32.to_le_bytes());
2383        header.extend_from_slice(name);
2384
2385        let block = BlockHeader {
2386            head_crc: 0,
2387            head_type: FILE_HEAD,
2388            flags: LONG_BLOCK | FHD_LARGE,
2389            head_size: head_size as u16,
2390            add_size: Some(0x89ab_cdef),
2391            offset: 0,
2392        };
2393        let file = parse_file_like_header(&header, block, 0).unwrap();
2394
2395        assert_eq!(file.pack_size, 0x0000_0001_89ab_cdef);
2396        assert_eq!(file.unp_size, 0x0000_0002_7654_3210);
2397        assert_eq!(file.name, name);
2398        assert_eq!(
2399            file.packed_range,
2400            head_size..head_size + 0x0000_0001_89ab_cdefusize
2401        );
2402    }
2403
2404    #[test]
2405    fn fhd_large_archive_extent_uses_high_packed_size_without_underflowing() {
2406        let name = b"large-zero-low.bin";
2407        let head_size = 32 + 8 + name.len();
2408        let mut archive = Vec::from(RAR15_SIGNATURE);
2409        test_write_main_header(&mut archive, 0);
2410
2411        let start = archive.len();
2412        archive.extend_from_slice(&0u16.to_le_bytes());
2413        archive.push(FILE_HEAD);
2414        archive.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2415        archive.extend_from_slice(&(head_size as u16).to_le_bytes());
2416        archive.extend_from_slice(&0u32.to_le_bytes());
2417        archive.extend_from_slice(&0u32.to_le_bytes());
2418        archive.push(3);
2419        archive.extend_from_slice(&0u32.to_le_bytes());
2420        archive.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2421        archive.push(29);
2422        archive.push(0x35);
2423        archive.extend_from_slice(&(name.len() as u16).to_le_bytes());
2424        archive.extend_from_slice(&0x20u32.to_le_bytes());
2425        archive.extend_from_slice(&1u32.to_le_bytes());
2426        archive.extend_from_slice(&1u32.to_le_bytes());
2427        archive.extend_from_slice(name);
2428        test_write_header_crc(&mut archive, start);
2429
2430        assert!(matches!(Archive::parse(&archive), Err(Error::TooShort)));
2431    }
2432
2433    fn block_header_with(flags: u16) -> BlockHeader {
2434        BlockHeader {
2435            head_crc: 0,
2436            head_type: FILE_HEAD,
2437            flags,
2438            head_size: 32,
2439            add_size: None,
2440            offset: 0,
2441        }
2442    }
2443
2444    fn file_header_with(flags: u16) -> FileHeader {
2445        FileHeader {
2446            block: block_header_with(flags),
2447            pack_size: 0,
2448            unp_size: 0,
2449            host_os: 0,
2450            file_crc: 0,
2451            file_time: 0,
2452            unp_ver: 29,
2453            method: 0x30,
2454            name: b"entry".to_vec(),
2455            attr: 0,
2456            salt: None,
2457            file_comment: Vec::new(),
2458            ext_time: Vec::new(),
2459            packed_range: 0..0,
2460        }
2461    }
2462
2463    fn main_header_with(flags: u16) -> MainHeader {
2464        MainHeader {
2465            head_crc: 0,
2466            flags,
2467            head_size: 13,
2468            reserved1: 0,
2469            reserved2: 0,
2470            encrypt_version: None,
2471        }
2472    }
2473
2474    #[test]
2475    fn main_header_predicates_match_each_flag_bit() {
2476        let cases = [
2477            (MHD_VOLUME, MainHeader::is_volume as fn(&MainHeader) -> bool),
2478            (MHD_COMMENT, MainHeader::has_archive_comment),
2479            (MHD_SOLID, MainHeader::is_solid),
2480            (MHD_NEWNUMBERING, MainHeader::uses_new_numbering),
2481            (MHD_PROTECT, MainHeader::has_recovery_record),
2482            (MHD_PASSWORD, MainHeader::has_encrypted_headers),
2483            (MHD_FIRSTVOLUME, MainHeader::is_first_volume),
2484        ];
2485        let zero = main_header_with(0);
2486        for (bit, predicate) in cases {
2487            assert!(
2488                !predicate(&zero),
2489                "expected false when bit {bit:#x} is clear"
2490            );
2491            let one = main_header_with(bit);
2492            assert!(predicate(&one), "expected true when bit {bit:#x} is set");
2493        }
2494    }
2495
2496    #[test]
2497    fn file_header_flag_predicates_track_each_bit() {
2498        let cases = [
2499            (
2500                FHD_SPLIT_BEFORE,
2501                FileHeader::is_split_before as fn(&FileHeader) -> bool,
2502            ),
2503            (FHD_SPLIT_AFTER, FileHeader::is_split_after),
2504            (FHD_PASSWORD, FileHeader::is_encrypted),
2505            (FHD_SOLID, FileHeader::is_solid),
2506            (FHD_EXTTIME, FileHeader::has_ext_time),
2507        ];
2508        let zero = file_header_with(0);
2509        for (bit, predicate) in cases {
2510            assert!(
2511                !predicate(&zero),
2512                "expected false on FileHeader for bit {bit:#x}"
2513            );
2514            let one = file_header_with(bit);
2515            assert!(
2516                predicate(&one),
2517                "expected true on FileHeader for bit {bit:#x}"
2518            );
2519        }
2520
2521        let directory = file_header_with(FHD_DIRECTORY_MASK);
2522        assert!(directory.is_directory());
2523        assert!(!file_header_with(0).is_directory());
2524
2525        let stored = file_header_with(0);
2526        assert!(stored.is_stored());
2527        let mut packed = file_header_with(0);
2528        packed.method = 0x33;
2529        assert!(!packed.is_stored());
2530    }
2531
2532    #[test]
2533    fn file_header_comment_extracts_payload_after_two_byte_size_prefix() {
2534        let mut without_flag = file_header_with(0);
2535        without_flag.file_comment = vec![3, 0, b'a', b'b', b'c'];
2536        assert!(!without_flag.has_file_comment());
2537        assert_eq!(without_flag.file_comment().unwrap(), None);
2538
2539        let mut with_flag = file_header_with(FHD_COMMENT);
2540        with_flag.file_comment = vec![3, 0, b'h', b'e', b'y'];
2541        assert!(with_flag.has_file_comment());
2542        assert_eq!(with_flag.file_comment().unwrap().unwrap(), b"hey");
2543
2544        let mut empty_flagged = file_header_with(FHD_COMMENT);
2545        empty_flagged.file_comment.clear();
2546        assert!(!empty_flagged.has_file_comment());
2547
2548        let mut truncated = file_header_with(FHD_COMMENT);
2549        truncated.file_comment = vec![10, 0, b'a'];
2550        assert!(matches!(truncated.file_comment(), Err(Error::TooShort)));
2551    }
2552
2553    #[test]
2554    fn file_header_name_metadata_and_crc_helpers_describe_entry() {
2555        let mut header = file_header_with(0);
2556        header.name = b"r\xc3\xa9sum\xc3\xa9.txt".to_vec();
2557        header.file_crc = crc32(b"hello");
2558        header.attr = 0x20;
2559        header.host_os = 3;
2560        header.file_time = 0x5a21_0000;
2561
2562        assert_eq!(header.name_bytes(), b"r\xc3\xa9sum\xc3\xa9.txt");
2563        assert_eq!(header.name_lossy(), "résumé.txt");
2564        let meta = header.metadata();
2565        assert_eq!(meta.name, header.name);
2566        assert_eq!(meta.attr, 0x20);
2567        assert_eq!(meta.host_os, 3);
2568        assert_eq!(meta.file_time, 0x5a21_0000);
2569        assert!(!meta.is_directory);
2570
2571        header.verify_crc32(b"hello").unwrap();
2572        match header.verify_crc32(b"different") {
2573            Err(Error::Crc32Mismatch { expected, actual }) => {
2574                assert_eq!(expected, header.file_crc);
2575                assert_ne!(actual, expected);
2576            }
2577            other => panic!("expected Crc32Mismatch, got {other:?}"),
2578        }
2579
2580        let directory = file_header_with(FHD_DIRECTORY_MASK);
2581        assert!(directory.metadata().is_directory);
2582
2583        // Garbage bytes still produce a String through lossy decoding.
2584        let mut garbage = file_header_with(0);
2585        garbage.name = vec![0xff, 0xfe, b'/', 0x80, b'x'];
2586        let lossy = garbage.name_lossy();
2587        assert!(lossy.ends_with("/\u{fffd}x"), "got {lossy:?}");
2588    }
2589
2590    #[test]
2591    fn newsub_header_name_lossy_delegates_to_inner_file_header() {
2592        let mut file = file_header_with(0);
2593        file.name = b"CMT".to_vec();
2594        let sub = NewSubHeader {
2595            file,
2596            kind: NewSubKind::ArchiveComment,
2597        };
2598        assert_eq!(sub.name_lossy(), "CMT");
2599    }
2600
2601    #[test]
2602    fn writer_options_constructor_and_default_match_documented_targets() {
2603        let default = WriterOptions::default();
2604        assert_eq!(default.target, ArchiveVersion::Rar15);
2605        assert_eq!(default.features, FeatureSet::store_only());
2606
2607        let explicit = WriterOptions::new(ArchiveVersion::Rar20, FeatureSet::store_only());
2608        assert_eq!(explicit.target, ArchiveVersion::Rar20);
2609        assert_eq!(explicit.features, FeatureSet::store_only());
2610    }
2611
2612    fn stored_archive_bytes(name: &[u8], data: &[u8]) -> Vec<u8> {
2613        write_stored_archive(
2614            &[StoredEntry {
2615                name,
2616                data,
2617                file_time: 0,
2618                file_attr: 0x20,
2619                host_os: 3,
2620                password: None,
2621                file_comment: None,
2622            }],
2623            WriterOptions::default(),
2624        )
2625        .unwrap()
2626    }
2627
2628    #[test]
2629    fn archive_parse_owned_consumes_buffer_without_changing_dispatch() {
2630        let bytes = stored_archive_bytes(b"owned.txt", b"hello rar15 owned");
2631        let archive = Archive::parse_owned(bytes.clone()).unwrap();
2632        assert_eq!(archive.files().count(), 1);
2633        let file = archive.files().next().unwrap();
2634        assert_eq!(file.name, b"owned.txt");
2635
2636        // Default ArchiveReadOptions delegate also drives the same code path.
2637        let with_options =
2638            Archive::parse_owned_with_options(bytes.clone(), crate::ArchiveReadOptions::default())
2639                .unwrap();
2640        assert_eq!(with_options.files().count(), 1);
2641
2642        let no_password = Archive::parse_owned_with_password(bytes, None).unwrap();
2643        assert_eq!(no_password.files().count(), 1);
2644    }
2645
2646    #[test]
2647    fn archive_parse_owned_with_password_unlocks_encrypted_archive() {
2648        let mut features = FeatureSet::store_only();
2649        features.file_encryption = true;
2650        let bytes = write_stored_archive(
2651            &[StoredEntry {
2652                name: b"locked.txt",
2653                data: b"encrypted owned payload",
2654                file_time: 0,
2655                file_attr: 0x20,
2656                host_os: 3,
2657                password: Some(b"pw"),
2658                file_comment: None,
2659            }],
2660            WriterOptions {
2661                target: ArchiveVersion::Rar20,
2662                features,
2663                compression_level: None,
2664                dictionary_size: None,
2665            },
2666        )
2667        .unwrap();
2668
2669        let archive = Archive::parse_owned_with_password(bytes, Some(b"pw")).unwrap();
2670        let file = archive.files().next().unwrap();
2671        assert!(file.is_encrypted());
2672    }
2673
2674    #[test]
2675    fn file_header_write_packed_data_streams_through_writer() {
2676        let payload = b"write_packed_data direct dump";
2677        let bytes = stored_archive_bytes(b"dump.bin", payload);
2678        let archive = Archive::parse(&bytes).unwrap();
2679        let file = archive.files().next().unwrap();
2680
2681        let mut sink = Vec::new();
2682        file.write_packed_data(&archive, &mut sink).unwrap();
2683        assert_eq!(sink, payload);
2684    }
2685
2686    #[test]
2687    fn file_header_unsupported_compression_describes_method_and_unpack_version() {
2688        let mut header = file_header_with(0);
2689        header.method = 0x33;
2690        header.unp_ver = 26;
2691        let err = header.unsupported_compression();
2692        assert!(matches!(
2693            err,
2694            Error::UnsupportedCompression {
2695                family: "RAR 1.5-4.x",
2696                unpack_version: 26,
2697                method: 0x33,
2698            }
2699        ));
2700    }
2701
2702    #[test]
2703    fn file_header_unsupported_encryption_describes_unpack_version() {
2704        let mut header = file_header_with(FHD_PASSWORD);
2705        header.unp_ver = 36;
2706        let err = header.unsupported_encryption();
2707        assert!(matches!(
2708            err,
2709            Error::UnsupportedEncryption {
2710                family: "RAR 1.5-4.x",
2711                unpack_version: 36,
2712            }
2713        ));
2714    }
2715
2716    #[test]
2717    fn crc_writer_flush_propagates_to_inner_writer() {
2718        struct FlushSpy {
2719            data: Vec<u8>,
2720            flushed: usize,
2721        }
2722        impl Write for FlushSpy {
2723            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2724                self.data.extend_from_slice(buf);
2725                Ok(buf.len())
2726            }
2727            fn flush(&mut self) -> std::io::Result<()> {
2728                self.flushed += 1;
2729                Ok(())
2730            }
2731        }
2732        let mut inner = FlushSpy {
2733            data: Vec::new(),
2734            flushed: 0,
2735        };
2736        let mut crc = Crc32::new();
2737        let mut writer = CrcWriter {
2738            inner: &mut inner,
2739            crc: &mut crc,
2740        };
2741        writer.write_all(b"hi").unwrap();
2742        writer.flush().unwrap();
2743        assert_eq!(inner.data, b"hi");
2744        assert_eq!(inner.flushed, 1);
2745    }
2746
2747    #[test]
2748    fn parse_main_header_rejects_block_size_below_minimum() {
2749        let block = BlockHeader {
2750            head_crc: 0,
2751            head_type: MAIN_HEAD,
2752            flags: 0,
2753            head_size: 12,
2754            add_size: None,
2755            offset: 0,
2756        };
2757        let err = parse_main_header(&[0u8; 32], &block).unwrap_err();
2758        assert_eq!(
2759            err,
2760            Error::InvalidHeader("RAR 1.5 main header is too short")
2761        );
2762    }
2763
2764    #[test]
2765    fn parse_main_header_rejects_block_extending_past_input_buffer() {
2766        let block = BlockHeader {
2767            head_crc: 0,
2768            head_type: MAIN_HEAD,
2769            flags: 0,
2770            head_size: 13,
2771            add_size: None,
2772            offset: 8,
2773        };
2774        // Buffer is shorter than offset + head_size.
2775        let err = parse_main_header(&[0u8; 16], &block).unwrap_err();
2776        assert_eq!(err, Error::TooShort);
2777    }
2778
2779    #[test]
2780    fn parse_main_header_reads_encrypt_version_when_flag_is_set() {
2781        // Build a synthetic main header at offset 0: 13 bytes of base + 1 byte
2782        // for the encrypt_version field at position 13.
2783        let mut input = vec![0u8; 14];
2784        input[13] = 0x29;
2785        let block = BlockHeader {
2786            head_crc: 0,
2787            head_type: MAIN_HEAD,
2788            flags: MHD_ENCRYPTVER,
2789            head_size: 14,
2790            add_size: None,
2791            offset: 0,
2792        };
2793        let main = parse_main_header(&input, &block).unwrap();
2794        assert_eq!(main.encrypt_version, Some(0x29));
2795    }
2796
2797    #[test]
2798    fn decode_file_name_returns_raw_when_unicode_flag_is_clear() {
2799        let raw = b"plain.txt";
2800        let decoded = decode_file_name(raw, 0);
2801        assert_eq!(decoded, raw);
2802    }
2803
2804    #[test]
2805    fn decode_file_name_returns_raw_when_unicode_marker_is_missing() {
2806        // FHD_UNICODE set but no zero byte to split fallback from encoded
2807        // payload — the decoder falls back to the raw bytes.
2808        let raw = b"no-zero-marker";
2809        let decoded = decode_file_name(raw, FHD_UNICODE);
2810        assert_eq!(decoded, raw);
2811    }
2812
2813    #[test]
2814    fn decode_file_name_returns_fallback_when_no_encoded_payload_follows_zero() {
2815        // Zero byte present but nothing after it — the decoder returns just
2816        // the fallback prefix.
2817        let mut raw = b"fallback".to_vec();
2818        raw.push(0);
2819        let decoded = decode_file_name(&raw, FHD_UNICODE);
2820        assert_eq!(decoded, b"fallback");
2821    }
2822
2823    #[test]
2824    fn decode_file_name_decodes_mode_zero_low_byte_only_units() {
2825        // Mode 0 emits one ASCII byte per code unit. Encoded payload format:
2826        //   high_byte, flag_byte, byte0, byte1, ...
2827        // flag_byte = 0b00_00_00_00 → four mode-0 emits.
2828        let mut raw = b"orig".to_vec();
2829        raw.push(0); // separator
2830        raw.push(0); // high_byte (unused for mode 0)
2831        raw.push(0b00_00_00_00); // flag_byte: four mode-0 codes
2832        raw.extend_from_slice(b"abcd");
2833        let decoded = decode_file_name(&raw, FHD_UNICODE);
2834        assert_eq!(decoded, b"abcd");
2835    }
2836
2837    #[test]
2838    fn decode_file_name_decodes_mode_two_full_two_byte_units() {
2839        // Mode 2 reads (low, high) bytes for a full UTF-16 code unit.
2840        // Encode "Hi" via mode 2 twice: flag_byte = 0b10_10_00_00.
2841        let mut raw = b"".to_vec();
2842        raw.push(0); // separator (zero_pos = 0)
2843        raw.push(0); // high_byte placeholder
2844                     // Two mode-2 units, then two mode-0 units which we will not reach.
2845        raw.push(0b10_10_00_00);
2846        // First unit 'H' = U+0048: low=0x48, high=0x00
2847        raw.extend_from_slice(&[0x48, 0x00]);
2848        // Second unit 'i' = U+0069
2849        raw.extend_from_slice(&[0x69, 0x00]);
2850        let decoded = decode_file_name(&raw, FHD_UNICODE);
2851        // The decoder may emit further units from the trailing flag bits;
2852        // accept any output that begins with "Hi".
2853        assert!(decoded.starts_with(b"Hi"), "got {decoded:?}");
2854    }
2855}