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);
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
557pub 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; 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 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 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}