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#[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
665pub 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; 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 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 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}