Skip to main content

rars_format/rar50/
extract.rs

1use super::{blake2sp, Archive, ExtractedEntryMeta, FileHeader};
2use crate::error::{Error, Result};
3use crate::volume_extract::{ChainedReader, SplitVolumeState, SplitVolumeStep};
4use rars_codec::rar50::{DecodeMode, DecodedChunk, StreamDecodeError, Unpack50Decoder};
5use rars_crc32::{crc32, Crc32};
6use rars_crypto::rar50::{Rar50Cipher, Rar50Keys};
7use std::io::{Read, Write};
8
9// Filtered RAR5 members still need whole-member byte transforms. Members at or
10// below this boundary use the buffered path, while larger members stream once
11// and reject filtered streams through the codec's typed sentinel.
12#[cfg(not(test))]
13const BUFFERED_DECODE_LIMIT: u64 = 512 * 1024 * 1024;
14#[cfg(test)]
15const BUFFERED_DECODE_LIMIT: u64 = 1024;
16
17impl FileHeader {
18    fn crypto_with_password(&self, password: Option<&[u8]>) -> Result<Option<Rar50Keys>> {
19        if !self.encrypted {
20            return Ok(None);
21        }
22        if let Some(crypto) = &self.crypto {
23            return Ok(Some(crypto.keys.clone()));
24        }
25        let password = password.ok_or(Error::NeedPassword)?;
26        let encryption = self.encryption.as_ref().ok_or(Error::InvalidHeader(
27            "RAR 5 encrypted file is missing encryption record",
28        ))?;
29        if encryption.version != 0 {
30            return Err(Error::UnsupportedFeature {
31                version: crate::version::ArchiveVersion::Rar50,
32                feature: "RAR 5 unknown file encryption version",
33            });
34        }
35        let keys = Rar50Keys::derive(password, encryption.salt, encryption.kdf_count)
36            .map_err(super::map_rar50_crypto_error)?;
37        if let Some(check_value) = encryption.check_value {
38            keys.check_password(&check_value)
39                .map_err(super::map_rar50_crypto_error)?;
40        }
41        Ok(Some(keys))
42    }
43
44    fn encryption_iv(&self) -> Result<[u8; 16]> {
45        if let Some(crypto) = &self.crypto {
46            return Ok(crypto.iv);
47        }
48        self.encryption
49            .as_ref()
50            .map(|encryption| encryption.iv)
51            .ok_or(Error::InvalidHeader(
52                "RAR 5 encrypted file is missing encryption record",
53            ))
54    }
55
56    fn packed_data_with_password(
57        &self,
58        archive: &Archive,
59        password: Option<&[u8]>,
60    ) -> Result<(Vec<u8>, Option<Rar50Keys>)> {
61        let (mut reader, keys) = self.packed_reader_with_password(archive, password)?;
62        let mut packed = Vec::new();
63        reader.read_to_end(&mut packed)?;
64        Ok((packed, keys))
65    }
66
67    fn packed_reader_with_password<'a>(
68        &self,
69        archive: &'a Archive,
70        password: Option<&[u8]>,
71    ) -> Result<(Box<dyn Read + 'a>, Option<Rar50Keys>)> {
72        let reader = archive.range_reader(self.block.data_range.clone())?;
73        if !self.encrypted {
74            return Ok((reader, None));
75        }
76        if !self.packed_size().is_multiple_of(16) {
77            return Err(Error::InvalidHeader(
78                "RAR 5 encrypted file payload is not block aligned",
79            ));
80        }
81        let keys = self
82            .crypto_with_password(password)?
83            .ok_or(Error::InvalidHeader(
84                "RAR 5 encrypted file is missing encryption keys",
85            ))?;
86        let reader = Rar50DecryptingReader::new(reader, keys.key, self.encryption_iv()?);
87        Ok((Box::new(reader), Some(keys)))
88    }
89
90    fn verify_integrity_with_keys(&self, data: &[u8], keys: Option<&Rar50Keys>) -> Result<()> {
91        if let Some(expected) = self.data_crc32 {
92            let actual = crc32(data);
93            let actual = if self.uses_hash_mac() {
94                let keys = keys.ok_or(Error::InvalidHeader(
95                    "RAR 5 encrypted hash MAC needs encryption keys",
96                ))?;
97                keys.mac_crc32(actual)
98            } else {
99                actual
100            };
101            if actual != expected {
102                return Err(Error::Crc32Mismatch { expected, actual });
103            }
104        }
105
106        let Some(hash) = &self.hash else {
107            return Ok(());
108        };
109        match hash.hash_type {
110            0 if hash.data.len() == 32 => {
111                let actual = blake2sp::hash(data);
112                let actual = if self.uses_hash_mac() {
113                    let keys = keys.ok_or(Error::InvalidHeader(
114                        "RAR 5 encrypted hash MAC needs encryption keys",
115                    ))?;
116                    keys.mac_hash32(actual)
117                } else {
118                    actual
119                };
120                if constant_time_eq(&hash.data, &actual) {
121                    Ok(())
122                } else {
123                    Err(Error::HashMismatch { hash_type: 0 })
124                }
125            }
126            0 => Err(Error::InvalidHeader(
127                "RAR 5 BLAKE2sp hash record has invalid length",
128            )),
129            _ => Ok(()),
130        }
131    }
132
133    fn verify_streaming_integrity(
134        &self,
135        crc: Crc32,
136        hash: Option<([u8; 32], blake2sp::Hasher)>,
137        keys: Option<&Rar50Keys>,
138    ) -> Result<()> {
139        if let Some(expected) = self.data_crc32 {
140            let actual = if self.uses_hash_mac() {
141                let keys = keys.ok_or(Error::InvalidHeader(
142                    "RAR 5 encrypted hash MAC needs encryption keys",
143                ))?;
144                keys.mac_crc32(crc.finish())
145            } else {
146                crc.finish()
147            };
148            if actual != expected {
149                return Err(Error::Crc32Mismatch { expected, actual });
150            }
151        }
152
153        if let Some((expected, hasher)) = hash {
154            let actual = if self.uses_hash_mac() {
155                let keys = keys.ok_or(Error::InvalidHeader(
156                    "RAR 5 encrypted hash MAC needs encryption keys",
157                ))?;
158                keys.mac_hash32(hasher.finalize())
159            } else {
160                hasher.finalize()
161            };
162            if !constant_time_eq(&expected, &actual) {
163                return Err(Error::HashMismatch { hash_type: 0 });
164            }
165        }
166        Ok(())
167    }
168
169    pub fn metadata(&self) -> ExtractedEntryMeta {
170        ExtractedEntryMeta {
171            name: self.name.clone(),
172            file_time: self.mtime.unwrap_or(0),
173            attr: self.attributes,
174            host_os: self.host_os,
175            is_directory: self.is_directory(),
176        }
177    }
178
179    pub fn write_to(
180        &self,
181        archive: &Archive,
182        password: Option<&[u8]>,
183        out: &mut impl Write,
184    ) -> Result<()> {
185        let mut session = DecoderSession::new_with_password(password, BUFFERED_DECODE_LIMIT);
186        session.write_file_to(archive, self, out)
187    }
188
189    pub(crate) fn decoded_data_unverified(
190        &self,
191        archive: &Archive,
192        password: Option<&[u8]>,
193    ) -> Result<Vec<u8>> {
194        let mut decoder = Unpack50Decoder::new();
195        Ok(self
196            .decoded_data_with_decoder(archive, &mut decoder, password)?
197            .data)
198    }
199
200    fn decoded_data_with_decoder(
201        &self,
202        archive: &Archive,
203        decoder: &mut Unpack50Decoder,
204        password: Option<&[u8]>,
205    ) -> Result<DecodedData> {
206        let (packed, keys) = self.packed_data_with_password(archive, password)?;
207        let data = self.decode_packed_with_decoder(&packed, decoder)?;
208        Ok(DecodedData { data, keys })
209    }
210
211    fn decoded_data_with_mode(
212        &self,
213        archive: &Archive,
214        decoder: &mut Unpack50Decoder,
215        password: Option<&[u8]>,
216        mode: DecodeMode,
217    ) -> Result<DecodedData> {
218        let (packed, keys) = self.packed_data_with_password(archive, password)?;
219        let data = self.decode_packed_with_decoder_mode(&packed, decoder, mode)?;
220        Ok(DecodedData { data, keys })
221    }
222
223    fn decode_packed_with_decoder(
224        &self,
225        packed: &[u8],
226        decoder: &mut Unpack50Decoder,
227    ) -> Result<Vec<u8>> {
228        self.decode_packed_with_decoder_mode(packed, decoder, DecodeMode::Lz)
229    }
230
231    fn decode_packed_with_decoder_mode(
232        &self,
233        packed: &[u8],
234        decoder: &mut Unpack50Decoder,
235        mode: DecodeMode,
236    ) -> Result<Vec<u8>> {
237        if self.is_stored() {
238            if self.encrypted {
239                let unpacked_size = usize::try_from(self.unpacked_size).map_err(|_| {
240                    Error::InvalidHeader("RAR 5 unpacked size overflows host address size")
241                })?;
242                if packed.len() < unpacked_size {
243                    return Err(Error::InvalidHeader(
244                        "RAR 5 encrypted stored file is shorter than unpacked size",
245                    ));
246                }
247                if packed[unpacked_size..].iter().any(|&byte| byte != 0) {
248                    return Err(Error::InvalidHeader(
249                        "RAR 5 encrypted stored file has non-zero padding",
250                    ));
251                }
252                return Ok(packed[..unpacked_size].to_vec());
253            }
254            if packed.len() as u64 != self.unpacked_size {
255                return Err(Error::InvalidHeader(
256                    "RAR 5 stored file has mismatched packed and unpacked sizes",
257                ));
258            }
259            return Ok(packed.to_vec());
260        }
261        if self.unpacked_size == 0 && packed.is_empty() {
262            return Ok(Vec::new());
263        }
264
265        let info = self.decoded_compression_info()?;
266        let dictionary_size = usize::try_from(info.dictionary_size).map_err(|_| {
267            Error::InvalidHeader("RAR 5 dictionary size overflows host address size")
268        })?;
269        let output_size = checked_unpacked_size(self.unpacked_size)?;
270        match decoder.decode_member_with_dictionary(
271            packed,
272            info.algorithm_version,
273            output_size,
274            dictionary_size,
275            info.solid,
276            mode,
277        ) {
278            Ok(data) => Ok(data),
279            Err(error) => self.map_truncated_unverified_payload(error),
280        }
281    }
282
283    fn map_truncated_unverified_payload(&self, error: rars_codec::Error) -> Result<Vec<u8>> {
284        if matches!(error, rars_codec::Error::NeedMoreInput)
285            && self.data_crc32.is_none()
286            && self.hash.is_none()
287        {
288            return Ok(Vec::new());
289        }
290        Err(Error::from(error))
291    }
292
293    fn stream_packed_with_decoder<R: Read>(
294        &self,
295        packed: &mut R,
296        keys: Option<&Rar50Keys>,
297        decoder: &mut Unpack50Decoder,
298        buffered_decode_limit: u64,
299        writer: &mut dyn Write,
300    ) -> Result<()> {
301        if self.is_stored() {
302            return Err(Error::InvalidHeader(
303                "RAR 5 stored file does not use streaming compressed decode",
304            ));
305        }
306
307        let info = self.decoded_compression_info()?;
308        let dictionary_size = usize::try_from(info.dictionary_size).map_err(|_| {
309            Error::InvalidHeader("RAR 5 dictionary size overflows host address size")
310        })?;
311        let output_size = usize::try_from(self.unpacked_size)
312            .map_err(|_| Error::InvalidHeader("RAR 5 unpacked size overflows host address size"))?;
313        let mut crc = Crc32::new();
314        let mut hash = streaming_hash_verifier(self)?;
315        decoder
316            .decode_member_from_reader_with_dictionary_to_sink(
317                packed,
318                info.algorithm_version,
319                output_size,
320                dictionary_size,
321                info.solid,
322                |chunk| match chunk {
323                    DecodedChunk::Bytes(chunk) => {
324                        crc.update(chunk);
325                        if let Some((_, hasher)) = &mut hash {
326                            hasher.update(chunk);
327                        }
328                        writer.write_all(chunk)
329                    }
330                    DecodedChunk::Repeated { byte, len } => {
331                        write_repeated_chunk(writer, &mut crc, &mut hash, byte, len)
332                    }
333                },
334            )
335            .map_err(|error| match error {
336                StreamDecodeError::Decode(error) => Error::from(error),
337                StreamDecodeError::FilteredMember => Error::Rar50BufferedDecodeLimitExceeded {
338                    limit: buffered_decode_limit,
339                    required: self.unpacked_size,
340                },
341                StreamDecodeError::Sink(error) => Error::from(error),
342            })?;
343        self.verify_streaming_integrity(crc, hash, keys)
344    }
345
346    fn write_stored_to(
347        &self,
348        archive: &Archive,
349        password: Option<&[u8]>,
350        writer: &mut dyn Write,
351    ) -> Result<()> {
352        let (mut reader, keys) = self
353            .packed_reader_with_password(archive, password)
354            .map_err(|error| self.entry_error("decoding", error))?;
355        let mut crc = Crc32::new();
356        let mut hash =
357            streaming_hash_verifier(self).map_err(|error| self.entry_error("decoding", error))?;
358        let mut written = 0u64;
359        let mut buf = [0u8; 64 * 1024];
360
361        loop {
362            let count = reader
363                .read(&mut buf)
364                .map_err(Error::from)
365                .map_err(|error| self.entry_error("decoding", error))?;
366            if count == 0 {
367                break;
368            }
369            let remaining =
370                usize::try_from(self.unpacked_size.saturating_sub(written)).unwrap_or(usize::MAX);
371            let chunk_len = count.min(remaining);
372            let chunk = &buf[..chunk_len];
373            if self.encrypted && buf[chunk_len..count].iter().any(|&byte| byte != 0) {
374                return Err(self.entry_error(
375                    "decoding",
376                    Error::InvalidHeader("RAR 5 encrypted stored file has non-zero padding"),
377                ));
378            }
379            written = written
380                .checked_add(chunk.len() as u64)
381                .ok_or(Error::InvalidHeader("RAR 5 stored size overflows"))
382                .map_err(|error| self.entry_error("decoding", error))?;
383            crc.update(chunk);
384            if let Some((_, hasher)) = &mut hash {
385                hasher.update(chunk);
386            }
387            writer
388                .write_all(chunk)
389                .map_err(Error::from)
390                .map_err(|error| self.entry_error("writing", error))?;
391        }
392
393        if written != self.unpacked_size {
394            return Err(self.entry_error(
395                "decoding",
396                Error::InvalidHeader("RAR 5 stored file has mismatched packed and unpacked sizes"),
397            ));
398        }
399        self.verify_streaming_integrity(crc, hash, keys.as_ref())
400            .map_err(|error| self.entry_error("verifying", error))
401    }
402
403    fn entry_error(&self, operation: &'static str, error: Error) -> Error {
404        error.at_entry(self.name.clone(), operation)
405    }
406}
407
408fn write_repeated_chunk(
409    writer: &mut dyn Write,
410    crc: &mut Crc32,
411    hash: &mut Option<([u8; 32], blake2sp::Hasher)>,
412    byte: u8,
413    mut len: usize,
414) -> std::io::Result<()> {
415    let buffer = [byte; 64 * 1024];
416    while len > 0 {
417        let take = len.min(buffer.len());
418        let chunk = &buffer[..take];
419        writer.write_all(chunk)?;
420        if byte == 0 {
421            crc.update_zeroes(take as u64);
422        } else {
423            crc.update(chunk);
424        }
425        if let Some((_, hasher)) = hash.as_mut() {
426            hasher.update(chunk);
427        }
428        len -= take;
429    }
430    Ok(())
431}
432
433impl Archive {
434    pub fn extract_to<F>(&self, options: crate::ArchiveReadOptions<'_>, mut open: F) -> Result<()>
435    where
436        F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
437    {
438        let buffered_decode_limit = rar50_buffered_decode_limit(options);
439        let mut session =
440            DecoderSession::new_with_password(options.password, buffered_decode_limit);
441        for file in self.files() {
442            if file.redirection.is_some() {
443                continue;
444            }
445            if file.is_split_before() || file.is_split_after() {
446                return Err(Error::InvalidHeader(
447                    "RAR 5 split entry requires multivolume extraction",
448                ));
449            }
450            let meta = file.metadata();
451            let mut writer = open(&meta)?;
452            if !meta.is_directory {
453                session.write_file_to(self, file, &mut writer)?;
454            }
455        }
456        Ok(())
457    }
458
459    #[cfg(feature = "parallel")]
460    pub fn extract_to_parallel_buffered<F>(
461        &self,
462        options: crate::ArchiveReadOptions<'_>,
463        mut open: F,
464    ) -> Result<()>
465    where
466        F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
467    {
468        if self.main.is_solid()
469            || self.files().any(|file| {
470                file.is_split_before()
471                    || file.is_split_after()
472                    || file.should_stream_decode(rar50_buffered_decode_limit(options))
473                    || file.decoded_compression_info().is_ok_and(|info| info.solid)
474            })
475        {
476            return self.extract_to(options, open);
477        }
478
479        let password = options.password;
480        let buffered_decode_limit = rar50_buffered_decode_limit(options);
481        let files: Vec<_> = self.files().collect();
482        if files.len() < 2 {
483            return self.extract_to(options, open);
484        }
485        let entries = crate::parallel::map_collect(files, |file| {
486            decode_parallel_entry(self, file, password, buffered_decode_limit)
487        })?;
488        for entry in entries {
489            write_parallel_entry(entry, &mut open)?;
490        }
491        Ok(())
492    }
493}
494
495#[cfg(feature = "parallel")]
496enum ParallelExtractedEntry {
497    Directory(ExtractedEntryMeta),
498    File {
499        meta: ExtractedEntryMeta,
500        data: Vec<u8>,
501    },
502    Skipped,
503}
504
505#[cfg(feature = "parallel")]
506fn decode_parallel_entry(
507    archive: &Archive,
508    file: &FileHeader,
509    password: Option<&[u8]>,
510    buffered_decode_limit: u64,
511) -> Result<ParallelExtractedEntry> {
512    if file.redirection.is_some() {
513        return Ok(ParallelExtractedEntry::Skipped);
514    }
515    if file.is_split_before() || file.is_split_after() {
516        return Err(Error::InvalidHeader(
517            "RAR 5 split entry requires multivolume extraction",
518        ));
519    }
520    let meta = file.metadata();
521    if meta.is_directory {
522        return Ok(ParallelExtractedEntry::Directory(meta));
523    }
524    let mut data = Vec::new();
525    let mut session = DecoderSession::new_with_password(password, buffered_decode_limit);
526    session.write_file_to(archive, file, &mut data)?;
527    Ok(ParallelExtractedEntry::File { meta, data })
528}
529
530#[cfg(feature = "parallel")]
531fn write_parallel_entry<F>(entry: ParallelExtractedEntry, open: &mut F) -> Result<()>
532where
533    F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
534{
535    match entry {
536        ParallelExtractedEntry::Directory(meta) => {
537            let _ = open(&meta)?;
538        }
539        ParallelExtractedEntry::File { meta, data } => {
540            let mut writer = open(&meta)?;
541            writer.write_all(&data)?;
542        }
543        ParallelExtractedEntry::Skipped => {}
544    }
545    Ok(())
546}
547
548struct DecodedData {
549    data: Vec<u8>,
550    keys: Option<Rar50Keys>,
551}
552
553struct DecoderSession<'a> {
554    decoder: Unpack50Decoder,
555    password: Option<&'a [u8]>,
556    buffered_decode_limit: u64,
557}
558
559impl<'a> DecoderSession<'a> {
560    fn new_with_password(password: Option<&'a [u8]>, buffered_decode_limit: u64) -> Self {
561        Self {
562            decoder: Unpack50Decoder::new(),
563            password,
564            buffered_decode_limit,
565        }
566    }
567
568    fn write_file_to(
569        &mut self,
570        archive: &Archive,
571        file: &FileHeader,
572        writer: &mut dyn Write,
573    ) -> Result<()> {
574        if file.is_stored() {
575            return file.write_stored_to(archive, self.password, writer);
576        }
577        if file.should_stream_decode(self.buffered_decode_limit) {
578            return self.stream_file_to(archive, file, writer);
579        }
580        let checkpoint = self.decoder.clone();
581        let decoded = self
582            .decoded_file_data(archive, file)
583            .map_err(|error| file.entry_error("decoding", error))?;
584        let decoded = match file.verify_integrity_with_keys(&decoded.data, decoded.keys.as_ref()) {
585            Ok(()) => decoded,
586            Err(filtered_error) => {
587                let mut unfiltered_decoder = checkpoint;
588                let unfiltered = file
589                    .decoded_data_with_mode(
590                        archive,
591                        &mut unfiltered_decoder,
592                        self.password,
593                        DecodeMode::LzNoFilters,
594                    )
595                    .map_err(|error| file.entry_error("decoding", error))?;
596                file.verify_integrity_with_keys(&unfiltered.data, unfiltered.keys.as_ref())
597                    .map_err(|_| file.entry_error("verifying", filtered_error))?;
598                self.decoder = unfiltered_decoder;
599                unfiltered
600            }
601        };
602        writer
603            .write_all(&decoded.data)
604            .map_err(Error::from)
605            .map_err(|error| file.entry_error("writing", error))
606    }
607
608    fn stream_file_to(
609        &mut self,
610        archive: &Archive,
611        file: &FileHeader,
612        writer: &mut dyn Write,
613    ) -> Result<()> {
614        let mut streaming_decoder = self.decoder.clone();
615        let (mut packed, keys) = file
616            .packed_reader_with_password(archive, self.password)
617            .map_err(|error| file.entry_error("reading", error))?;
618        file.stream_packed_with_decoder(
619            &mut packed,
620            keys.as_ref(),
621            &mut streaming_decoder,
622            self.buffered_decode_limit,
623            writer,
624        )
625        .map_err(|error| file.entry_error("decoding", error))?;
626        self.decoder = streaming_decoder;
627        Ok(())
628    }
629
630    fn decoded_file_data(&mut self, archive: &Archive, file: &FileHeader) -> Result<DecodedData> {
631        file.decoded_data_with_decoder(archive, &mut self.decoder, self.password)
632    }
633
634    fn split_decryptor(
635        &self,
636        split: &PendingSplitRefs,
637        volumes: &[Archive],
638    ) -> Result<Option<SplitDecryptor>> {
639        split.split_decryptor(volumes, self.password)
640    }
641
642    fn decode_split(
643        &mut self,
644        volumes: &[Archive],
645        split: &PendingSplitRefs,
646        final_file: &FileHeader,
647        decryptor: Option<&SplitDecryptor>,
648    ) -> Result<Vec<u8>> {
649        final_file.decode_split_with_decoder(volumes, split, &mut self.decoder, decryptor)
650    }
651}
652
653impl FileHeader {
654    fn should_stream_decode(&self, buffered_decode_limit: u64) -> bool {
655        !self.is_stored() && self.unpacked_size > buffered_decode_limit
656    }
657}
658
659fn rar50_buffered_decode_limit(options: crate::ArchiveReadOptions<'_>) -> u64 {
660    options
661        .rar50_buffered_decode_limit
662        .unwrap_or(BUFFERED_DECODE_LIMIT)
663}
664
665/// Streams a RAR 5 multivolume archive set to caller-provided writers.
666pub fn extract_volumes_to<F>(
667    volumes: &[Archive],
668    options: crate::ArchiveReadOptions<'_>,
669    mut open: F,
670) -> Result<()>
671where
672    F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
673{
674    if volumes.is_empty() {
675        return Err(Error::InvalidHeader("RAR 5 volume set is empty"));
676    }
677
678    let password = options.password;
679    let mut split = SplitVolumeState::new();
680    let buffered_decode_limit = rar50_buffered_decode_limit(options);
681    let mut session = DecoderSession::new_with_password(password, buffered_decode_limit);
682
683    for (volume_index, archive) in volumes.iter().enumerate() {
684        for (file_index, file) in archive.files().enumerate() {
685            match split.advance(file.is_split_before(), file.is_split_after()) {
686                SplitVolumeStep::Regular => {
687                    if file.redirection.is_some() {
688                        continue;
689                    }
690                    let meta = file.metadata();
691                    let mut writer = open(&meta)?;
692                    if !meta.is_directory {
693                        session.write_file_to(archive, file, &mut writer)?;
694                    }
695                }
696                SplitVolumeStep::Start => {
697                    validate_split_fragment(file, password)?;
698                    split.begin(PendingSplitRefs::new(file, volume_index, file_index));
699                }
700                SplitVolumeStep::Continue(current) => {
701                    validate_split_continuation_refs(current, file, password)?;
702                    current.append(volume_index, file_index);
703                }
704                SplitVolumeStep::Finish(mut completed) => {
705                    validate_split_continuation_refs(&completed, file, password)?;
706                    completed.append(volume_index, file_index);
707                    completed.write_to(volumes, file, &mut session, &mut open)?;
708                }
709                SplitVolumeStep::MissingFirst => {
710                    return Err(Error::InvalidHeader(
711                        "RAR 5 split entry is missing its first part",
712                    ));
713                }
714                SplitVolumeStep::Interrupted => {
715                    return Err(Error::InvalidHeader(
716                        "RAR 5 split entry is interrupted by a regular entry",
717                    ));
718                }
719            }
720        }
721    }
722
723    if split.is_pending() {
724        return Err(Error::InvalidHeader("RAR 5 split entry is incomplete"));
725    }
726
727    Ok(())
728}
729
730fn validate_split_fragment(file: &FileHeader, password: Option<&[u8]>) -> Result<()> {
731    if file.is_directory() {
732        return Err(Error::InvalidHeader(
733            "RAR 5 split directory entry is invalid",
734        ));
735    }
736    if file.encrypted && password.is_none() && file.crypto.is_none() {
737        return Err(Error::NeedPassword);
738    }
739    Ok(())
740}
741
742fn validate_split_continuation_refs(
743    pending: &PendingSplitRefs,
744    file: &FileHeader,
745    password: Option<&[u8]>,
746) -> Result<()> {
747    validate_split_fragment(file, password)?;
748    if file.name != pending.name {
749        return Err(Error::InvalidHeader("RAR 5 split entry name changed"));
750    }
751    if file.compression_info != pending.compression_info {
752        return Err(Error::InvalidHeader(
753            "RAR 5 split entry compression info changed",
754        ));
755    }
756    if file.encrypted != pending.encrypted {
757        return Err(Error::InvalidHeader(
758            "RAR 5 split entry encryption flag changed",
759        ));
760    }
761    Ok(())
762}
763
764struct PendingSplitRefs {
765    name: Vec<u8>,
766    fragments: Vec<(usize, usize)>,
767    file_time: u32,
768    attr: u64,
769    host_os: u64,
770    compression_info: u64,
771    encrypted: bool,
772}
773
774impl PendingSplitRefs {
775    fn new(file: &FileHeader, volume_index: usize, file_index: usize) -> Self {
776        Self {
777            name: file.name.clone(),
778            fragments: vec![(volume_index, file_index)],
779            file_time: file.mtime.unwrap_or(0),
780            attr: file.attributes,
781            host_os: file.host_os,
782            compression_info: file.compression_info,
783            encrypted: file.encrypted,
784        }
785    }
786
787    fn append(&mut self, volume_index: usize, file_index: usize) {
788        self.fragments.push((volume_index, file_index));
789    }
790
791    fn write_to<F>(
792        self,
793        volumes: &[Archive],
794        final_file: &FileHeader,
795        session: &mut DecoderSession<'_>,
796        open: &mut F,
797    ) -> Result<()>
798    where
799        F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
800    {
801        let decryptor = session.split_decryptor(&self, volumes)?;
802        let meta = ExtractedEntryMeta {
803            name: self.name.clone(),
804            file_time: self.file_time,
805            attr: self.attr,
806            host_os: self.host_os,
807            is_directory: false,
808        };
809        let mut writer = open(&meta)?;
810        if final_file.is_stored() {
811            return self
812                .write_stored_to(volumes, final_file, decryptor.as_ref(), &mut writer)
813                .map_err(|error| final_file.entry_error("extracting", error));
814        }
815
816        let data = session
817            .decode_split(volumes, &self, final_file, decryptor.as_ref())
818            .map_err(|error| final_file.entry_error("decoding", error))?;
819        final_file
820            .verify_integrity_with_keys(&data, decryptor.as_ref().map(|decryptor| &decryptor.keys))
821            .map_err(|error| final_file.entry_error("verifying", error))?;
822        writer
823            .write_all(&data)
824            .map_err(Error::from)
825            .map_err(|error| final_file.entry_error("writing", error))?;
826        Ok(())
827    }
828
829    fn write_stored_to(
830        &self,
831        volumes: &[Archive],
832        final_file: &FileHeader,
833        decryptor: Option<&SplitDecryptor>,
834        writer: &mut dyn Write,
835    ) -> Result<()> {
836        let mut reader = self.fragment_reader(volumes, decryptor)?;
837        let mut crc = Crc32::new();
838        let mut hash = streaming_hash_verifier(final_file)?;
839        let mut written = 0u64;
840        let mut buf = [0u8; 64 * 1024];
841
842        loop {
843            let count = reader.read(&mut buf)?;
844            if count == 0 {
845                break;
846            }
847            let chunk = if final_file.encrypted {
848                let remaining = usize::try_from(final_file.unpacked_size.saturating_sub(written))
849                    .unwrap_or(usize::MAX);
850                let chunk_len = count.min(remaining);
851                if buf[chunk_len..count].iter().any(|&byte| byte != 0) {
852                    return Err(Error::InvalidHeader(
853                        "RAR 5 encrypted stored split file has non-zero padding",
854                    ));
855                }
856                &buf[..chunk_len]
857            } else {
858                &buf[..count]
859            };
860            written = written
861                .checked_add(chunk.len() as u64)
862                .ok_or(Error::InvalidHeader("RAR 5 stored split size overflows"))?;
863            crc.update(chunk);
864            if let Some((_, hasher)) = &mut hash {
865                hasher.update(chunk);
866            }
867            writer.write_all(chunk)?;
868        }
869
870        if written != final_file.unpacked_size {
871            return Err(Error::InvalidHeader(
872                "RAR 5 stored split file has mismatched packed and unpacked sizes",
873            ));
874        }
875        if let Some(expected) = final_file.data_crc32 {
876            let actual = if final_file.encrypted {
877                let decryptor = decryptor.ok_or(Error::InvalidHeader(
878                    "RAR 5 encrypted split CRC needs encryption keys",
879                ))?;
880                decryptor.keys.mac_crc32(crc.finish())
881            } else {
882                crc.finish()
883            };
884            if actual != expected {
885                return Err(Error::Crc32Mismatch { expected, actual });
886            }
887        }
888        if let Some((expected, hasher)) = hash {
889            let actual = if final_file.encrypted {
890                let decryptor = decryptor.ok_or(Error::InvalidHeader(
891                    "RAR 5 encrypted split hash needs encryption keys",
892                ))?;
893                decryptor.keys.mac_hash32(hasher.finalize())
894            } else {
895                hasher.finalize()
896            };
897            if !constant_time_eq(&expected, &actual) {
898                return Err(Error::HashMismatch { hash_type: 0 });
899            }
900        }
901        Ok(())
902    }
903
904    fn split_decryptor(
905        &self,
906        volumes: &[Archive],
907        password: Option<&[u8]>,
908    ) -> Result<Option<SplitDecryptor>> {
909        if !self.encrypted {
910            return Ok(None);
911        }
912        let (volume_index, file_index) = self.fragments[0];
913        let archive = volumes
914            .get(volume_index)
915            .ok_or(Error::InvalidHeader("RAR 5 split volume is missing"))?;
916        let file = archive
917            .files()
918            .nth(file_index)
919            .ok_or(Error::InvalidHeader("RAR 5 split entry is missing"))?;
920        let keys = file
921            .crypto_with_password(password)?
922            .ok_or(Error::InvalidHeader(
923                "RAR 5 encrypted split file is missing encryption keys",
924            ))?;
925        Ok(Some(SplitDecryptor {
926            keys,
927            iv: file.encryption_iv()?,
928        }))
929    }
930
931    fn fragment_reader<'a>(
932        &self,
933        volumes: &'a [Archive],
934        decryptor: Option<&SplitDecryptor>,
935    ) -> Result<Box<dyn Read + 'a>> {
936        let mut readers = Vec::with_capacity(self.fragments.len());
937        for &(volume_index, file_index) in &self.fragments {
938            let archive = volumes
939                .get(volume_index)
940                .ok_or(Error::InvalidHeader("RAR 5 split volume is missing"))?;
941            let file = archive
942                .files()
943                .nth(file_index)
944                .ok_or(Error::InvalidHeader("RAR 5 split entry is missing"))?;
945            readers.push(archive.range_reader(file.block.data_range.clone())?);
946        }
947        let chained = ChainedReader::new(readers);
948        if let Some(decryptor) = decryptor {
949            Ok(Box::new(Rar50DecryptingReader::new(
950                chained,
951                decryptor.keys.key,
952                decryptor.iv,
953            )))
954        } else {
955            Ok(Box::new(chained))
956        }
957    }
958}
959
960struct SplitDecryptor {
961    keys: Rar50Keys,
962    iv: [u8; 16],
963}
964
965fn streaming_hash_verifier(file: &FileHeader) -> Result<Option<([u8; 32], blake2sp::Hasher)>> {
966    let Some(hash) = &file.hash else {
967        return Ok(None);
968    };
969    match hash.hash_type {
970        0 if hash.data.len() == 32 => {
971            let mut expected = [0u8; 32];
972            expected.copy_from_slice(&hash.data);
973            Ok(Some((expected, blake2sp::Hasher::new())))
974        }
975        0 => Err(Error::InvalidHeader(
976            "RAR 5 BLAKE2sp hash record has invalid length",
977        )),
978        _ => Ok(None),
979    }
980}
981
982fn checked_unpacked_size(size: u64) -> Result<usize> {
983    usize::try_from(size)
984        .map_err(|_| Error::InvalidHeader("RAR 5 unpacked size overflows host address size"))
985}
986
987fn constant_time_eq(left: &[u8], right: &[u8]) -> bool {
988    if left.len() != right.len() {
989        return false;
990    }
991    let mut diff = 0u8;
992    for (&left, &right) in left.iter().zip(right) {
993        diff |= left ^ right;
994    }
995    diff == 0
996}
997
998impl FileHeader {
999    fn decode_split_with_decoder(
1000        &self,
1001        volumes: &[Archive],
1002        split: &PendingSplitRefs,
1003        decoder: &mut Unpack50Decoder,
1004        decryptor: Option<&SplitDecryptor>,
1005    ) -> Result<Vec<u8>> {
1006        if self.is_stored() {
1007            let mut data = Vec::new();
1008            let mut reader = split.fragment_reader(volumes, decryptor)?;
1009            reader.read_to_end(&mut data)?;
1010            if data.len() as u64 != self.unpacked_size {
1011                return Err(Error::InvalidHeader(
1012                    "RAR 5 stored split file has mismatched packed and unpacked sizes",
1013                ));
1014            }
1015            return Ok(data);
1016        }
1017
1018        let info = self.decoded_compression_info()?;
1019        let dictionary_size = usize::try_from(info.dictionary_size).map_err(|_| {
1020            Error::InvalidHeader("RAR 5 dictionary size overflows host address size")
1021        })?;
1022        let mut reader = split.fragment_reader(volumes, decryptor)?;
1023        let output_size = checked_unpacked_size(self.unpacked_size)?;
1024        decoder
1025            .decode_member_from_reader_with_dictionary(
1026                &mut reader,
1027                info.algorithm_version,
1028                output_size,
1029                dictionary_size,
1030                info.solid,
1031                DecodeMode::Lz,
1032            )
1033            .map_err(Error::from)
1034    }
1035}
1036
1037struct Rar50DecryptingReader<R> {
1038    inner: R,
1039    cipher: Rar50Cipher,
1040    buffer: [u8; 16],
1041    pos: usize,
1042    len: usize,
1043}
1044
1045impl<R: Read> Rar50DecryptingReader<R> {
1046    fn new(inner: R, key: [u8; 32], iv: [u8; 16]) -> Self {
1047        Self {
1048            inner,
1049            cipher: Rar50Cipher::new(key, iv),
1050            buffer: [0; 16],
1051            pos: 0,
1052            len: 0,
1053        }
1054    }
1055
1056    fn fill_buffer(&mut self) -> std::io::Result<bool> {
1057        let mut encrypted = [0; 16];
1058        let mut read = 0;
1059        while read < encrypted.len() {
1060            let count = self.inner.read(&mut encrypted[read..])?;
1061            if count == 0 {
1062                if read == 0 {
1063                    return Ok(false);
1064                }
1065                return Err(std::io::Error::new(
1066                    std::io::ErrorKind::UnexpectedEof,
1067                    "truncated RAR 5 encrypted stream",
1068                ));
1069            }
1070            read += count;
1071        }
1072        self.buffer = encrypted;
1073        self.cipher
1074            .decrypt_in_place(&mut self.buffer)
1075            .map_err(super::map_rar50_crypto_error)
1076            .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
1077        self.pos = 0;
1078        self.len = self.buffer.len();
1079        Ok(true)
1080    }
1081}
1082
1083impl<R: Read> Read for Rar50DecryptingReader<R> {
1084    fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
1085        if out.is_empty() {
1086            return Ok(0);
1087        }
1088        if self.pos == self.len && !self.fill_buffer()? {
1089            return Ok(0);
1090        }
1091        let count = out.len().min(self.len - self.pos);
1092        out[..count].copy_from_slice(&self.buffer[self.pos..self.pos + count]);
1093        self.pos += count;
1094        Ok(count)
1095    }
1096}
1097
1098#[cfg(test)]
1099mod tests {
1100    use super::super::{
1101        ArchiveSource, Block, BlockHeader, CompressedEntry, FileEncryption, FileHash, FilterKind,
1102        FilterPolicy, MainHeader, Rar50Writer, WriterOptions, HEAD_FILE, HFL_SPLIT_AFTER,
1103        HFL_SPLIT_BEFORE,
1104    };
1105    use super::*;
1106    use std::cell::RefCell;
1107    use std::io::Cursor;
1108    use std::rc::Rc;
1109    use std::sync::Arc;
1110
1111    fn plain_file(name: &[u8], data: &[u8], hash: Option<FileHash>) -> FileHeader {
1112        FileHeader {
1113            block: empty_block(HEAD_FILE, 0, 0..0),
1114            file_flags: 0,
1115            unpacked_size: data.len() as u64,
1116            attributes: 0x20,
1117            mtime: None,
1118            data_crc32: None,
1119            compression_info: 0,
1120            host_os: 2,
1121            name: name.to_vec(),
1122            hash,
1123            redirection: None,
1124            service_data: None,
1125            encrypted: false,
1126            encryption: None,
1127            crypto: None,
1128        }
1129    }
1130
1131    #[test]
1132    fn decrypting_reader_streams_rar50_blocks() {
1133        let key = [3u8; 32];
1134        let iv = [4u8; 16];
1135        let plain = *b"0123456789abcdefRAR5 block two!!";
1136        let mut encrypted = plain;
1137        Rar50Cipher::new(key, iv)
1138            .encrypt_in_place(&mut encrypted)
1139            .unwrap();
1140        let mut reader = Rar50DecryptingReader::new(Cursor::new(encrypted), key, iv);
1141        let mut out = Vec::new();
1142        let mut buf = [0u8; 5];
1143
1144        loop {
1145            let count = reader.read(&mut buf).unwrap();
1146            if count == 0 {
1147                break;
1148            }
1149            out.extend_from_slice(&buf[..count]);
1150        }
1151
1152        assert_eq!(out, plain);
1153    }
1154
1155    #[test]
1156    fn stored_split_entries_stream_fragments_to_writer() {
1157        struct SharedWriter(Rc<RefCell<Vec<u8>>>);
1158
1159        impl Write for SharedWriter {
1160            fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1161                self.0.borrow_mut().extend_from_slice(buf);
1162                Ok(buf.len())
1163            }
1164
1165            fn flush(&mut self) -> std::io::Result<()> {
1166                Ok(())
1167            }
1168        }
1169
1170        let first = b"stored ";
1171        let second = b"split payload";
1172        let full = [first.as_slice(), second.as_slice()].concat();
1173        let expected_crc = crc32(&full);
1174        let volumes = vec![
1175            stored_split_archive(first, &full, expected_crc, HFL_SPLIT_AFTER),
1176            stored_split_archive(second, &full, expected_crc, HFL_SPLIT_BEFORE),
1177        ];
1178        let captured = Rc::new(RefCell::new(Vec::new()));
1179        let sink = captured.clone();
1180
1181        extract_volumes_to(
1182            &volumes,
1183            crate::ArchiveReadOptions::default(),
1184            move |_meta| Ok(Box::new(SharedWriter(sink.clone()))),
1185        )
1186        .unwrap();
1187
1188        assert_eq!(&*captured.borrow(), &full);
1189    }
1190
1191    #[test]
1192    fn bounded_filtered_members_use_buffered_decode() {
1193        let mut data = Vec::new();
1194        while data.len() + 29 <= BUFFERED_DECODE_LIMIT as usize {
1195            data.extend_from_slice(b"\xe8\0\0\0\0filtered payload block\n");
1196        }
1197        assert!(data.len() as u64 <= BUFFERED_DECODE_LIMIT);
1198
1199        let archive = Rar50Writer::new(WriterOptions {
1200            target: crate::ArchiveVersion::Rar50,
1201            features: crate::FeatureSet::store_only(),
1202            compression_level: None,
1203            dictionary_size: None,
1204        })
1205        .compressed_entries(&[CompressedEntry {
1206            name: b"filtered.bin",
1207            data: &data,
1208            mtime: None,
1209            attributes: 0x20,
1210            host_os: 3,
1211        }])
1212        .filter_policy(FilterPolicy::Explicit(FilterKind::E8))
1213        .finish()
1214        .unwrap();
1215        let archive = Archive::parse(&archive).unwrap();
1216        let file = archive.files().next().unwrap();
1217        assert!(!file.should_stream_decode(BUFFERED_DECODE_LIMIT));
1218
1219        let mut out = Vec::new();
1220        file.write_to(&archive, None, &mut out).unwrap();
1221
1222        assert_eq!(out, data);
1223    }
1224
1225    #[test]
1226    fn streaming_filtered_members_return_typed_error_without_preflight_decode() {
1227        let mut data = Vec::new();
1228        while data.len() as u64 <= BUFFERED_DECODE_LIMIT {
1229            data.extend_from_slice(b"\xe8\0\0\0\0filtered payload block\n");
1230        }
1231
1232        let archive = Rar50Writer::new(WriterOptions {
1233            target: crate::ArchiveVersion::Rar50,
1234            features: crate::FeatureSet::store_only(),
1235            compression_level: None,
1236            dictionary_size: None,
1237        })
1238        .compressed_entries(&[CompressedEntry {
1239            name: b"filtered.bin",
1240            data: &data,
1241            mtime: None,
1242            attributes: 0x20,
1243            host_os: 3,
1244        }])
1245        .filter_policy(FilterPolicy::Explicit(FilterKind::E8))
1246        .finish()
1247        .unwrap();
1248        let archive = Archive::parse(&archive).unwrap();
1249        let file = archive.files().next().unwrap();
1250        assert!(file.should_stream_decode(BUFFERED_DECODE_LIMIT));
1251
1252        let mut out = Vec::new();
1253        let error = file.write_to(&archive, None, &mut out).unwrap_err();
1254
1255        assert!(matches!(
1256            error,
1257            Error::AtEntry {
1258                operation: "decoding",
1259                source,
1260                ..
1261            } if matches!(*source, Error::Rar50BufferedDecodeLimitExceeded { .. })
1262        ));
1263    }
1264
1265    #[test]
1266    fn streaming_crc32_zero_advance_matches_byte_update() {
1267        let mut bytewise = Crc32::new();
1268        bytewise.update(&vec![0; 100_000]);
1269
1270        let mut skipped = Crc32::new();
1271        skipped.update_zeroes(100_000);
1272
1273        assert_eq!(skipped.finish(), bytewise.finish());
1274    }
1275
1276    #[test]
1277    fn repeated_chunk_does_not_advance_crc_after_sink_error() {
1278        struct FailingWriter;
1279
1280        impl Write for FailingWriter {
1281            fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
1282                Err(std::io::Error::other("sink failed"))
1283            }
1284
1285            fn flush(&mut self) -> std::io::Result<()> {
1286                Ok(())
1287            }
1288        }
1289
1290        let mut writer = FailingWriter;
1291        let mut crc = Crc32::new();
1292        let expected = Crc32::new().finish();
1293
1294        assert!(write_repeated_chunk(&mut writer, &mut crc, &mut None, 0, 1024).is_err());
1295        assert_eq!(crc.finish(), expected);
1296    }
1297
1298    #[test]
1299    fn encrypted_stored_decode_rejects_nonzero_discarded_padding() {
1300        let mut file = plain_file(b"secret.txt", b"secret", None);
1301        file.encrypted = true;
1302        file.unpacked_size = 6;
1303        let mut decoder = Unpack50Decoder::new();
1304
1305        assert_eq!(
1306            file.decode_packed_with_decoder(b"secret\0\0", &mut decoder)
1307                .unwrap(),
1308            b"secret"
1309        );
1310        assert!(matches!(
1311            file.decode_packed_with_decoder(b"secret\0\x01", &mut decoder),
1312            Err(Error::InvalidHeader(
1313                "RAR 5 encrypted stored file has non-zero padding"
1314            ))
1315        ));
1316    }
1317
1318    #[test]
1319    fn checked_unpacked_size_rejects_values_above_host_usize() {
1320        assert_eq!(checked_unpacked_size(123).unwrap(), 123usize);
1321
1322        let overflowing = usize::MAX as u128 + 1;
1323        if overflowing <= u64::MAX as u128 {
1324            assert!(checked_unpacked_size(overflowing as u64).is_err());
1325        }
1326    }
1327
1328    #[test]
1329    fn constant_time_hash_comparison_keeps_hash_validation_behaviour() {
1330        let data = b"hash me";
1331        let file = FileHeader {
1332            block: empty_block(HEAD_FILE, 0, 0..0),
1333            file_flags: 0,
1334            unpacked_size: data.len() as u64,
1335            attributes: 0x20,
1336            mtime: None,
1337            data_crc32: None,
1338            compression_info: 0,
1339            host_os: 2,
1340            name: b"hash.txt".to_vec(),
1341            hash: Some(FileHash {
1342                hash_type: 0,
1343                data: blake2sp::hash(data).to_vec(),
1344            }),
1345            redirection: None,
1346            service_data: None,
1347            encrypted: false,
1348            encryption: None,
1349            crypto: None,
1350        };
1351
1352        file.verify_integrity_with_keys(data, None).unwrap();
1353
1354        let mut wrong = file;
1355        wrong.hash.as_mut().unwrap().data[31] ^= 0x01;
1356        assert!(matches!(
1357            wrong.verify_integrity_with_keys(data, None),
1358            Err(Error::HashMismatch { hash_type: 0 })
1359        ));
1360    }
1361
1362    #[test]
1363    fn verify_integrity_rejects_bad_blake2sp_length_and_ignores_unknown_hash_type() {
1364        let data = b"hash me";
1365        let mut bad_length = plain_file(
1366            b"a.txt",
1367            data,
1368            Some(FileHash {
1369                hash_type: 0,
1370                data: vec![0u8; 16],
1371            }),
1372        );
1373        assert!(matches!(
1374            bad_length.verify_integrity_with_keys(data, None),
1375            Err(Error::InvalidHeader(_))
1376        ));
1377
1378        bad_length.hash.as_mut().unwrap().hash_type = 99;
1379        bad_length.hash.as_mut().unwrap().data = vec![0u8; 32];
1380        bad_length.verify_integrity_with_keys(data, None).unwrap();
1381    }
1382
1383    #[test]
1384    fn streaming_hash_verifier_rejects_bad_blake2sp_length_and_ignores_unknown_hash_type() {
1385        let mut file = plain_file(
1386            b"a.txt",
1387            b"",
1388            Some(FileHash {
1389                hash_type: 0,
1390                data: vec![0u8; 16],
1391            }),
1392        );
1393        assert!(matches!(
1394            streaming_hash_verifier(&file),
1395            Err(Error::InvalidHeader(_))
1396        ));
1397
1398        file.hash.as_mut().unwrap().hash_type = 7;
1399        file.hash.as_mut().unwrap().data = vec![0u8; 32];
1400        assert!(matches!(streaming_hash_verifier(&file), Ok(None)));
1401
1402        let nohash = plain_file(b"a.txt", b"", None);
1403        assert!(matches!(streaming_hash_verifier(&nohash), Ok(None)));
1404    }
1405
1406    #[test]
1407    fn crypto_with_password_short_circuits_for_unencrypted_or_unsupported_versions() {
1408        let plain = plain_file(b"a.txt", b"", None);
1409        assert!(plain.crypto_with_password(None).unwrap().is_none());
1410        assert!(plain.crypto_with_password(Some(b"pw")).unwrap().is_none());
1411
1412        let mut missing = plain_file(b"a.txt", b"", None);
1413        missing.encrypted = true;
1414        assert!(matches!(
1415            missing.crypto_with_password(None),
1416            Err(Error::NeedPassword)
1417        ));
1418        assert!(matches!(
1419            missing.crypto_with_password(Some(b"pw")),
1420            Err(Error::InvalidHeader(_))
1421        ));
1422
1423        let mut bad_version = plain_file(b"a.txt", b"", None);
1424        bad_version.encrypted = true;
1425        bad_version.encryption = Some(FileEncryption {
1426            version: 1,
1427            flags: 0,
1428            kdf_count: 0,
1429            salt: [0u8; 16],
1430            iv: [0u8; 16],
1431            check_value: None,
1432        });
1433        assert!(matches!(
1434            bad_version.crypto_with_password(Some(b"pw")),
1435            Err(Error::UnsupportedFeature { .. })
1436        ));
1437    }
1438
1439    #[test]
1440    fn crypto_with_password_handles_missing_check_value() {
1441        let mut file = plain_file(b"a.txt", b"", None);
1442        file.encrypted = true;
1443        file.encryption = Some(FileEncryption {
1444            version: 0,
1445            flags: 0,
1446            kdf_count: 0,
1447            salt: [0u8; 16],
1448            iv: [0u8; 16],
1449            check_value: None,
1450        });
1451        assert!(file.crypto_with_password(Some(b"pw")).unwrap().is_some());
1452    }
1453
1454    #[test]
1455    fn decode_packed_rejects_stored_size_mismatch() {
1456        let mut decoder = Unpack50Decoder::new();
1457
1458        let mut file = plain_file(b"a.txt", &[0u8; 32], None);
1459        file.unpacked_size = 32;
1460        let short = vec![0u8; 16];
1461        assert!(matches!(
1462            file.decode_packed_with_decoder(&short, &mut decoder),
1463            Err(Error::InvalidHeader(_))
1464        ));
1465
1466        let mut encrypted = plain_file(b"b.txt", &[0u8; 32], None);
1467        encrypted.encrypted = true;
1468        encrypted.unpacked_size = 32;
1469        let too_short = vec![0u8; 16];
1470        assert!(matches!(
1471            encrypted.decode_packed_with_decoder(&too_short, &mut decoder),
1472            Err(Error::InvalidHeader(_))
1473        ));
1474
1475        let exact = vec![0u8; 64];
1476        let trimmed = encrypted
1477            .decode_packed_with_decoder(&exact, &mut decoder)
1478            .unwrap();
1479        assert_eq!(trimmed.len(), encrypted.unpacked_size as usize);
1480    }
1481
1482    #[test]
1483    fn verify_streaming_integrity_validates_crc_and_hash() {
1484        let payload = b"streaming";
1485        let crc_value = crc32(payload);
1486        let hash_value = blake2sp::hash(payload);
1487
1488        let mut file = plain_file(b"s.txt", payload, None);
1489        file.data_crc32 = Some(crc_value);
1490        file.hash = Some(FileHash {
1491            hash_type: 0,
1492            data: hash_value.to_vec(),
1493        });
1494
1495        let make_state = || {
1496            let mut crc = Crc32::new();
1497            crc.update(payload);
1498            let mut hasher = blake2sp::Hasher::new();
1499            hasher.update(payload);
1500            (crc, Some((hash_value, hasher)))
1501        };
1502
1503        let (crc, hash) = make_state();
1504        file.verify_streaming_integrity(crc, hash, None).unwrap();
1505
1506        let (crc, hash) = make_state();
1507        let mut bad = file.clone();
1508        bad.data_crc32 = Some(crc_value ^ 0x1);
1509        assert!(matches!(
1510            bad.verify_streaming_integrity(crc, hash, None),
1511            Err(Error::Crc32Mismatch { .. })
1512        ));
1513
1514        let (crc, _) = make_state();
1515        let mut wrong_expected = hash_value;
1516        wrong_expected[0] ^= 0xff;
1517        let mut hasher = blake2sp::Hasher::new();
1518        hasher.update(payload);
1519        let mut bad_hash = file.clone();
1520        bad_hash.data_crc32 = None;
1521        assert!(matches!(
1522            bad_hash.verify_streaming_integrity(crc, Some((wrong_expected, hasher)), None),
1523            Err(Error::HashMismatch { hash_type: 0 })
1524        ));
1525
1526        let empty = plain_file(b"e.txt", b"", None);
1527        empty
1528            .verify_streaming_integrity(Crc32::new(), None, None)
1529            .unwrap();
1530    }
1531
1532    #[test]
1533    fn write_repeated_chunk_updates_crc_hash_and_writer() {
1534        let mut writer = Vec::new();
1535        let mut crc_zero = Crc32::new();
1536        let mut hash = Some(([0u8; 32], blake2sp::Hasher::new()));
1537        write_repeated_chunk(&mut writer, &mut crc_zero, &mut hash, 0, 70_000).unwrap();
1538        assert_eq!(writer.len(), 70_000);
1539        let zero_crc = crc_zero.finish();
1540
1541        let mut bytewise = Crc32::new();
1542        bytewise.update(&vec![0u8; 70_000]);
1543        assert_eq!(zero_crc, bytewise.finish());
1544
1545        let mut writer = Vec::new();
1546        let mut crc_ff = Crc32::new();
1547        let mut hash_none: Option<([u8; 32], blake2sp::Hasher)> = None;
1548        write_repeated_chunk(&mut writer, &mut crc_ff, &mut hash_none, 0xff, 1024).unwrap();
1549        assert_eq!(writer, vec![0xffu8; 1024]);
1550    }
1551
1552    #[test]
1553    fn map_rar50_crypto_error_translates_kdf_count() {
1554        assert!(matches!(
1555            super::super::map_rar50_crypto_error(rars_crypto::rar50::Error::KdfCountTooLarge),
1556            Error::UnsupportedFeature { .. }
1557        ));
1558        assert!(matches!(
1559            super::super::map_rar50_crypto_error(rars_crypto::rar50::Error::BadPassword),
1560            Error::WrongPasswordOrCorruptData
1561        ));
1562    }
1563
1564    #[test]
1565    fn constant_time_eq_returns_false_for_length_mismatch() {
1566        assert!(!constant_time_eq(b"abc", b"abcd"));
1567        assert!(constant_time_eq(b"abc", b"abc"));
1568        assert!(!constant_time_eq(b"abc", b"abd"));
1569    }
1570
1571    fn stored_split_archive(data: &[u8], full: &[u8], crc: u32, flags: u64) -> Archive {
1572        let source: Arc<[u8]> = Arc::from(data.to_vec().into_boxed_slice());
1573        Archive {
1574            sfx_offset: 0,
1575            main: MainHeader {
1576                block: empty_block(1, 0, 0..0),
1577                archive_flags: 0,
1578                volume_number: None,
1579                extras: Vec::new(),
1580            },
1581            blocks: vec![Block::File(FileHeader {
1582                block: empty_block(HEAD_FILE, flags, 0..data.len()),
1583                file_flags: 0,
1584                unpacked_size: full.len() as u64,
1585                attributes: 0x20,
1586                mtime: None,
1587                data_crc32: Some(crc),
1588                compression_info: 0,
1589                host_os: 2,
1590                name: b"split.txt".to_vec(),
1591                hash: Some(FileHash {
1592                    hash_type: 0,
1593                    data: blake2sp::hash(full).to_vec(),
1594                }),
1595                redirection: None,
1596                service_data: None,
1597                encrypted: false,
1598                encryption: None,
1599                crypto: None,
1600            })],
1601            source: ArchiveSource::Memory(source),
1602        }
1603    }
1604
1605    fn empty_block(
1606        header_type: u64,
1607        flags: u64,
1608        data_range: std::ops::Range<usize>,
1609    ) -> BlockHeader {
1610        BlockHeader {
1611            header_crc: 0,
1612            header_size: 0,
1613            header_type,
1614            flags,
1615            extra_area_size: None,
1616            data_size: Some(data_range.len() as u64),
1617            offset: 0,
1618            header_range: 0..0,
1619            data_range,
1620        }
1621    }
1622
1623    fn split_fragment_file(name: &[u8], hfl_flags: u64) -> FileHeader {
1624        FileHeader {
1625            block: empty_block(HEAD_FILE, hfl_flags, 0..0),
1626            file_flags: 0,
1627            unpacked_size: 0,
1628            attributes: 0x20,
1629            mtime: None,
1630            data_crc32: None,
1631            compression_info: 0,
1632            host_os: 2,
1633            name: name.to_vec(),
1634            hash: None,
1635            redirection: None,
1636            service_data: None,
1637            encrypted: false,
1638            encryption: None,
1639            crypto: None,
1640        }
1641    }
1642
1643    fn archive_with_blocks(blocks: Vec<Block>, source: Vec<u8>) -> Archive {
1644        let bytes: Arc<[u8]> = Arc::from(source.into_boxed_slice());
1645        Archive {
1646            sfx_offset: 0,
1647            main: MainHeader {
1648                block: empty_block(1, 0, 0..0),
1649                archive_flags: 0,
1650                volume_number: None,
1651                extras: Vec::new(),
1652            },
1653            blocks,
1654            source: ArchiveSource::Memory(bytes),
1655        }
1656    }
1657
1658    fn never_open(_meta: &ExtractedEntryMeta) -> Result<Box<dyn Write>> {
1659        panic!("open should not be invoked for this test");
1660    }
1661
1662    #[test]
1663    fn extract_volumes_to_rejects_volume_state_violations() {
1664        let empty: Vec<Archive> = Vec::new();
1665        assert!(matches!(
1666            extract_volumes_to(&empty, crate::ArchiveReadOptions::default(), never_open),
1667            Err(Error::InvalidHeader(_))
1668        ));
1669
1670        let only_continuation = vec![archive_with_blocks(
1671            vec![Block::File(split_fragment_file(b"a.txt", HFL_SPLIT_BEFORE))],
1672            Vec::new(),
1673        )];
1674        assert!(matches!(
1675            extract_volumes_to(
1676                &only_continuation,
1677                crate::ArchiveReadOptions::default(),
1678                never_open,
1679            ),
1680            Err(Error::InvalidHeader(_))
1681        ));
1682
1683        let interrupted = vec![archive_with_blocks(
1684            vec![
1685                Block::File(split_fragment_file(b"a.txt", HFL_SPLIT_AFTER)),
1686                Block::File(plain_file(b"other.txt", b"", None)),
1687            ],
1688            Vec::new(),
1689        )];
1690        assert!(matches!(
1691            extract_volumes_to(
1692                &interrupted,
1693                crate::ArchiveReadOptions::default(),
1694                never_open,
1695            ),
1696            Err(Error::InvalidHeader(_))
1697        ));
1698
1699        let incomplete = vec![archive_with_blocks(
1700            vec![Block::File(split_fragment_file(b"a.txt", HFL_SPLIT_AFTER))],
1701            Vec::new(),
1702        )];
1703        assert!(matches!(
1704            extract_volumes_to(
1705                &incomplete,
1706                crate::ArchiveReadOptions::default(),
1707                never_open,
1708            ),
1709            Err(Error::InvalidHeader(_))
1710        ));
1711    }
1712
1713    #[test]
1714    fn validate_split_fragment_rejects_directories_and_demands_password_for_encrypted() {
1715        let mut dir = split_fragment_file(b"d", HFL_SPLIT_AFTER);
1716        dir.file_flags = 0x0001;
1717        assert!(matches!(
1718            validate_split_fragment(&dir, None),
1719            Err(Error::InvalidHeader(_))
1720        ));
1721
1722        let mut encrypted = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1723        encrypted.encrypted = true;
1724        assert!(matches!(
1725            validate_split_fragment(&encrypted, None),
1726            Err(Error::NeedPassword)
1727        ));
1728        validate_split_fragment(&encrypted, Some(b"pw")).unwrap();
1729
1730        let plain = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1731        validate_split_fragment(&plain, None).unwrap();
1732    }
1733
1734    #[test]
1735    fn validate_split_continuation_refs_rejects_property_drift_between_fragments() {
1736        let first = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1737        let pending = PendingSplitRefs::new(&first, 0, 0);
1738
1739        let renamed = split_fragment_file(b"b.txt", HFL_SPLIT_BEFORE);
1740        assert!(matches!(
1741            validate_split_continuation_refs(&pending, &renamed, None),
1742            Err(Error::InvalidHeader(_))
1743        ));
1744
1745        let mut new_compression = split_fragment_file(b"a.txt", HFL_SPLIT_BEFORE);
1746        new_compression.compression_info = 0x123;
1747        assert!(matches!(
1748            validate_split_continuation_refs(&pending, &new_compression, None),
1749            Err(Error::InvalidHeader(_))
1750        ));
1751
1752        let mut new_encryption = split_fragment_file(b"a.txt", HFL_SPLIT_BEFORE);
1753        new_encryption.encrypted = true;
1754        assert!(matches!(
1755            validate_split_continuation_refs(&pending, &new_encryption, Some(b"pw")),
1756            Err(Error::InvalidHeader(_))
1757        ));
1758
1759        let same = split_fragment_file(b"a.txt", HFL_SPLIT_BEFORE);
1760        validate_split_continuation_refs(&pending, &same, None).unwrap();
1761    }
1762
1763    #[test]
1764    fn archive_extract_to_rejects_split_entries_in_single_volume_archive() {
1765        let split = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1766        let archive = archive_with_blocks(vec![Block::File(split)], Vec::new());
1767        let err = archive
1768            .extract_to(crate::ArchiveReadOptions::default(), never_open)
1769            .unwrap_err();
1770        assert!(
1771            matches!(err, Error::InvalidHeader(msg) if msg.contains("requires multivolume")),
1772            "expected multivolume error, got {err:?}"
1773        );
1774    }
1775
1776    #[test]
1777    fn archive_extract_to_skips_redirection_entries_without_opening_writer() {
1778        let mut redirect = plain_file(b"link", b"", None);
1779        redirect.redirection = Some(super::super::FileRedirection {
1780            redirection_type: 1,
1781            flags: 0,
1782            target_name: b"target".to_vec(),
1783        });
1784        let archive = archive_with_blocks(vec![Block::File(redirect)], Vec::new());
1785        archive
1786            .extract_to(crate::ArchiveReadOptions::default(), never_open)
1787            .unwrap();
1788    }
1789
1790    #[test]
1791    fn extract_volumes_to_skips_redirection_entries_without_opening_writer() {
1792        let mut redirect = plain_file(b"link", b"", None);
1793        redirect.redirection = Some(super::super::FileRedirection {
1794            redirection_type: 1,
1795            flags: 0,
1796            target_name: b"target".to_vec(),
1797        });
1798        let volumes = vec![archive_with_blocks(vec![Block::File(redirect)], Vec::new())];
1799        extract_volumes_to(&volumes, crate::ArchiveReadOptions::default(), never_open).unwrap();
1800    }
1801
1802    #[test]
1803    fn stream_packed_with_decoder_rejects_stored_files() {
1804        let file = plain_file(b"stored.txt", b"hello", None);
1805        assert!(file.is_stored());
1806        let mut decoder = Unpack50Decoder::new();
1807        let mut out: Vec<u8> = Vec::new();
1808        let err = file
1809            .stream_packed_with_decoder(
1810                &mut Cursor::new(Vec::<u8>::new()),
1811                None,
1812                &mut decoder,
1813                BUFFERED_DECODE_LIMIT,
1814                &mut out,
1815            )
1816            .unwrap_err();
1817        assert!(
1818            matches!(err, Error::InvalidHeader(msg) if msg.contains("does not use streaming")),
1819            "expected streaming-rejection error, got {err:?}"
1820        );
1821    }
1822
1823    #[test]
1824    fn pending_split_refs_write_stored_to_rejects_unpacked_size_mismatch() {
1825        let payload: &[u8] = b"unmatched-size payload";
1826        let mut first = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1827        first.block.data_range = 0..payload.len();
1828        first.block.data_size = Some(payload.len() as u64);
1829        first.unpacked_size = (payload.len() + 5) as u64; // mismatch
1830        let final_file = first.clone();
1831        let pending = PendingSplitRefs::new(&first, 0, 0);
1832        let volumes = vec![archive_with_blocks(
1833            vec![Block::File(first)],
1834            payload.to_vec(),
1835        )];
1836
1837        let mut out: Vec<u8> = Vec::new();
1838        let err = pending
1839            .write_stored_to(&volumes, &final_file, None, &mut out)
1840            .unwrap_err();
1841        assert!(
1842            matches!(err, Error::InvalidHeader(msg) if msg.contains("mismatched packed and unpacked")),
1843            "expected size mismatch error, got {err:?}"
1844        );
1845    }
1846
1847    #[test]
1848    fn pending_split_refs_write_stored_to_rejects_crc_mismatch_on_unencrypted() {
1849        let payload: &[u8] = b"crc-mismatch payload";
1850        let mut first = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1851        first.block.data_range = 0..payload.len();
1852        first.block.data_size = Some(payload.len() as u64);
1853        first.unpacked_size = payload.len() as u64;
1854        first.data_crc32 = Some(crc32(payload).wrapping_add(1));
1855        let final_file = first.clone();
1856        let pending = PendingSplitRefs::new(&first, 0, 0);
1857        let volumes = vec![archive_with_blocks(
1858            vec![Block::File(first)],
1859            payload.to_vec(),
1860        )];
1861
1862        let mut out: Vec<u8> = Vec::new();
1863        let err = pending
1864            .write_stored_to(&volumes, &final_file, None, &mut out)
1865            .unwrap_err();
1866        assert!(
1867            matches!(err, Error::Crc32Mismatch { .. }),
1868            "expected CRC mismatch, got {err:?}"
1869        );
1870    }
1871
1872    #[test]
1873    fn pending_split_refs_write_stored_to_rejects_hash_mismatch_on_unencrypted() {
1874        let payload: &[u8] = b"hash-mismatch payload";
1875        let mut wrong_hash = blake2sp::hash(payload);
1876        wrong_hash[0] ^= 0xff;
1877
1878        let mut first = split_fragment_file(b"a.txt", HFL_SPLIT_AFTER);
1879        first.block.data_range = 0..payload.len();
1880        first.block.data_size = Some(payload.len() as u64);
1881        first.unpacked_size = payload.len() as u64;
1882        first.data_crc32 = Some(crc32(payload));
1883        first.hash = Some(FileHash {
1884            hash_type: 0,
1885            data: wrong_hash.to_vec(),
1886        });
1887        let final_file = first.clone();
1888        let pending = PendingSplitRefs::new(&first, 0, 0);
1889        let volumes = vec![archive_with_blocks(
1890            vec![Block::File(first)],
1891            payload.to_vec(),
1892        )];
1893
1894        let mut out: Vec<u8> = Vec::new();
1895        let err = pending
1896            .write_stored_to(&volumes, &final_file, None, &mut out)
1897            .unwrap_err();
1898        assert!(
1899            matches!(err, Error::HashMismatch { hash_type: 0 }),
1900            "expected hash mismatch, got {err:?}"
1901        );
1902    }
1903
1904    #[test]
1905    fn decoded_data_with_mode_dispatches_through_decode_packed_for_stored_files() {
1906        let payload = b"decoded_data_with_mode stored payload";
1907        let mut file = plain_file(b"a.txt", payload, None);
1908        file.block.data_range = 0..payload.len();
1909        file.block.data_size = Some(payload.len() as u64);
1910        file.unpacked_size = payload.len() as u64;
1911
1912        let archive = archive_with_blocks(vec![Block::File(file.clone())], payload.to_vec());
1913        let mut decoder = Unpack50Decoder::new();
1914        let decoded = file
1915            .decoded_data_with_mode(&archive, &mut decoder, None, DecodeMode::Lz)
1916            .unwrap();
1917        assert_eq!(decoded.data, payload);
1918        assert!(decoded.keys.is_none());
1919
1920        // LzNoFilters dispatches through the same stored short-circuit.
1921        let mut decoder = Unpack50Decoder::new();
1922        let decoded = file
1923            .decoded_data_with_mode(&archive, &mut decoder, None, DecodeMode::LzNoFilters)
1924            .unwrap();
1925        assert_eq!(decoded.data, payload);
1926    }
1927
1928    #[test]
1929    fn decoded_data_unverified_returns_stored_payload_without_crc_check() {
1930        let payload = b"decoded_data_unverified stored payload";
1931        let mut file = plain_file(b"a.txt", payload, None);
1932        file.block.data_range = 0..payload.len();
1933        file.block.data_size = Some(payload.len() as u64);
1934        file.unpacked_size = payload.len() as u64;
1935        // Set wrong CRC — unverified path must not check it.
1936        file.data_crc32 = Some(crc32(payload).wrapping_add(1));
1937
1938        let archive = archive_with_blocks(vec![Block::File(file.clone())], payload.to_vec());
1939        let decoded = file.decoded_data_unverified(&archive, None).unwrap();
1940        assert_eq!(decoded, payload);
1941    }
1942
1943    #[test]
1944    fn decoded_data_unverified_accepts_empty_compressed_member() {
1945        let mut file = plain_file(b"empty.txt", b"", None);
1946        file.compression_info = 5 << 7;
1947        file.data_crc32 = Some(0);
1948
1949        let archive = archive_with_blocks(vec![Block::File(file.clone())], Vec::new());
1950        let decoded = file.decoded_data_unverified(&archive, None).unwrap();
1951
1952        assert!(decoded.is_empty());
1953    }
1954
1955    #[test]
1956    fn map_truncated_unverified_payload_swallows_need_more_input_when_no_integrity_record() {
1957        let mut file = plain_file(b"a.txt", b"", None);
1958        file.data_crc32 = None;
1959        file.hash = None;
1960        assert!(file
1961            .map_truncated_unverified_payload(rars_codec::Error::NeedMoreInput)
1962            .unwrap()
1963            .is_empty());
1964
1965        file.data_crc32 = Some(0);
1966        assert!(file
1967            .map_truncated_unverified_payload(rars_codec::Error::NeedMoreInput)
1968            .is_err());
1969    }
1970
1971    #[test]
1972    fn encryption_iv_falls_back_to_encryption_record_and_errors_when_missing() {
1973        let mut with_record = plain_file(b"a.txt", b"", None);
1974        with_record.encrypted = true;
1975        with_record.encryption = Some(FileEncryption {
1976            version: 0,
1977            flags: 0,
1978            kdf_count: 0,
1979            salt: [0u8; 16],
1980            iv: [5u8; 16],
1981            check_value: None,
1982        });
1983        assert_eq!(with_record.encryption_iv().unwrap(), [5u8; 16]);
1984
1985        let missing = plain_file(b"a.txt", b"", None);
1986        assert!(matches!(
1987            missing.encryption_iv(),
1988            Err(Error::InvalidHeader(_))
1989        ));
1990    }
1991}