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