1#[cfg(test)]
13#[path = "../tests/support/scratch.rs"]
14mod scratch;
15
16pub mod builder;
17#[doc(hidden)]
18pub mod codec;
19pub mod crc32;
20#[doc(hidden)]
21pub mod crypto;
22pub mod detect;
23pub mod error;
24mod fast;
25pub mod features;
26pub mod filter;
27mod filter_search;
28mod io_util;
29mod parallel;
30pub mod rar13;
31pub mod rar15_40;
32pub mod rar50;
33#[doc(hidden)]
34pub mod recovery;
35mod source;
36mod streaming;
37pub mod version;
38mod volume_extract;
39pub mod write_plan;
40mod write_progress;
41mod write_stream;
42mod x86_filter_scan;
43
44pub use builder::{entry_relative_path, validate_entry_name, Builder};
45pub use detect::{detect_archive_family, find_archive_start, ArchiveSignature, SFX_SCAN_LIMIT};
46pub use error::{Error, Result};
47pub use features::{Feature, FeatureSet};
48pub use filter::{
49 formats_supporting_filter, FilterKind, FilterPolicy, FilterSpec, UnsupportedFilterKind,
50};
51use std::io::{Read, Write};
52use std::path::Path;
53pub use streaming::{EntryReader, EntrySource, WriterResources, DEFAULT_WRITER_MEMORY_LIMIT};
54pub use version::{ArchiveFamily, ArchiveVersion};
55pub use write_plan::{
56 formats_supporting, supported_features, supports, MemberCoding, PlanShape, WriterOption,
57};
58pub use write_progress::{WriteOperation, WriteProgress, WriteProgressEvent};
59
60#[derive(Debug, Clone, Copy, Default)]
61#[non_exhaustive]
62pub struct ArchiveReadOptions<'a> {
64 pub password: Option<&'a [u8]>,
66 pub rar50_buffered_decode_limit: Option<u64>,
72}
73
74impl<'a> ArchiveReadOptions<'a> {
75 pub fn new() -> Self {
77 Self::default()
78 }
79
80 pub fn with_password(password: &'a [u8]) -> Self {
82 Self {
83 password: Some(password),
84 ..Self::default()
85 }
86 }
87
88 pub fn with_optional_password(password: Option<&'a [u8]>) -> Self {
90 Self {
91 password,
92 ..Self::default()
93 }
94 }
95
96 pub fn with_rar50_buffered_decode_limit(mut self, limit: u64) -> Self {
98 self.rar50_buffered_decode_limit = Some(limit);
99 self
100 }
101}
102
103#[derive(Debug, Clone)]
104#[non_exhaustive]
105pub enum Archive {
107 Rar13(rar13::Archive),
109 Rar15To40(rar15_40::Archive),
111 Rar50Plus(rar50::Archive),
113}
114
115#[derive(Debug, Clone, PartialEq, Eq)]
116#[non_exhaustive]
117pub struct ExtractedEntryMeta {
119 pub name: Vec<u8>,
121 pub file_time: u32,
123 pub file_attr: u64,
125 pub is_directory: bool,
127}
128
129impl ExtractedEntryMeta {
130 pub fn new(name: Vec<u8>, file_time: u32, file_attr: u64, is_directory: bool) -> Self {
132 Self {
133 name,
134 file_time,
135 file_attr,
136 is_directory,
137 }
138 }
139
140 pub fn name_bytes(&self) -> &[u8] {
142 &self.name
143 }
144
145 pub fn name_lossy(&self) -> String {
149 String::from_utf8_lossy(&self.name).into_owned()
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
154#[non_exhaustive]
155pub struct ArchiveMember {
157 pub meta: ArchiveMemberMeta,
159 pub detail: ArchiveMemberDetail,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164#[non_exhaustive]
165pub struct ArchiveMemberMeta {
167 pub family: ArchiveFamily,
169 pub name: Vec<u8>,
171 pub packed_size: u64,
173 pub unpacked_size: u64,
175 pub file_time: Option<u32>,
177 pub file_attr: u64,
179 pub host_os: Option<u64>,
181 pub is_directory: bool,
183 pub is_encrypted: bool,
185 pub is_stored: bool,
187 pub is_split_before: bool,
189 pub is_split_after: bool,
191}
192
193impl ArchiveMemberMeta {
194 pub fn name_bytes(&self) -> &[u8] {
196 &self.name
197 }
198
199 pub fn name_lossy(&self) -> String {
203 String::from_utf8_lossy(&self.name).into_owned()
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq)]
208#[non_exhaustive]
209pub enum ArchiveMemberDetail {
211 #[non_exhaustive]
213 Rar13 {
214 method: u8,
216 unpack_version: u8,
218 file_checksum: u16,
220 has_file_comment: bool,
222 },
223 #[non_exhaustive]
225 Rar15To40 {
226 method: u8,
228 unpack_version: u8,
230 crc32: u32,
232 solid: bool,
234 salt: Option<[u8; 8]>,
236 has_file_comment: bool,
238 },
239 #[non_exhaustive]
241 Rar50Plus {
242 compression_info: u64,
244 crc32: Option<u32>,
246 hash: Option<ArchiveMemberHash>,
248 },
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252#[non_exhaustive]
253pub enum ArchiveMemberHash {
255 Blake2sp([u8; 32]),
257 Other { hash_type: u64, data: Vec<u8> },
259}
260
261#[derive(Debug, Clone)]
262#[non_exhaustive]
263pub struct ArchiveMembers<'a> {
265 inner: ArchiveMembersInner<'a>,
266 index: usize,
267}
268
269#[derive(Debug, Clone)]
270enum ArchiveMembersInner<'a> {
271 Rar13(&'a [rar13::Entry]),
272 Rar15To40(&'a [rar15_40::Block]),
273 Rar50Plus(&'a [rar50::Block]),
274}
275
276impl Iterator for ArchiveMembers<'_> {
277 type Item = ArchiveMember;
278
279 fn next(&mut self) -> Option<Self::Item> {
280 match self.inner {
281 ArchiveMembersInner::Rar13(entries) => {
282 let entry = entries.get(self.index)?;
283 self.index += 1;
284 Some(rar13_member(entry))
285 }
286 ArchiveMembersInner::Rar15To40(blocks) => {
287 while let Some(block) = blocks.get(self.index) {
288 self.index += 1;
289 if let rar15_40::Block::File(file) = block {
290 return Some(rar15_40_member(file));
291 }
292 }
293 None
294 }
295 ArchiveMembersInner::Rar50Plus(blocks) => {
296 while let Some(block) = blocks.get(self.index) {
297 self.index += 1;
298 if let rar50::Block::File(file) = block {
299 return Some(rar50_member(file));
300 }
301 }
302 None
303 }
304 }
305 }
306}
307
308struct SharedBuffer(std::sync::Arc<std::sync::Mutex<Option<Vec<u8>>>>);
311
312impl SharedBuffer {
313 fn lock(&self) -> std::sync::MutexGuard<'_, Option<Vec<u8>>> {
314 self.0
315 .lock()
316 .unwrap_or_else(|poisoned| poisoned.into_inner())
317 }
318}
319
320impl Write for SharedBuffer {
321 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
322 self.lock()
323 .get_or_insert_with(Vec::new)
324 .extend_from_slice(buf);
325 Ok(buf.len())
326 }
327
328 fn flush(&mut self) -> std::io::Result<()> {
329 Ok(())
330 }
331}
332
333impl Archive {
334 pub fn family(&self) -> ArchiveFamily {
336 match self {
337 Self::Rar13(_) => ArchiveFamily::Rar13,
338 Self::Rar15To40(_) => ArchiveFamily::Rar15To40,
339 Self::Rar50Plus(_) => ArchiveFamily::Rar50Plus,
340 }
341 }
342
343 pub fn sfx_offset(&self) -> usize {
345 match self {
346 Self::Rar13(archive) => archive.sfx_offset,
347 Self::Rar15To40(archive) => archive.sfx_offset,
348 Self::Rar50Plus(archive) => archive.sfx_offset,
349 }
350 }
351
352 pub fn members(&self) -> ArchiveMembers<'_> {
354 match self {
355 Self::Rar13(archive) => ArchiveMembers {
356 inner: ArchiveMembersInner::Rar13(&archive.entries),
357 index: 0,
358 },
359 Self::Rar15To40(archive) => ArchiveMembers {
360 inner: ArchiveMembersInner::Rar15To40(&archive.blocks),
361 index: 0,
362 },
363 Self::Rar50Plus(archive) => ArchiveMembers {
364 inner: ArchiveMembersInner::Rar50Plus(&archive.blocks),
365 index: 0,
366 },
367 }
368 }
369
370 pub fn extract_to<F>(&self, password: Option<&[u8]>, open: F) -> Result<()>
372 where
373 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
374 {
375 self.extract_to_with_options(read_options(password), open)
376 }
377
378 pub fn extract_to_with_options<F>(
380 &self,
381 options: ArchiveReadOptions<'_>,
382 mut open: F,
383 ) -> Result<()>
384 where
385 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
386 {
387 match self {
388 Self::Rar13(archive) => {
389 archive.extract_to(options.password, |meta| open(&rar13_meta(meta)))
390 }
391 Self::Rar15To40(archive) => {
392 archive.extract_to(options, |meta| open(&rar15_40_meta(meta)))
393 }
394 Self::Rar50Plus(archive) => archive.extract_to(options, |meta| open(&rar50_meta(meta))),
395 }
396 }
397
398 pub fn extract_to_parallel_buffered<F>(&self, password: Option<&[u8]>, open: F) -> Result<()>
404 where
405 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
406 {
407 self.extract_to_parallel_buffered_with_options(read_options(password), open)
408 }
409
410 pub fn extract_to_parallel_buffered_with_options<F>(
412 &self,
413 options: ArchiveReadOptions<'_>,
414 mut open: F,
415 ) -> Result<()>
416 where
417 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
418 {
419 match self {
420 Self::Rar13(archive) => {
421 archive.extract_to(options.password, |meta| open(&rar13_meta(meta)))
422 }
423 Self::Rar15To40(archive) => {
424 archive.extract_to_parallel_buffered(options, |meta| open(&rar15_40_meta(meta)))
425 }
426 Self::Rar50Plus(archive) => {
427 archive.extract_to_parallel_buffered(options, |meta| open(&rar50_meta(meta)))
428 }
429 }
430 }
431
432 pub fn read_member(&self, name: &[u8], password: Option<&[u8]>) -> Result<Option<Vec<u8>>> {
441 let collected = std::sync::Arc::new(std::sync::Mutex::new(None::<Vec<u8>>));
444 self.extract_to_parallel_buffered(password, |meta| {
445 if meta.name != name || meta.is_directory {
446 return Ok(Box::new(std::io::sink()) as Box<dyn Write>);
447 }
448 let sink = SharedBuffer(std::sync::Arc::clone(&collected));
449 *sink.lock() = Some(Vec::new());
450 Ok(Box::new(sink) as Box<dyn Write>)
451 })?;
452 let taken = collected
453 .lock()
454 .unwrap_or_else(|poisoned| poisoned.into_inner())
455 .take();
456 Ok(taken)
457 }
458
459 pub fn test(&self, password: Option<&[u8]>) -> Result<()> {
462 self.extract_to_parallel_buffered(password, |_| {
463 Ok(Box::new(std::io::sink()) as Box<dyn Write>)
464 })
465 }
466
467 pub fn comment(&self, password: Option<&[u8]>) -> Result<Option<Vec<u8>>> {
470 match self {
471 Self::Rar13(archive) => archive.archive_comment(),
472 Self::Rar15To40(archive) => archive.archive_comment(),
473 Self::Rar50Plus(archive) => archive.archive_comment_with_password(password),
474 }
475 }
476
477 pub fn repair_recovery(&self) -> Result<Vec<u8>> {
480 let mut repaired = Vec::new();
481 self.repair_recovery_to(&mut repaired)?;
482 Ok(repaired)
483 }
484
485 pub fn repair_recovery_to(&self, writer: &mut dyn Write) -> Result<()> {
488 match self {
489 Self::Rar15To40(archive) => {
490 writer.write_all(&archive.repair_protect_head()?)?;
491 Ok(())
492 }
493 Self::Rar50Plus(archive) => archive.repair_recovery_to(writer),
494 Self::Rar13(_) => Err(Error::UnsupportedFamilyFeature {
495 family: ArchiveFamily::Rar13,
496 feature: "recovery repair for RAR 1.3/1.4 archives",
497 }),
498 }
499 }
500
501 pub fn as_rar13(&self) -> Option<&rar13::Archive> {
503 match self {
504 Self::Rar13(archive) => Some(archive),
505 Self::Rar15To40(_) => None,
506 Self::Rar50Plus(_) => None,
507 }
508 }
509
510 pub fn as_rar15_40(&self) -> Option<&rar15_40::Archive> {
512 match self {
513 Self::Rar13(_) => None,
514 Self::Rar15To40(archive) => Some(archive),
515 Self::Rar50Plus(_) => None,
516 }
517 }
518
519 pub fn as_rar50(&self) -> Option<&rar50::Archive> {
521 match self {
522 Self::Rar13(_) | Self::Rar15To40(_) => None,
523 Self::Rar50Plus(archive) => Some(archive),
524 }
525 }
526}
527
528fn rar13_member(entry: &rar13::Entry) -> ArchiveMember {
529 ArchiveMember {
530 meta: ArchiveMemberMeta {
531 family: ArchiveFamily::Rar13,
532 name: entry.name.clone(),
533 packed_size: u64::from(entry.header.pack_size),
534 unpacked_size: u64::from(entry.header.unp_size),
535 file_time: Some(entry.header.file_time),
536 file_attr: u64::from(entry.header.file_attr),
537 host_os: None,
538 is_directory: entry.is_directory(),
539 is_encrypted: entry.is_encrypted(),
540 is_stored: entry.is_stored(),
541 is_split_before: entry.is_split_before(),
542 is_split_after: entry.is_split_after(),
543 },
544 detail: ArchiveMemberDetail::Rar13 {
545 method: entry.header.method,
546 unpack_version: entry.header.unp_ver,
547 file_checksum: entry.header.file_crc,
548 has_file_comment: entry.has_file_comment(),
549 },
550 }
551}
552
553fn rar15_40_member(file: &rar15_40::FileHeader) -> ArchiveMember {
554 ArchiveMember {
555 meta: ArchiveMemberMeta {
556 family: ArchiveFamily::Rar15To40,
557 name: file.name.clone(),
558 packed_size: file.pack_size,
559 unpacked_size: file.unp_size,
560 file_time: Some(file.file_time),
561 file_attr: u64::from(file.attr),
562 host_os: Some(u64::from(file.host_os)),
563 is_directory: file.is_directory(),
564 is_encrypted: file.is_encrypted(),
565 is_stored: file.is_stored(),
566 is_split_before: file.is_split_before(),
567 is_split_after: file.is_split_after(),
568 },
569 detail: ArchiveMemberDetail::Rar15To40 {
570 method: file.method,
571 unpack_version: file.unp_ver,
572 crc32: file.file_crc,
573 solid: file.is_solid(),
574 salt: file.salt,
575 has_file_comment: file.has_file_comment(),
576 },
577 }
578}
579
580fn rar50_member(file: &rar50::FileHeader) -> ArchiveMember {
581 ArchiveMember {
582 meta: ArchiveMemberMeta {
583 family: ArchiveFamily::Rar50Plus,
584 name: file.name.clone(),
585 packed_size: file.packed_size(),
586 unpacked_size: file.unpacked_size,
587 file_time: file.mtime,
588 file_attr: file.attributes,
589 host_os: Some(file.host_os),
590 is_directory: file.is_directory(),
591 is_encrypted: file.encrypted,
592 is_stored: file.is_stored(),
593 is_split_before: file.is_split_before(),
594 is_split_after: file.is_split_after(),
595 },
596 detail: ArchiveMemberDetail::Rar50Plus {
597 compression_info: file.compression_info,
598 crc32: file.data_crc32,
599 hash: file.hash.as_ref().map(rar50_member_hash),
600 },
601 }
602}
603
604fn rar50_member_hash(hash: &rar50::FileHash) -> ArchiveMemberHash {
605 match hash.hash_type {
606 0 if hash.data.len() == 32 => {
607 let mut data = [0; 32];
608 data.copy_from_slice(&hash.data);
609 ArchiveMemberHash::Blake2sp(data)
610 }
611 _ => ArchiveMemberHash::Other {
612 hash_type: hash.hash_type,
613 data: hash.data.clone(),
614 },
615 }
616}
617
618#[derive(Debug, Clone, Copy, Default)]
619#[non_exhaustive]
620pub struct ArchiveReader;
622
623impl ArchiveReader {
624 pub fn detect(input: &[u8]) -> Result<ArchiveSignature> {
626 detect_archive_family(input).ok_or(Error::UnsupportedSignature)
627 }
628
629 pub fn read(input: &[u8]) -> Result<Archive> {
631 Self::read_with_options(input, ArchiveReadOptions::default())
632 }
633
634 pub fn read_owned(input: Vec<u8>) -> Result<Archive> {
636 Self::read_owned_with_options(input, ArchiveReadOptions::default())
637 }
638
639 pub fn read_with_options(input: &[u8], options: ArchiveReadOptions<'_>) -> Result<Archive> {
641 let signature =
642 find_archive_start(input, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
643 match signature.family {
644 ArchiveFamily::Rar13 => Ok(Archive::Rar13(rar13::Archive::parse(input)?)),
645 ArchiveFamily::Rar15To40 => Ok(Archive::Rar15To40(
646 rar15_40::Archive::parse_with_options(input, options)?,
647 )),
648 ArchiveFamily::Rar50Plus => Ok(Archive::Rar50Plus(rar50::Archive::parse_with_options(
649 input, options,
650 )?)),
651 }
652 }
653
654 pub fn read_owned_with_options(
656 input: Vec<u8>,
657 options: ArchiveReadOptions<'_>,
658 ) -> Result<Archive> {
659 let signature =
660 find_archive_start(&input, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
661 match signature.family {
662 ArchiveFamily::Rar13 => Ok(Archive::Rar13(rar13::Archive::parse_owned(input)?)),
663 ArchiveFamily::Rar15To40 => Ok(Archive::Rar15To40(
664 rar15_40::Archive::parse_owned_with_options(input, options)?,
665 )),
666 ArchiveFamily::Rar50Plus => Ok(Archive::Rar50Plus(
667 rar50::Archive::parse_owned_with_options(input, options)?,
668 )),
669 }
670 }
671
672 pub fn read_path(path: impl AsRef<Path>) -> Result<Archive> {
674 Self::read_path_with_options(path, ArchiveReadOptions::default())
675 }
676
677 pub fn read_path_with_options(
679 path: impl AsRef<Path>,
680 options: ArchiveReadOptions<'_>,
681 ) -> Result<Archive> {
682 let path = path.as_ref();
683 let mut file = std::fs::File::open(path)?;
684 let len = file.metadata()?.len();
685 let mut scan = vec![0; len.min(SFX_SCAN_LIMIT as u64) as usize];
686 file.read_exact(&mut scan)?;
687 let signature =
688 find_archive_start(&scan, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
689 match signature.family {
690 ArchiveFamily::Rar13 => Ok(Archive::Rar13(rar13::Archive::parse_path_with_signature(
691 path, signature,
692 )?)),
693 ArchiveFamily::Rar15To40 => Ok(Archive::Rar15To40(
694 rar15_40::Archive::parse_path_with_signature(path, signature, options)?,
695 )),
696 ArchiveFamily::Rar50Plus => Ok(Archive::Rar50Plus(
697 rar50::Archive::parse_path_with_signature(path, signature, options)?,
698 )),
699 }
700 }
701}
702
703fn read_options(password: Option<&[u8]>) -> ArchiveReadOptions<'_> {
704 match password {
705 Some(password) => ArchiveReadOptions::with_password(password),
706 None => ArchiveReadOptions::new(),
707 }
708}
709
710pub fn extract_volumes_to<F>(archives: &[Archive], password: Option<&[u8]>, open: F) -> Result<()>
712where
713 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
714{
715 extract_volumes_to_with_options(archives, read_options(password), open)
716}
717
718pub fn extract_volumes_to_with_options<F>(
720 archives: &[Archive],
721 options: ArchiveReadOptions<'_>,
722 mut open: F,
723) -> Result<()>
724where
725 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
726{
727 let Some(first) = archives.first() else {
728 return Err(Error::InvalidHeader("volume set is empty"));
729 };
730
731 match first.family() {
732 ArchiveFamily::Rar13 => {
733 let typed = rar13_volumes(archives)?;
734 rar13::extract_volumes_to(&typed, options.password, |meta| open(&rar13_meta(meta)))
735 }
736 ArchiveFamily::Rar15To40 => {
737 let typed = rar15_40_volumes(archives)?;
738 rar15_40::extract_volumes_to(&typed, options, |meta| open(&rar15_40_meta(meta)))
739 }
740 ArchiveFamily::Rar50Plus => {
741 let typed = rar50_volumes(archives)?;
742 rar50::extract_volumes_to(&typed, options, |meta| open(&rar50_meta(meta)))
743 }
744 }
745}
746
747fn rar13_meta(meta: &rar13::ExtractedEntryMeta) -> ExtractedEntryMeta {
748 ExtractedEntryMeta {
749 name: meta.name.clone(),
750 file_time: meta.file_time,
751 file_attr: u64::from(meta.file_attr),
752 is_directory: meta.is_directory,
753 }
754}
755
756fn rar15_40_meta(meta: &rar15_40::ExtractedEntryMeta) -> ExtractedEntryMeta {
757 ExtractedEntryMeta {
758 name: meta.name.clone(),
759 file_time: meta.file_time,
760 file_attr: u64::from(meta.attr),
761 is_directory: meta.is_directory,
762 }
763}
764
765fn rar50_meta(meta: &rar50::ExtractedEntryMeta) -> ExtractedEntryMeta {
766 ExtractedEntryMeta {
767 name: meta.name.clone(),
768 file_time: meta.file_time,
769 file_attr: meta.attr,
770 is_directory: meta.is_directory,
771 }
772}
773
774fn rar13_volumes(archives: &[Archive]) -> Result<Vec<rar13::Archive>> {
775 archives
776 .iter()
777 .map(|archive| match archive {
778 Archive::Rar13(archive) => Ok(archive.clone()),
779 Archive::Rar15To40(_) | Archive::Rar50Plus(_) => {
780 Err(Error::InvalidHeader("mixed archive families in volume set"))
781 }
782 })
783 .collect()
784}
785
786fn rar15_40_volumes(archives: &[Archive]) -> Result<Vec<rar15_40::Archive>> {
787 archives
788 .iter()
789 .map(|archive| match archive {
790 Archive::Rar15To40(archive) => Ok(archive.clone()),
791 Archive::Rar13(_) | Archive::Rar50Plus(_) => {
792 Err(Error::InvalidHeader("mixed archive families in volume set"))
793 }
794 })
795 .collect()
796}
797
798fn rar50_volumes(archives: &[Archive]) -> Result<Vec<rar50::Archive>> {
799 archives
800 .iter()
801 .map(|archive| match archive {
802 Archive::Rar50Plus(archive) => Ok(archive.clone()),
803 Archive::Rar13(_) | Archive::Rar15To40(_) => {
804 Err(Error::InvalidHeader("mixed archive families in volume set"))
805 }
806 })
807 .collect()
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813 use std::cell::RefCell;
814 use std::path::{Path, PathBuf};
815 use std::rc::Rc;
816
817 struct CollectWriter {
818 data: Rc<RefCell<Vec<u8>>>,
819 }
820
821 #[derive(Debug, Clone, PartialEq, Eq)]
822 struct CollectedEntry {
823 name: Vec<u8>,
824 data: Vec<u8>,
825 file_time: u32,
826 file_attr: u64,
827 is_directory: bool,
828 }
829
830 fn deterministic_noise(len: usize) -> Vec<u8> {
831 let mut state = 0x1234_5678u32;
832 (0..len)
833 .map(|_| {
834 state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
835 (state >> 24) as u8
836 })
837 .collect()
838 }
839
840 fn rar15_40_fixture(name: &str) -> PathBuf {
841 Path::new(env!("CARGO_MANIFEST_DIR"))
842 .join("tests/fixtures/rar15_40")
843 .join(name)
844 }
845
846 #[test]
847 fn extracted_entry_meta_exposes_raw_and_lossy_names() {
848 let meta = ExtractedEntryMeta {
849 name: vec![0xff, b'.', b't', b'x', b't'],
850 file_time: 0,
851 file_attr: 0,
852 is_directory: false,
853 };
854
855 assert_eq!(meta.name_bytes(), [0xff, b'.', b't', b'x', b't']);
856 assert_eq!(meta.name_lossy(), "\u{fffd}.txt");
857 }
858
859 impl Write for CollectWriter {
860 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
861 self.data.borrow_mut().extend_from_slice(buf);
862 Ok(buf.len())
863 }
864
865 fn flush(&mut self) -> std::io::Result<()> {
866 Ok(())
867 }
868 }
869
870 fn collect_extract(archive: &Archive, password: Option<&[u8]>) -> Result<Vec<CollectedEntry>> {
871 let entries = RefCell::new(Vec::new());
872 archive.extract_to(password, |meta| {
873 let data = Rc::new(RefCell::new(Vec::new()));
874 entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
875 Ok(Box::new(CollectWriter { data }))
876 })?;
877 Ok(entries
878 .into_inner()
879 .into_iter()
880 .map(|(meta, data)| CollectedEntry {
881 name: meta.name,
882 data: data.borrow().clone(),
883 file_time: meta.file_time,
884 file_attr: meta.file_attr,
885 is_directory: meta.is_directory,
886 })
887 .collect())
888 }
889
890 fn collect_rar15_40(archive: &rar15_40::Archive) -> Result<Vec<CollectedEntry>> {
891 let entries = RefCell::new(Vec::new());
892 archive.extract_to(ArchiveReadOptions::default(), |meta| {
893 let data = Rc::new(RefCell::new(Vec::new()));
894 entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
895 Ok(Box::new(CollectWriter { data }))
896 })?;
897 Ok(entries
898 .into_inner()
899 .into_iter()
900 .map(|(meta, data)| CollectedEntry {
901 name: meta.name,
902 data: data.borrow().clone(),
903 file_time: meta.file_time,
904 file_attr: u64::from(meta.attr),
905 is_directory: meta.is_directory,
906 })
907 .collect())
908 }
909
910 fn collect_rar15_40_volumes(
911 archives: &[rar15_40::Archive],
912 password: Option<&[u8]>,
913 ) -> Result<Vec<CollectedEntry>> {
914 let entries = RefCell::new(Vec::new());
915 rar15_40::extract_volumes_to(archives, read_options(password), |meta| {
916 let data = Rc::new(RefCell::new(Vec::new()));
917 entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
918 Ok(Box::new(CollectWriter { data }))
919 })?;
920 Ok(entries
921 .into_inner()
922 .into_iter()
923 .map(|(meta, data)| CollectedEntry {
924 name: meta.name,
925 data: data.borrow().clone(),
926 file_time: meta.file_time,
927 file_attr: u64::from(meta.attr),
928 is_directory: meta.is_directory,
929 })
930 .collect())
931 }
932
933 fn collect_rar50_volumes(
934 archives: &[rar50::Archive],
935 password: Option<&[u8]>,
936 ) -> Result<Vec<CollectedEntry>> {
937 let entries = RefCell::new(Vec::new());
938 rar50::extract_volumes_to(archives, read_options(password), |meta| {
939 let data = Rc::new(RefCell::new(Vec::new()));
940 entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
941 Ok(Box::new(CollectWriter { data }))
942 })?;
943 Ok(entries
944 .into_inner()
945 .into_iter()
946 .map(|(meta, data)| CollectedEntry {
947 name: meta.name,
948 data: data.borrow().clone(),
949 file_time: meta.file_time,
950 file_attr: meta.attr,
951 is_directory: meta.is_directory,
952 })
953 .collect())
954 }
955
956 fn collect_rar50_file(
957 archive: &rar50::Archive,
958 file: &rar50::FileHeader,
959 ) -> Result<CollectedEntry> {
960 let meta = file.metadata();
961 let data = Rc::new(RefCell::new(Vec::new()));
962 file.write_to(
963 archive,
964 None,
965 &mut CollectWriter {
966 data: Rc::clone(&data),
967 },
968 )?;
969 let data = data.borrow().clone();
970 Ok(CollectedEntry {
971 name: meta.name,
972 data,
973 file_time: meta.file_time,
974 file_attr: meta.attr,
975 is_directory: meta.is_directory,
976 })
977 }
978
979 fn rar13_options(target: ArchiveVersion) -> rar13::WriterOptions {
980 rar13::WriterOptions::new(target, FeatureSet::store_only())
981 }
982
983 fn rar15_options(target: ArchiveVersion) -> rar15_40::WriterOptions {
984 rar15_options_with_features(target, FeatureSet::store_only())
985 }
986
987 fn rar15_options_with_features(
988 target: ArchiveVersion,
989 features: FeatureSet,
990 ) -> rar15_40::WriterOptions {
991 rar15_40::WriterOptions::new(target, features)
992 }
993
994 fn rar50_options(target: ArchiveVersion) -> rar50::WriterOptions {
995 rar50_options_with_features(target, FeatureSet::store_only())
996 }
997
998 fn rar50_options_with_features(
999 target: ArchiveVersion,
1000 features: FeatureSet,
1001 ) -> rar50::WriterOptions {
1002 rar50::WriterOptions::new(target, features)
1003 }
1004
1005 fn rar50_entry(name: &[u8], data: &[u8]) -> rar50::ArchiveEntry {
1007 rar50::ArchiveEntry::new(
1008 name.to_vec(),
1009 EntrySource::from_bytes(std::sync::Arc::<[u8]>::from(data.to_vec())),
1010 )
1011 }
1012
1013 fn write_rar50_volume_set(
1014 entries: &[rar50::ArchiveEntry],
1015 options: rar50::WriterOptions,
1016 max_payload_per_volume: u64,
1017 recovery_percent: Option<u64>,
1018 ) -> Vec<Vec<u8>> {
1019 let mut sink = rar50::CollectedVolumes::new();
1020 rar50::write_streaming_volumes_to(
1021 entries,
1022 options,
1023 rar50::ArchiveExtras::default().with_recovery_percent(recovery_percent),
1024 max_payload_per_volume,
1025 &mut sink,
1026 &WriterResources::default(),
1027 )
1028 .unwrap();
1029 sink.take()
1030 }
1031
1032 fn write_rar29_filter(
1033 options: rar15_40::WriterOptions,
1034 entries: &[rar15_40::FileEntry<'_>],
1035 kind: rar15_40::FilterKind,
1036 ) -> Result<Vec<u8>> {
1037 rar15_40::write_rar29_compressed_archive_with_filter_policy(
1038 entries,
1039 options,
1040 rar15_40::FilterPolicy::Explicit(rar15_40::FilterSpec::whole(kind)),
1041 )
1042 }
1043
1044 fn write_rar29_filter_range(
1045 options: rar15_40::WriterOptions,
1046 entries: &[rar15_40::FileEntry<'_>],
1047 kind: rar15_40::FilterKind,
1048 range: std::ops::Range<usize>,
1049 ) -> Result<Vec<u8>> {
1050 rar15_40::write_rar29_compressed_archive_with_filter_policy(
1051 entries,
1052 options,
1053 rar15_40::FilterPolicy::Explicit(rar15_40::FilterSpec::range(kind, range)),
1054 )
1055 }
1056
1057 fn assert_rar50_volume_recovery_records(archives: &[rar50::Archive], percent: u64) {
1058 assert!(archives.iter().all(|archive| archive.main.is_volume()));
1059 assert!(archives
1060 .iter()
1061 .all(|archive| archive.main.has_recovery_record()));
1062 for archive in archives {
1063 let service = archive.services().next().unwrap();
1064 assert_eq!(service.name, b"RR");
1065 assert_eq!(service.recovery_record().unwrap().unwrap().percent, percent);
1066 let data = collect_rar50_file(archive, service).unwrap().data;
1067 assert!(data.starts_with(b"{RB}"));
1068 assert_eq!(
1069 u32::from_le_bytes(data[0x0c..0x10].try_into().unwrap()) as usize,
1070 data.len()
1071 );
1072 }
1073 }
1074
1075 #[test]
1076 fn direct_writer_creates_rar15_stored_archive() {
1077 let bytes = rar15_40::write_stored_archive(
1078 &[rar15_40::StoredEntry {
1079 name: b"hello.txt",
1080 data: b"hello via facade\n",
1081 file_time: 0,
1082 file_attr: 0x20,
1083 host_os: 3,
1084 password: None,
1085 file_comment: None,
1086 }],
1087 rar15_options(ArchiveVersion::Rar15),
1088 )
1089 .unwrap();
1090
1091 let archive = ArchiveReader::read(&bytes).unwrap();
1092 assert_eq!(archive.family(), ArchiveFamily::Rar15To40);
1093 let extracted = collect_extract(&archive, None).unwrap();
1094 assert_eq!(extracted.len(), 1);
1095 assert_eq!(extracted[0].data, b"hello via facade\n");
1096 }
1097
1098 #[test]
1099 fn archive_reader_accepts_owned_buffers_without_changing_dispatch() {
1100 let rar13_bytes = rar13::write_stored_archive(
1101 &[rar13::StoredEntry {
1102 name: b"old.txt",
1103 data: b"owned rar13\n",
1104 file_time: 0,
1105 file_attr: 0x20,
1106 password: None,
1107 file_comment: None,
1108 }],
1109 rar13_options(ArchiveVersion::Rar14),
1110 )
1111 .unwrap();
1112 let rar13_archive = ArchiveReader::read_owned(rar13_bytes).unwrap();
1113 assert_eq!(rar13_archive.family(), ArchiveFamily::Rar13);
1114 assert_eq!(
1115 collect_extract(&rar13_archive, None).unwrap()[0].data,
1116 b"owned rar13\n"
1117 );
1118
1119 let rar15_bytes = rar15_40::write_stored_archive(
1120 &[rar15_40::StoredEntry {
1121 name: b"mid.txt",
1122 data: b"owned rar15\n",
1123 file_time: 0,
1124 file_attr: 0x20,
1125 host_os: 3,
1126 password: None,
1127 file_comment: None,
1128 }],
1129 rar15_options(ArchiveVersion::Rar15),
1130 )
1131 .unwrap();
1132 let rar15_archive = ArchiveReader::read_owned(rar15_bytes).unwrap();
1133 assert_eq!(rar15_archive.family(), ArchiveFamily::Rar15To40);
1134 assert_eq!(
1135 collect_extract(&rar15_archive, None).unwrap()[0].data,
1136 b"owned rar15\n"
1137 );
1138
1139 let rar50_bytes =
1140 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
1141 .entries(
1142 [rar50_entry(b"new.txt", b"owned rar50\n")
1143 .with_attributes(0x20)
1144 .with_host_os(3)]
1145 .to_vec(),
1146 )
1147 .finish()
1148 .unwrap();
1149 let rar50_archive = ArchiveReader::read_owned(rar50_bytes).unwrap();
1150 assert_eq!(rar50_archive.family(), ArchiveFamily::Rar50Plus);
1151 assert_eq!(
1152 collect_extract(&rar50_archive, None).unwrap()[0].data,
1153 b"owned rar50\n"
1154 );
1155 }
1156
1157 #[test]
1158 fn direct_writer_keeps_rar13_methods_version_typed() {
1159 let err =
1160 rar13::write_stored_archive(&[], rar13_options(ArchiveVersion::Rar15)).unwrap_err();
1161
1162 assert!(matches!(
1163 err,
1164 Error::UnsupportedVersion(ArchiveVersion::Rar15)
1165 ));
1166 }
1167
1168 #[test]
1169 fn archive_members_exposes_rar13_common_metadata_and_typed_detail() {
1170 let bytes = rar13::write_stored_archive(
1171 &[rar13::StoredEntry {
1172 name: b"old.txt",
1173 data: b"old rar member",
1174 file_time: 0x1234_5678,
1175 file_attr: 0x20,
1176 password: None,
1177 file_comment: Some(b"note"),
1178 }],
1179 rar13_options(ArchiveVersion::Rar14),
1180 )
1181 .unwrap();
1182
1183 let archive = ArchiveReader::read(&bytes).unwrap();
1184 let members: Vec<_> = archive.members().collect();
1185
1186 assert_eq!(members.len(), 1);
1187 assert_eq!(members[0].meta.family, ArchiveFamily::Rar13);
1188 assert_eq!(members[0].meta.name, b"old.txt");
1189 assert_eq!(members[0].meta.name_bytes(), b"old.txt");
1190 assert_eq!(members[0].meta.name_lossy(), "old.txt");
1191 assert_eq!(members[0].meta.packed_size, b"old rar member".len() as u64);
1192 assert_eq!(
1193 members[0].meta.unpacked_size,
1194 b"old rar member".len() as u64
1195 );
1196 assert_eq!(members[0].meta.file_time, Some(0x1234_5678));
1197 assert_eq!(members[0].meta.file_attr, 0x20);
1198 assert_eq!(members[0].meta.host_os, None);
1199 assert!(members[0].meta.is_stored);
1200 assert!(!members[0].meta.is_encrypted);
1201 assert!(!members[0].meta.is_split_before);
1202 assert!(!members[0].meta.is_split_after);
1203 assert!(matches!(
1204 members[0].detail,
1205 ArchiveMemberDetail::Rar13 {
1206 method: 0,
1207 unpack_version: _,
1208 file_checksum: _,
1209 has_file_comment: true,
1210 }
1211 ));
1212 }
1213
1214 #[test]
1215 fn archive_members_exposes_rar15_40_common_metadata_and_typed_detail() {
1216 let features = FeatureSet::store_only();
1217 let payload = b"rar 2.9 member metadata ".repeat(32);
1218 let bytes = rar15_40::write_compressed_archive(
1219 &[rar15_40::FileEntry {
1220 name: b"newer.txt",
1221 data: &payload,
1222 file_time: 0x0102_0304,
1223 file_attr: 0x20,
1224 host_os: 2,
1225 password: None,
1226 file_comment: Some(b"rar29 note"),
1227 }],
1228 rar15_options_with_features(ArchiveVersion::Rar29, features),
1229 )
1230 .unwrap();
1231
1232 let archive = ArchiveReader::read(&bytes).unwrap();
1233 let members: Vec<_> = archive.members().collect();
1234
1235 assert_eq!(members.len(), 1);
1236 assert_eq!(members[0].meta.family, ArchiveFamily::Rar15To40);
1237 assert_eq!(members[0].meta.name, b"newer.txt");
1238 assert_eq!(members[0].meta.unpacked_size, payload.len() as u64);
1239 assert_eq!(members[0].meta.file_time, Some(0x0102_0304));
1240 assert_eq!(members[0].meta.file_attr, 0x20);
1241 assert_eq!(members[0].meta.host_os, Some(2));
1242 assert!(!members[0].meta.is_stored);
1243 assert!(!members[0].meta.is_encrypted);
1244 assert!(matches!(
1245 members[0].detail,
1246 ArchiveMemberDetail::Rar15To40 {
1247 method: 0x33 | 0x35,
1248 unpack_version: 29,
1249 crc32: _,
1250 solid: false,
1251 salt: None,
1252 has_file_comment: true,
1253 }
1254 ));
1255 }
1256
1257 #[test]
1258 fn archive_members_exposes_rar50_common_metadata_and_typed_detail() {
1259 let bytes =
1260 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
1261 .entries(
1262 [rar50_entry(b"five.txt", b"rar 5 member metadata")
1263 .with_mtime(Some(0x1111_2222))
1264 .with_attributes(0x1_0000_0020)
1265 .with_host_os(3)]
1266 .to_vec(),
1267 )
1268 .finish()
1269 .unwrap();
1270
1271 let archive = ArchiveReader::read(&bytes).unwrap();
1272 let members: Vec<_> = archive.members().collect();
1273
1274 assert_eq!(members.len(), 1);
1275 assert_eq!(members[0].meta.family, ArchiveFamily::Rar50Plus);
1276 assert_eq!(members[0].meta.name, b"five.txt");
1277 assert_eq!(
1278 members[0].meta.packed_size,
1279 b"rar 5 member metadata".len() as u64
1280 );
1281 assert_eq!(
1282 members[0].meta.unpacked_size,
1283 b"rar 5 member metadata".len() as u64
1284 );
1285 assert_eq!(members[0].meta.file_time, Some(0x1111_2222));
1286 assert_eq!(members[0].meta.file_attr, 0x1_0000_0020);
1287 assert_eq!(members[0].meta.host_os, Some(3));
1288 assert!(members[0].meta.is_stored);
1289 assert!(!members[0].meta.is_encrypted);
1290 assert!(matches!(
1291 members[0].detail,
1292 ArchiveMemberDetail::Rar50Plus {
1293 compression_info: _,
1294 crc32: _,
1295 hash: _,
1296 }
1297 ));
1298 }
1299
1300 #[test]
1301 fn extraction_metadata_preserves_rar50_u64_file_attributes() {
1302 let bytes =
1303 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
1304 .entries(
1305 [rar50_entry(b"wide-attrs.txt", b"wide RAR5 file attributes")
1306 .with_mtime(Some(0))
1307 .with_attributes(0x1_0000_0020)
1308 .with_host_os(3)]
1309 .to_vec(),
1310 )
1311 .finish()
1312 .unwrap();
1313
1314 let archive = ArchiveReader::read(&bytes).unwrap();
1315 let extracted = collect_extract(&archive, None).unwrap();
1316
1317 assert_eq!(extracted.len(), 1);
1318 assert_eq!(extracted[0].name, b"wide-attrs.txt");
1319 assert_eq!(extracted[0].file_attr, 0x1_0000_0020);
1320 }
1321
1322 #[test]
1323 fn direct_writer_creates_rar15_compressed_archive() {
1324 let bytes = rar15_40::write_compressed_archive(
1325 &[rar15_40::FileEntry {
1326 name: b"text.txt",
1327 data: b"facade compressed facade compressed facade compressed\n",
1328 file_time: 0,
1329 file_attr: 0x20,
1330 host_os: 3,
1331 password: None,
1332 file_comment: None,
1333 }],
1334 rar15_options(ArchiveVersion::Rar15),
1335 )
1336 .unwrap();
1337
1338 let archive = ArchiveReader::read(&bytes).unwrap();
1339 assert_eq!(archive.family(), ArchiveFamily::Rar15To40);
1340 let extracted = collect_extract(&archive, None).unwrap();
1341 assert_eq!(
1342 extracted[0].data,
1343 b"facade compressed facade compressed facade compressed\n"
1344 );
1345 }
1346
1347 #[test]
1348 fn direct_writer_creates_rar29_compressed_archive_with_default_auto_policy() {
1349 let payload =
1350 b"facade rar29 default auto text alpha beta gamma alpha beta gamma\n".repeat(256);
1351 let bytes = rar15_40::write_compressed_archive(
1352 &[rar15_40::FileEntry {
1353 name: b"rar29-default-auto.txt",
1354 data: &payload,
1355 file_time: 0,
1356 file_attr: 0x20,
1357 host_os: 3,
1358 password: None,
1359 file_comment: None,
1360 }],
1361 rar15_options(ArchiveVersion::Rar29),
1362 )
1363 .unwrap();
1364
1365 let archive = ArchiveReader::read(&bytes).unwrap();
1366 let raw = archive.as_rar15_40().unwrap();
1367 assert_eq!(raw.files().next().unwrap().method, 0x33);
1372 let extracted = collect_extract(&archive, None).unwrap();
1373 assert_eq!(extracted[0].data, payload);
1374 }
1375
1376 #[test]
1377 fn direct_writer_creates_rar29_e8_filtered_compressed_archive() {
1378 let payload = b"\xe8\0\0\0\0facade rar29 e8 filter payload\n".repeat(12);
1379 let bytes = write_rar29_filter(
1380 rar15_options(ArchiveVersion::Rar29),
1381 &[rar15_40::FileEntry {
1382 name: b"rar29-e8.bin",
1383 data: &payload,
1384 file_time: 0,
1385 file_attr: 0x20,
1386 host_os: 3,
1387 password: None,
1388 file_comment: None,
1389 }],
1390 rar15_40::FilterKind::E8,
1391 )
1392 .unwrap();
1393
1394 let archive = ArchiveReader::read(&bytes).unwrap();
1395 let extracted = collect_extract(&archive, None).unwrap();
1396 assert_eq!(extracted[0].data, payload);
1397 }
1398
1399 #[test]
1400 fn direct_writer_creates_rar29_auto_filtered_compressed_archive() {
1401 let payload = b"\xe8\0\0\0\0facade rar29 auto filter payload\n".repeat(12);
1402 let bytes = rar15_40::write_rar29_compressed_archive_with_filter_policy(
1403 &[rar15_40::FileEntry {
1404 name: b"rar29-auto.bin",
1405 data: &payload,
1406 file_time: 0,
1407 file_attr: 0x20,
1408 host_os: 3,
1409 password: None,
1410 file_comment: None,
1411 }],
1412 rar15_options(ArchiveVersion::Rar29),
1413 rar15_40::FilterPolicy::Auto,
1414 )
1415 .unwrap();
1416
1417 let archive = ArchiveReader::read(&bytes).unwrap();
1418 let extracted = collect_extract(&archive, None).unwrap();
1419 assert_eq!(extracted[0].data, payload);
1420 }
1421
1422 #[test]
1423 fn direct_writer_creates_rar29_ppmd_compressed_archive() {
1424 let payload = b"facade rar29 ppmd text payload alpha beta gamma\n".repeat(64);
1425 let bytes = rar15_40::write_rar29_compressed_archive_with_filter_policy(
1426 &[rar15_40::FileEntry {
1427 name: b"rar29-ppmd.txt",
1428 data: &payload,
1429 file_time: 0,
1430 file_attr: 0x20,
1431 host_os: 3,
1432 password: None,
1433 file_comment: None,
1434 }],
1435 rar15_options(ArchiveVersion::Rar29).with_method(rar15_40::Rar29Method::Ppmd),
1436 rar15_40::FilterPolicy::None,
1437 )
1438 .unwrap();
1439
1440 let archive = ArchiveReader::read(&bytes).unwrap();
1441 let raw = archive.as_rar15_40().unwrap();
1442 let file = raw.files().next().unwrap();
1443 assert_eq!(file.method, 0x33);
1446 assert_eq!(collect_extract(&archive, None).unwrap()[0].data, payload);
1447 }
1448
1449 #[test]
1450 fn direct_writer_creates_rar29_segmented_e8_filtered_compressed_archive() {
1451 let mut payload = b"facade unfiltered prefix before x86 segment ".to_vec();
1452 let filter_start = payload.len();
1453 payload.extend_from_slice(b"\xe8\0\0\0\0facade segmented e8 filter payload\n");
1454 let filter_end = payload.len();
1455 payload.extend_from_slice(b"facade unfiltered suffix after x86 segment\n");
1456 let bytes = write_rar29_filter_range(
1457 rar15_options(ArchiveVersion::Rar29),
1458 &[rar15_40::FileEntry {
1459 name: b"rar29-segmented-e8.bin",
1460 data: &payload,
1461 file_time: 0,
1462 file_attr: 0x20,
1463 host_os: 3,
1464 password: None,
1465 file_comment: None,
1466 }],
1467 rar15_40::FilterKind::E8,
1468 filter_start..filter_end,
1469 )
1470 .unwrap();
1471
1472 let archive = ArchiveReader::read(&bytes).unwrap();
1473 let extracted = collect_extract(&archive, None).unwrap();
1474 assert_eq!(extracted[0].data, payload);
1475 }
1476
1477 #[test]
1478 fn direct_writer_creates_rar29_solid_e8_filtered_compressed_archive() {
1479 let first = b"\xe8\0\0\0\0facade rar29 solid e8 first payload\n".repeat(12);
1480 let second = b"\xe8\0\0\0\0facade rar29 solid e8 second payload\n".repeat(12);
1481 let mut features = FeatureSet::store_only();
1482 features.solid = true;
1483 let bytes = write_rar29_filter(
1484 rar15_options_with_features(ArchiveVersion::Rar29, features),
1485 &[
1486 rar15_40::FileEntry {
1487 name: b"rar29-solid-e8-first.bin",
1488 data: &first,
1489 file_time: 0,
1490 file_attr: 0x20,
1491 host_os: 3,
1492 password: None,
1493 file_comment: None,
1494 },
1495 rar15_40::FileEntry {
1496 name: b"rar29-solid-e8-second.bin",
1497 data: &second,
1498 file_time: 0,
1499 file_attr: 0x20,
1500 host_os: 3,
1501 password: None,
1502 file_comment: None,
1503 },
1504 ],
1505 rar15_40::FilterKind::E8,
1506 )
1507 .unwrap();
1508
1509 let archive = ArchiveReader::read(&bytes).unwrap();
1510 let raw = archive.as_rar15_40().unwrap();
1511 let files: Vec<_> = raw.files().collect();
1512 assert!(raw.main.is_solid());
1513 assert!(!files[0].is_solid());
1514 assert!(files[1].is_solid());
1515 let extracted = collect_extract(&archive, None).unwrap();
1516 assert_eq!(extracted[0].data, first);
1517 assert_eq!(extracted[1].data, second);
1518 }
1519
1520 #[test]
1521 fn direct_writer_creates_rar29_encrypted_e8_filtered_compressed_archive() {
1522 let payload = b"\xe8\0\0\0\0facade rar29 encrypted e8 payload\n".repeat(12);
1523 let features = FeatureSet::store_only();
1524 let bytes = write_rar29_filter(
1525 rar15_options_with_features(ArchiveVersion::Rar29, features),
1526 &[rar15_40::FileEntry {
1527 name: b"rar29-encrypted-e8.bin",
1528 data: &payload,
1529 file_time: 0,
1530 file_attr: 0x20,
1531 host_os: 3,
1532 password: Some(b"password"),
1533 file_comment: None,
1534 }],
1535 rar15_40::FilterKind::E8,
1536 )
1537 .unwrap();
1538
1539 let archive = ArchiveReader::read(&bytes).unwrap();
1540 let raw = archive.as_rar15_40().unwrap();
1541 let file = raw.files().next().unwrap();
1542 assert!(file.is_encrypted());
1543 assert!(file.salt.is_some());
1544 assert!(matches!(
1545 collect_extract(&archive, Some(b"wrong")),
1546 Err(Error::WrongPasswordOrCorruptData)
1547 ));
1548 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
1549 assert_eq!(extracted[0].data, payload);
1550 }
1551
1552 #[test]
1553 fn direct_writer_creates_rar30_header_encrypted_e8_filtered_compressed_archive() {
1554 let payload = b"\xe8\0\0\0\0facade rar30 header encrypted e8 payload\n".repeat(12);
1555 let mut features = FeatureSet::store_only();
1556 features.header_encryption = true;
1557 let bytes = write_rar29_filter(
1558 rar15_options_with_features(ArchiveVersion::Rar30, features),
1559 &[rar15_40::FileEntry {
1560 name: b"rar30-header-encrypted-e8.bin",
1561 data: &payload,
1562 file_time: 0,
1563 file_attr: 0x20,
1564 host_os: 3,
1565 password: Some(b"password"),
1566 file_comment: None,
1567 }],
1568 rar15_40::FilterKind::E8,
1569 )
1570 .unwrap();
1571
1572 assert!(matches!(
1573 ArchiveReader::read(&bytes),
1574 Err(Error::NeedPassword)
1575 ));
1576 let archive = ArchiveReader::read_with_options(
1577 &bytes,
1578 ArchiveReadOptions::with_password(b"password"),
1579 )
1580 .unwrap();
1581 let raw = archive.as_rar15_40().unwrap();
1582 assert!(raw.main.has_encrypted_headers());
1583 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
1584 assert_eq!(extracted[0].data, payload);
1585 }
1586
1587 #[test]
1588 fn direct_writer_creates_rar29_e8e9_filtered_compressed_archive() {
1589 let payload = b"\xe9\0\0\0\0facade rar29 e8e9 filter payload\n".repeat(12);
1590 let bytes = write_rar29_filter(
1591 rar15_options(ArchiveVersion::Rar29),
1592 &[rar15_40::FileEntry {
1593 name: b"rar29-e8e9.bin",
1594 data: &payload,
1595 file_time: 0,
1596 file_attr: 0x20,
1597 host_os: 3,
1598 password: None,
1599 file_comment: None,
1600 }],
1601 rar15_40::FilterKind::E8E9,
1602 )
1603 .unwrap();
1604
1605 let archive = ArchiveReader::read(&bytes).unwrap();
1606 let extracted = collect_extract(&archive, None).unwrap();
1607 assert_eq!(extracted[0].data, payload);
1608 }
1609
1610 #[test]
1611 fn direct_writer_creates_rar29_delta_filtered_compressed_archive() {
1612 let payload: Vec<u8> = (0..384).map(|index| (index * 19 + 5) as u8).collect();
1613 let bytes = write_rar29_filter(
1614 rar15_options(ArchiveVersion::Rar29),
1615 &[rar15_40::FileEntry {
1616 name: b"rar29-delta.bin",
1617 data: &payload,
1618 file_time: 0,
1619 file_attr: 0x20,
1620 host_os: 3,
1621 password: None,
1622 file_comment: None,
1623 }],
1624 rar15_40::FilterKind::Delta { channels: 3 },
1625 )
1626 .unwrap();
1627
1628 let archive = ArchiveReader::read(&bytes).unwrap();
1629 let extracted = collect_extract(&archive, None).unwrap();
1630 assert_eq!(extracted[0].data, payload);
1631 }
1632
1633 #[test]
1634 fn direct_writer_creates_rar29_segmented_delta_filtered_compressed_archive() {
1635 let mut payload = b"facade unfiltered prefix before delta segment ".to_vec();
1636 let filter_start = payload.len();
1637 payload.extend((0..384).map(|index| (index * 19 + 5) as u8));
1638 let filter_end = payload.len();
1639 payload.extend_from_slice(b"facade unfiltered suffix after delta segment\n");
1640 let bytes = write_rar29_filter_range(
1641 rar15_options(ArchiveVersion::Rar29),
1642 &[rar15_40::FileEntry {
1643 name: b"rar29-segmented-delta.bin",
1644 data: &payload,
1645 file_time: 0,
1646 file_attr: 0x20,
1647 host_os: 3,
1648 password: None,
1649 file_comment: None,
1650 }],
1651 rar15_40::FilterKind::Delta { channels: 3 },
1652 filter_start..filter_end,
1653 )
1654 .unwrap();
1655
1656 let archive = ArchiveReader::read(&bytes).unwrap();
1657 let extracted = collect_extract(&archive, None).unwrap();
1658 assert_eq!(extracted[0].data, payload);
1659 }
1660
1661 #[test]
1662 fn direct_writer_creates_rar29_itanium_filtered_compressed_archive() {
1663 let mut payload = vec![0u8; 48];
1664 payload[16] = 22;
1665 payload[21] = 20;
1666 payload.extend_from_slice(b"facade rar29 itanium filter payload\n");
1667 let bytes = write_rar29_filter(
1668 rar15_options(ArchiveVersion::Rar29),
1669 &[rar15_40::FileEntry {
1670 name: b"rar29-itanium.bin",
1671 data: &payload,
1672 file_time: 0,
1673 file_attr: 0x20,
1674 host_os: 3,
1675 password: None,
1676 file_comment: None,
1677 }],
1678 rar15_40::FilterKind::Itanium,
1679 )
1680 .unwrap();
1681
1682 let archive = ArchiveReader::read(&bytes).unwrap();
1683 let extracted = collect_extract(&archive, None).unwrap();
1684 assert_eq!(extracted[0].data, payload);
1685 }
1686
1687 #[test]
1688 fn direct_writer_creates_rar29_segmented_itanium_filtered_compressed_archive() {
1689 let mut payload = b"facade unfiltered prefix before itanium segment ".to_vec();
1690 let filter_start = payload.len();
1691 payload.extend_from_slice(&[0; 48]);
1692 payload[filter_start + 16] = 22;
1693 payload[filter_start + 21] = 20;
1694 payload.extend_from_slice(b"facade segmented itanium filter payload\n");
1695 let filter_end = payload.len();
1696 payload.extend_from_slice(b"facade unfiltered suffix after itanium segment\n");
1697 let bytes = write_rar29_filter_range(
1698 rar15_options(ArchiveVersion::Rar29),
1699 &[rar15_40::FileEntry {
1700 name: b"rar29-segmented-itanium.bin",
1701 data: &payload,
1702 file_time: 0,
1703 file_attr: 0x20,
1704 host_os: 3,
1705 password: None,
1706 file_comment: None,
1707 }],
1708 rar15_40::FilterKind::Itanium,
1709 filter_start..filter_end,
1710 )
1711 .unwrap();
1712
1713 let archive = ArchiveReader::read(&bytes).unwrap();
1714 let extracted = collect_extract(&archive, None).unwrap();
1715 assert_eq!(extracted[0].data, payload);
1716 }
1717
1718 #[test]
1719 fn direct_writer_creates_rar29_rgb_filtered_compressed_archive() {
1720 let width = 12;
1721 let payload: Vec<u8> = (0..96).map(|index| (index * 37 + 17) as u8).collect();
1722 let bytes = write_rar29_filter(
1723 rar15_options(ArchiveVersion::Rar29),
1724 &[rar15_40::FileEntry {
1725 name: b"rar29-rgb.bin",
1726 data: &payload,
1727 file_time: 0,
1728 file_attr: 0x20,
1729 host_os: 3,
1730 password: None,
1731 file_comment: None,
1732 }],
1733 rar15_40::FilterKind::Rgb { width, pos_r: 0 },
1734 )
1735 .unwrap();
1736
1737 let archive = ArchiveReader::read(&bytes).unwrap();
1738 let extracted = collect_extract(&archive, None).unwrap();
1739 assert_eq!(extracted[0].data, payload);
1740 }
1741
1742 #[test]
1743 fn direct_writer_creates_rar29_segmented_rgb_filtered_compressed_archive() {
1744 let width = 12;
1745 let mut payload = b"facade unfiltered prefix before rgb segment ".to_vec();
1746 let filter_start = payload.len();
1747 payload.extend((0..96).map(|index| (index * 37 + 17) as u8));
1748 let filter_end = payload.len();
1749 payload.extend_from_slice(b"facade unfiltered suffix after rgb segment\n");
1750 let bytes = write_rar29_filter_range(
1751 rar15_options(ArchiveVersion::Rar29),
1752 &[rar15_40::FileEntry {
1753 name: b"rar29-segmented-rgb.bin",
1754 data: &payload,
1755 file_time: 0,
1756 file_attr: 0x20,
1757 host_os: 3,
1758 password: None,
1759 file_comment: None,
1760 }],
1761 rar15_40::FilterKind::Rgb { width, pos_r: 0 },
1762 filter_start..filter_end,
1763 )
1764 .unwrap();
1765
1766 let archive = ArchiveReader::read(&bytes).unwrap();
1767 let extracted = collect_extract(&archive, None).unwrap();
1768 assert_eq!(extracted[0].data, payload);
1769 }
1770
1771 #[test]
1772 fn direct_writer_creates_rar29_audio_filtered_compressed_archive() {
1773 let payload: Vec<u8> = (0..160)
1774 .map(|index| (index * 11 + index / 7) as u8)
1775 .collect();
1776 let bytes = write_rar29_filter(
1777 rar15_options(ArchiveVersion::Rar29),
1778 &[rar15_40::FileEntry {
1779 name: b"rar29-audio.bin",
1780 data: &payload,
1781 file_time: 0,
1782 file_attr: 0x20,
1783 host_os: 3,
1784 password: None,
1785 file_comment: None,
1786 }],
1787 rar15_40::FilterKind::Audio { channels: 2 },
1788 )
1789 .unwrap();
1790
1791 let archive = ArchiveReader::read(&bytes).unwrap();
1792 let extracted = collect_extract(&archive, None).unwrap();
1793 assert_eq!(extracted[0].data, payload);
1794 }
1795
1796 #[test]
1797 fn direct_writer_creates_rar29_segmented_audio_filtered_compressed_archive() {
1798 let mut payload = b"facade unfiltered prefix before audio segment ".to_vec();
1799 let filter_start = payload.len();
1800 payload.extend((0..160).map(|index| (index * 11 + index / 7) as u8));
1801 let filter_end = payload.len();
1802 payload.extend_from_slice(b"facade unfiltered suffix after audio segment\n");
1803 let bytes = write_rar29_filter_range(
1804 rar15_options(ArchiveVersion::Rar29),
1805 &[rar15_40::FileEntry {
1806 name: b"rar29-segmented-audio.bin",
1807 data: &payload,
1808 file_time: 0,
1809 file_attr: 0x20,
1810 host_os: 3,
1811 password: None,
1812 file_comment: None,
1813 }],
1814 rar15_40::FilterKind::Audio { channels: 2 },
1815 filter_start..filter_end,
1816 )
1817 .unwrap();
1818
1819 let archive = ArchiveReader::read(&bytes).unwrap();
1820 let extracted = collect_extract(&archive, None).unwrap();
1821 assert_eq!(extracted[0].data, payload);
1822 }
1823
1824 #[test]
1825 fn direct_writer_creates_rar20_compressed_archive() {
1826 let payload = b"facade rar20 literal compressed payload\n".repeat(32);
1827 let bytes = rar15_40::write_compressed_archive(
1828 &[rar15_40::FileEntry {
1829 name: b"rar20.txt",
1830 data: &payload,
1831 file_time: 0,
1832 file_attr: 0x20,
1833 host_os: 3,
1834 password: None,
1835 file_comment: None,
1836 }],
1837 rar15_options(ArchiveVersion::Rar20),
1838 )
1839 .unwrap();
1840
1841 let archive = ArchiveReader::read(&bytes).unwrap();
1842 assert_eq!(archive.family(), ArchiveFamily::Rar15To40);
1843 let raw = archive.as_rar15_40().unwrap();
1844 let file = raw.files().next().unwrap();
1845 assert_eq!(file.unp_ver, 20);
1846 assert_eq!(file.method, 0x33);
1847 assert_eq!(collect_extract(&archive, None).unwrap()[0].data, payload);
1848 }
1849
1850 #[test]
1851 fn direct_writer_creates_rar29_compressed_archive() {
1852 let payload = b"facade rar29 literal compressed payload\n".repeat(32);
1853 let bytes = rar15_40::write_compressed_archive(
1854 &[rar15_40::FileEntry {
1855 name: b"rar29.txt",
1856 data: &payload,
1857 file_time: 0,
1858 file_attr: 0x20,
1859 host_os: 3,
1860 password: None,
1861 file_comment: None,
1862 }],
1863 rar15_options(ArchiveVersion::Rar29),
1864 )
1865 .unwrap();
1866
1867 let archive = ArchiveReader::read(&bytes).unwrap();
1868 assert_eq!(archive.family(), ArchiveFamily::Rar15To40);
1869 let raw = archive.as_rar15_40().unwrap();
1870 let file = raw.files().next().unwrap();
1871 assert_eq!(file.unp_ver, 29);
1872 assert!(matches!(file.method, 0x33 | 0x35));
1873 assert_eq!(collect_extract(&archive, None).unwrap()[0].data, payload);
1874 }
1875
1876 #[test]
1877 fn direct_writer_creates_rar29_solid_compressed_archive() {
1878 let mut features = FeatureSet::store_only();
1879 features.solid = true;
1880 let bytes = rar15_40::write_compressed_archive(
1881 &[
1882 rar15_40::FileEntry {
1883 name: b"one.txt",
1884 data: b"facade rar29 solid one alpha beta\n",
1885 file_time: 0,
1886 file_attr: 0x20,
1887 host_os: 3,
1888 password: None,
1889 file_comment: None,
1890 },
1891 rar15_40::FileEntry {
1892 name: b"two.txt",
1893 data: b"facade rar29 solid two alpha beta\n",
1894 file_time: 0,
1895 file_attr: 0x20,
1896 host_os: 3,
1897 password: None,
1898 file_comment: None,
1899 },
1900 ],
1901 rar15_options_with_features(ArchiveVersion::Rar29, features),
1902 )
1903 .unwrap();
1904
1905 let archive = ArchiveReader::read(&bytes).unwrap();
1906 let raw = archive.as_rar15_40().unwrap();
1907 assert!(raw.main.is_solid());
1908 let files: Vec<_> = raw.files().collect();
1909 assert!(!files[0].is_solid());
1910 assert!(files[1].is_solid());
1911 let extracted = collect_extract(&archive, None).unwrap();
1912 assert_eq!(extracted[0].data, b"facade rar29 solid one alpha beta\n");
1913 assert_eq!(extracted[1].data, b"facade rar29 solid two alpha beta\n");
1914 }
1915
1916 #[test]
1917 fn direct_writer_creates_rar20_solid_compressed_archive() {
1918 let mut features = FeatureSet::store_only();
1919 features.solid = true;
1920 let first = b"facade rar20 solid shared line alpha beta gamma\n".repeat(48);
1921 let second = b"facade rar20 solid shared line alpha beta gamma\nsecond\n".repeat(24);
1922 let bytes = rar15_40::write_compressed_archive(
1923 &[
1924 rar15_40::FileEntry {
1925 name: b"one.txt",
1926 data: &first,
1927 file_time: 0,
1928 file_attr: 0x20,
1929 host_os: 3,
1930 password: None,
1931 file_comment: None,
1932 },
1933 rar15_40::FileEntry {
1934 name: b"two.txt",
1935 data: &second,
1936 file_time: 0,
1937 file_attr: 0x20,
1938 host_os: 3,
1939 password: None,
1940 file_comment: None,
1941 },
1942 ],
1943 rar15_options_with_features(ArchiveVersion::Rar20, features),
1944 )
1945 .unwrap();
1946
1947 let archive = ArchiveReader::read(&bytes).unwrap();
1948 let raw = archive.as_rar15_40().unwrap();
1949 assert!(raw.main.is_solid());
1950 let files: Vec<_> = raw.files().collect();
1951 assert_eq!(files[0].unp_ver, 20);
1952 assert_eq!(files[1].unp_ver, 20);
1953 assert!(!files[0].is_solid());
1954 assert!(files[1].is_solid());
1955 let extracted = collect_extract(&archive, None).unwrap();
1956 assert_eq!(extracted[0].data, first);
1957 assert_eq!(extracted[1].data, second);
1958 }
1959
1960 #[test]
1961 fn direct_writer_creates_rar15_archive_comment() {
1962 let features = FeatureSet::store_only();
1963 let bytes = rar15_40::write_compressed_archive_with_comment(
1964 &[rar15_40::FileEntry {
1965 name: b"commented.txt",
1966 data: b"facade commented payload\n",
1967 file_time: 0,
1968 file_attr: 0x20,
1969 host_os: 3,
1970 password: None,
1971 file_comment: None,
1972 }],
1973 rar15_options_with_features(ArchiveVersion::Rar15, features),
1974 Some(b"facade note\n"),
1975 )
1976 .unwrap();
1977
1978 let archive = ArchiveReader::read(&bytes).unwrap();
1979 let archive = archive.as_rar15_40().unwrap();
1980 assert_eq!(
1981 archive.archive_comment().unwrap().as_deref(),
1982 Some(&b"facade note\n"[..])
1983 );
1984 assert_eq!(
1985 collect_rar15_40(archive).unwrap()[0].data,
1986 b"facade commented payload\n"
1987 );
1988 }
1989
1990 #[test]
1991 fn direct_writer_creates_rar15_file_comment() {
1992 let features = FeatureSet::store_only();
1993 let bytes = rar15_40::write_stored_archive(
1994 &[rar15_40::StoredEntry {
1995 name: b"file-comment.txt",
1996 data: b"facade file comment payload\n",
1997 file_time: 0,
1998 file_attr: 0x20,
1999 host_os: 3,
2000 password: None,
2001 file_comment: Some(b"facade file note"),
2002 }],
2003 rar15_options_with_features(ArchiveVersion::Rar15, features),
2004 )
2005 .unwrap();
2006
2007 let archive = ArchiveReader::read(&bytes).unwrap();
2008 let archive = archive.as_rar15_40().unwrap();
2009 let file = archive.files().next().unwrap();
2010 assert_eq!(
2011 file.file_comment().unwrap().as_deref(),
2012 Some(&b"facade file note"[..])
2013 );
2014 assert_eq!(
2015 collect_rar15_40(archive).unwrap()[0].data,
2016 b"facade file comment payload\n"
2017 );
2018 }
2019
2020 #[test]
2021 fn direct_writer_creates_rar20_old_style_comments() {
2022 let archive_features = FeatureSet::store_only();
2023 let bytes = rar15_40::write_compressed_archive_with_comment(
2024 &[rar15_40::FileEntry {
2025 name: b"rar20-commented.txt",
2026 data: b"facade rar20 archive comment payload\n",
2027 file_time: 0,
2028 file_attr: 0x20,
2029 host_os: 3,
2030 password: None,
2031 file_comment: None,
2032 }],
2033 rar15_options_with_features(ArchiveVersion::Rar20, archive_features),
2034 Some(b"facade rar20 archive note"),
2035 )
2036 .unwrap();
2037 let archive = ArchiveReader::read(&bytes).unwrap();
2038 let raw = archive.as_rar15_40().unwrap();
2039 assert!(raw.main.has_archive_comment());
2040 assert_eq!(
2041 raw.archive_comment().unwrap().as_deref(),
2042 Some(b"facade rar20 archive note".as_slice())
2043 );
2044
2045 let file_features = FeatureSet::store_only();
2046 let bytes = rar15_40::write_compressed_archive(
2047 &[rar15_40::FileEntry {
2048 name: b"rar20-file-commented.txt",
2049 data: b"facade rar20 file comment payload payload\n",
2050 file_time: 0,
2051 file_attr: 0x20,
2052 host_os: 3,
2053 password: None,
2054 file_comment: Some(b"facade rar20 file note"),
2055 }],
2056 rar15_options_with_features(ArchiveVersion::Rar20, file_features),
2057 )
2058 .unwrap();
2059 let archive = ArchiveReader::read(&bytes).unwrap();
2060 let raw = archive.as_rar15_40().unwrap();
2061 let file = raw.files().next().unwrap();
2062 assert_eq!(file.unp_ver, 20);
2063 assert_eq!(
2064 file.file_comment().unwrap().as_deref(),
2065 Some(b"facade rar20 file note".as_slice())
2066 );
2067 }
2068
2069 #[test]
2070 fn direct_writer_creates_rar29_old_style_comments() {
2071 let archive_features = FeatureSet::store_only();
2072 let bytes = rar15_40::write_compressed_archive_with_comment(
2073 &[rar15_40::FileEntry {
2074 name: b"rar29-commented.txt",
2075 data: b"facade rar29 archive comment payload\n",
2076 file_time: 0,
2077 file_attr: 0x20,
2078 host_os: 3,
2079 password: None,
2080 file_comment: None,
2081 }],
2082 rar15_options_with_features(ArchiveVersion::Rar29, archive_features),
2083 Some(b"facade rar29 archive note"),
2084 )
2085 .unwrap();
2086 let archive = ArchiveReader::read(&bytes).unwrap();
2087 let raw = archive.as_rar15_40().unwrap();
2088 assert!(raw.main.has_archive_comment());
2089 assert_eq!(
2090 raw.archive_comment().unwrap().as_deref(),
2091 Some(b"facade rar29 archive note".as_slice())
2092 );
2093
2094 let file_features = FeatureSet::store_only();
2095 let bytes = rar15_40::write_compressed_archive(
2096 &[rar15_40::FileEntry {
2097 name: b"rar29-file-commented.txt",
2098 data: b"facade rar29 file comment payload payload\n",
2099 file_time: 0,
2100 file_attr: 0x20,
2101 host_os: 3,
2102 password: None,
2103 file_comment: Some(b"facade rar29 file note"),
2104 }],
2105 rar15_options_with_features(ArchiveVersion::Rar29, file_features),
2106 )
2107 .unwrap();
2108 let archive = ArchiveReader::read(&bytes).unwrap();
2109 let raw = archive.as_rar15_40().unwrap();
2110 let file = raw.files().next().unwrap();
2111 assert_eq!(file.unp_ver, 29);
2112 assert_eq!(
2113 file.file_comment().unwrap().as_deref(),
2114 Some(b"facade rar29 file note".as_slice())
2115 );
2116 }
2117
2118 #[test]
2119 fn direct_writer_creates_rar30_newsub_archive_comment() {
2120 let features = FeatureSet::store_only();
2121 let bytes = rar15_40::write_compressed_archive_with_comment(
2122 &[rar15_40::FileEntry {
2123 name: b"rar30-commented.txt",
2124 data: b"facade rar30 NEWSUB archive comment payload\n",
2125 file_time: 0,
2126 file_attr: 0x20,
2127 host_os: 3,
2128 password: None,
2129 file_comment: None,
2130 }],
2131 rar15_options_with_features(ArchiveVersion::Rar30, features),
2132 Some(b"facade rar30 NEWSUB note"),
2133 )
2134 .unwrap();
2135
2136 let archive = ArchiveReader::read(&bytes).unwrap();
2137 let raw = archive.as_rar15_40().unwrap();
2138 assert!(!raw.main.has_archive_comment());
2139 let subblock = raw.new_subs().next().unwrap();
2140 assert_eq!(subblock.kind, rar15_40::NewSubKind::ArchiveComment);
2141 assert_eq!(subblock.file.name, b"CMT");
2142 assert_eq!(
2143 raw.archive_comment().unwrap().as_deref(),
2144 Some(b"facade rar30 NEWSUB note".as_slice())
2145 );
2146 }
2147
2148 #[test]
2149 fn direct_writer_creates_rar15_solid_compressed_archive() {
2150 let mut features = FeatureSet::store_only();
2151 features.solid = true;
2152 let bytes = rar15_40::write_compressed_archive(
2153 &[
2154 rar15_40::FileEntry {
2155 name: b"one.txt",
2156 data: b"shared facade prefix one\n",
2157 file_time: 0,
2158 file_attr: 0x20,
2159 host_os: 3,
2160 password: None,
2161 file_comment: None,
2162 },
2163 rar15_40::FileEntry {
2164 name: b"two.txt",
2165 data: b"shared facade prefix two\n",
2166 file_time: 0,
2167 file_attr: 0x20,
2168 host_os: 3,
2169 password: None,
2170 file_comment: None,
2171 },
2172 ],
2173 rar15_options_with_features(ArchiveVersion::Rar15, features),
2174 )
2175 .unwrap();
2176
2177 let archive = ArchiveReader::read(&bytes).unwrap();
2178 let extracted = collect_extract(&archive, None).unwrap();
2179 assert_eq!(extracted[0].data, b"shared facade prefix one\n");
2180 assert_eq!(extracted[1].data, b"shared facade prefix two\n");
2181 }
2182
2183 #[test]
2184 fn direct_writer_creates_rar15_encrypted_compressed_archive() {
2185 let features = FeatureSet::store_only();
2186 let bytes = rar15_40::write_compressed_archive(
2187 &[rar15_40::FileEntry {
2188 name: b"secret.txt",
2189 data: b"facade encrypted payload\n",
2190 file_time: 0,
2191 file_attr: 0x20,
2192 host_os: 3,
2193 password: Some(b"password"),
2194 file_comment: None,
2195 }],
2196 rar15_options_with_features(ArchiveVersion::Rar15, features),
2197 )
2198 .unwrap();
2199
2200 let archive = ArchiveReader::read(&bytes).unwrap();
2201 assert!(matches!(
2202 collect_extract(&archive, None),
2203 Err(Error::NeedPassword)
2204 ));
2205 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2206 assert_eq!(extracted[0].data, b"facade encrypted payload\n");
2207 }
2208
2209 #[test]
2210 fn direct_writer_creates_rar15_stored_volumes() {
2211 let parts = rar15_40::write_stored_volumes(
2212 rar15_40::StoredEntry {
2213 name: b"split.bin",
2214 data: b"abcdefghijklmnopqrstuvwxyz0123456789",
2215 file_time: 0,
2216 file_attr: 0x20,
2217 host_os: 3,
2218 password: None,
2219 file_comment: None,
2220 },
2221 rar15_options(ArchiveVersion::Rar15),
2222 10,
2223 )
2224 .unwrap();
2225 let archives: Vec<_> = parts
2226 .iter()
2227 .map(|part| rar15_40::Archive::parse(part).unwrap())
2228 .collect();
2229 let extracted = collect_rar15_40_volumes(&archives, None).unwrap();
2230
2231 assert_eq!(extracted[0].name, b"split.bin");
2232 assert_eq!(extracted[0].data, b"abcdefghijklmnopqrstuvwxyz0123456789");
2233 }
2234
2235 #[test]
2236 fn direct_writer_creates_rar20_compressed_volumes() {
2237 let data = b"facade rar20 split phrase alpha beta gamma\n".repeat(32);
2238 let parts = rar15_40::write_compressed_volumes(
2239 rar15_40::FileEntry {
2240 name: b"split-rar20.txt",
2241 data: &data,
2242 file_time: 0,
2243 file_attr: 0x20,
2244 host_os: 3,
2245 password: None,
2246 file_comment: None,
2247 },
2248 rar15_options(ArchiveVersion::Rar20),
2249 8,
2250 )
2251 .unwrap();
2252 let archives: Vec<_> = parts
2253 .iter()
2254 .map(|part| rar15_40::Archive::parse(part).unwrap())
2255 .collect();
2256 let first_file = archives[0].files().next().unwrap();
2257 assert_eq!(first_file.unp_ver, 20);
2258 assert!(first_file.is_split_after());
2259
2260 let extracted = collect_rar15_40_volumes(&archives, None).unwrap();
2261 assert_eq!(extracted[0].name, b"split-rar20.txt");
2262 assert_eq!(extracted[0].data, data);
2263 }
2264
2265 #[test]
2266 fn direct_writer_creates_rar29_compressed_volumes() {
2267 let data = b"facade rar29 split phrase alpha beta gamma\n".repeat(32);
2268 let parts = rar15_40::write_compressed_volumes(
2269 rar15_40::FileEntry {
2270 name: b"split-rar29.txt",
2271 data: &data,
2272 file_time: 0,
2273 file_attr: 0x20,
2274 host_os: 3,
2275 password: None,
2276 file_comment: None,
2277 },
2278 rar15_options(ArchiveVersion::Rar29),
2279 8,
2280 )
2281 .unwrap();
2282 let archives: Vec<_> = parts
2283 .iter()
2284 .map(|part| rar15_40::Archive::parse(part).unwrap())
2285 .collect();
2286 let first_file = archives[0].files().next().unwrap();
2287 assert_eq!(first_file.unp_ver, 29);
2288 assert!(first_file.is_split_after());
2289
2290 let extracted = collect_rar15_40_volumes(&archives, None).unwrap();
2291 assert_eq!(extracted[0].name, b"split-rar29.txt");
2292 assert_eq!(extracted[0].data, data);
2293 }
2294
2295 #[test]
2296 fn direct_writer_creates_rar29_encrypted_compressed_volumes() {
2297 let features = FeatureSet::store_only();
2298 let parts = rar15_40::write_compressed_volumes(
2299 rar15_40::FileEntry {
2300 name: b"split-rar29-secret.txt",
2301 data: b"facade rar29 encrypted split facade rar29 encrypted split\n",
2302 file_time: 0,
2303 file_attr: 0x20,
2304 host_os: 3,
2305 password: Some(b"password"),
2306 file_comment: None,
2307 },
2308 rar15_options_with_features(ArchiveVersion::Rar29, features),
2309 8,
2310 )
2311 .unwrap();
2312 let archives: Vec<_> = parts
2313 .iter()
2314 .map(|part| rar15_40::Archive::parse(part).unwrap())
2315 .collect();
2316
2317 assert!(matches!(
2318 collect_rar15_40_volumes(&archives, None),
2319 Err(Error::NeedPassword)
2320 ));
2321 let extracted = collect_rar15_40_volumes(&archives, Some(b"password")).unwrap();
2322 assert_eq!(extracted[0].name, b"split-rar29-secret.txt");
2323 assert_eq!(
2324 extracted[0].data,
2325 b"facade rar29 encrypted split facade rar29 encrypted split\n"
2326 );
2327 }
2328
2329 #[test]
2330 fn direct_writer_creates_rar15_encrypted_compressed_volumes() {
2331 let features = FeatureSet::store_only();
2332 let parts = rar15_40::write_compressed_volumes(
2333 rar15_40::FileEntry {
2334 name: b"split-secret.txt",
2335 data: b"facade encrypted split facade encrypted split\n",
2336 file_time: 0,
2337 file_attr: 0x20,
2338 host_os: 3,
2339 password: Some(b"password"),
2340 file_comment: None,
2341 },
2342 rar15_options_with_features(ArchiveVersion::Rar15, features),
2343 8,
2344 )
2345 .unwrap();
2346 let archives: Vec<_> = parts
2347 .iter()
2348 .map(|part| rar15_40::Archive::parse(part).unwrap())
2349 .collect();
2350
2351 assert!(matches!(
2352 collect_rar15_40_volumes(&archives, None),
2353 Err(Error::NeedPassword)
2354 ));
2355 let extracted = collect_rar15_40_volumes(&archives, Some(b"password")).unwrap();
2356 assert_eq!(extracted[0].name, b"split-secret.txt");
2357 assert_eq!(
2358 extracted[0].data,
2359 b"facade encrypted split facade encrypted split\n"
2360 );
2361 }
2362
2363 #[test]
2364 fn direct_writer_creates_rar30_encrypted_compressed_volumes() {
2365 let features = FeatureSet::store_only();
2366 let parts = rar15_40::write_compressed_volumes(
2367 rar15_40::FileEntry {
2368 name: b"split-rar30-secret.txt",
2369 data: b"facade rar30 encrypted split facade rar30 encrypted split\n",
2370 file_time: 0,
2371 file_attr: 0x20,
2372 host_os: 3,
2373 password: Some(b"password"),
2374 file_comment: None,
2375 },
2376 rar15_options_with_features(ArchiveVersion::Rar30, features),
2377 8,
2378 )
2379 .unwrap();
2380 let archives: Vec<_> = parts
2381 .iter()
2382 .map(|part| rar15_40::Archive::parse(part).unwrap())
2383 .collect();
2384
2385 assert!(matches!(
2386 collect_rar15_40_volumes(&archives, None),
2387 Err(Error::NeedPassword)
2388 ));
2389 let extracted = collect_rar15_40_volumes(&archives, Some(b"password")).unwrap();
2390 assert_eq!(extracted[0].name, b"split-rar30-secret.txt");
2391 assert_eq!(
2392 extracted[0].data,
2393 b"facade rar30 encrypted split facade rar30 encrypted split\n"
2394 );
2395 }
2396
2397 #[test]
2398 fn direct_writer_creates_rar30_header_encrypted_compressed_volumes() {
2399 let mut features = FeatureSet::store_only();
2400 features.header_encryption = true;
2401 let parts = rar15_40::write_compressed_volumes(
2402 rar15_40::FileEntry {
2403 name: b"split-rar30-header-secret.txt",
2404 data: b"facade rar30 header encrypted split facade rar30 header encrypted split\n",
2405 file_time: 0,
2406 file_attr: 0x20,
2407 host_os: 3,
2408 password: Some(b"password"),
2409 file_comment: None,
2410 },
2411 rar15_options_with_features(ArchiveVersion::Rar30, features),
2412 8,
2413 )
2414 .unwrap();
2415 assert!(matches!(
2416 rar15_40::Archive::parse(&parts[0]),
2417 Err(Error::NeedPassword)
2418 ));
2419 let archives: Vec<_> = parts
2420 .iter()
2421 .map(|part| rar15_40::Archive::parse_with_password(part, Some(b"password")).unwrap())
2422 .collect();
2423
2424 let extracted = collect_rar15_40_volumes(&archives, Some(b"password")).unwrap();
2425 assert_eq!(extracted[0].name, b"split-rar30-header-secret.txt");
2426 assert_eq!(
2427 extracted[0].data,
2428 b"facade rar30 header encrypted split facade rar30 header encrypted split\n"
2429 );
2430 }
2431
2432 #[test]
2433 fn direct_writer_creates_rar30_aes_encrypted_compressed_archive() {
2434 let features = FeatureSet::store_only();
2435 let bytes = rar15_40::write_compressed_archive(
2436 &[rar15_40::FileEntry {
2437 name: b"rar30-secret.txt",
2438 data: b"facade rar30 aes encrypted payload\n",
2439 file_time: 0,
2440 file_attr: 0x20,
2441 host_os: 3,
2442 password: Some(b"password"),
2443 file_comment: None,
2444 }],
2445 rar15_options_with_features(ArchiveVersion::Rar30, features),
2446 )
2447 .unwrap();
2448
2449 let archive = ArchiveReader::read(&bytes).unwrap();
2450 assert!(matches!(
2451 collect_extract(&archive, None),
2452 Err(Error::NeedPassword)
2453 ));
2454 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2455 assert_eq!(extracted[0].data, b"facade rar30 aes encrypted payload\n");
2456 }
2457
2458 #[test]
2459 fn direct_writer_creates_rar29_aes_encrypted_compressed_archive() {
2460 let features = FeatureSet::store_only();
2461 let bytes = rar15_40::write_compressed_archive(
2462 &[rar15_40::FileEntry {
2463 name: b"rar29-secret.txt",
2464 data: b"facade rar29 aes encrypted payload\n",
2465 file_time: 0,
2466 file_attr: 0x20,
2467 host_os: 3,
2468 password: Some(b"password"),
2469 file_comment: None,
2470 }],
2471 rar15_options_with_features(ArchiveVersion::Rar29, features),
2472 )
2473 .unwrap();
2474
2475 let archive = ArchiveReader::read(&bytes).unwrap();
2476 let raw = archive.as_rar15_40().unwrap();
2477 let file = raw.files().next().unwrap();
2478 assert_eq!(file.unp_ver, 29);
2479 assert!(file.is_encrypted());
2480 assert!(file.salt.is_some());
2481 assert!(matches!(
2482 collect_extract(&archive, None),
2483 Err(Error::NeedPassword)
2484 ));
2485 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2486 assert_eq!(extracted[0].data, b"facade rar29 aes encrypted payload\n");
2487 }
2488
2489 #[test]
2490 fn direct_writer_creates_rar20_encrypted_compressed_archive() {
2491 let features = FeatureSet::store_only();
2492 let bytes = rar15_40::write_compressed_archive(
2493 &[rar15_40::FileEntry {
2494 name: b"rar20-secret.txt",
2495 data: b"facade rar20 encrypted payload payload\n",
2496 file_time: 0,
2497 file_attr: 0x20,
2498 host_os: 3,
2499 password: Some(b"password"),
2500 file_comment: None,
2501 }],
2502 rar15_options_with_features(ArchiveVersion::Rar20, features),
2503 )
2504 .unwrap();
2505
2506 let archive = ArchiveReader::read(&bytes).unwrap();
2507 let raw = archive.as_rar15_40().unwrap();
2508 let file = raw.files().next().unwrap();
2509 assert_eq!(file.unp_ver, 20);
2510 assert!(file.is_encrypted());
2511 assert!(matches!(
2512 collect_extract(&archive, None),
2513 Err(Error::NeedPassword)
2514 ));
2515 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2516 assert_eq!(
2517 extracted[0].data,
2518 b"facade rar20 encrypted payload payload\n"
2519 );
2520 }
2521
2522 #[test]
2523 fn direct_writer_creates_rar30_header_encrypted_compressed_archive() {
2524 let mut features = FeatureSet::store_only();
2525 features.header_encryption = true;
2526 let bytes = rar15_40::write_compressed_archive(
2527 &[rar15_40::FileEntry {
2528 name: b"rar30-header-secret.txt",
2529 data: b"facade rar30 header encrypted payload\n",
2530 file_time: 0,
2531 file_attr: 0x20,
2532 host_os: 3,
2533 password: Some(b"password"),
2534 file_comment: None,
2535 }],
2536 rar15_options_with_features(ArchiveVersion::Rar30, features),
2537 )
2538 .unwrap();
2539
2540 assert!(matches!(
2541 ArchiveReader::read(&bytes),
2542 Err(Error::NeedPassword)
2543 ));
2544 let archive = ArchiveReader::read_with_options(
2545 &bytes,
2546 ArchiveReadOptions::with_password(b"password"),
2547 )
2548 .unwrap();
2549 let raw = archive.as_rar15_40().unwrap();
2550 assert!(raw.main.has_encrypted_headers());
2551 assert_eq!(raw.files().next().unwrap().name, b"rar30-header-secret.txt");
2552 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2553 assert_eq!(
2554 extracted[0].data,
2555 b"facade rar30 header encrypted payload\n"
2556 );
2557 }
2558
2559 #[test]
2560 fn direct_writer_creates_rar30_solid_header_encrypted_compressed_archive() {
2561 let mut features = FeatureSet::store_only();
2562 features.header_encryption = true;
2563 features.solid = true;
2564 let bytes = rar15_40::write_compressed_archive(
2565 &[
2566 rar15_40::FileEntry {
2567 name: b"solid-header-one.txt",
2568 data: b"facade solid header encrypted one one one\n",
2569 file_time: 0,
2570 file_attr: 0x20,
2571 host_os: 3,
2572 password: Some(b"password"),
2573 file_comment: None,
2574 },
2575 rar15_40::FileEntry {
2576 name: b"solid-header-two.txt",
2577 data: b"facade solid header encrypted two two two\n",
2578 file_time: 0,
2579 file_attr: 0x20,
2580 host_os: 3,
2581 password: Some(b"password"),
2582 file_comment: None,
2583 },
2584 ],
2585 rar15_options_with_features(ArchiveVersion::Rar30, features),
2586 )
2587 .unwrap();
2588
2589 assert!(matches!(
2590 ArchiveReader::read(&bytes),
2591 Err(Error::NeedPassword)
2592 ));
2593 let archive = ArchiveReader::read_with_options(
2594 &bytes,
2595 ArchiveReadOptions::with_password(b"password"),
2596 )
2597 .unwrap();
2598 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2599 assert_eq!(
2600 extracted[0].data,
2601 b"facade solid header encrypted one one one\n"
2602 );
2603 assert_eq!(
2604 extracted[1].data,
2605 b"facade solid header encrypted two two two\n"
2606 );
2607 }
2608
2609 #[test]
2610 fn direct_writer_creates_rar50_stored_archive() {
2611 let bytes =
2612 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
2613 .entries(
2614 [
2615 rar50_entry(b"rar5-store.txt", b"facade rar5 stored payload\n")
2616 .with_mtime(Some(0))
2617 .with_attributes(0x20)
2618 .with_host_os(3),
2619 ]
2620 .to_vec(),
2621 )
2622 .finish()
2623 .unwrap();
2624
2625 let archive = ArchiveReader::read(&bytes).unwrap();
2626 assert_eq!(archive.family(), ArchiveFamily::Rar50Plus);
2627 let extracted = collect_extract(&archive, None).unwrap();
2628 assert_eq!(extracted[0].data, b"facade rar5 stored payload\n");
2629 }
2630
2631 #[test]
2632 fn direct_writer_creates_rar50_compressed_archive() {
2633 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50))
2634 .entries(
2635 [rar50_entry(
2636 b"rar5-compressed.txt",
2637 b"facade rar5 compressed payload\nfacade rar5 compressed payload\n",
2638 )
2639 .with_mtime(Some(0))
2640 .with_attributes(0x20)
2641 .with_host_os(3)]
2642 .to_vec(),
2643 )
2644 .finish()
2645 .unwrap();
2646
2647 let archive = ArchiveReader::read(&bytes).unwrap();
2648 let raw = archive.as_rar50().unwrap();
2649 let file = raw.files().next().unwrap();
2650 assert_eq!(file.decoded_compression_info().unwrap().method, 3);
2651 let extracted = collect_extract(&archive, None).unwrap();
2652 assert_eq!(
2653 extracted[0].data,
2654 b"facade rar5 compressed payload\nfacade rar5 compressed payload\n"
2655 );
2656 }
2657
2658 #[test]
2659 fn direct_writer_creates_rar50_solid_compressed_archive() {
2660 let mut features = FeatureSet::store_only();
2661 features.solid = true;
2662 let first = b"facade rar50 solid shared phrase alpha beta gamma\n".repeat(16);
2663 let second = b"facade rar50 solid shared phrase alpha beta gamma\nsecond\n".repeat(8);
2664 let bytes =
2665 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
2666 .entries(
2667 [
2668 rar50_entry(b"rar5-solid-one.txt", &first)
2669 .with_mtime(Some(0))
2670 .with_attributes(0x20)
2671 .with_host_os(3),
2672 rar50_entry(b"rar5-solid-two.txt", &second)
2673 .with_mtime(Some(0))
2674 .with_attributes(0x20)
2675 .with_host_os(3),
2676 ]
2677 .to_vec(),
2678 )
2679 .finish()
2680 .unwrap();
2681
2682 let archive = ArchiveReader::read(&bytes).unwrap();
2683 let raw = archive.as_rar50().unwrap();
2684 let files: Vec<_> = raw.files().collect();
2685 assert!(raw.main.is_solid());
2686 assert!(!files[0].decoded_compression_info().unwrap().solid);
2687 assert!(files[1].decoded_compression_info().unwrap().solid);
2688 let extracted = collect_extract(&archive, None).unwrap();
2689 assert_eq!(extracted[0].data, first);
2690 assert_eq!(extracted[1].data, second);
2691 }
2692
2693 #[test]
2694 fn direct_writer_creates_rar50_delta_filtered_compressed_archive() {
2695 let payload: Vec<u8> = (0..180)
2696 .map(|index| (index * 11 + index / 5) as u8)
2697 .collect();
2698 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50))
2699 .entries(
2700 [rar50_entry(b"rar5-delta-filtered.bin", &payload)
2701 .with_mtime(Some(0))
2702 .with_attributes(0x20)
2703 .with_host_os(3)]
2704 .to_vec(),
2705 )
2706 .filter_policy(rar50::FilterPolicy::explicit(rar50::FilterKind::Delta {
2707 channels: 3,
2708 }))
2709 .finish()
2710 .unwrap();
2711
2712 let archive = ArchiveReader::read(&bytes).unwrap();
2713 let extracted = collect_extract(&archive, None).unwrap();
2714 assert_eq!(extracted[0].data, payload);
2715 }
2716
2717 #[test]
2718 fn direct_writer_creates_rar50_e8_filtered_compressed_archive() {
2719 let payload = b"\xe8\0\0\0\0facade rar5 e8 filter payload".to_vec();
2720 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50))
2721 .entries(
2722 [rar50_entry(b"rar5-e8-filtered.bin", &payload)
2723 .with_mtime(Some(0))
2724 .with_attributes(0x20)
2725 .with_host_os(3)]
2726 .to_vec(),
2727 )
2728 .filter_policy(rar50::FilterPolicy::explicit(rar50::FilterKind::E8))
2729 .finish()
2730 .unwrap();
2731
2732 let archive = ArchiveReader::read(&bytes).unwrap();
2733 let extracted = collect_extract(&archive, None).unwrap();
2734 assert_eq!(extracted[0].data, payload);
2735 }
2736
2737 #[test]
2738 fn direct_writer_creates_rar50_arm_filtered_compressed_archive() {
2739 let payload = [0x04, 0x00, 0x00, 0xeb, b'A', b'R', b'M', b'!'];
2740 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50))
2741 .entries(
2742 [rar50_entry(b"rar5-arm-filtered.bin", &payload)
2743 .with_mtime(Some(0))
2744 .with_attributes(0x20)
2745 .with_host_os(3)]
2746 .to_vec(),
2747 )
2748 .filter_policy(rar50::FilterPolicy::explicit(rar50::FilterKind::Arm))
2749 .finish()
2750 .unwrap();
2751
2752 let archive = ArchiveReader::read(&bytes).unwrap();
2753 let extracted = collect_extract(&archive, None).unwrap();
2754 assert_eq!(extracted[0].data, payload);
2755 }
2756
2757 #[test]
2758 fn direct_writer_creates_rar50_auto_filtered_compressed_archive() {
2759 let payload = b"\xe8\0\0\0\0facade rar5 auto filter payload\n".repeat(16);
2760 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50))
2761 .entries(
2762 [rar50_entry(b"rar5-auto-filtered.bin", &payload)
2763 .with_mtime(Some(0))
2764 .with_attributes(0x20)
2765 .with_host_os(3)]
2766 .to_vec(),
2767 )
2768 .filter_policy(rar50::FilterPolicy::Auto)
2769 .finish()
2770 .unwrap();
2771
2772 let archive = ArchiveReader::read(&bytes).unwrap();
2773 let extracted = collect_extract(&archive, None).unwrap();
2774 assert_eq!(extracted[0].data, payload);
2775 }
2776
2777 #[test]
2778 fn direct_writer_creates_rar50_stored_archive_with_comment_service() {
2779 let features = FeatureSet::store_only();
2780 let bytes = rar50::Rar50Writer::new(
2781 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
2782 )
2783 .entries(
2784 [
2785 rar50_entry(b"rar5-commented.txt", b"facade rar5 comment payload\n")
2786 .with_attributes(0x20)
2787 .with_host_os(3),
2788 ]
2789 .to_vec(),
2790 )
2791 .archive_comment(Some(b"facade rar5 comment\n"))
2792 .finish()
2793 .unwrap();
2794
2795 let archive = ArchiveReader::read(&bytes).unwrap();
2796 let raw = archive.as_rar50().unwrap();
2797 let services: Vec<_> = raw.services().collect();
2798 assert_eq!(services.len(), 1);
2799 assert_eq!(services[0].name, b"CMT");
2800 assert_eq!(
2801 collect_rar50_file(raw, services[0]).unwrap().data,
2802 b"facade rar5 comment\n"
2803 );
2804 let extracted = collect_extract(&archive, None).unwrap();
2805 assert_eq!(extracted[0].data, b"facade rar5 comment payload\n");
2806 }
2807
2808 #[test]
2809 fn direct_writer_creates_rar50_stored_file_comment_service() {
2810 let bytes =
2811 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
2812 .entry(
2813 rar50_entry(
2814 b"rar5-file-commented.txt",
2815 b"facade rar5 file comment payload\n",
2816 )
2817 .with_attributes(0x20)
2818 .with_host_os(3)
2819 .with_service(rar50::ServiceEntry::new(
2820 b"CMT".to_vec(),
2821 b"facade rar5 file comment\n".to_vec(),
2822 )),
2823 )
2824 .finish()
2825 .unwrap();
2826
2827 let archive = ArchiveReader::read(&bytes).unwrap();
2828 let raw = archive.as_rar50().unwrap();
2829 let services: Vec<_> = raw.services().collect();
2830 assert_eq!(services.len(), 1);
2831 assert_eq!(services[0].name, b"CMT");
2832 assert_eq!(
2833 collect_rar50_file(raw, services[0]).unwrap().data,
2834 b"facade rar5 file comment\n"
2835 );
2836 let extracted = collect_extract(&archive, None).unwrap();
2837 assert_eq!(extracted[0].data, b"facade rar5 file comment payload\n");
2838 }
2839
2840 #[test]
2841 fn direct_writer_creates_rar50_encrypted_stored_file_comment_service() {
2842 let features = FeatureSet::store_only();
2843 let bytes = rar50::Rar50Writer::new(
2844 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
2845 )
2846 .entry(
2847 rar50_entry(
2848 b"rar5-encrypted-file-commented.txt",
2849 b"facade encrypted rar5 file comment payload\n",
2850 )
2851 .with_attributes(0x20)
2852 .with_host_os(3)
2853 .with_password(b"password".to_vec())
2854 .with_service(
2855 rar50::ServiceEntry::new(
2856 b"CMT".to_vec(),
2857 b"facade encrypted rar5 file comment\n".to_vec(),
2858 )
2859 .with_password(b"password".to_vec()),
2860 ),
2861 )
2862 .finish()
2863 .unwrap();
2864
2865 let archive = ArchiveReader::read_with_options(
2866 &bytes,
2867 ArchiveReadOptions::with_password(b"password"),
2868 )
2869 .unwrap();
2870 let raw = archive.as_rar50().unwrap();
2871 let service = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
2872 assert_eq!(service.data, b"facade encrypted rar5 file comment\n");
2873 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2874 assert_eq!(
2875 extracted[0].data,
2876 b"facade encrypted rar5 file comment payload\n"
2877 );
2878 }
2879
2880 #[test]
2881 fn direct_writer_creates_rar50_header_encrypted_stored_file_comment_service() {
2882 let mut features = FeatureSet::store_only();
2883 features.header_encryption = true;
2884 let bytes = rar50::Rar50Writer::new(
2885 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
2886 )
2887 .entry(
2888 rar50_entry(
2889 b"rar5-header-file-commented.txt",
2890 b"facade header encrypted rar5 file comment payload\n",
2891 )
2892 .with_attributes(0x20)
2893 .with_host_os(3)
2894 .with_password(b"password".to_vec())
2895 .with_service(
2896 rar50::ServiceEntry::new(
2897 b"CMT".to_vec(),
2898 b"facade header encrypted rar5 file comment\n".to_vec(),
2899 )
2900 .with_password(b"password".to_vec()),
2901 ),
2902 )
2903 .finish()
2904 .unwrap();
2905
2906 assert!(matches!(
2907 ArchiveReader::read(&bytes),
2908 Err(Error::NeedPassword)
2909 ));
2910 let archive = ArchiveReader::read_with_options(
2911 &bytes,
2912 ArchiveReadOptions::with_password(b"password"),
2913 )
2914 .unwrap();
2915 let raw = archive.as_rar50().unwrap();
2916 let service = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
2917 assert_eq!(service.data, b"facade header encrypted rar5 file comment\n");
2918 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
2919 assert_eq!(
2920 extracted[0].data,
2921 b"facade header encrypted rar5 file comment payload\n"
2922 );
2923 }
2924
2925 #[test]
2926 fn direct_writer_creates_rar50_stored_archive_with_quick_open_service() {
2927 let mut features = FeatureSet::store_only();
2928 features.quick_open = true;
2929 let bytes = rar50::Rar50Writer::new(
2930 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
2931 )
2932 .entries(
2933 [
2934 rar50_entry(b"rar5-qo.txt", b"facade rar5 quick-open payload\n")
2935 .with_attributes(0x20)
2936 .with_host_os(3),
2937 ]
2938 .to_vec(),
2939 )
2940 .finish()
2941 .unwrap();
2942
2943 let archive = ArchiveReader::read(&bytes).unwrap();
2944 let raw = archive.as_rar50().unwrap();
2945 assert!(raw.main.locator().unwrap().quick_open_offset.unwrap() > 0);
2946 let services: Vec<_> = raw.services().collect();
2947 assert_eq!(services.len(), 1);
2948 assert_eq!(services[0].name, b"QO");
2949 assert!(!collect_rar50_file(raw, services[0])
2950 .unwrap()
2951 .data
2952 .is_empty());
2953 let extracted = collect_extract(&archive, None).unwrap();
2954 assert_eq!(extracted[0].data, b"facade rar5 quick-open payload\n");
2955 }
2956
2957 #[test]
2958 fn direct_writer_creates_rar50_stored_archive_with_file_services() {
2959 let bytes =
2960 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
2961 .entry(
2962 rar50_entry(b"rar5-services.txt", b"facade rar5 service payload\n")
2963 .with_attributes(0x20)
2964 .with_host_os(3)
2965 .with_service(rar50::ServiceEntry::new(
2966 b"ACL".to_vec(),
2967 b"facade acl".to_vec(),
2968 ))
2969 .with_service(rar50::ServiceEntry::new(
2970 b"STM".to_vec(),
2971 b"facade stream".to_vec(),
2972 )),
2973 )
2974 .finish()
2975 .unwrap();
2976
2977 let archive = ArchiveReader::read(&bytes).unwrap();
2978 let raw = archive.as_rar50().unwrap();
2979 let services: Vec<_> = raw.services().collect();
2980 assert_eq!(services.len(), 2);
2981 assert_eq!(services[0].name, b"ACL");
2982 assert_eq!(services[1].name, b"STM");
2983 assert_eq!(
2984 collect_rar50_file(raw, services[0]).unwrap().data,
2985 b"facade acl"
2986 );
2987 assert_eq!(
2988 collect_rar50_file(raw, services[1]).unwrap().data,
2989 b"facade stream"
2990 );
2991 let extracted = collect_extract(&archive, None).unwrap();
2992 assert_eq!(extracted[0].data, b"facade rar5 service payload\n");
2993 }
2994
2995 #[test]
2996 fn direct_writer_creates_rar50_stored_archive_with_recovery_service() {
2997 let features = FeatureSet::store_only();
2998 let bytes = rar50::Rar50Writer::new(
2999 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3000 )
3001 .entries(
3002 [
3003 rar50_entry(b"rar5-recovery.txt", b"facade rar5 recovery payload\n")
3004 .with_attributes(0x20)
3005 .with_host_os(3),
3006 ]
3007 .to_vec(),
3008 )
3009 .recovery_percent(Some(9))
3010 .finish()
3011 .unwrap();
3012
3013 let archive = ArchiveReader::read(&bytes).unwrap();
3014 let raw = archive.as_rar50().unwrap();
3015 assert!(raw.main.has_recovery_record());
3016 let service = raw.services().next().unwrap();
3017 assert_eq!(service.name, b"RR");
3018 let recovery = service.recovery_record().unwrap().unwrap();
3019 assert_eq!(recovery.percent, 9);
3020 let extracted = collect_extract(&archive, None).unwrap();
3021 assert_eq!(extracted[0].data, b"facade rar5 recovery payload\n");
3022 }
3023
3024 #[test]
3025 fn archive_facade_repairs_rar50_inline_recovery_damage() {
3026 let features = FeatureSet::store_only();
3027 let payload = b"facade rar5 repair payload\n".repeat(64);
3028 let bytes = rar50::Rar50Writer::new(
3029 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3030 )
3031 .entries(
3032 [rar50_entry(b"rar5-repair.txt", &payload)
3033 .with_attributes(0x20)
3034 .with_host_os(3)]
3035 .to_vec(),
3036 )
3037 .recovery_percent(Some(20))
3038 .finish()
3039 .unwrap();
3040 let archive = ArchiveReader::read(&bytes).unwrap();
3041 let data_range = archive
3042 .as_rar50()
3043 .unwrap()
3044 .files()
3045 .next()
3046 .unwrap()
3047 .block
3048 .data_range
3049 .clone();
3050 let mut damaged = bytes.clone();
3051 damaged[data_range.start + 4..data_range.start + 80].fill(0xa5);
3052 let damaged_archive = ArchiveReader::read(&damaged).unwrap();
3053 assert!(collect_extract(&damaged_archive, None).is_err());
3054
3055 let mut repaired = Vec::new();
3056 damaged_archive.repair_recovery_to(&mut repaired).unwrap();
3057
3058 assert_eq!(repaired, bytes);
3059 let repaired_archive = ArchiveReader::read(&repaired).unwrap();
3060 assert_eq!(
3061 collect_extract(&repaired_archive, None).unwrap()[0].data,
3062 payload
3063 );
3064 }
3065
3066 #[test]
3067 fn archive_facade_reports_rar13_family_for_unsupported_recovery_repair() {
3068 let bytes = rar13::write_stored_archive(
3069 &[rar13::StoredEntry {
3070 name: b"old.txt",
3071 data: b"old rar payload",
3072 file_time: 0,
3073 file_attr: 0x20,
3074 password: None,
3075 file_comment: None,
3076 }],
3077 rar13_options(ArchiveVersion::Rar13),
3078 )
3079 .unwrap();
3080 let archive = ArchiveReader::read(&bytes).unwrap();
3081 let mut repaired = Vec::new();
3082 let err = archive.repair_recovery_to(&mut repaired).unwrap_err();
3083
3084 assert_eq!(
3085 err,
3086 Error::UnsupportedFamilyFeature {
3087 family: ArchiveFamily::Rar13,
3088 feature: "recovery repair for RAR 1.3/1.4 archives"
3089 }
3090 );
3091 }
3092
3093 #[test]
3094 fn archive_facade_repairs_rar15_40_recovery_as_full_archive_bytes() {
3095 let bytes = std::fs::read(rar15_40_fixture("rar250_protect_head_rr5.rar")).unwrap();
3096 let mut damaged = bytes.clone();
3097 damaged[512 + 16..512 + 80].fill(0xa5);
3098 let damaged_archive = ArchiveReader::read(&damaged).unwrap();
3099 assert!(collect_extract(&damaged_archive, None).is_err());
3100
3101 let mut repaired = Vec::new();
3102 damaged_archive.repair_recovery_to(&mut repaired).unwrap();
3103
3104 assert_eq!(repaired, bytes);
3105 let repaired_archive = ArchiveReader::read(&repaired).unwrap();
3106 assert_eq!(
3107 collect_extract(&repaired_archive, None).unwrap()[0].name,
3108 b"BIG.BIN"
3109 );
3110 }
3111
3112 #[test]
3113 fn archive_facade_repairs_rar3_newsub_recovery_as_full_archive_bytes() {
3114 let bytes = std::fs::read(rar15_40_fixture("rar300/with_recovery_rar300.rar")).unwrap();
3115 let mut damaged = bytes.clone();
3116 damaged[512 + 16..512 + 80].fill(0xa5);
3117 let damaged_archive = ArchiveReader::read(&damaged).unwrap();
3118 assert!(collect_extract(&damaged_archive, None).is_err());
3119
3120 let mut repaired = Vec::new();
3121 damaged_archive.repair_recovery_to(&mut repaired).unwrap();
3122
3123 assert_eq!(repaired, bytes);
3124 let repaired_archive = ArchiveReader::read(&repaired).unwrap();
3125 assert_eq!(
3126 collect_extract(&repaired_archive, None).unwrap()[0].name,
3127 b"bigtext_64k.bin"
3128 );
3129 }
3130
3131 #[test]
3132 fn direct_writer_creates_rar50_compressed_archive_with_recovery_service() {
3133 let features = FeatureSet::store_only();
3134 let payload = b"facade rar5 compressed recovery payload repeated repeated\n".repeat(8);
3135 let bytes =
3136 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3137 .entries(
3138 [rar50_entry(b"rar5-compressed-recovery.txt", &payload)
3139 .with_attributes(0x20)
3140 .with_host_os(3)]
3141 .to_vec(),
3142 )
3143 .recovery_percent(Some(9))
3144 .finish()
3145 .unwrap();
3146
3147 let archive = ArchiveReader::read(&bytes).unwrap();
3148 let raw = archive.as_rar50().unwrap();
3149 assert!(raw.main.has_recovery_record());
3150 let service = raw.services().next().unwrap();
3151 assert_eq!(service.name, b"RR");
3152 assert_eq!(service.recovery_record().unwrap().unwrap().percent, 9);
3153 let extracted = collect_extract(&archive, None).unwrap();
3154 assert_eq!(extracted[0].data, payload);
3155 }
3156
3157 #[test]
3158 fn direct_writer_creates_rar70_stored_archive_with_metadata() {
3159 let bytes =
3160 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar70).with_compression_level(0))
3161 .entries(
3162 [
3163 rar50_entry(b"rar7-metadata.txt", b"facade rar7 metadata payload\n")
3164 .with_attributes(0x20)
3165 .with_host_os(3),
3166 ]
3167 .to_vec(),
3168 )
3169 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3170 name: Some(b"facade-metadata.rar"),
3171 creation_time: Some(0x01dcd60e_662d7a32),
3172 }))
3173 .finish()
3174 .unwrap();
3175
3176 let archive = ArchiveReader::read(&bytes).unwrap();
3177 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3178 assert_eq!(
3179 metadata.name.as_deref(),
3180 Some(b"facade-metadata.rar".as_slice())
3181 );
3182 let extracted = collect_extract(&archive, None).unwrap();
3183 assert_eq!(extracted[0].data, b"facade rar7 metadata payload\n");
3184 }
3185
3186 #[test]
3187 fn direct_writer_creates_rar70_compressed_archive_with_metadata() {
3188 let payload = b"facade rar7 compressed metadata payload repeated\n".repeat(8);
3189 let bytes = rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar70))
3190 .entries(
3191 [rar50_entry(b"rar7-compressed-metadata.txt", &payload)
3192 .with_attributes(0x20)
3193 .with_host_os(3)]
3194 .to_vec(),
3195 )
3196 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3197 name: Some(b"facade-compressed-metadata.rar"),
3198 creation_time: Some(0x01dcd60e_662d7a32),
3199 }))
3200 .finish()
3201 .unwrap();
3202
3203 let archive = ArchiveReader::read(&bytes).unwrap();
3204 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3205 assert_eq!(
3206 metadata.name.as_deref(),
3207 Some(b"facade-compressed-metadata.rar".as_slice())
3208 );
3209 let extracted = collect_extract(&archive, None).unwrap();
3210 assert_eq!(extracted[0].data, payload);
3211 }
3212
3213 #[test]
3214 fn direct_writer_creates_rar50_compressed_archive_with_comment() {
3215 let payload = b"facade rar5 compressed archive comment payload repeated\n".repeat(8);
3216 let features = FeatureSet::store_only();
3217 let bytes =
3218 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3219 .entries(
3220 [rar50_entry(b"rar5-compressed-comment.txt", &payload)
3221 .with_attributes(0x20)
3222 .with_host_os(3)]
3223 .to_vec(),
3224 )
3225 .archive_comment(Some(b"facade compressed comment\n"))
3226 .finish()
3227 .unwrap();
3228
3229 let archive = ArchiveReader::read(&bytes).unwrap();
3230 let raw = archive.as_rar50().unwrap();
3231 let comment = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
3232 assert_eq!(comment.data, b"facade compressed comment\n");
3233 let extracted = collect_extract(&archive, None).unwrap();
3234 assert_eq!(extracted[0].data, payload);
3235 }
3236
3237 #[test]
3238 fn direct_writer_creates_rar70_encrypted_stored_archive_with_metadata() {
3239 let features = FeatureSet::store_only();
3240 let bytes = rar50::Rar50Writer::new(
3241 rar50_options_with_features(ArchiveVersion::Rar70, features).with_compression_level(0),
3242 )
3243 .entries(
3244 [rar50_entry(
3245 b"rar7-encrypted-metadata.txt",
3246 b"facade rar7 encrypted metadata payload\n",
3247 )
3248 .with_attributes(0x20)
3249 .with_host_os(3)
3250 .with_password(b"password".to_vec())]
3251 .to_vec(),
3252 )
3253 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3254 name: Some(b"facade-encrypted-metadata.rar"),
3255 creation_time: Some(0x01dcd60e_662d7a32),
3256 }))
3257 .finish()
3258 .unwrap();
3259
3260 let archive = ArchiveReader::read(&bytes).unwrap();
3261 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3262 assert_eq!(
3263 metadata.name.as_deref(),
3264 Some(b"facade-encrypted-metadata.rar".as_slice())
3265 );
3266 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3267 assert_eq!(
3268 extracted[0].data,
3269 b"facade rar7 encrypted metadata payload\n"
3270 );
3271 }
3272
3273 #[test]
3274 fn direct_writer_creates_rar70_encrypted_compressed_archive_with_metadata() {
3275 let features = FeatureSet::store_only();
3276 let payload = b"facade rar7 encrypted compressed metadata payload repeated\n".repeat(8);
3277 let bytes =
3278 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar70, features))
3279 .entries(
3280 [
3281 rar50_entry(b"rar7-encrypted-compressed-metadata.txt", &payload)
3282 .with_attributes(0x20)
3283 .with_host_os(3)
3284 .with_password(b"password".to_vec()),
3285 ]
3286 .to_vec(),
3287 )
3288 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3289 name: Some(b"facade-encrypted-compressed-metadata.rar"),
3290 creation_time: Some(0x01dcd60e_662d7a32),
3291 }))
3292 .finish()
3293 .unwrap();
3294
3295 let archive = ArchiveReader::read(&bytes).unwrap();
3296 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3297 assert_eq!(
3298 metadata.name.as_deref(),
3299 Some(b"facade-encrypted-compressed-metadata.rar".as_slice())
3300 );
3301 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3302 assert_eq!(extracted[0].data, payload);
3303 }
3304
3305 #[test]
3306 fn direct_writer_creates_rar70_header_encrypted_stored_archive_with_metadata() {
3307 let mut features = FeatureSet::store_only();
3308 features.header_encryption = true;
3309 let bytes = rar50::Rar50Writer::new(
3310 rar50_options_with_features(ArchiveVersion::Rar70, features).with_compression_level(0),
3311 )
3312 .entries(
3313 [rar50_entry(
3314 b"rar7-header-metadata.txt",
3315 b"facade rar7 header encrypted metadata payload\n",
3316 )
3317 .with_attributes(0x20)
3318 .with_host_os(3)
3319 .with_password(b"password".to_vec())]
3320 .to_vec(),
3321 )
3322 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3323 name: Some(b"facade-header-metadata.rar"),
3324 creation_time: Some(0x01dcd60e_662d7a32),
3325 }))
3326 .finish()
3327 .unwrap();
3328
3329 assert!(matches!(
3330 ArchiveReader::read(&bytes),
3331 Err(Error::NeedPassword)
3332 ));
3333 let archive = ArchiveReader::read_with_options(
3334 &bytes,
3335 ArchiveReadOptions::with_password(b"password"),
3336 )
3337 .unwrap();
3338 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3339 assert_eq!(
3340 metadata.name.as_deref(),
3341 Some(b"facade-header-metadata.rar".as_slice())
3342 );
3343 let extracted = collect_extract(&archive, None).unwrap();
3344 assert_eq!(
3345 extracted[0].data,
3346 b"facade rar7 header encrypted metadata payload\n"
3347 );
3348 }
3349
3350 #[test]
3351 fn direct_writer_creates_rar70_header_encrypted_compressed_archive_with_metadata() {
3352 let mut features = FeatureSet::store_only();
3353 features.header_encryption = true;
3354 let payload =
3355 b"facade rar7 header encrypted compressed metadata payload repeated\n".repeat(8);
3356 let bytes =
3357 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar70, features))
3358 .entries(
3359 [
3360 rar50_entry(b"rar7-header-compressed-metadata.txt", &payload)
3361 .with_attributes(0x20)
3362 .with_host_os(3)
3363 .with_password(b"password".to_vec()),
3364 ]
3365 .to_vec(),
3366 )
3367 .archive_metadata(Some(rar50::ArchiveMetadataEntry {
3368 name: Some(b"facade-header-compressed-metadata.rar"),
3369 creation_time: Some(0x01dcd60e_662d7a32),
3370 }))
3371 .finish()
3372 .unwrap();
3373
3374 assert!(matches!(
3375 ArchiveReader::read(&bytes),
3376 Err(Error::NeedPassword)
3377 ));
3378 let archive = ArchiveReader::read_with_options(
3379 &bytes,
3380 ArchiveReadOptions::with_password(b"password"),
3381 )
3382 .unwrap();
3383 let metadata = archive.as_rar50().unwrap().main.archive_metadata().unwrap();
3384 assert_eq!(
3385 metadata.name.as_deref(),
3386 Some(b"facade-header-compressed-metadata.rar".as_slice())
3387 );
3388 let extracted = collect_extract(&archive, None).unwrap();
3389 assert_eq!(extracted[0].data, payload);
3390 }
3391
3392 #[test]
3393 fn direct_writer_creates_rar50_encrypted_stored_archive() {
3394 let features = FeatureSet::store_only();
3395 let bytes = rar50::Rar50Writer::new(
3396 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3397 )
3398 .entries(
3399 [rar50_entry(
3400 b"rar5-secret.txt",
3401 b"facade rar5 encrypted stored payload\n",
3402 )
3403 .with_mtime(Some(0))
3404 .with_attributes(0x20)
3405 .with_host_os(3)
3406 .with_password(b"password".to_vec())]
3407 .to_vec(),
3408 )
3409 .finish()
3410 .unwrap();
3411
3412 let archive = ArchiveReader::read(&bytes).unwrap();
3413 assert!(matches!(
3414 collect_extract(&archive, None),
3415 Err(Error::AtEntry { source, .. }) if matches!(*source, Error::NeedPassword)
3416 ));
3417 assert!(matches!(
3418 collect_extract(&archive, Some(b"wrong")),
3419 Err(Error::AtEntry { source, .. })
3420 if matches!(*source, Error::WrongPasswordOrCorruptData)
3421 ));
3422 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3423 assert_eq!(extracted[0].data, b"facade rar5 encrypted stored payload\n");
3424 }
3425
3426 #[test]
3427 fn direct_writer_creates_rar50_encrypted_compressed_archive() {
3428 let features = FeatureSet::store_only();
3429 let payload = b"facade rar5 encrypted compressed\n".repeat(16);
3430 let bytes =
3431 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3432 .entries(
3433 [rar50_entry(b"rar5-secret-compressed.txt", &payload)
3434 .with_attributes(0x20)
3435 .with_host_os(3)
3436 .with_password(b"password".to_vec())]
3437 .to_vec(),
3438 )
3439 .finish()
3440 .unwrap();
3441
3442 let archive = ArchiveReader::read(&bytes).unwrap();
3443 let raw = archive.as_rar50().unwrap();
3444 let file = raw.files().next().unwrap();
3445 assert!(file.encrypted);
3446 assert_eq!(file.decoded_compression_info().unwrap().method, 3);
3447 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3448 assert_eq!(extracted[0].data, payload);
3449 }
3450
3451 #[test]
3452 fn direct_writer_creates_rar50_encrypted_solid_compressed_archive() {
3453 let mut features = FeatureSet::store_only();
3454 features.solid = true;
3455 let first = b"facade rar50 encrypted solid shared phrase alpha beta gamma\n".repeat(12);
3456 let second =
3457 b"facade rar50 encrypted solid shared phrase alpha beta gamma\nsecond\n".repeat(6);
3458 let bytes =
3459 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3460 .entries(
3461 [
3462 rar50_entry(b"rar5-encrypted-solid-one.txt", &first)
3463 .with_attributes(0x20)
3464 .with_host_os(3)
3465 .with_password(b"password".to_vec()),
3466 rar50_entry(b"rar5-encrypted-solid-two.txt", &second)
3467 .with_attributes(0x20)
3468 .with_host_os(3)
3469 .with_password(b"password".to_vec()),
3470 ]
3471 .to_vec(),
3472 )
3473 .finish()
3474 .unwrap();
3475
3476 let archive = ArchiveReader::read(&bytes).unwrap();
3477 let raw = archive.as_rar50().unwrap();
3478 let files: Vec<_> = raw.files().collect();
3479 assert!(raw.main.is_solid());
3480 assert!(files.iter().all(|file| file.encrypted));
3481 assert!(!files[0].decoded_compression_info().unwrap().solid);
3482 assert!(files[1].decoded_compression_info().unwrap().solid);
3483 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3484 assert_eq!(extracted[0].data, first);
3485 assert_eq!(extracted[1].data, second);
3486 }
3487
3488 #[test]
3489 fn direct_writer_creates_rar50_header_encrypted_compressed_archive() {
3490 let mut features = FeatureSet::store_only();
3491 features.header_encryption = true;
3492 let bytes = rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3493 .entries([rar50_entry(b"rar5-header-secret-compressed.txt", b"facade rar5 header encrypted compressed\nfacade rar5 header encrypted compressed\n").with_attributes(0x20).with_host_os(3).with_password(b"password".to_vec())].to_vec())
3494 .finish()
3495 .unwrap();
3496
3497 assert!(matches!(
3498 ArchiveReader::read(&bytes),
3499 Err(Error::NeedPassword)
3500 ));
3501 let archive = ArchiveReader::read_with_options(
3502 &bytes,
3503 ArchiveReadOptions::with_password(b"password"),
3504 )
3505 .unwrap();
3506 let raw = archive.as_rar50().unwrap();
3507 let file = raw.files().next().unwrap();
3508 assert!(file.encrypted);
3509 assert_eq!(file.decoded_compression_info().unwrap().method, 3);
3510 let extracted = collect_extract(&archive, None).unwrap();
3511 assert_eq!(
3512 extracted[0].data,
3513 b"facade rar5 header encrypted compressed\nfacade rar5 header encrypted compressed\n"
3514 );
3515 }
3516
3517 #[test]
3518 fn direct_writer_creates_rar50_header_encrypted_solid_compressed_archive() {
3519 let mut features = FeatureSet::store_only();
3520 features.header_encryption = true;
3521 features.solid = true;
3522 let first =
3523 b"facade rar50 header encrypted solid shared phrase alpha beta gamma\n".repeat(12);
3524 let second =
3525 b"facade rar50 header encrypted solid shared phrase alpha beta gamma\nsecond\n"
3526 .repeat(6);
3527 let bytes =
3528 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3529 .entries(
3530 [
3531 rar50_entry(b"rar5-header-solid-one.txt", &first)
3532 .with_attributes(0x20)
3533 .with_host_os(3)
3534 .with_password(b"password".to_vec()),
3535 rar50_entry(b"rar5-header-solid-two.txt", &second)
3536 .with_attributes(0x20)
3537 .with_host_os(3)
3538 .with_password(b"password".to_vec()),
3539 ]
3540 .to_vec(),
3541 )
3542 .finish()
3543 .unwrap();
3544
3545 assert!(matches!(
3546 ArchiveReader::read(&bytes),
3547 Err(Error::NeedPassword)
3548 ));
3549 let archive = ArchiveReader::read_with_options(
3550 &bytes,
3551 ArchiveReadOptions::with_password(b"password"),
3552 )
3553 .unwrap();
3554 let raw = archive.as_rar50().unwrap();
3555 let files: Vec<_> = raw.files().collect();
3556 assert!(raw.main.is_solid());
3557 assert!(files.iter().all(|file| file.encrypted));
3558 assert!(!files[0].decoded_compression_info().unwrap().solid);
3559 assert!(files[1].decoded_compression_info().unwrap().solid);
3560 let extracted = collect_extract(&archive, None).unwrap();
3561 assert_eq!(extracted[0].data, first);
3562 assert_eq!(extracted[1].data, second);
3563 }
3564
3565 #[test]
3566 fn direct_writer_creates_rar50_encrypted_stored_archive_with_comment() {
3567 let features = FeatureSet::store_only();
3568 let bytes = rar50::Rar50Writer::new(
3569 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3570 )
3571 .entries(
3572 [rar50_entry(
3573 b"rar5-secret.txt",
3574 b"facade rar5 encrypted stored payload\n",
3575 )
3576 .with_mtime(Some(0))
3577 .with_attributes(0x20)
3578 .with_host_os(3)
3579 .with_password(b"password".to_vec())]
3580 .to_vec(),
3581 )
3582 .encrypted_archive_comment(b"facade encrypted comment\n", b"password")
3583 .finish()
3584 .unwrap();
3585
3586 let archive = ArchiveReader::read_with_options(
3587 &bytes,
3588 ArchiveReadOptions::with_password(b"password"),
3589 )
3590 .unwrap();
3591 let raw = archive.as_rar50().unwrap();
3592 let comment = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
3593 assert_eq!(comment.data, b"facade encrypted comment\n");
3594 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3595 assert_eq!(extracted[0].data, b"facade rar5 encrypted stored payload\n");
3596 }
3597
3598 #[test]
3599 fn direct_writer_creates_rar50_encrypted_compressed_archive_with_comment() {
3600 let payload = b"facade rar5 encrypted compressed comment payload\n".repeat(8);
3601 let features = FeatureSet::store_only();
3602 let bytes =
3603 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3604 .entries(
3605 [
3606 rar50_entry(b"rar5-encrypted-compressed-comment.txt", &payload)
3607 .with_mtime(Some(0))
3608 .with_attributes(0x20)
3609 .with_host_os(3)
3610 .with_password(b"password".to_vec()),
3611 ]
3612 .to_vec(),
3613 )
3614 .encrypted_archive_comment(b"facade encrypted compressed comment\n", b"password")
3615 .finish()
3616 .unwrap();
3617
3618 let archive = ArchiveReader::read_with_options(
3619 &bytes,
3620 ArchiveReadOptions::with_password(b"password"),
3621 )
3622 .unwrap();
3623 let raw = archive.as_rar50().unwrap();
3624 let comment = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
3625 assert_eq!(comment.data, b"facade encrypted compressed comment\n");
3626 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3627 assert_eq!(extracted[0].data, payload);
3628 }
3629
3630 #[test]
3631 fn direct_writer_creates_rar50_header_encrypted_stored_archive() {
3632 let mut features = FeatureSet::store_only();
3633 features.header_encryption = true;
3634 let bytes = rar50::Rar50Writer::new(
3635 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3636 )
3637 .entries(
3638 [rar50_entry(
3639 b"rar5-header-secret.txt",
3640 b"facade rar5 header encrypted stored payload\n",
3641 )
3642 .with_mtime(Some(0))
3643 .with_attributes(0x20)
3644 .with_host_os(3)
3645 .with_password(b"password".to_vec())]
3646 .to_vec(),
3647 )
3648 .finish()
3649 .unwrap();
3650
3651 assert!(matches!(
3652 ArchiveReader::read(&bytes),
3653 Err(Error::NeedPassword)
3654 ));
3655 assert!(matches!(
3656 ArchiveReader::read_with_options(&bytes, ArchiveReadOptions::with_password(b"wrong")),
3657 Err(Error::WrongPasswordOrCorruptData)
3658 ));
3659 let archive = ArchiveReader::read_with_options(
3660 &bytes,
3661 ArchiveReadOptions::with_password(b"password"),
3662 )
3663 .unwrap();
3664 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3665 assert_eq!(
3666 extracted[0].data,
3667 b"facade rar5 header encrypted stored payload\n"
3668 );
3669 }
3670
3671 #[test]
3672 fn direct_writer_creates_rar50_header_encrypted_stored_archive_with_comment() {
3673 let mut features = FeatureSet::store_only();
3674 features.header_encryption = true;
3675 let bytes = rar50::Rar50Writer::new(
3676 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3677 )
3678 .entries(
3679 [rar50_entry(
3680 b"rar5-header-comment-secret.txt",
3681 b"facade rar5 header encrypted comment payload\n",
3682 )
3683 .with_mtime(Some(0))
3684 .with_attributes(0x20)
3685 .with_host_os(3)
3686 .with_password(b"password".to_vec())]
3687 .to_vec(),
3688 )
3689 .encrypted_archive_comment(b"facade header encrypted comment\n", b"password")
3690 .finish()
3691 .unwrap();
3692
3693 let archive = ArchiveReader::read_with_options(
3694 &bytes,
3695 ArchiveReadOptions::with_password(b"password"),
3696 )
3697 .unwrap();
3698 let raw = archive.as_rar50().unwrap();
3699 let comment = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
3700 assert_eq!(comment.data, b"facade header encrypted comment\n");
3701 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3702 assert_eq!(
3703 extracted[0].data,
3704 b"facade rar5 header encrypted comment payload\n"
3705 );
3706 }
3707
3708 #[test]
3709 fn direct_writer_creates_rar50_header_encrypted_compressed_archive_with_comment() {
3710 let payload = b"facade rar5 header encrypted compressed comment payload\n".repeat(8);
3711 let mut features = FeatureSet::store_only();
3712 features.header_encryption = true;
3713 let bytes =
3714 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3715 .entries(
3716 [
3717 rar50_entry(b"rar5-header-compressed-comment-secret.txt", &payload)
3718 .with_mtime(Some(0))
3719 .with_attributes(0x20)
3720 .with_host_os(3)
3721 .with_password(b"password".to_vec()),
3722 ]
3723 .to_vec(),
3724 )
3725 .encrypted_archive_comment(
3726 b"facade header encrypted compressed comment\n",
3727 b"password",
3728 )
3729 .finish()
3730 .unwrap();
3731
3732 let archive = ArchiveReader::read_with_options(
3733 &bytes,
3734 ArchiveReadOptions::with_password(b"password"),
3735 )
3736 .unwrap();
3737 let raw = archive.as_rar50().unwrap();
3738 let comment = collect_rar50_file(raw, raw.services().next().unwrap()).unwrap();
3739 assert_eq!(
3740 comment.data,
3741 b"facade header encrypted compressed comment\n"
3742 );
3743 let extracted = collect_extract(&archive, Some(b"password")).unwrap();
3744 assert_eq!(extracted[0].data, payload);
3745 }
3746
3747 #[test]
3748 fn direct_writer_creates_rar50_encrypted_stored_archive_with_recovery() {
3749 let features = FeatureSet::store_only();
3750 let bytes = rar50::Rar50Writer::new(
3751 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3752 )
3753 .entries(
3754 [rar50_entry(
3755 b"rar5-encrypted-recovery.txt",
3756 b"facade rar5 encrypted recovery payload\n",
3757 )
3758 .with_attributes(0x20)
3759 .with_host_os(3)
3760 .with_password(b"password".to_vec())]
3761 .to_vec(),
3762 )
3763 .recovery_percent(Some(6))
3764 .finish()
3765 .unwrap();
3766
3767 let archive = ArchiveReader::read_with_options(
3768 &bytes,
3769 ArchiveReadOptions::with_password(b"password"),
3770 )
3771 .unwrap();
3772 let raw = archive.as_rar50().unwrap();
3773 assert!(raw.main.has_recovery_record());
3774 let service = raw.services().next().unwrap();
3775 assert_eq!(service.name, b"RR");
3776 assert_eq!(service.recovery_record().unwrap().unwrap().percent, 6);
3777 let recovery_data = collect_rar50_file(raw, service).unwrap().data;
3778 assert!(recovery_data.starts_with(b"{RB}"));
3779 assert_eq!(
3780 u32::from_le_bytes(recovery_data[0x0c..0x10].try_into().unwrap()) as usize,
3781 recovery_data.len()
3782 );
3783 let extracted = collect_extract(&archive, None).unwrap();
3784 assert_eq!(
3785 extracted[0].data,
3786 b"facade rar5 encrypted recovery payload\n"
3787 );
3788 }
3789
3790 #[test]
3791 fn direct_writer_creates_rar50_encrypted_compressed_archive_with_recovery() {
3792 let features = FeatureSet::store_only();
3793 let payload = b"facade rar5 encrypted compressed recovery payload repeated\n".repeat(8);
3794 let bytes =
3795 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3796 .entries(
3797 [
3798 rar50_entry(b"rar5-encrypted-compressed-recovery.txt", &payload)
3799 .with_attributes(0x20)
3800 .with_host_os(3)
3801 .with_password(b"password".to_vec()),
3802 ]
3803 .to_vec(),
3804 )
3805 .recovery_percent(Some(6))
3806 .finish()
3807 .unwrap();
3808
3809 let archive = ArchiveReader::read_with_options(
3810 &bytes,
3811 ArchiveReadOptions::with_password(b"password"),
3812 )
3813 .unwrap();
3814 let raw = archive.as_rar50().unwrap();
3815 assert!(raw.main.has_recovery_record());
3816 let service = raw.services().next().unwrap();
3817 assert_eq!(service.name, b"RR");
3818 assert_eq!(service.recovery_record().unwrap().unwrap().percent, 6);
3819 let extracted = collect_extract(&archive, None).unwrap();
3820 assert_eq!(extracted[0].data, payload);
3821 }
3822
3823 #[test]
3824 fn direct_writer_creates_rar50_header_encrypted_stored_archive_with_recovery() {
3825 let mut features = FeatureSet::store_only();
3826 features.header_encryption = true;
3827 let bytes = rar50::Rar50Writer::new(
3828 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3829 )
3830 .entries(
3831 [rar50_entry(
3832 b"rar5-header-recovery.txt",
3833 b"facade rar5 header encrypted recovery payload\n",
3834 )
3835 .with_attributes(0x20)
3836 .with_host_os(3)
3837 .with_password(b"password".to_vec())]
3838 .to_vec(),
3839 )
3840 .recovery_percent(Some(4))
3841 .finish()
3842 .unwrap();
3843
3844 assert!(matches!(
3845 ArchiveReader::read(&bytes),
3846 Err(Error::NeedPassword)
3847 ));
3848 let archive = ArchiveReader::read_with_options(
3849 &bytes,
3850 ArchiveReadOptions::with_password(b"password"),
3851 )
3852 .unwrap();
3853 let raw = archive.as_rar50().unwrap();
3854 assert!(raw.main.has_recovery_record());
3855 let service = raw.services().next().unwrap();
3856 assert_eq!(service.name, b"RR");
3857 assert!(!service.encrypted);
3858 assert_eq!(service.recovery_record().unwrap().unwrap().percent, 4);
3859 let recovery_data = collect_rar50_file(raw, service).unwrap().data;
3860 assert!(recovery_data.starts_with(b"{RB}"));
3861 assert_eq!(
3862 u32::from_le_bytes(recovery_data[0x0c..0x10].try_into().unwrap()) as usize,
3863 recovery_data.len()
3864 );
3865 let extracted = collect_extract(&archive, None).unwrap();
3866 assert_eq!(
3867 extracted[0].data,
3868 b"facade rar5 header encrypted recovery payload\n"
3869 );
3870 }
3871
3872 #[test]
3873 fn direct_writer_creates_rar50_header_encrypted_compressed_archive_with_recovery() {
3874 let mut features = FeatureSet::store_only();
3875 features.header_encryption = true;
3876 let payload =
3877 b"facade rar5 header encrypted compressed recovery payload repeated\n".repeat(8);
3878 let bytes =
3879 rar50::Rar50Writer::new(rar50_options_with_features(ArchiveVersion::Rar50, features))
3880 .entries(
3881 [
3882 rar50_entry(b"rar5-header-compressed-recovery.txt", &payload)
3883 .with_attributes(0x20)
3884 .with_host_os(3)
3885 .with_password(b"password".to_vec()),
3886 ]
3887 .to_vec(),
3888 )
3889 .recovery_percent(Some(4))
3890 .finish()
3891 .unwrap();
3892
3893 assert!(matches!(
3894 ArchiveReader::read(&bytes),
3895 Err(Error::NeedPassword)
3896 ));
3897 let archive = ArchiveReader::read_with_options(
3898 &bytes,
3899 ArchiveReadOptions::with_password(b"password"),
3900 )
3901 .unwrap();
3902 let raw = archive.as_rar50().unwrap();
3903 assert!(raw.main.has_recovery_record());
3904 let service = raw.services().next().unwrap();
3905 assert_eq!(service.name, b"RR");
3906 assert!(!service.encrypted);
3907 assert_eq!(service.recovery_record().unwrap().unwrap().percent, 4);
3908 let extracted = collect_extract(&archive, None).unwrap();
3909 assert_eq!(extracted[0].data, payload);
3910 }
3911
3912 #[test]
3913 fn direct_writer_creates_rar50_encrypted_stored_volumes() {
3914 let features = FeatureSet::store_only();
3915 let payload = b"facade rar5 encrypted split payload\n".repeat(12);
3916 let parts = write_rar50_volume_set(
3917 &[rar50_entry(b"split-secret50.txt", &payload)
3918 .with_attributes(0x20)
3919 .with_host_os(3)
3920 .with_password(b"password".to_vec())],
3921 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3922 16,
3923 None,
3924 );
3925 let archives: Vec<_> = parts
3926 .iter()
3927 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
3928 .collect();
3929
3930 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
3931 assert_eq!(extracted[0].name, b"split-secret50.txt");
3932 assert_eq!(extracted[0].data, payload);
3933 }
3934
3935 #[test]
3936 fn direct_writer_creates_rar50_encrypted_stored_volumes_with_recovery() {
3937 let features = FeatureSet::store_only();
3938 let payload = b"facade rar5 encrypted recovery split payload\n".repeat(12);
3939 let parts = write_rar50_volume_set(
3940 &[rar50_entry(b"split-secret50-rr.txt", &payload)
3941 .with_attributes(0x20)
3942 .with_host_os(3)
3943 .with_password(b"password".to_vec())],
3944 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3945 16,
3946 Some(8),
3947 );
3948 let archives: Vec<_> = parts
3949 .iter()
3950 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
3951 .collect();
3952 assert_rar50_volume_recovery_records(&archives, 8);
3953
3954 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
3955 assert_eq!(extracted[0].name, b"split-secret50-rr.txt");
3956 assert_eq!(extracted[0].data, payload);
3957 }
3958
3959 #[test]
3960 fn direct_writer_creates_rar50_header_encrypted_stored_volumes_with_recovery() {
3961 let mut features = FeatureSet::store_only();
3962 features.header_encryption = true;
3963 let payload = b"facade rar5 header encrypted recovery split payload\n".repeat(4);
3964 let parts = write_rar50_volume_set(
3965 &[rar50_entry(b"split-header-secret50-rr.txt", &payload)
3966 .with_attributes(0x20)
3967 .with_host_os(3)
3968 .with_password(b"password".to_vec())],
3969 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3970 16,
3971 Some(8),
3972 );
3973 assert!(matches!(
3974 rar50::Archive::parse(&parts[0]),
3975 Err(Error::NeedPassword)
3976 ));
3977 let archives: Vec<_> = parts
3978 .iter()
3979 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
3980 .collect();
3981 assert_rar50_volume_recovery_records(&archives, 8);
3982
3983 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
3984 assert_eq!(extracted[0].name, b"split-header-secret50-rr.txt");
3985 assert_eq!(extracted[0].data, payload);
3986 }
3987
3988 #[test]
3989 fn direct_writer_creates_rar50_header_encrypted_stored_volumes() {
3990 let mut features = FeatureSet::store_only();
3991 features.header_encryption = true;
3992 let payload = b"facade rar5 header encrypted split payload\n".repeat(12);
3993 let parts = write_rar50_volume_set(
3994 &[rar50_entry(b"split-header-secret50.txt", &payload)
3995 .with_attributes(0x20)
3996 .with_host_os(3)
3997 .with_password(b"password".to_vec())],
3998 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
3999 16,
4000 None,
4001 );
4002 assert!(matches!(
4003 rar50::Archive::parse(&parts[0]),
4004 Err(Error::NeedPassword)
4005 ));
4006 let archives: Vec<_> = parts
4007 .iter()
4008 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
4009 .collect();
4010
4011 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
4012 assert_eq!(extracted[0].name, b"split-header-secret50.txt");
4013 assert_eq!(extracted[0].data, payload);
4014 }
4015
4016 #[test]
4017 fn direct_writer_creates_rar50_encrypted_compressed_volumes() {
4018 let features = FeatureSet::store_only();
4019 let payload = b"facade rar5 encrypted compressed split payload\n".repeat(12);
4020 let parts = write_rar50_volume_set(
4021 &[rar50_entry(b"split-secret-compressed50.txt", &payload)
4022 .with_attributes(0x20)
4023 .with_host_os(3)
4024 .with_password(b"password".to_vec())],
4025 rar50_options_with_features(ArchiveVersion::Rar50, features),
4026 32,
4027 None,
4028 );
4029 let archives: Vec<_> = parts
4030 .iter()
4031 .map(|part| rar50::Archive::parse(part).unwrap())
4032 .collect();
4033
4034 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
4035 assert_eq!(extracted[0].name, b"split-secret-compressed50.txt");
4036 assert_eq!(extracted[0].data, payload);
4037 }
4038
4039 #[test]
4040 fn direct_writer_creates_rar50_encrypted_compressed_volumes_with_recovery() {
4041 let features = FeatureSet::store_only();
4042 let payload = b"facade rar5 encrypted compressed recovery split payload\n".repeat(12);
4043 let entries = [rar50_entry(b"split-secret-compressed50-rr.txt", &payload)
4044 .with_attributes(0x20)
4045 .with_host_os(3)
4046 .with_password(b"password".to_vec())];
4047 let parts = write_rar50_volume_set(
4048 &entries,
4049 rar50_options_with_features(ArchiveVersion::Rar50, features),
4050 32,
4051 Some(8),
4052 );
4053 let archives: Vec<_> = parts
4054 .iter()
4055 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
4056 .collect();
4057 assert_rar50_volume_recovery_records(&archives, 8);
4058
4059 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
4060 assert_eq!(extracted[0].name, b"split-secret-compressed50-rr.txt");
4061 assert_eq!(extracted[0].data, payload);
4062 }
4063
4064 #[test]
4065 fn direct_writer_creates_rar50_header_encrypted_compressed_volumes_with_recovery() {
4066 let mut features = FeatureSet::store_only();
4067 features.header_encryption = true;
4068 let payload =
4069 b"facade rar5 header encrypted compressed recovery split payload\n".repeat(12);
4070 let entries = [
4071 rar50_entry(b"split-header-secret-compressed50-rr.txt", &payload)
4072 .with_attributes(0x20)
4073 .with_host_os(3)
4074 .with_password(b"password".to_vec()),
4075 ];
4076 let parts = write_rar50_volume_set(
4077 &entries,
4078 rar50_options_with_features(ArchiveVersion::Rar50, features),
4079 32,
4080 Some(8),
4081 );
4082 assert!(matches!(
4083 rar50::Archive::parse(&parts[0]),
4084 Err(Error::NeedPassword)
4085 ));
4086 let archives: Vec<_> = parts
4087 .iter()
4088 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
4089 .collect();
4090 assert_rar50_volume_recovery_records(&archives, 8);
4091
4092 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
4093 assert_eq!(
4094 extracted[0].name,
4095 b"split-header-secret-compressed50-rr.txt"
4096 );
4097 assert_eq!(extracted[0].data, payload);
4098 }
4099
4100 #[test]
4101 fn direct_writer_creates_rar50_encrypted_solid_compressed_volumes() {
4102 let mut features = FeatureSet::store_only();
4103 features.solid = true;
4104 let payload = b"facade rar5 encrypted solid compressed split payload\n".repeat(12);
4105 let parts = write_rar50_volume_set(
4106 &[
4107 rar50_entry(b"split-solid-secret-compressed50.txt", &payload)
4108 .with_attributes(0x20)
4109 .with_host_os(3)
4110 .with_password(b"password".to_vec()),
4111 ],
4112 rar50_options_with_features(ArchiveVersion::Rar50, features),
4113 32,
4114 None,
4115 );
4116 let archives: Vec<_> = parts
4117 .iter()
4118 .map(|part| rar50::Archive::parse(part).unwrap())
4119 .collect();
4120 assert!(archives.iter().all(|archive| archive.main.is_solid()));
4121
4122 let extracted = collect_rar50_volumes(&archives, Some(b"password")).unwrap();
4123 assert_eq!(extracted[0].name, b"split-solid-secret-compressed50.txt");
4124 assert_eq!(extracted[0].data, payload);
4125 }
4126
4127 #[test]
4128 fn direct_writer_creates_rar50_header_encrypted_compressed_volumes() {
4129 let mut features = FeatureSet::store_only();
4130 features.header_encryption = true;
4131 let payload: Vec<u8> = (0..512).map(|index| (index * 37 + 11) as u8).collect();
4132 let parts = write_rar50_volume_set(
4133 &[
4134 rar50_entry(b"split-header-secret-compressed50.txt", &payload)
4135 .with_attributes(0x20)
4136 .with_host_os(3)
4137 .with_password(b"password".to_vec()),
4138 ],
4139 rar50_options_with_features(ArchiveVersion::Rar50, features),
4140 64,
4141 None,
4142 );
4143 assert!(matches!(
4144 rar50::Archive::parse(&parts[0]),
4145 Err(Error::NeedPassword)
4146 ));
4147 let archives: Vec<_> = parts
4148 .iter()
4149 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
4150 .collect();
4151
4152 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4153 assert_eq!(extracted[0].name, b"split-header-secret-compressed50.txt");
4154 assert_eq!(extracted[0].data, payload);
4155 }
4156
4157 #[test]
4158 fn direct_writer_creates_rar50_header_encrypted_solid_compressed_volumes() {
4159 let mut features = FeatureSet::store_only();
4160 features.header_encryption = true;
4161 features.solid = true;
4162 let payload = b"facade rar5 header encrypted solid compressed split payload\n".repeat(12);
4163 let parts = write_rar50_volume_set(
4164 &[
4165 rar50_entry(b"split-header-solid-secret-compressed50.txt", &payload)
4166 .with_attributes(0x20)
4167 .with_host_os(3)
4168 .with_password(b"password".to_vec()),
4169 ],
4170 rar50_options_with_features(ArchiveVersion::Rar50, features),
4171 32,
4172 None,
4173 );
4174 assert!(matches!(
4175 rar50::Archive::parse(&parts[0]),
4176 Err(Error::NeedPassword)
4177 ));
4178 let archives: Vec<_> = parts
4179 .iter()
4180 .map(|part| rar50::Archive::parse_with_password(part, Some(b"password")).unwrap())
4181 .collect();
4182 assert!(archives.iter().all(|archive| archive.main.is_solid()));
4183
4184 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4185 assert_eq!(
4186 extracted[0].name,
4187 b"split-header-solid-secret-compressed50.txt"
4188 );
4189 assert_eq!(extracted[0].data, payload);
4190 }
4191
4192 #[test]
4193 fn direct_writer_creates_rar50_stored_volumes() {
4194 let payload = b"facade rar5 stored split payload\n".repeat(20);
4195 let parts = write_rar50_volume_set(
4196 &[rar50_entry(b"split50.txt", &payload)
4197 .with_attributes(0x20)
4198 .with_host_os(3)],
4199 rar50_options(ArchiveVersion::Rar50).with_compression_level(0),
4200 80,
4201 None,
4202 );
4203 let archives: Vec<_> = parts
4204 .iter()
4205 .map(|part| rar50::Archive::parse(part).unwrap())
4206 .collect();
4207 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4208
4209 assert_eq!(extracted[0].name, b"split50.txt");
4210 assert_eq!(extracted[0].data, payload);
4211 }
4212
4213 #[test]
4214 fn direct_writer_creates_rar50_stored_volumes_with_recovery() {
4215 let features = FeatureSet::store_only();
4216 let payload = b"facade rar5 stored recovery split payload\n".repeat(20);
4217 let parts = write_rar50_volume_set(
4218 &[rar50_entry(b"split50-rr.txt", &payload)
4219 .with_attributes(0x20)
4220 .with_host_os(3)],
4221 rar50_options_with_features(ArchiveVersion::Rar50, features).with_compression_level(0),
4222 80,
4223 Some(8),
4224 );
4225 let archives: Vec<_> = parts
4226 .iter()
4227 .map(|part| rar50::Archive::parse(part).unwrap())
4228 .collect();
4229 assert_rar50_volume_recovery_records(&archives, 8);
4230 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4231
4232 assert_eq!(extracted[0].name, b"split50-rr.txt");
4233 assert_eq!(extracted[0].data, payload);
4234 }
4235
4236 #[test]
4237 fn direct_writer_creates_rar50_compressed_volumes() {
4238 let payload: Vec<u8> = (0..512).map(|index| (index * 53 + 17) as u8).collect();
4239 let parts = write_rar50_volume_set(
4240 &[rar50_entry(b"split-compressed50.txt", &payload)
4241 .with_attributes(0x20)
4242 .with_host_os(3)],
4243 rar50_options(ArchiveVersion::Rar50),
4244 64,
4245 None,
4246 );
4247 let archives: Vec<_> = parts
4248 .iter()
4249 .map(|part| rar50::Archive::parse(part).unwrap())
4250 .collect();
4251 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4252
4253 assert_eq!(extracted[0].name, b"split-compressed50.txt");
4254 assert_eq!(extracted[0].data, payload);
4255 }
4256
4257 #[test]
4258 fn direct_writer_creates_rar50_compressed_volumes_with_recovery() {
4259 let features = FeatureSet::store_only();
4260 let payload: Vec<u8> = (0..512).map(|index| (index * 53 + 17) as u8).collect();
4261 let entries = [rar50_entry(b"split-compressed50-rr.txt", &payload)
4262 .with_attributes(0x20)
4263 .with_host_os(3)];
4264 let parts = write_rar50_volume_set(
4265 &entries,
4266 rar50_options_with_features(ArchiveVersion::Rar50, features),
4267 64,
4268 Some(8),
4269 );
4270 let archives: Vec<_> = parts
4271 .iter()
4272 .map(|part| rar50::Archive::parse(part).unwrap())
4273 .collect();
4274 assert_rar50_volume_recovery_records(&archives, 8);
4275 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4276
4277 assert_eq!(extracted[0].name, b"split-compressed50-rr.txt");
4278 assert_eq!(extracted[0].data, payload);
4279 }
4280
4281 #[test]
4282 fn direct_writer_creates_rar50_solid_compressed_volumes() {
4283 let mut features = FeatureSet::store_only();
4284 features.solid = true;
4285 let payload = b"facade rar5 solid compressed split payload\n".repeat(12);
4286 let parts = write_rar50_volume_set(
4287 &[rar50_entry(b"split-solid-compressed50.txt", &payload)
4288 .with_attributes(0x20)
4289 .with_host_os(3)],
4290 rar50_options_with_features(ArchiveVersion::Rar50, features),
4291 32,
4292 None,
4293 );
4294 let archives: Vec<_> = parts
4295 .iter()
4296 .map(|part| rar50::Archive::parse(part).unwrap())
4297 .collect();
4298 assert!(archives.iter().all(|archive| archive.main.is_solid()));
4299 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4300
4301 assert_eq!(extracted[0].name, b"split-solid-compressed50.txt");
4302 assert_eq!(extracted[0].data, payload);
4303 }
4304
4305 #[test]
4306 fn direct_writer_creates_rar50_multi_file_solid_compressed_volumes() {
4307 let mut features = FeatureSet::store_only();
4308 features.solid = true;
4309 let mut first = b"facade rar5 multi-file solid split shared phrase\n"
4310 .repeat(8)
4311 .to_vec();
4312 first.extend_from_slice(&deterministic_noise(2048));
4313 let mut second = b"facade rar5 multi-file solid split shared phrase\nsecond\n"
4314 .repeat(8)
4315 .to_vec();
4316 second.extend_from_slice(&deterministic_noise(2048));
4317 let entries = [
4318 rar50_entry(b"solid-volume-one.txt", &first)
4319 .with_attributes(0x20)
4320 .with_host_os(3),
4321 rar50_entry(b"solid-volume-two.txt", &second)
4322 .with_attributes(0x20)
4323 .with_host_os(3),
4324 ];
4325 let parts = write_rar50_volume_set(
4326 &entries,
4327 rar50_options_with_features(ArchiveVersion::Rar50, features),
4328 512,
4329 None,
4330 );
4331 let archives: Vec<_> = parts
4332 .iter()
4333 .map(|part| rar50::Archive::parse(part).unwrap())
4334 .collect();
4335 assert!(archives.iter().all(|archive| archive.main.is_solid()));
4336 let extracted = collect_rar50_volumes(&archives, None).unwrap();
4337
4338 assert_eq!(extracted[0].name, b"solid-volume-one.txt");
4339 assert_eq!(extracted[0].data, first);
4340 assert_eq!(extracted[1].name, b"solid-volume-two.txt");
4341 assert_eq!(extracted[1].data, second);
4342 }
4343
4344 #[test]
4345 fn archive_as_rar13_returns_some_only_for_rar13_family() {
4346 let bytes = rar13::write_stored_archive(
4347 &[rar13::StoredEntry {
4348 name: b"old.txt",
4349 data: b"r13 downcast",
4350 file_time: 0,
4351 file_attr: 0x20,
4352 password: None,
4353 file_comment: None,
4354 }],
4355 rar13_options(ArchiveVersion::Rar14),
4356 )
4357 .unwrap();
4358 let archive = ArchiveReader::read(&bytes).unwrap();
4359 let raw = archive.as_rar13().unwrap();
4360 assert_eq!(raw.entries[0].name, b"old.txt");
4361 assert!(archive.as_rar15_40().is_none());
4362 assert!(archive.as_rar50().is_none());
4363
4364 let rar15_bytes = rar15_40::write_stored_archive(
4366 &[rar15_40::StoredEntry {
4367 name: b"mid.txt",
4368 data: b"r15 downcast",
4369 file_time: 0,
4370 file_attr: 0x20,
4371 host_os: 3,
4372 password: None,
4373 file_comment: None,
4374 }],
4375 rar15_options(ArchiveVersion::Rar15),
4376 )
4377 .unwrap();
4378 let rar15_archive = ArchiveReader::read(&rar15_bytes).unwrap();
4379 assert!(rar15_archive.as_rar13().is_none());
4380
4381 let rar50_bytes =
4382 rar50::Rar50Writer::new(rar50_options(ArchiveVersion::Rar50).with_compression_level(0))
4383 .entries(
4384 [rar50_entry(b"new.txt", b"r50 downcast")
4385 .with_attributes(0x20)
4386 .with_host_os(3)]
4387 .to_vec(),
4388 )
4389 .finish()
4390 .unwrap();
4391 let rar50_archive = ArchiveReader::read(&rar50_bytes).unwrap();
4392 assert!(rar50_archive.as_rar13().is_none());
4393 }
4394
4395 #[test]
4396 fn archive_facade_repair_recovery_returns_full_repaired_archive_bytes() {
4397 let bytes = std::fs::read(rar15_40_fixture("rar250_protect_head_rr5.rar")).unwrap();
4398 let mut damaged = bytes.clone();
4399 damaged[512 + 16..512 + 80].fill(0xa5);
4400 let damaged_archive = ArchiveReader::read(&damaged).unwrap();
4401
4402 let repaired = damaged_archive.repair_recovery().unwrap();
4403 assert_eq!(repaired, bytes);
4404 }
4405
4406 #[test]
4407 fn archive_facade_repair_recovery_rejects_rar13_archives() {
4408 let bytes = rar13::write_stored_archive(
4409 &[rar13::StoredEntry {
4410 name: b"old.txt",
4411 data: b"old data",
4412 file_time: 0,
4413 file_attr: 0x20,
4414 password: None,
4415 file_comment: None,
4416 }],
4417 rar13_options(ArchiveVersion::Rar14),
4418 )
4419 .unwrap();
4420 let archive = ArchiveReader::read(&bytes).unwrap();
4421 assert_eq!(
4422 archive.repair_recovery(),
4423 Err(Error::UnsupportedFamilyFeature {
4424 family: ArchiveFamily::Rar13,
4425 feature: "recovery repair for RAR 1.3/1.4 archives",
4426 })
4427 );
4428 }
4429
4430 #[test]
4431 fn archive_reader_read_path_dispatches_to_default_options() {
4432 let archive =
4435 ArchiveReader::read_path(rar15_40_fixture("rar250_protect_head_rr5.rar")).unwrap();
4436 assert_eq!(archive.family(), ArchiveFamily::Rar15To40);
4437 assert!(archive.as_rar15_40().unwrap().main.has_recovery_record());
4438 }
4439}