1use super::*;
2pub use rars_codec::rar50::Rar50FilterKind as FilterKind;
3use rars_codec::rar50::Unpack50Encoder;
4use rars_crypto::rar50::{Rar50Cipher, Rar50Keys};
5use rars_recovery::rar5::build_structural_inline_recovery_data;
6
7mod filter_policy;
8mod volume;
9#[cfg(test)]
10use filter_policy::encode_member_with_filter_policy;
11use filter_policy::{
12 compression_info, compression_method_for_level, dictionary_size_for_options,
13 encode_member_with_filter_policy_candidates, encode_option_candidates_for_level,
14 encode_options_for_level, encode_safe_lz_member, encode_with_solid_reset_policy,
15 rar50_algorithm_version, should_store_compressed_payload, validate_compression_level,
16};
17use volume::{
18 write_compressed_volume_set_impl, write_encrypted_compressed_volume_set_impl,
19 write_encrypted_stored_volumes_impl, write_stored_volumes_impl,
20};
21
22const MAX_MATCH_CANDIDATES_DEFAULT: usize = 256;
23const DEFAULT_RAR50_DICTIONARY_SIZE: u64 = 128 * 1024;
24const AUTO_DELTA_EDGE_SKIP: usize = 64;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct WriterOptions {
29 pub target: crate::ArchiveVersion,
30 pub features: crate::FeatureSet,
31 pub compression_level: Option<u8>,
32 pub dictionary_size: Option<u64>,
33}
34
35impl WriterOptions {
36 pub const fn new(target: crate::ArchiveVersion, features: crate::FeatureSet) -> Self {
37 Self {
38 target,
39 features,
40 compression_level: None,
41 dictionary_size: None,
42 }
43 }
44
45 pub const fn with_compression_level(mut self, level: u8) -> Self {
46 self.compression_level = Some(level);
47 self
48 }
49
50 pub const fn with_dictionary_size(mut self, size: u64) -> Self {
51 self.dictionary_size = Some(size);
52 self
53 }
54}
55
56impl Default for WriterOptions {
57 fn default() -> Self {
58 Self {
59 target: crate::ArchiveVersion::Rar50,
60 features: crate::FeatureSet::store_only(),
61 compression_level: None,
62 dictionary_size: None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct StoredEntry<'a> {
69 pub name: &'a [u8],
70 pub data: &'a [u8],
71 pub mtime: Option<u32>,
72 pub attributes: u64,
73 pub host_os: u64,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct CompressedEntry<'a> {
78 pub name: &'a [u8],
79 pub data: &'a [u8],
80 pub mtime: Option<u32>,
81 pub attributes: u64,
82 pub host_os: u64,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct StoredServiceEntry<'a> {
87 pub name: &'a [u8],
88 pub data: &'a [u8],
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub struct StoredEntryWithServices<'a> {
93 pub entry: StoredEntry<'a>,
94 pub services: &'a [StoredServiceEntry<'a>],
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct EncryptedStoredServiceEntry<'a> {
99 pub name: &'a [u8],
100 pub data: &'a [u8],
101 pub password: &'a [u8],
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct EncryptedStoredEntryWithServices<'a> {
106 pub entry: EncryptedStoredEntry<'a>,
107 pub services: &'a [EncryptedStoredServiceEntry<'a>],
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct ArchiveMetadataEntry<'a> {
112 pub name: Option<&'a [u8]>,
113 pub creation_time: Option<u64>,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct EncryptedStoredEntry<'a> {
118 pub name: &'a [u8],
119 pub data: &'a [u8],
120 pub mtime: Option<u32>,
121 pub attributes: u64,
122 pub host_os: u64,
123 pub password: &'a [u8],
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct EncryptedCompressedEntry<'a> {
128 pub name: &'a [u8],
129 pub data: &'a [u8],
130 pub mtime: Option<u32>,
131 pub attributes: u64,
132 pub host_os: u64,
133 pub password: &'a [u8],
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub struct EncryptedArchiveCommentEntry<'a> {
138 pub data: &'a [u8],
139 pub password: &'a [u8],
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[non_exhaustive]
144pub enum FilterPolicy {
145 None,
146 AutoSize,
147 Explicit(FilterKind),
148}
149
150#[derive(Debug, Clone)]
151#[non_exhaustive]
152pub struct Rar50Writer<'a> {
153 options: WriterOptions,
154 members: Vec<Rar50WriteMember<'a>>,
155 archive_comment: Option<ArchiveComment<'a>>,
156 archive_metadata: Option<ArchiveMetadataEntry<'a>>,
157 filter_policy: FilterPolicy,
158 recovery_percent: Option<u64>,
159 recovery_password: Option<&'a [u8]>,
160}
161
162#[derive(Debug, Clone)]
163#[non_exhaustive]
164pub struct Rar50VolumeWriter<'a> {
165 options: WriterOptions,
166 entries: Option<Rar50VolumeEntries<'a>>,
167 max_payload_per_volume: Option<usize>,
168 recovery_percent: Option<u64>,
169}
170
171#[derive(Debug, Clone)]
172enum Rar50VolumeEntries<'a> {
173 Stored(StoredEntry<'a>),
174 Compressed(&'a [CompressedEntry<'a>]),
175 EncryptedStored(EncryptedStoredEntry<'a>),
176 EncryptedCompressed(&'a [EncryptedCompressedEntry<'a>]),
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180enum ArchiveComment<'a> {
181 Plain(&'a [u8]),
182 Encrypted(EncryptedArchiveCommentEntry<'a>),
183}
184
185impl<'a> ArchiveComment<'a> {
186 fn encrypted(self) -> Option<EncryptedArchiveCommentEntry<'a>> {
187 match self {
188 Self::Plain(_) => None,
189 Self::Encrypted(comment) => Some(comment),
190 }
191 }
192
193 fn password(self) -> Option<&'a [u8]> {
194 self.encrypted().map(|comment| comment.password)
195 }
196
197 fn plain_only(self) -> Option<Self> {
198 matches!(self, Self::Plain(_)).then_some(self)
199 }
200
201 fn encrypted_only(self) -> Option<Self> {
202 matches!(self, Self::Encrypted(_)).then_some(self)
203 }
204}
205
206impl<'a> Rar50VolumeWriter<'a> {
207 pub fn new(options: WriterOptions) -> Self {
208 Self {
209 options,
210 entries: None,
211 max_payload_per_volume: None,
212 recovery_percent: None,
213 }
214 }
215
216 pub fn stored_entry(mut self, entry: StoredEntry<'a>) -> Self {
217 self.entries = Some(Rar50VolumeEntries::Stored(entry));
218 self
219 }
220
221 pub fn compressed_entries(mut self, entries: &'a [CompressedEntry<'a>]) -> Self {
222 self.entries = Some(Rar50VolumeEntries::Compressed(entries));
223 self
224 }
225
226 pub fn encrypted_stored_entry(mut self, entry: EncryptedStoredEntry<'a>) -> Self {
227 self.entries = Some(Rar50VolumeEntries::EncryptedStored(entry));
228 self
229 }
230
231 pub fn encrypted_compressed_entries(
232 mut self,
233 entries: &'a [EncryptedCompressedEntry<'a>],
234 ) -> Self {
235 self.entries = Some(Rar50VolumeEntries::EncryptedCompressed(entries));
236 self
237 }
238
239 pub fn max_payload_per_volume(mut self, size: usize) -> Self {
240 self.max_payload_per_volume = Some(size);
241 self
242 }
243
244 pub fn recovery_percent(mut self, percent: Option<u64>) -> Self {
245 self.recovery_percent = percent;
246 self
247 }
248
249 pub fn finish(self) -> Result<Vec<Vec<u8>>> {
250 let max_payload_per_volume = self.max_payload_per_volume.ok_or(Error::InvalidHeader(
251 "RAR 5 volume payload size is required",
252 ))?;
253 match self.entries.ok_or(Error::InvalidHeader(
254 "RAR 5 volume writer needs an entry set",
255 ))? {
256 Rar50VolumeEntries::Stored(entry) => write_stored_volumes_impl(
257 entry,
258 self.options,
259 max_payload_per_volume,
260 self.recovery_percent,
261 ),
262 Rar50VolumeEntries::Compressed(entries) => write_compressed_volume_set_impl(
263 entries,
264 self.options,
265 max_payload_per_volume,
266 self.recovery_percent,
267 ),
268 Rar50VolumeEntries::EncryptedStored(entry) => write_encrypted_stored_volumes_impl(
269 entry,
270 self.options,
271 max_payload_per_volume,
272 self.recovery_percent,
273 ),
274 Rar50VolumeEntries::EncryptedCompressed(entries) => {
275 write_encrypted_compressed_volume_set_impl(
276 entries,
277 self.options,
278 max_payload_per_volume,
279 self.recovery_percent,
280 )
281 }
282 }
283 }
284}
285
286impl<'a> Rar50Writer<'a> {
287 pub fn new(options: WriterOptions) -> Self {
288 Self {
289 options,
290 members: Vec::new(),
291 archive_comment: None,
292 archive_metadata: None,
293 filter_policy: FilterPolicy::None,
294 recovery_percent: None,
295 recovery_password: None,
296 }
297 }
298
299 pub fn stored_entries(mut self, entries: &[StoredEntry<'a>]) -> Self {
300 self.members
301 .extend(entries.iter().copied().map(Rar50WriteMember::Stored));
302 self
303 }
304
305 pub fn compressed_entries(mut self, entries: &[CompressedEntry<'a>]) -> Self {
306 self.members
307 .extend(entries.iter().copied().map(Rar50WriteMember::Compressed));
308 self
309 }
310
311 pub fn encrypted_stored_entries(mut self, entries: &[EncryptedStoredEntry<'a>]) -> Self {
312 self.members.extend(
313 entries
314 .iter()
315 .copied()
316 .map(Rar50WriteMember::EncryptedStored),
317 );
318 self
319 }
320
321 pub fn stored_entries_with_services(mut self, entries: &[StoredEntryWithServices<'a>]) -> Self {
322 self.members.extend(
323 entries
324 .iter()
325 .copied()
326 .map(Rar50WriteMember::StoredWithServices),
327 );
328 self
329 }
330
331 pub fn encrypted_compressed_entries(
332 mut self,
333 entries: &[EncryptedCompressedEntry<'a>],
334 ) -> Self {
335 self.members.extend(
336 entries
337 .iter()
338 .copied()
339 .map(Rar50WriteMember::EncryptedCompressed),
340 );
341 self
342 }
343
344 pub fn encrypted_stored_entries_with_services(
345 mut self,
346 entries: &[EncryptedStoredEntryWithServices<'a>],
347 ) -> Self {
348 self.members.extend(
349 entries
350 .iter()
351 .copied()
352 .map(Rar50WriteMember::EncryptedStoredWithServices),
353 );
354 self
355 }
356
357 pub fn archive_comment(mut self, comment: Option<&'a [u8]>) -> Self {
358 self.archive_comment = comment.map(ArchiveComment::Plain);
359 self
360 }
361
362 pub fn encrypted_archive_comment(
363 mut self,
364 comment: Option<EncryptedArchiveCommentEntry<'a>>,
365 ) -> Self {
366 self.archive_comment = comment.map(ArchiveComment::Encrypted);
367 self
368 }
369
370 pub fn archive_metadata(mut self, metadata: Option<ArchiveMetadataEntry<'a>>) -> Self {
371 self.archive_metadata = metadata;
372 self
373 }
374
375 pub fn filter_policy(mut self, policy: FilterPolicy) -> Self {
376 self.filter_policy = policy;
377 self
378 }
379
380 pub fn recovery_percent(mut self, percent: Option<u64>) -> Self {
381 self.recovery_percent = percent;
382 self
383 }
384
385 pub fn recovery_password(mut self, password: Option<&'a [u8]>) -> Self {
386 self.recovery_password = password;
387 self
388 }
389
390 pub fn finish(self) -> Result<Vec<u8>> {
391 emit_resolved_writer_plan(self.resolve()?)
392 }
393
394 fn resolve(self) -> Result<ResolvedRar50WritePlan<'a>> {
395 let member_kind = self.members.iter().try_fold(None, |seen, member| {
396 let kind = member.kind();
397 if seen.is_some_and(|seen| seen != kind) {
398 return Err(Error::UnsupportedFeature {
399 version: self.options.target,
400 feature: "RAR 5 mixed stored/compressed writer plan",
401 });
402 }
403 Ok(Some(kind))
404 })?;
405 if self.options.features.quick_open {
406 validate_options(self.options)?;
407 }
408 if let Some(recovery_percent) = self.recovery_percent {
409 validate_recovery_percent(recovery_percent)?;
410 if self.options.features.quick_open
411 || self.options.features.archive_comment
412 || self.archive_comment.is_some()
413 {
414 return Err(Error::UnsupportedFeature {
415 version: self.options.target,
416 feature: "RAR 5 recovery writer option combination",
417 });
418 }
419 }
420 let algorithm_version = rar50_algorithm_version(self.options)?;
421 let compression_method = compression_method_for_level(self.options.compression_level)?;
422 let dictionary_size = dictionary_size_for_options(self.options)?;
423 let encode_options =
424 encode_options_for_level(self.options.compression_level, dictionary_size)?;
425 let encode_option_candidates =
426 encode_option_candidates_for_level(self.options.compression_level, dictionary_size)?;
427
428 let mut resolved_members = Vec::with_capacity(self.members.len());
429 match member_kind.unwrap_or(Rar50WriteMemberKind::Stored) {
430 Rar50WriteMemberKind::Stored => {
431 if self.filter_policy != FilterPolicy::None {
432 return Err(Error::UnsupportedFeature {
433 version: self.options.target,
434 feature: "RAR 5 stored writer filter policy",
435 });
436 }
437 if self.recovery_percent.is_some() {
438 validate_recovery_options(self.options)?;
439 } else {
440 validate_options(self.options)?;
441 }
442 for member in self.members {
443 let entry = member.into_stored(self.options.target)?;
444 validate_entry(&entry)?;
445 resolved_members.push(ResolvedRar50WriteMember::Stored(entry));
446 }
447 Ok(ResolvedRar50WritePlan {
448 main_flags: 0,
449 archive_comment: self.archive_comment.and_then(ArchiveComment::plain_only),
450 archive_metadata: self.archive_metadata,
451 quick_open: self.options.features.quick_open,
452 recovery_percent: self.recovery_percent,
453 header_keys: None,
454 members: resolved_members,
455 })
456 }
457 Rar50WriteMemberKind::StoredWithServices => {
458 if self.archive_comment.is_some()
459 || self.archive_metadata.is_some()
460 || self.recovery_percent.is_some()
461 || self.filter_policy != FilterPolicy::None
462 {
463 return Err(Error::UnsupportedFeature {
464 version: self.options.target,
465 feature: "RAR 5 stored file-service writer option combination",
466 });
467 }
468 validate_file_service_options(self.options)?;
469 for member in self.members {
470 let entry = member.into_stored_with_services(self.options.target)?;
471 validate_entry(&entry.entry)?;
472 for service in entry.services {
473 validate_file_service(service)?;
474 }
475 resolved_members.push(ResolvedRar50WriteMember::StoredWithServices(entry));
476 }
477 Ok(ResolvedRar50WritePlan {
478 main_flags: 0,
479 archive_comment: None,
480 archive_metadata: None,
481 quick_open: false,
482 recovery_percent: None,
483 header_keys: None,
484 members: resolved_members,
485 })
486 }
487 Rar50WriteMemberKind::Compressed => {
488 if self.options.features.quick_open {
489 return Err(Error::UnsupportedFeature {
490 version: self.options.target,
491 feature: "RAR 5 compressed quick-open writer",
492 });
493 }
494 if self.recovery_percent.is_some() {
495 validate_compressed_recovery_options(self.options)?;
496 } else {
497 validate_compressed_options(self.options)?;
498 }
499 if self.filter_policy != FilterPolicy::None && self.options.features.solid {
500 return Err(Error::UnsupportedFeature {
501 version: self.options.target,
502 feature: "RAR 5 solid filtered compressed writer",
503 });
504 }
505 if self.filter_policy != FilterPolicy::None
506 && (self.archive_comment.is_some() || self.archive_metadata.is_some())
507 {
508 return Err(Error::UnsupportedFeature {
509 version: self.options.target,
510 feature: "RAR 5 filtered compressed writer service or metadata",
511 });
512 }
513 let mut solid_encoder = self
514 .options
515 .features
516 .solid
517 .then(|| Unpack50Encoder::with_options(encode_options));
518 for (index, member) in self.members.into_iter().enumerate() {
519 let entry = member.into_compressed(self.options.target)?;
520 validate_compressed_entry(&entry)?;
521 if compression_method == 0 {
522 resolved_members.push(ResolvedRar50WriteMember::StoredCompressed(entry));
523 continue;
524 }
525 let (packed, solid_continuation) = if let Some(encoder) = solid_encoder.as_mut()
526 {
527 encode_with_solid_reset_policy(
528 encoder,
529 entry.data,
530 algorithm_version,
531 encode_options,
532 index,
533 )?
534 } else {
535 (
536 encode_member_with_filter_policy_candidates(
537 entry.data,
538 algorithm_version,
539 self.filter_policy,
540 &encode_option_candidates,
541 )?,
542 false,
543 )
544 };
545 if should_store_compressed_payload(
546 entry.data,
547 &packed,
548 self.options.features.solid,
549 self.filter_policy,
550 ) {
551 resolved_members.push(ResolvedRar50WriteMember::StoredCompressed(entry));
552 } else {
553 resolved_members.push(ResolvedRar50WriteMember::Compressed {
554 entry,
555 packed,
556 algorithm_version,
557 compression_method,
558 dictionary_size,
559 solid_continuation,
560 });
561 }
562 }
563 Ok(ResolvedRar50WritePlan {
564 main_flags: if self.options.features.solid {
565 MHFL_SOLID
566 } else {
567 0
568 },
569 archive_comment: self.archive_comment.and_then(ArchiveComment::plain_only),
570 archive_metadata: self.archive_metadata,
571 quick_open: false,
572 recovery_percent: self.recovery_percent,
573 header_keys: None,
574 members: resolved_members,
575 })
576 }
577 Rar50WriteMemberKind::EncryptedStored => {
578 if self.recovery_percent.is_some() {
579 validate_encrypted_recovery_options(self.options)?;
580 } else {
581 validate_encrypted_options(self.options)?;
582 }
583 let header_keys = if self.options.features.header_encryption {
584 let password = header_encryption_password(
585 self.members
586 .iter()
587 .map(|member| member.encrypted_stored_password(self.options.target))
588 .collect::<Result<Vec<_>>>()?
589 .into_iter()
590 .chain(self.archive_comment.and_then(ArchiveComment::password))
591 .chain(self.recovery_password),
592 )?;
593 Some(header_encryption_keys(password)?)
594 } else {
595 None
596 };
597 for member in self.members {
598 let entry = member.into_encrypted_stored(self.options.target)?;
599 validate_encrypted_entry(&entry)?;
600 let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
601 resolved_members
602 .push(ResolvedRar50WriteMember::EncryptedStored { entry, encrypted });
603 }
604 Ok(ResolvedRar50WritePlan {
605 main_flags: 0,
606 archive_comment: self
607 .archive_comment
608 .and_then(ArchiveComment::encrypted_only),
609 archive_metadata: self.archive_metadata,
610 quick_open: false,
611 recovery_percent: self.recovery_percent,
612 header_keys,
613 members: resolved_members,
614 })
615 }
616 Rar50WriteMemberKind::EncryptedStoredWithServices => {
617 if self.archive_comment.is_some()
618 || self.archive_metadata.is_some()
619 || self.recovery_percent.is_some()
620 || self.filter_policy != FilterPolicy::None
621 {
622 return Err(Error::UnsupportedFeature {
623 version: self.options.target,
624 feature: "RAR 5 encrypted stored file-service writer option combination",
625 });
626 }
627 validate_encrypted_file_service_options(self.options)?;
628 let header_keys = if self.options.features.header_encryption {
629 let mut passwords = Vec::new();
630 for member in &self.members {
631 let entry =
632 member.encrypted_stored_with_services_ref(self.options.target)?;
633 passwords.push(entry.entry.password);
634 passwords.extend(entry.services.iter().map(|service| service.password));
635 }
636 let password = header_encryption_password(passwords.into_iter())?;
637 Some(header_encryption_keys(password)?)
638 } else {
639 None
640 };
641 for member in self.members {
642 let entry = member.into_encrypted_stored_with_services(self.options.target)?;
643 validate_encrypted_entry(&entry.entry)?;
644 for service in entry.services {
645 validate_encrypted_file_service(service)?;
646 }
647 let encrypted =
648 encrypted_stored_payload(entry.entry.data, entry.entry.password)?;
649 resolved_members.push(ResolvedRar50WriteMember::EncryptedStoredWithServices {
650 entry,
651 encrypted,
652 });
653 }
654 Ok(ResolvedRar50WritePlan {
655 main_flags: 0,
656 archive_comment: None,
657 archive_metadata: None,
658 quick_open: false,
659 recovery_percent: None,
660 header_keys,
661 members: resolved_members,
662 })
663 }
664 Rar50WriteMemberKind::EncryptedCompressed => {
665 if self.recovery_percent.is_some() {
666 validate_encrypted_compressed_recovery_options(self.options)?;
667 } else {
668 validate_encrypted_compressed_options(self.options)?;
669 }
670 let header_keys = if self.options.features.header_encryption {
671 let password = header_encryption_password(
672 self.members
673 .iter()
674 .map(|member| member.encrypted_compressed_password(self.options.target))
675 .collect::<Result<Vec<_>>>()?
676 .into_iter()
677 .chain(self.archive_comment.and_then(ArchiveComment::password)),
678 )?;
679 Some(header_encryption_keys(password)?)
680 } else {
681 None
682 };
683 let mut solid_encoder = self
684 .options
685 .features
686 .solid
687 .then(|| Unpack50Encoder::with_options(encode_options));
688 for (index, member) in self.members.into_iter().enumerate() {
689 let entry = member.into_encrypted_compressed(self.options.target)?;
690 validate_encrypted_compressed_entry(&entry)?;
691 if compression_method == 0 {
692 let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
693 resolved_members.push(
694 ResolvedRar50WriteMember::EncryptedStoredCompressed {
695 entry,
696 encrypted,
697 },
698 );
699 continue;
700 }
701 let (packed, solid_continuation) = if let Some(encoder) = solid_encoder.as_mut()
702 {
703 encode_with_solid_reset_policy(
704 encoder,
705 entry.data,
706 algorithm_version,
707 encode_options,
708 index,
709 )?
710 } else {
711 (
712 encode_member_with_filter_policy_candidates(
713 entry.data,
714 algorithm_version,
715 FilterPolicy::None,
716 &encode_option_candidates,
717 )?,
718 false,
719 )
720 };
721 if should_store_compressed_payload(
722 entry.data,
723 &packed,
724 self.options.features.solid,
725 FilterPolicy::None,
726 ) {
727 let encrypted = encrypted_stored_payload(entry.data, entry.password)?;
728 resolved_members.push(
729 ResolvedRar50WriteMember::EncryptedStoredCompressed {
730 entry,
731 encrypted,
732 },
733 );
734 } else {
735 let encrypted = encrypted_payload(&packed, entry.data, entry.password)?;
736 resolved_members.push(ResolvedRar50WriteMember::EncryptedCompressed {
737 entry,
738 encrypted,
739 algorithm_version,
740 compression_method,
741 dictionary_size,
742 solid_continuation,
743 });
744 }
745 }
746 Ok(ResolvedRar50WritePlan {
747 main_flags: if self.options.features.solid {
748 MHFL_SOLID
749 } else {
750 0
751 },
752 archive_comment: self
753 .archive_comment
754 .and_then(ArchiveComment::encrypted_only),
755 archive_metadata: self.archive_metadata,
756 quick_open: false,
757 recovery_percent: self.recovery_percent,
758 header_keys,
759 members: resolved_members,
760 })
761 }
762 }
763 }
764}
765
766#[derive(Debug, Clone, Copy, PartialEq, Eq)]
767enum Rar50WriteMemberKind {
768 Stored,
769 StoredWithServices,
770 Compressed,
771 EncryptedStored,
772 EncryptedStoredWithServices,
773 EncryptedCompressed,
774}
775
776#[derive(Debug, Clone, Copy, PartialEq, Eq)]
777enum Rar50WriteMember<'a> {
778 Stored(StoredEntry<'a>),
779 StoredWithServices(StoredEntryWithServices<'a>),
780 Compressed(CompressedEntry<'a>),
781 EncryptedStored(EncryptedStoredEntry<'a>),
782 EncryptedStoredWithServices(EncryptedStoredEntryWithServices<'a>),
783 EncryptedCompressed(EncryptedCompressedEntry<'a>),
784}
785
786impl<'a> Rar50WriteMember<'a> {
787 fn kind(self) -> Rar50WriteMemberKind {
788 match self {
789 Self::Stored(_) => Rar50WriteMemberKind::Stored,
790 Self::StoredWithServices(_) => Rar50WriteMemberKind::StoredWithServices,
791 Self::Compressed(_) => Rar50WriteMemberKind::Compressed,
792 Self::EncryptedStored(_) => Rar50WriteMemberKind::EncryptedStored,
793 Self::EncryptedStoredWithServices(_) => {
794 Rar50WriteMemberKind::EncryptedStoredWithServices
795 }
796 Self::EncryptedCompressed(_) => Rar50WriteMemberKind::EncryptedCompressed,
797 }
798 }
799
800 fn into_stored(self, target: crate::ArchiveVersion) -> Result<StoredEntry<'a>> {
801 match self {
802 Self::Stored(entry) => Ok(entry),
803 _ => Err(mixed_member_plan_error(target)),
804 }
805 }
806
807 fn into_stored_with_services(
808 self,
809 target: crate::ArchiveVersion,
810 ) -> Result<StoredEntryWithServices<'a>> {
811 match self {
812 Self::StoredWithServices(entry) => Ok(entry),
813 _ => Err(mixed_member_plan_error(target)),
814 }
815 }
816
817 fn into_compressed(self, target: crate::ArchiveVersion) -> Result<CompressedEntry<'a>> {
818 match self {
819 Self::Compressed(entry) => Ok(entry),
820 _ => Err(mixed_member_plan_error(target)),
821 }
822 }
823
824 fn into_encrypted_stored(
825 self,
826 target: crate::ArchiveVersion,
827 ) -> Result<EncryptedStoredEntry<'a>> {
828 match self {
829 Self::EncryptedStored(entry) => Ok(entry),
830 _ => Err(mixed_member_plan_error(target)),
831 }
832 }
833
834 fn into_encrypted_stored_with_services(
835 self,
836 target: crate::ArchiveVersion,
837 ) -> Result<EncryptedStoredEntryWithServices<'a>> {
838 match self {
839 Self::EncryptedStoredWithServices(entry) => Ok(entry),
840 _ => Err(mixed_member_plan_error(target)),
841 }
842 }
843
844 fn into_encrypted_compressed(
845 self,
846 target: crate::ArchiveVersion,
847 ) -> Result<EncryptedCompressedEntry<'a>> {
848 match self {
849 Self::EncryptedCompressed(entry) => Ok(entry),
850 _ => Err(mixed_member_plan_error(target)),
851 }
852 }
853
854 fn encrypted_stored_password(&self, target: crate::ArchiveVersion) -> Result<&'a [u8]> {
855 match self {
856 Self::EncryptedStored(entry) => Ok(entry.password),
857 _ => Err(mixed_member_plan_error(target)),
858 }
859 }
860
861 fn encrypted_stored_with_services_ref(
862 &self,
863 target: crate::ArchiveVersion,
864 ) -> Result<&EncryptedStoredEntryWithServices<'a>> {
865 match self {
866 Self::EncryptedStoredWithServices(entry) => Ok(entry),
867 _ => Err(mixed_member_plan_error(target)),
868 }
869 }
870
871 fn encrypted_compressed_password(&self, target: crate::ArchiveVersion) -> Result<&'a [u8]> {
872 match self {
873 Self::EncryptedCompressed(entry) => Ok(entry.password),
874 _ => Err(mixed_member_plan_error(target)),
875 }
876 }
877}
878
879fn mixed_member_plan_error(target: crate::ArchiveVersion) -> Error {
880 Error::UnsupportedFeature {
881 version: target,
882 feature: "RAR 5 mixed stored/compressed writer plan",
883 }
884}
885
886struct ResolvedRar50WritePlan<'a> {
887 main_flags: u64,
888 archive_comment: Option<ArchiveComment<'a>>,
889 archive_metadata: Option<ArchiveMetadataEntry<'a>>,
890 quick_open: bool,
891 recovery_percent: Option<u64>,
892 header_keys: Option<HeaderEncryptionKeys>,
893 members: Vec<ResolvedRar50WriteMember<'a>>,
894}
895
896enum ResolvedRar50WriteMember<'a> {
897 Stored(StoredEntry<'a>),
898 StoredWithServices(StoredEntryWithServices<'a>),
899 StoredCompressed(CompressedEntry<'a>),
900 Compressed {
901 entry: CompressedEntry<'a>,
902 packed: Vec<u8>,
903 algorithm_version: u8,
904 compression_method: u8,
905 dictionary_size: u64,
906 solid_continuation: bool,
907 },
908 EncryptedStored {
909 entry: EncryptedStoredEntry<'a>,
910 encrypted: EncryptedStoredPayload,
911 },
912 EncryptedStoredWithServices {
913 entry: EncryptedStoredEntryWithServices<'a>,
914 encrypted: EncryptedStoredPayload,
915 },
916 EncryptedStoredCompressed {
917 entry: EncryptedCompressedEntry<'a>,
918 encrypted: EncryptedStoredPayload,
919 },
920 EncryptedCompressed {
921 entry: EncryptedCompressedEntry<'a>,
922 encrypted: EncryptedStoredPayload,
923 algorithm_version: u8,
924 compression_method: u8,
925 dictionary_size: u64,
926 solid_continuation: bool,
927 },
928}
929
930fn emit_resolved_writer_plan(plan: ResolvedRar50WritePlan<'_>) -> Result<Vec<u8>> {
931 if plan.quick_open {
932 return resolve_writer_plan_offset(
933 |quick_open_offset| {
934 emit_resolved_writer_plan_pass(&plan, Some(quick_open_offset), None)
935 .map(|(out, next_quick_open_offset, _)| (out, next_quick_open_offset))
936 },
937 "RAR 5 quick-open pass did not report an offset",
938 "RAR 5 quick-open offset did not converge",
939 );
940 }
941 if plan.recovery_percent.is_some() {
942 return resolve_writer_plan_offset(
943 |recovery_offset| {
944 emit_resolved_writer_plan_pass(&plan, None, Some(recovery_offset))
945 .map(|(out, _, next_recovery_offset)| (out, next_recovery_offset))
946 },
947 "RAR 5 recovery pass did not report an offset",
948 "RAR 5 recovery offset did not converge",
949 );
950 }
951 emit_resolved_writer_plan_pass(&plan, None, None).map(|(out, _, _)| out)
952}
953
954fn resolve_writer_plan_offset<F>(
955 mut pass: F,
956 missing_offset_error: &'static str,
957 convergence_error: &'static str,
958) -> Result<Vec<u8>>
959where
960 F: FnMut(u64) -> Result<(Vec<u8>, Option<u64>)>,
961{
962 let mut offset = 0;
963 for _ in 0..4 {
964 let (out, next_offset) = pass(offset)?;
965 if next_offset == Some(offset) {
966 return Ok(out);
967 }
968 offset = next_offset.ok_or(Error::InvalidHeader(missing_offset_error))?;
969 }
970 Err(Error::InvalidHeader(convergence_error))
971}
972
973fn emit_resolved_writer_plan_pass(
974 plan: &ResolvedRar50WritePlan<'_>,
975 quick_open_offset: Option<u64>,
976 recovery_offset: Option<u64>,
977) -> Result<(Vec<u8>, Option<u64>, Option<u64>)> {
978 let mut out = Vec::new();
979 let mut cached_headers = Vec::new();
980 out.extend_from_slice(RAR50_SIGNATURE);
981 let main_extra =
982 resolved_main_extra(plan.archive_metadata, quick_open_offset, recovery_offset)?;
983 let main_flags = plan.main_flags
984 | if plan.recovery_percent.is_some() {
985 MHFL_RECOVERY
986 } else {
987 0
988 };
989 if let Some(header_keys) = &plan.header_keys {
990 write_head_crypt(&mut out, header_keys)?;
991 out.extend_from_slice(&encrypted_main_header_block(
992 &header_keys.keys,
993 main_flags,
994 None,
995 &main_extra,
996 )?);
997 } else {
998 write_main_header(&mut out, main_flags, None, &main_extra)?;
999 }
1000 if let Some(comment) = plan.archive_comment {
1001 match comment {
1002 ArchiveComment::Plain(comment) => {
1003 if plan.quick_open {
1004 write_stored_service_with_cache(
1005 &mut out,
1006 &mut cached_headers,
1007 b"CMT",
1008 comment,
1009 )?;
1010 } else {
1011 write_stored_service(&mut out, b"CMT", comment)?;
1012 }
1013 }
1014 ArchiveComment::Encrypted(comment) => {
1015 write_encrypted_stored_service_with_header_keys(
1016 &mut out,
1017 b"CMT",
1018 comment,
1019 plan.header_keys.as_ref().map(|keys| &keys.keys),
1020 )?;
1021 }
1022 }
1023 }
1024 for member in &plan.members {
1025 match member {
1026 ResolvedRar50WriteMember::Stored(entry) => {
1027 if plan.quick_open {
1028 write_stored_entry_with_cache(&mut out, &mut cached_headers, entry)?;
1029 } else {
1030 write_stored_entry(&mut out, entry)?;
1031 }
1032 }
1033 ResolvedRar50WriteMember::StoredWithServices(entry) => {
1034 write_stored_entry(&mut out, &entry.entry)?;
1035 for service in entry.services {
1036 write_stored_service(&mut out, service.name, service.data)?;
1037 }
1038 }
1039 ResolvedRar50WriteMember::StoredCompressed(entry) => {
1040 write_stored_compressed_entry(&mut out, entry)?;
1041 }
1042 ResolvedRar50WriteMember::Compressed {
1043 entry,
1044 packed,
1045 algorithm_version,
1046 compression_method,
1047 dictionary_size,
1048 solid_continuation,
1049 } => write_compressed_entry_payload(
1050 &mut out,
1051 entry,
1052 packed,
1053 *algorithm_version,
1054 *compression_method,
1055 *dictionary_size,
1056 *solid_continuation,
1057 )?,
1058 ResolvedRar50WriteMember::EncryptedStored { entry, encrypted } => {
1059 write_encrypted_stored_entry_fragment_with_header_keys(
1060 &mut out,
1061 entry,
1062 &encrypted.data,
1063 encrypted,
1064 false,
1065 false,
1066 plan.header_keys.as_ref().map(|keys| &keys.keys),
1067 )?
1068 }
1069 ResolvedRar50WriteMember::EncryptedStoredWithServices { entry, encrypted } => {
1070 write_encrypted_stored_entry_fragment_with_header_keys(
1071 &mut out,
1072 &entry.entry,
1073 &encrypted.data,
1074 encrypted,
1075 false,
1076 false,
1077 plan.header_keys.as_ref().map(|keys| &keys.keys),
1078 )?;
1079 for service in entry.services {
1080 write_encrypted_stored_service_with_header_keys(
1081 &mut out,
1082 service.name,
1083 EncryptedArchiveCommentEntry {
1084 data: service.data,
1085 password: service.password,
1086 },
1087 plan.header_keys.as_ref().map(|keys| &keys.keys),
1088 )?;
1089 }
1090 }
1091 ResolvedRar50WriteMember::EncryptedStoredCompressed { entry, encrypted } => {
1092 write_encrypted_stored_compressed_entry_with_header_keys(
1093 &mut out,
1094 entry,
1095 encrypted,
1096 plan.header_keys.as_ref().map(|keys| &keys.keys),
1097 )?;
1098 }
1099 ResolvedRar50WriteMember::EncryptedCompressed {
1100 entry,
1101 encrypted,
1102 algorithm_version,
1103 compression_method,
1104 dictionary_size,
1105 solid_continuation,
1106 } => write_encrypted_compressed_entry_fragment_with_header_keys(
1107 &mut out,
1108 EncryptedCompressedFragment {
1109 entry,
1110 data: &encrypted.data,
1111 encrypted,
1112 algorithm_version: *algorithm_version,
1113 compression_method: *compression_method,
1114 dictionary_size: *dictionary_size,
1115 solid_continuation: *solid_continuation,
1116 split_before: false,
1117 split_after: false,
1118 },
1119 plan.header_keys.as_ref().map(|keys| &keys.keys),
1120 )?,
1121 }
1122 }
1123 let next_quick_open_offset = if plan.quick_open {
1124 let qo_pos = out.len();
1125 let qo_payload = quick_open_payload(&cached_headers, qo_pos)?;
1126 write_stored_service(&mut out, b"QO", &qo_payload)?;
1127 Some((qo_pos - RAR50_SIGNATURE.len()) as u64)
1128 } else {
1129 None
1130 };
1131 let next_recovery_offset = if let Some(recovery_percent) = plan.recovery_percent {
1132 let rr_pos = out.len();
1133 if let Some(header_keys) = &plan.header_keys {
1134 write_header_encrypted_recovery_service(&mut out, recovery_percent, &header_keys.keys)?;
1135 } else {
1136 write_recovery_service(&mut out, recovery_percent)?;
1137 }
1138 Some((rr_pos - RAR50_SIGNATURE.len()) as u64)
1139 } else {
1140 None
1141 };
1142 if let Some(header_keys) = &plan.header_keys {
1143 out.extend_from_slice(&encrypted_header_block(
1144 &header_keys.keys,
1145 HEAD_END,
1146 0,
1147 None,
1148 &[],
1149 &[],
1150 &[],
1151 )?);
1152 } else {
1153 write_block(&mut out, HEAD_END, 0, None, &[], &[], &[])?;
1154 }
1155 Ok((out, next_quick_open_offset, next_recovery_offset))
1156}
1157
1158fn resolved_main_extra(
1159 archive_metadata: Option<ArchiveMetadataEntry<'_>>,
1160 quick_open_offset: Option<u64>,
1161 recovery_offset: Option<u64>,
1162) -> Result<Vec<u8>> {
1163 let mut main_extra = Vec::new();
1164 let locator_quick_open_offset = quick_open_offset.or_else(|| archive_metadata.map(|_| 0));
1165 if locator_quick_open_offset.is_some() || recovery_offset.is_some() {
1166 write_locator_record(&mut main_extra, locator_quick_open_offset, recovery_offset);
1167 }
1168 if let Some(archive_metadata) = archive_metadata {
1169 main_extra.extend_from_slice(&archive_metadata_record(archive_metadata)?);
1170 }
1171 Ok(main_extra)
1172}
1173
1174fn write_main_header(
1175 out: &mut Vec<u8>,
1176 archive_flags: u64,
1177 volume_number: Option<u64>,
1178 extra: &[u8],
1179) -> Result<()> {
1180 let mut specific = Vec::new();
1181 write_vint(&mut specific, archive_flags);
1182 if let Some(volume_number) = volume_number {
1183 write_vint(&mut specific, volume_number);
1184 }
1185 write_block(
1186 out,
1187 HEAD_MAIN,
1188 if extra.is_empty() { 0 } else { HFL_EXTRA },
1189 None,
1190 &specific,
1191 extra,
1192 &[],
1193 )
1194}
1195
1196fn encrypted_main_header_block(
1197 keys: &Rar50Keys,
1198 archive_flags: u64,
1199 volume_number: Option<u64>,
1200 extra: &[u8],
1201) -> Result<Vec<u8>> {
1202 let mut specific = Vec::new();
1203 write_vint(&mut specific, archive_flags);
1204 if let Some(volume_number) = volume_number {
1205 write_vint(&mut specific, volume_number);
1206 }
1207 encrypted_header_block(
1208 keys,
1209 HEAD_MAIN,
1210 if extra.is_empty() { 0 } else { HFL_EXTRA },
1211 None,
1212 &specific,
1213 extra,
1214 &[],
1215 )
1216}
1217
1218fn validate_options(options: WriterOptions) -> Result<()> {
1219 validate_plain_options(options, false)
1220}
1221
1222fn validate_recovery_options(options: WriterOptions) -> Result<()> {
1223 validate_plain_options(options, true)
1224}
1225
1226fn validate_plain_options(options: WriterOptions, allow_recovery_record: bool) -> Result<()> {
1227 validate_compression_level(options)?;
1228 if !matches!(
1229 options.target,
1230 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1231 ) {
1232 return Err(Error::UnsupportedVersion(options.target));
1233 }
1234 let mut allowed = crate::FeatureSet::store_only();
1235 allowed.archive_comment = options.features.archive_comment;
1236 allowed.quick_open = options.features.quick_open;
1237 if allow_recovery_record {
1238 allowed.recovery_record = options.features.recovery_record;
1239 }
1240 if options.features != allowed {
1241 return Err(Error::UnsupportedFeature {
1242 version: options.target,
1243 feature: "RAR 5 writer feature",
1244 });
1245 }
1246 Ok(())
1247}
1248
1249fn validate_file_service_options(options: WriterOptions) -> Result<()> {
1250 validate_compression_level(options)?;
1251 if !matches!(
1252 options.target,
1253 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1254 ) {
1255 return Err(Error::UnsupportedVersion(options.target));
1256 }
1257 let mut allowed = crate::FeatureSet::store_only();
1258 allowed.file_comment = options.features.file_comment;
1259 if options.features != allowed {
1260 return Err(Error::UnsupportedFeature {
1261 version: options.target,
1262 feature: "RAR 5 stored file-service writer feature",
1263 });
1264 }
1265 Ok(())
1266}
1267
1268fn validate_compressed_options(options: WriterOptions) -> Result<()> {
1269 validate_compressed_feature_options(options, false)
1270}
1271
1272fn validate_compressed_recovery_options(options: WriterOptions) -> Result<()> {
1273 validate_compressed_feature_options(options, true)
1274}
1275
1276fn validate_compressed_feature_options(
1277 options: WriterOptions,
1278 allow_recovery_record: bool,
1279) -> Result<()> {
1280 validate_compression_level(options)?;
1281 if !matches!(
1282 options.target,
1283 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1284 ) {
1285 return Err(Error::UnsupportedVersion(options.target));
1286 }
1287 let mut allowed = crate::FeatureSet::store_only();
1288 allowed.solid = options.features.solid;
1289 allowed.archive_comment = options.features.archive_comment;
1290 if allow_recovery_record {
1291 allowed.recovery_record = options.features.recovery_record;
1292 }
1293 if options.features != allowed {
1294 return Err(Error::UnsupportedFeature {
1295 version: options.target,
1296 feature: "RAR 5 compressed writer feature",
1297 });
1298 }
1299 Ok(())
1300}
1301
1302fn validate_encrypted_compressed_options(options: WriterOptions) -> Result<()> {
1303 validate_encrypted_compressed_feature_options(options, false)
1304}
1305
1306fn validate_encrypted_compressed_recovery_options(options: WriterOptions) -> Result<()> {
1307 validate_encrypted_compressed_feature_options(options, true)
1308}
1309
1310fn validate_encrypted_compressed_feature_options(
1311 options: WriterOptions,
1312 allow_recovery_record: bool,
1313) -> Result<()> {
1314 validate_compression_level(options)?;
1315 if !matches!(
1316 options.target,
1317 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1318 ) {
1319 return Err(Error::UnsupportedVersion(options.target));
1320 }
1321 let mut allowed = crate::FeatureSet::store_only();
1322 allowed.file_encryption = true;
1323 allowed.header_encryption = options.features.header_encryption;
1324 allowed.solid = options.features.solid;
1325 allowed.archive_comment = options.features.archive_comment;
1326 if allow_recovery_record {
1327 allowed.recovery_record = options.features.recovery_record;
1328 }
1329 if options.features != allowed {
1330 return Err(Error::UnsupportedFeature {
1331 version: options.target,
1332 feature: "RAR 5 encrypted compressed writer feature",
1333 });
1334 }
1335 Ok(())
1336}
1337
1338struct CachedHeader {
1339 offset: usize,
1340 header: Vec<u8>,
1341}
1342
1343struct BlockParts<'a> {
1344 header_type: u64,
1345 flags: u64,
1346 data_size: Option<u64>,
1347 type_specific: &'a [u8],
1348 extra: &'a [u8],
1349 data: &'a [u8],
1350}
1351
1352fn validate_encrypted_options(options: WriterOptions) -> Result<()> {
1353 validate_encrypted_feature_options(options, false)
1354}
1355
1356fn validate_encrypted_recovery_options(options: WriterOptions) -> Result<()> {
1357 validate_encrypted_feature_options(options, true)
1358}
1359
1360fn validate_encrypted_feature_options(
1361 options: WriterOptions,
1362 allow_recovery_record: bool,
1363) -> Result<()> {
1364 validate_compression_level(options)?;
1365 if !matches!(
1366 options.target,
1367 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1368 ) {
1369 return Err(Error::UnsupportedVersion(options.target));
1370 }
1371 let mut allowed = crate::FeatureSet::store_only();
1372 allowed.file_encryption = true;
1373 allowed.header_encryption = options.features.header_encryption;
1374 allowed.archive_comment = options.features.archive_comment;
1375 if allow_recovery_record {
1376 allowed.recovery_record = options.features.recovery_record;
1377 }
1378 if options.features != allowed {
1379 return Err(Error::UnsupportedFeature {
1380 version: options.target,
1381 feature: "RAR 5 encrypted stored writer feature",
1382 });
1383 }
1384 Ok(())
1385}
1386
1387fn validate_encrypted_file_service_options(options: WriterOptions) -> Result<()> {
1388 validate_compression_level(options)?;
1389 if !matches!(
1390 options.target,
1391 crate::ArchiveVersion::Rar50 | crate::ArchiveVersion::Rar70
1392 ) {
1393 return Err(Error::UnsupportedVersion(options.target));
1394 }
1395 let mut allowed = crate::FeatureSet::store_only();
1396 allowed.file_encryption = true;
1397 allowed.header_encryption = options.features.header_encryption;
1398 allowed.file_comment = options.features.file_comment;
1399 if options.features != allowed {
1400 return Err(Error::UnsupportedFeature {
1401 version: options.target,
1402 feature: "RAR 5 encrypted stored file-service writer feature",
1403 });
1404 }
1405 Ok(())
1406}
1407
1408struct HeaderEncryptionKeys {
1409 keys: Rar50Keys,
1410 salt: [u8; 16],
1411}
1412
1413fn header_encryption_keys(password: &[u8]) -> Result<HeaderEncryptionKeys> {
1414 let mut salt = [0u8; 16];
1415 getrandom::fill(&mut salt)
1416 .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption salt"))?;
1417 let keys = Rar50Keys::derive(password, salt, 0).map_err(super::map_rar50_crypto_error)?;
1418 Ok(HeaderEncryptionKeys { keys, salt })
1419}
1420
1421fn header_encryption_password<'a>(
1422 mut passwords: impl Iterator<Item = &'a [u8]>,
1423) -> Result<&'a [u8]> {
1424 let first = passwords.next().ok_or(Error::NeedPassword)?;
1425 for password in passwords {
1426 if password != first {
1427 return Err(Error::InvalidHeader(
1428 "RAR 5 header-encrypted writer needs one shared password",
1429 ));
1430 }
1431 }
1432 Ok(first)
1433}
1434
1435fn write_head_crypt(out: &mut Vec<u8>, header_keys: &HeaderEncryptionKeys) -> Result<()> {
1436 let mut specific = Vec::new();
1437 write_vint(&mut specific, 0);
1438 write_vint(&mut specific, 0x0001);
1439 specific.push(0);
1440 specific.extend_from_slice(&header_keys.salt);
1441 specific.extend_from_slice(&header_keys.keys.password_check_record());
1442 write_block(out, HEAD_CRYPT, 0, None, &specific, &[], &[])
1443}
1444
1445fn archive_metadata_record(metadata: ArchiveMetadataEntry<'_>) -> Result<Vec<u8>> {
1446 if metadata.name.is_none() && metadata.creation_time.is_none() {
1447 return Err(Error::InvalidHeader(
1448 "RAR 5 archive metadata writer needs a name or creation time",
1449 ));
1450 }
1451 if metadata.name.is_some() && metadata.creation_time.is_none() {
1452 return Err(Error::InvalidHeader(
1453 "RAR 5 archive metadata name needs a creation time",
1454 ));
1455 }
1456 let mut flags = 0;
1457 if metadata.name.is_some() {
1458 flags |= MHEXTRA_ARCHIVE_METADATA_NAME;
1459 }
1460 if metadata.creation_time.is_some() {
1461 flags |= MHEXTRA_ARCHIVE_METADATA_TIME;
1462 }
1463
1464 let mut record = Vec::new();
1465 write_vint(&mut record, flags);
1466 if let Some(name) = metadata.name {
1467 if name.is_empty() {
1468 return Err(Error::InvalidHeader("RAR 5 archive metadata name is empty"));
1469 }
1470 write_vint(&mut record, name.len() as u64);
1471 record.extend_from_slice(name);
1472 }
1473 if let Some(creation_time) = metadata.creation_time {
1474 record.extend_from_slice(&creation_time.to_le_bytes());
1475 }
1476
1477 let mut extra = Vec::new();
1478 write_extra_record(&mut extra, MHEXTRA_ARCHIVE_METADATA, &record);
1479 Ok(extra)
1480}
1481
1482fn write_locator_record(
1483 out: &mut Vec<u8>,
1484 quick_open_offset: Option<u64>,
1485 recovery_record_offset: Option<u64>,
1486) {
1487 let mut flags = 0;
1488 if quick_open_offset.is_some() {
1489 flags |= MHEXTRA_LOCATOR_QUICK_OPEN;
1490 }
1491 if recovery_record_offset.is_some() {
1492 flags |= MHEXTRA_LOCATOR_RECOVERY;
1493 }
1494
1495 let mut record = Vec::new();
1496 write_vint(&mut record, flags);
1497 if let Some(quick_open_offset) = quick_open_offset {
1498 write_vint(&mut record, quick_open_offset);
1499 }
1500 if let Some(recovery_record_offset) = recovery_record_offset {
1501 write_vint(&mut record, recovery_record_offset);
1502 }
1503 write_extra_record(out, MHEXTRA_LOCATOR, &record);
1504}
1505
1506fn write_stored_entry(out: &mut Vec<u8>, entry: &StoredEntry<'_>) -> Result<()> {
1507 validate_entry(entry)?;
1508 write_stored_entry_fragment(
1509 out,
1510 entry,
1511 entry.data,
1512 entry.data.len() as u64,
1513 Some(crc32(entry.data)),
1514 false,
1515 false,
1516 )
1517}
1518
1519fn write_stored_compressed_entry(out: &mut Vec<u8>, entry: &CompressedEntry<'_>) -> Result<()> {
1520 validate_compressed_entry(entry)?;
1521 let stored = stored_entry_from_compressed_entry(entry);
1522 write_stored_entry(out, &stored)
1523}
1524
1525fn stored_entry_from_compressed_entry<'a>(entry: &CompressedEntry<'a>) -> StoredEntry<'a> {
1526 StoredEntry {
1527 name: entry.name,
1528 data: entry.data,
1529 mtime: entry.mtime,
1530 attributes: entry.attributes,
1531 host_os: entry.host_os,
1532 }
1533}
1534
1535fn write_compressed_entry_payload(
1536 out: &mut Vec<u8>,
1537 entry: &CompressedEntry<'_>,
1538 packed: &[u8],
1539 algorithm_version: u8,
1540 compression_method: u8,
1541 dictionary_size: u64,
1542 solid_continuation: bool,
1543) -> Result<()> {
1544 let mut extra = Vec::new();
1545 write_hash_record(&mut extra, entry.data);
1546 let compression_info = compression_info(
1547 algorithm_version,
1548 compression_method,
1549 dictionary_size,
1550 solid_continuation,
1551 )?;
1552 let specific = file_specific(
1553 entry.name,
1554 entry.data.len() as u64,
1555 Some(crc32(entry.data)),
1556 entry.attributes,
1557 entry.mtime,
1558 compression_info,
1559 entry.host_os,
1560 )?;
1561 write_block(
1562 out,
1563 HEAD_FILE,
1564 HFL_EXTRA | HFL_DATA,
1565 Some(packed.len() as u64),
1566 &specific,
1567 &extra,
1568 packed,
1569 )
1570}
1571
1572struct CompressedFragment<'a, 'b> {
1573 entry: &'a CompressedEntry<'b>,
1574 data: &'a [u8],
1575 algorithm_version: u8,
1576 compression_method: u8,
1577 dictionary_size: u64,
1578 solid_continuation: bool,
1579 split_before: bool,
1580 split_after: bool,
1581}
1582
1583fn write_compressed_entry_fragment(
1584 out: &mut Vec<u8>,
1585 fragment: CompressedFragment<'_, '_>,
1586) -> Result<()> {
1587 let CompressedFragment {
1588 entry,
1589 data,
1590 algorithm_version,
1591 compression_method,
1592 dictionary_size,
1593 solid_continuation,
1594 split_before,
1595 split_after,
1596 } = fragment;
1597
1598 let mut extra = Vec::new();
1599 if !split_after {
1600 write_hash_record(&mut extra, entry.data);
1601 }
1602 let compression_info = compression_info(
1603 algorithm_version,
1604 compression_method,
1605 dictionary_size,
1606 solid_continuation,
1607 )?;
1608 let specific = file_specific(
1609 entry.name,
1610 entry.data.len() as u64,
1611 (!split_after).then_some(crc32(entry.data)),
1612 entry.attributes,
1613 entry.mtime,
1614 compression_info,
1615 entry.host_os,
1616 )?;
1617 let mut block_flags = HFL_DATA;
1618 if split_before {
1619 block_flags |= HFL_SPLIT_BEFORE;
1620 }
1621 if split_after {
1622 block_flags |= HFL_SPLIT_AFTER;
1623 }
1624 if !extra.is_empty() {
1625 block_flags |= HFL_EXTRA;
1626 }
1627
1628 write_block(
1629 out,
1630 HEAD_FILE,
1631 block_flags,
1632 Some(data.len() as u64),
1633 &specific,
1634 &extra,
1635 data,
1636 )
1637}
1638
1639fn write_stored_entry_with_cache(
1640 out: &mut Vec<u8>,
1641 cached_headers: &mut Vec<CachedHeader>,
1642 entry: &StoredEntry<'_>,
1643) -> Result<()> {
1644 validate_entry(entry)?;
1645 let mut extra = Vec::new();
1646 write_hash_record(&mut extra, entry.data);
1647 let specific = stored_file_specific(
1648 entry.name,
1649 entry.data.len() as u64,
1650 Some(crc32(entry.data)),
1651 entry.attributes,
1652 entry.mtime,
1653 entry.host_os,
1654 )?;
1655 write_block_with_cache(
1656 out,
1657 cached_headers,
1658 BlockParts {
1659 header_type: HEAD_FILE,
1660 flags: HFL_EXTRA | HFL_DATA,
1661 data_size: Some(entry.data.len() as u64),
1662 type_specific: &specific,
1663 extra: &extra,
1664 data: entry.data,
1665 },
1666 )
1667}
1668
1669struct EncryptedStoredPayload {
1670 data: Vec<u8>,
1671 salt: [u8; 16],
1672 iv: [u8; 16],
1673 check_value: [u8; 12],
1674 crc32_mac: u32,
1675 blake2sp_mac: [u8; 32],
1676}
1677
1678fn encrypted_stored_payload(data: &[u8], password: &[u8]) -> Result<EncryptedStoredPayload> {
1679 encrypted_payload(data, data, password)
1680}
1681
1682fn encrypted_payload(
1683 packed_data: &[u8],
1684 integrity_data: &[u8],
1685 password: &[u8],
1686) -> Result<EncryptedStoredPayload> {
1687 let mut salt = [0u8; 16];
1688 let mut iv = [0u8; 16];
1689 getrandom::fill(&mut salt)
1690 .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption salt"))?;
1691 getrandom::fill(&mut iv)
1692 .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption IV"))?;
1693 let keys = Rar50Keys::derive(password, salt, 0).map_err(super::map_rar50_crypto_error)?;
1694
1695 let mut encrypted_data = packed_data.to_vec();
1696 let padded_len = encrypted_data
1697 .len()
1698 .checked_add(15)
1699 .ok_or(Error::InvalidHeader("RAR 5 encrypted data size overflows"))?
1700 & !15;
1701 encrypted_data.resize(padded_len, 0);
1702 Rar50Cipher::new(keys.key, iv)
1703 .encrypt_in_place(&mut encrypted_data)
1704 .map_err(super::map_rar50_crypto_error)?;
1705
1706 Ok(EncryptedStoredPayload {
1707 data: encrypted_data,
1708 salt,
1709 iv,
1710 check_value: keys.password_check_record(),
1711 crc32_mac: keys.mac_crc32(crc32(integrity_data)),
1712 blake2sp_mac: keys.mac_hash32(blake2sp::hash(integrity_data)),
1713 })
1714}
1715
1716fn write_encrypted_stored_entry_fragment_with_header_keys(
1717 out: &mut Vec<u8>,
1718 entry: &EncryptedStoredEntry<'_>,
1719 data: &[u8],
1720 encrypted: &EncryptedStoredPayload,
1721 split_before: bool,
1722 split_after: bool,
1723 header_keys: Option<&Rar50Keys>,
1724) -> Result<()> {
1725 let mut extra = Vec::new();
1726 write_file_encryption_record(
1727 &mut extra,
1728 encrypted.salt,
1729 encrypted.iv,
1730 encrypted.check_value,
1731 );
1732 if !split_after {
1733 write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
1734 }
1735
1736 let specific = stored_file_specific(
1737 entry.name,
1738 entry.data.len() as u64,
1739 (!split_after).then_some(encrypted.crc32_mac),
1740 entry.attributes,
1741 entry.mtime,
1742 entry.host_os,
1743 )?;
1744 let mut block_flags = HFL_EXTRA | HFL_DATA;
1745 if split_before {
1746 block_flags |= HFL_SPLIT_BEFORE;
1747 }
1748 if split_after {
1749 block_flags |= HFL_SPLIT_AFTER;
1750 }
1751
1752 if let Some(header_keys) = header_keys {
1753 out.extend_from_slice(&encrypted_header_block(
1754 header_keys,
1755 HEAD_FILE,
1756 block_flags,
1757 Some(data.len() as u64),
1758 &specific,
1759 &extra,
1760 data,
1761 )?);
1762 Ok(())
1763 } else {
1764 write_block(
1765 out,
1766 HEAD_FILE,
1767 block_flags,
1768 Some(data.len() as u64),
1769 &specific,
1770 &extra,
1771 data,
1772 )
1773 }
1774}
1775
1776fn write_encrypted_stored_compressed_entry_with_header_keys(
1777 out: &mut Vec<u8>,
1778 entry: &EncryptedCompressedEntry<'_>,
1779 encrypted: &EncryptedStoredPayload,
1780 header_keys: Option<&Rar50Keys>,
1781) -> Result<()> {
1782 validate_encrypted_compressed_entry(entry)?;
1783 let stored = encrypted_stored_entry_from_compressed_entry(entry);
1784 write_encrypted_stored_entry_fragment_with_header_keys(
1785 out,
1786 &stored,
1787 &encrypted.data,
1788 encrypted,
1789 false,
1790 false,
1791 header_keys,
1792 )
1793}
1794
1795fn encrypted_stored_entry_from_compressed_entry<'a>(
1796 entry: &EncryptedCompressedEntry<'a>,
1797) -> EncryptedStoredEntry<'a> {
1798 EncryptedStoredEntry {
1799 name: entry.name,
1800 data: entry.data,
1801 mtime: entry.mtime,
1802 attributes: entry.attributes,
1803 host_os: entry.host_os,
1804 password: entry.password,
1805 }
1806}
1807
1808struct EncryptedCompressedFragment<'a, 'b> {
1809 entry: &'a EncryptedCompressedEntry<'b>,
1810 data: &'a [u8],
1811 encrypted: &'a EncryptedStoredPayload,
1812 algorithm_version: u8,
1813 compression_method: u8,
1814 dictionary_size: u64,
1815 solid_continuation: bool,
1816 split_before: bool,
1817 split_after: bool,
1818}
1819
1820fn write_encrypted_compressed_entry_fragment_with_header_keys(
1821 out: &mut Vec<u8>,
1822 fragment: EncryptedCompressedFragment<'_, '_>,
1823 header_keys: Option<&Rar50Keys>,
1824) -> Result<()> {
1825 let EncryptedCompressedFragment {
1826 entry,
1827 data,
1828 encrypted,
1829 algorithm_version,
1830 compression_method,
1831 dictionary_size,
1832 solid_continuation,
1833 split_before,
1834 split_after,
1835 } = fragment;
1836
1837 let mut extra = Vec::new();
1838 write_file_encryption_record(
1839 &mut extra,
1840 encrypted.salt,
1841 encrypted.iv,
1842 encrypted.check_value,
1843 );
1844 if !split_after {
1845 write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
1846 }
1847
1848 let compression_info = compression_info(
1849 algorithm_version,
1850 compression_method,
1851 dictionary_size,
1852 solid_continuation,
1853 )?;
1854 let specific = file_specific(
1855 entry.name,
1856 entry.data.len() as u64,
1857 (!split_after).then_some(encrypted.crc32_mac),
1858 entry.attributes,
1859 entry.mtime,
1860 compression_info,
1861 entry.host_os,
1862 )?;
1863 let mut block_flags = HFL_EXTRA | HFL_DATA;
1864 if split_before {
1865 block_flags |= HFL_SPLIT_BEFORE;
1866 }
1867 if split_after {
1868 block_flags |= HFL_SPLIT_AFTER;
1869 }
1870
1871 if let Some(header_keys) = header_keys {
1872 out.extend_from_slice(&encrypted_header_block(
1873 header_keys,
1874 HEAD_FILE,
1875 block_flags,
1876 Some(data.len() as u64),
1877 &specific,
1878 &extra,
1879 data,
1880 )?);
1881 Ok(())
1882 } else {
1883 write_block(
1884 out,
1885 HEAD_FILE,
1886 block_flags,
1887 Some(data.len() as u64),
1888 &specific,
1889 &extra,
1890 data,
1891 )
1892 }
1893}
1894
1895fn write_stored_entry_fragment(
1896 out: &mut Vec<u8>,
1897 entry: &StoredEntry<'_>,
1898 data: &[u8],
1899 unpacked_size: u64,
1900 data_crc32: Option<u32>,
1901 split_before: bool,
1902 split_after: bool,
1903) -> Result<()> {
1904 let mut extra = Vec::new();
1905 if !split_before && !split_after {
1906 write_hash_record(&mut extra, data);
1907 }
1908 let specific = stored_file_specific(
1909 entry.name,
1910 unpacked_size,
1911 data_crc32,
1912 entry.attributes,
1913 entry.mtime,
1914 entry.host_os,
1915 )?;
1916 let mut block_flags = HFL_DATA;
1917 if split_before {
1918 block_flags |= HFL_SPLIT_BEFORE;
1919 }
1920 if split_after {
1921 block_flags |= HFL_SPLIT_AFTER;
1922 }
1923 if !extra.is_empty() {
1924 block_flags |= HFL_EXTRA;
1925 }
1926
1927 write_block(
1928 out,
1929 HEAD_FILE,
1930 block_flags,
1931 Some(data.len() as u64),
1932 &specific,
1933 &extra,
1934 data,
1935 )
1936}
1937
1938fn write_stored_service(out: &mut Vec<u8>, name: &[u8], data: &[u8]) -> Result<()> {
1939 let mut extra = Vec::new();
1940 write_extra_record(&mut extra, FHEXTRA_SUBDATA, &[]);
1941 let specific = stored_file_specific(name, data.len() as u64, Some(crc32(data)), 0, None, 0)?;
1942
1943 write_block(
1944 out,
1945 HEAD_SERVICE,
1946 HFL_EXTRA | HFL_DATA,
1947 Some(data.len() as u64),
1948 &specific,
1949 &extra,
1950 data,
1951 )
1952}
1953
1954fn write_recovery_service(out: &mut Vec<u8>, recovery_percent: u64) -> Result<()> {
1955 let mut service_data = Vec::new();
1956 write_vint(&mut service_data, recovery_percent);
1957 let mut extra = Vec::new();
1958 write_extra_record(&mut extra, FHEXTRA_SUBDATA, &service_data);
1959
1960 let data = build_structural_inline_recovery_data(out, recovery_percent)?;
1961 let specific = stored_file_specific(b"RR", data.len() as u64, Some(crc32(&data)), 0, None, 0)?;
1962 write_block(
1963 out,
1964 HEAD_SERVICE,
1965 HFL_EXTRA | HFL_DATA,
1966 Some(data.len() as u64),
1967 &specific,
1968 &extra,
1969 &data,
1970 )
1971}
1972
1973fn write_stored_service_with_cache(
1974 out: &mut Vec<u8>,
1975 cached_headers: &mut Vec<CachedHeader>,
1976 name: &[u8],
1977 data: &[u8],
1978) -> Result<()> {
1979 let mut extra = Vec::new();
1980 write_extra_record(&mut extra, FHEXTRA_SUBDATA, &[]);
1981 let specific = stored_file_specific(name, data.len() as u64, Some(crc32(data)), 0, None, 0)?;
1982
1983 write_block_with_cache(
1984 out,
1985 cached_headers,
1986 BlockParts {
1987 header_type: HEAD_SERVICE,
1988 flags: HFL_EXTRA | HFL_DATA,
1989 data_size: Some(data.len() as u64),
1990 type_specific: &specific,
1991 extra: &extra,
1992 data,
1993 },
1994 )
1995}
1996
1997fn write_encrypted_stored_service_with_header_keys(
1998 out: &mut Vec<u8>,
1999 name: &[u8],
2000 comment: EncryptedArchiveCommentEntry<'_>,
2001 header_keys: Option<&Rar50Keys>,
2002) -> Result<()> {
2003 write_encrypted_service_with_header_keys(
2004 out,
2005 name,
2006 comment.data,
2007 &[],
2008 comment.password,
2009 header_keys,
2010 )
2011}
2012
2013fn write_header_encrypted_recovery_service(
2014 out: &mut Vec<u8>,
2015 recovery_percent: u64,
2016 header_keys: &Rar50Keys,
2017) -> Result<()> {
2018 let mut service_data = Vec::new();
2019 write_vint(&mut service_data, recovery_percent);
2020 let data = build_structural_inline_recovery_data(out, recovery_percent)?;
2021 let mut extra = Vec::new();
2022 write_extra_record(&mut extra, FHEXTRA_SUBDATA, &service_data);
2023 let specific = stored_file_specific(b"RR", data.len() as u64, Some(crc32(&data)), 0, None, 0)?;
2024 out.extend_from_slice(&encrypted_header_block(
2025 header_keys,
2026 HEAD_SERVICE,
2027 HFL_EXTRA | HFL_DATA,
2028 Some(data.len() as u64),
2029 &specific,
2030 &extra,
2031 &data,
2032 )?);
2033 Ok(())
2034}
2035
2036fn write_encrypted_service_with_header_keys(
2037 out: &mut Vec<u8>,
2038 name: &[u8],
2039 data: &[u8],
2040 service_data: &[u8],
2041 password: &[u8],
2042 header_keys: Option<&Rar50Keys>,
2043) -> Result<()> {
2044 validate_nonempty_password(password)?;
2045 let encrypted = encrypted_stored_payload(data, password)?;
2046 let mut extra = Vec::new();
2047 write_extra_record(&mut extra, FHEXTRA_SUBDATA, service_data);
2048 write_file_encryption_record(
2049 &mut extra,
2050 encrypted.salt,
2051 encrypted.iv,
2052 encrypted.check_value,
2053 );
2054 write_hash_record_with_value(&mut extra, encrypted.blake2sp_mac);
2055 let specific = stored_file_specific(
2056 name,
2057 data.len() as u64,
2058 Some(encrypted.crc32_mac),
2059 0,
2060 None,
2061 0,
2062 )?;
2063
2064 if let Some(header_keys) = header_keys {
2065 out.extend_from_slice(&encrypted_header_block(
2066 header_keys,
2067 HEAD_SERVICE,
2068 HFL_EXTRA | HFL_DATA,
2069 Some(encrypted.data.len() as u64),
2070 &specific,
2071 &extra,
2072 &encrypted.data,
2073 )?);
2074 Ok(())
2075 } else {
2076 write_block(
2077 out,
2078 HEAD_SERVICE,
2079 HFL_EXTRA | HFL_DATA,
2080 Some(encrypted.data.len() as u64),
2081 &specific,
2082 &extra,
2083 &encrypted.data,
2084 )
2085 }
2086}
2087
2088fn stored_file_specific(
2089 name: &[u8],
2090 unpacked_size: u64,
2091 data_crc32: Option<u32>,
2092 attributes: u64,
2093 mtime: Option<u32>,
2094 host_os: u64,
2095) -> Result<Vec<u8>> {
2096 file_specific(
2097 name,
2098 unpacked_size,
2099 data_crc32,
2100 attributes,
2101 mtime,
2102 0,
2103 host_os,
2104 )
2105}
2106
2107fn file_specific(
2108 name: &[u8],
2109 unpacked_size: u64,
2110 data_crc32: Option<u32>,
2111 attributes: u64,
2112 mtime: Option<u32>,
2113 compression_info: u64,
2114 host_os: u64,
2115) -> Result<Vec<u8>> {
2116 if name.is_empty() {
2117 return Err(Error::InvalidHeader("RAR 5 file name is empty"));
2118 }
2119 let mut file_flags = if data_crc32.is_some() { FHFL_CRC32 } else { 0 };
2120 if mtime.is_some() {
2121 file_flags |= FHFL_MTIME;
2122 }
2123
2124 let mut specific = Vec::new();
2125 write_vint(&mut specific, file_flags);
2126 write_vint(&mut specific, unpacked_size);
2127 write_vint(&mut specific, attributes);
2128 if let Some(mtime) = mtime {
2129 specific.extend_from_slice(&mtime.to_le_bytes());
2130 }
2131 if let Some(data_crc32) = data_crc32 {
2132 specific.extend_from_slice(&data_crc32.to_le_bytes());
2133 }
2134 write_vint(&mut specific, compression_info);
2135 write_vint(&mut specific, host_os);
2136 write_vint(&mut specific, name.len() as u64);
2137 specific.extend_from_slice(name);
2138 Ok(specific)
2139}
2140
2141fn validate_entry(entry: &StoredEntry<'_>) -> Result<()> {
2142 validate_file_entry(entry.name)
2143}
2144
2145fn validate_compressed_entry(entry: &CompressedEntry<'_>) -> Result<()> {
2146 validate_file_entry(entry.name)
2147}
2148
2149fn validate_encrypted_entry(entry: &EncryptedStoredEntry<'_>) -> Result<()> {
2150 validate_file_entry(entry.name)?;
2151 if entry.password.is_empty() {
2152 return Err(Error::InvalidHeader(
2153 "RAR 5 encrypted writer needs a non-empty password",
2154 ));
2155 }
2156 Ok(())
2157}
2158
2159fn validate_encrypted_compressed_entry(entry: &EncryptedCompressedEntry<'_>) -> Result<()> {
2160 validate_file_entry(entry.name)?;
2161 if entry.password.is_empty() {
2162 return Err(Error::InvalidHeader(
2163 "RAR 5 encrypted writer needs a non-empty password",
2164 ));
2165 }
2166 Ok(())
2167}
2168
2169fn validate_file_service(service: &StoredServiceEntry<'_>) -> Result<()> {
2170 if !matches!(service.name, b"ACL" | b"STM" | b"CMT") {
2171 return Err(Error::UnsupportedFeature {
2172 version: crate::ArchiveVersion::Rar50,
2173 feature: "RAR 5 stored file service name",
2174 });
2175 }
2176 if service.data.is_empty() {
2177 return Err(Error::InvalidHeader(
2178 "RAR 5 stored file service data is empty",
2179 ));
2180 }
2181 Ok(())
2182}
2183
2184fn validate_encrypted_file_service(service: &EncryptedStoredServiceEntry<'_>) -> Result<()> {
2185 if !matches!(service.name, b"CMT") {
2186 return Err(Error::UnsupportedFeature {
2187 version: crate::ArchiveVersion::Rar50,
2188 feature: "RAR 5 encrypted stored file service name",
2189 });
2190 }
2191 if service.data.is_empty() {
2192 return Err(Error::InvalidHeader(
2193 "RAR 5 encrypted stored file service data is empty",
2194 ));
2195 }
2196 validate_nonempty_password(service.password)
2197}
2198
2199fn validate_recovery_percent(percent: u64) -> Result<()> {
2200 if !(1..=100).contains(&percent) {
2201 return Err(Error::InvalidHeader(
2202 "RAR 5 recovery percent must be in 1..=100",
2203 ));
2204 }
2205 Ok(())
2206}
2207
2208fn validate_nonempty_password(password: &[u8]) -> Result<()> {
2209 if password.is_empty() {
2210 return Err(Error::InvalidHeader(
2211 "RAR 5 encrypted writer needs a non-empty password",
2212 ));
2213 }
2214 Ok(())
2215}
2216
2217fn validate_file_entry(name: &[u8]) -> Result<()> {
2218 if name.is_empty() {
2219 return Err(Error::InvalidHeader("RAR 5 file name is empty"));
2220 }
2221 Ok(())
2222}
2223
2224fn write_block(
2225 out: &mut Vec<u8>,
2226 header_type: u64,
2227 flags: u64,
2228 data_size: Option<u64>,
2229 type_specific: &[u8],
2230 extra: &[u8],
2231 data: &[u8],
2232) -> Result<()> {
2233 let header = block_header_image(header_type, flags, data_size, type_specific, extra)?;
2234 out.extend_from_slice(&header);
2235 out.extend_from_slice(data);
2236 Ok(())
2237}
2238
2239fn write_block_with_cache(
2240 out: &mut Vec<u8>,
2241 cached_headers: &mut Vec<CachedHeader>,
2242 parts: BlockParts<'_>,
2243) -> Result<()> {
2244 let offset = out.len();
2245 let header = block_header_image(
2246 parts.header_type,
2247 parts.flags,
2248 parts.data_size,
2249 parts.type_specific,
2250 parts.extra,
2251 )?;
2252 cached_headers.push(CachedHeader {
2253 offset,
2254 header: header.clone(),
2255 });
2256 out.extend_from_slice(&header);
2257 out.extend_from_slice(parts.data);
2258 Ok(())
2259}
2260
2261fn quick_open_payload(cached_headers: &[CachedHeader], qo_pos: usize) -> Result<Vec<u8>> {
2262 let mut out = Vec::new();
2263 for cached in cached_headers {
2264 let offset = qo_pos
2265 .checked_sub(cached.offset)
2266 .ok_or(Error::InvalidHeader(
2267 "RAR 5 quick-open cached header is after QO header",
2268 ))?;
2269 let mut body = Vec::new();
2270 write_vint(&mut body, 0);
2271 write_vint(&mut body, offset as u64);
2272 write_vint(&mut body, cached.header.len() as u64);
2273 body.extend_from_slice(&cached.header);
2274
2275 out.extend_from_slice(&crc32(&body).to_le_bytes());
2276 write_vint(&mut out, body.len() as u64);
2277 out.extend_from_slice(&body);
2278 }
2279 Ok(out)
2280}
2281
2282fn encrypted_header_block(
2283 keys: &Rar50Keys,
2284 header_type: u64,
2285 flags: u64,
2286 data_size: Option<u64>,
2287 type_specific: &[u8],
2288 extra: &[u8],
2289 data: &[u8],
2290) -> Result<Vec<u8>> {
2291 let header = block_header_image(header_type, flags, data_size, type_specific, extra)?;
2292 let mut iv = [0u8; 16];
2293 getrandom::fill(&mut iv)
2294 .map_err(|_| Error::InvalidHeader("RAR 5 writer could not generate encryption IV"))?;
2295 let padded_len = header.len().checked_add(15).ok_or(Error::InvalidHeader(
2296 "RAR 5 encrypted header size overflows",
2297 ))? & !15;
2298 let mut encrypted_header = header;
2299 encrypted_header.resize(padded_len, 0);
2300 Rar50Cipher::new(keys.key, iv)
2301 .encrypt_in_place(&mut encrypted_header)
2302 .map_err(super::map_rar50_crypto_error)?;
2303 let mut out = Vec::with_capacity(16 + encrypted_header.len() + data.len());
2304 out.extend_from_slice(&iv);
2305 out.extend_from_slice(&encrypted_header);
2306 out.extend_from_slice(data);
2307 Ok(out)
2308}
2309
2310fn block_header_image(
2311 header_type: u64,
2312 flags: u64,
2313 data_size: Option<u64>,
2314 type_specific: &[u8],
2315 extra: &[u8],
2316) -> Result<Vec<u8>> {
2317 let mut body = Vec::new();
2318 write_vint(&mut body, header_type);
2319 write_vint(&mut body, flags);
2320 if flags & HFL_EXTRA != 0 {
2321 write_vint(&mut body, extra.len() as u64);
2322 }
2323 if let Some(data_size) = data_size {
2324 write_vint(&mut body, data_size);
2325 }
2326 body.extend_from_slice(type_specific);
2327 body.extend_from_slice(extra);
2328
2329 let mut header_size = Vec::new();
2330 write_vint(&mut header_size, body.len() as u64);
2331
2332 let mut header = Vec::with_capacity(4 + header_size.len() + body.len());
2333 header.extend_from_slice(&0u32.to_le_bytes());
2334 header.extend_from_slice(&header_size);
2335 header.extend_from_slice(&body);
2336 let header_crc = crc32(&header[4..]);
2337 header[..4].copy_from_slice(&header_crc.to_le_bytes());
2338 Ok(header)
2339}
2340
2341fn write_extra_record(out: &mut Vec<u8>, record_type: u64, data: &[u8]) {
2342 let mut body = Vec::new();
2343 write_vint(&mut body, record_type);
2344 body.extend_from_slice(data);
2345 write_vint(out, body.len() as u64);
2346 out.extend_from_slice(&body);
2347}
2348
2349fn write_hash_record(out: &mut Vec<u8>, data: &[u8]) {
2350 write_hash_record_with_value(out, blake2sp::hash(data));
2351}
2352
2353fn write_hash_record_with_value(out: &mut Vec<u8>, hash: [u8; 32]) {
2354 let mut record = Vec::new();
2355 write_vint(&mut record, 0);
2356 record.extend_from_slice(&hash);
2357 write_extra_record(out, FHEXTRA_HASH, &record);
2358}
2359
2360fn write_file_encryption_record(
2361 out: &mut Vec<u8>,
2362 salt: [u8; 16],
2363 iv: [u8; 16],
2364 check_value: [u8; 12],
2365) {
2366 let mut record = Vec::new();
2367 write_vint(&mut record, 0);
2368 write_vint(&mut record, 0x0003);
2369 record.push(0);
2370 record.extend_from_slice(&salt);
2371 record.extend_from_slice(&iv);
2372 record.extend_from_slice(&check_value);
2373 write_extra_record(out, FHEXTRA_CRYPT, &record);
2374}
2375
2376fn write_vint(out: &mut Vec<u8>, mut value: u64) {
2377 while value >= 0x80 {
2378 out.push((value as u8) | 0x80);
2379 value >>= 7;
2380 }
2381 out.push(value as u8);
2382}
2383
2384#[cfg(test)]
2385mod tests {
2386 use super::filter_policy::{
2387 auto_delta_filter_range, disjoint_filter_ranges, encode_member_with_auto_size_filter,
2388 encode_member_with_filter_spec, encode_member_with_filter_specs,
2389 };
2390 use super::*;
2391 use crate::x86_filter_scan::auto_x86_filter_ranges;
2392 use rars_codec::rar50::{encode_literal_only, encode_lz_member};
2393 use rars_codec::rar50::{encode_lz_member_with_options, EncodeOptions, Rar50FilterSpec};
2394 use std::cell::RefCell;
2395 use std::fs;
2396 use std::io::{Result as IoResult, Write};
2397 use std::process::Command;
2398 use std::rc::Rc;
2399
2400 struct CollectWriter(Rc<RefCell<Vec<u8>>>);
2401
2402 impl Write for CollectWriter {
2403 fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
2404 self.0.borrow_mut().extend_from_slice(buf);
2405 Ok(buf.len())
2406 }
2407
2408 fn flush(&mut self) -> IoResult<()> {
2409 Ok(())
2410 }
2411 }
2412
2413 #[derive(Debug, Clone, PartialEq, Eq)]
2414 struct CollectedEntry {
2415 name: Vec<u8>,
2416 data: Vec<u8>,
2417 file_time: u32,
2418 attr: u64,
2419 host_os: u64,
2420 is_directory: bool,
2421 }
2422
2423 fn collect_extract(archive: &Archive) -> Result<Vec<CollectedEntry>> {
2424 let entries = RefCell::new(Vec::new());
2425 archive.extract_to(crate::ArchiveReadOptions::default(), |meta| {
2426 let data = Rc::new(RefCell::new(Vec::new()));
2427 entries.borrow_mut().push((meta.clone(), Rc::clone(&data)));
2428 Ok(Box::new(CollectWriter(data)))
2429 })?;
2430 Ok(entries
2431 .into_inner()
2432 .into_iter()
2433 .map(|(meta, data)| CollectedEntry {
2434 name: meta.name,
2435 data: data.borrow().clone(),
2436 file_time: meta.file_time,
2437 attr: meta.attr,
2438 host_os: meta.host_os,
2439 is_directory: meta.is_directory,
2440 })
2441 .collect())
2442 }
2443
2444 #[test]
2445 fn writer_plan_offset_resolution_errors_if_offset_never_converges() {
2446 let mut offsets = Vec::new();
2447 let result = resolve_writer_plan_offset(
2448 |offset| {
2449 offsets.push(offset);
2450 Ok((Vec::new(), Some(offset + 1)))
2451 },
2452 "missing offset",
2453 "did not converge",
2454 );
2455
2456 assert!(matches!(
2457 result,
2458 Err(Error::InvalidHeader("did not converge"))
2459 ));
2460 assert_eq!(offsets, [0, 1, 2, 3]);
2461 }
2462
2463 #[test]
2464 fn writer_plan_offset_resolution_rejects_missing_reported_offset() {
2465 let result = resolve_writer_plan_offset(
2466 |_| Ok((Vec::new(), None)),
2467 "missing offset",
2468 "did not converge",
2469 );
2470
2471 assert!(matches!(
2472 result,
2473 Err(Error::InvalidHeader("missing offset"))
2474 ));
2475 }
2476
2477 #[test]
2478 fn internal_literal_only_compressed_member_round_trips_through_rar50_reader() {
2479 let data = b"RAR5 literal-only compressed format-layer experiment\n";
2480 let packed = encode_literal_only(data, 0).unwrap();
2481 let name = b"compressed.txt";
2482
2483 let mut archive = Vec::new();
2484 archive.extend_from_slice(RAR50_SIGNATURE);
2485 write_main_header(&mut archive, 0, None, &[]).unwrap();
2486
2487 let mut extra = Vec::new();
2488 write_hash_record(&mut extra, data);
2489 let compression_info = 1 << 7; let specific = file_specific(
2491 name,
2492 data.len() as u64,
2493 Some(crc32(data)),
2494 0x20,
2495 None,
2496 compression_info,
2497 0,
2498 )
2499 .unwrap();
2500 write_block(
2501 &mut archive,
2502 HEAD_FILE,
2503 HFL_EXTRA | HFL_DATA,
2504 Some(packed.len() as u64),
2505 &specific,
2506 &extra,
2507 &packed,
2508 )
2509 .unwrap();
2510 write_block(&mut archive, HEAD_END, 0, None, &[], &[], &[]).unwrap();
2511
2512 let parsed = Archive::parse(&archive).unwrap();
2513 let file = parsed.files().next().unwrap();
2514 let info = file.decoded_compression_info().unwrap();
2515 assert_eq!(info.method, 1);
2516 assert_eq!(info.dictionary_size, 128 * 1024);
2517
2518 let extracted = collect_extract(&parsed).unwrap();
2519 assert_eq!(extracted[0].name, name);
2520 assert_eq!(extracted[0].data, data);
2521 }
2522
2523 #[test]
2524 fn writer_stamps_requested_rar50_dictionary_size() {
2525 let data = b"RAR5 dictionary-size writer option fixture".repeat(64);
2526 let options =
2527 WriterOptions::new(crate::ArchiveVersion::Rar50, crate::FeatureSet::default())
2528 .with_dictionary_size(512 * 1024);
2529 let entries = [CompressedEntry {
2530 name: b"dict.bin",
2531 data: &data,
2532 mtime: None,
2533 attributes: 0x20,
2534 host_os: 3,
2535 }];
2536 let archive = Rar50Writer::new(options)
2537 .compressed_entries(&entries)
2538 .finish()
2539 .unwrap();
2540
2541 let parsed = Archive::parse(&archive).unwrap();
2542 let info = parsed
2543 .files()
2544 .next()
2545 .unwrap()
2546 .decoded_compression_info()
2547 .unwrap();
2548 let extracted = collect_extract(&parsed).unwrap();
2549
2550 assert_eq!(info.algorithm_version, 0);
2551 assert_eq!(info.dictionary_size, 512 * 1024);
2552 assert_eq!(extracted[0].data, data);
2553 }
2554
2555 #[test]
2556 fn writer_uses_rar7_dictionary_fields_when_size_needs_v1_encoding() {
2557 let data = b"RAR7 dictionary-size writer option fixture".repeat(64);
2558 let options =
2559 WriterOptions::new(crate::ArchiveVersion::Rar70, crate::FeatureSet::default())
2560 .with_dictionary_size(192 * 1024);
2561 let entries = [CompressedEntry {
2562 name: b"dict7.bin",
2563 data: &data,
2564 mtime: None,
2565 attributes: 0x20,
2566 host_os: 3,
2567 }];
2568 let archive = Rar50Writer::new(options)
2569 .compressed_entries(&entries)
2570 .finish()
2571 .unwrap();
2572
2573 let parsed = Archive::parse(&archive).unwrap();
2574 let info = parsed
2575 .files()
2576 .next()
2577 .unwrap()
2578 .decoded_compression_info()
2579 .unwrap();
2580 let extracted = collect_extract(&parsed).unwrap();
2581
2582 assert_eq!(info.algorithm_version, 1);
2583 assert_eq!(info.dictionary_size, 192 * 1024);
2584 assert_eq!(extracted[0].data, data);
2585 }
2586
2587 #[test]
2588 fn writer_rejects_unencodable_rar50_dictionary_size() {
2589 let options =
2590 WriterOptions::new(crate::ArchiveVersion::Rar50, crate::FeatureSet::default())
2591 .with_dictionary_size(192 * 1024);
2592 let entries = [CompressedEntry {
2593 name: b"bad.bin",
2594 data: b"data data data data",
2595 mtime: None,
2596 attributes: 0x20,
2597 host_os: 3,
2598 }];
2599
2600 assert!(matches!(
2601 Rar50Writer::new(options)
2602 .compressed_entries(&entries)
2603 .finish(),
2604 Err(Error::InvalidHeader(
2605 "RAR 5 v0 dictionary size must be a power-of-two multiple of 128 KiB"
2606 ))
2607 ));
2608 }
2609
2610 #[test]
2611 fn non_solid_level_five_considers_lower_level_parse_fallbacks() {
2612 let long_tail = b"stable long match payload for RAR5 best-level search ".repeat(10);
2613 let mut data = Vec::new();
2614 data.extend_from_slice(b"abc");
2615 data.extend_from_slice(&long_tail);
2616 for index in 0..320usize {
2617 data.extend_from_slice(b"abc");
2618 data.push((index as u8).wrapping_mul(37));
2619 data.extend_from_slice(b" near same-hash decoy ");
2620 data.extend_from_slice(&(index as u32).to_le_bytes());
2621 }
2622 data.extend_from_slice(b"abc");
2623 data.extend_from_slice(&long_tail);
2624
2625 let level_five = encode_options_for_level(Some(5), DEFAULT_RAR50_DICTIONARY_SIZE).unwrap();
2626 let fallback_candidates =
2627 encode_option_candidates_for_level(Some(5), DEFAULT_RAR50_DICTIONARY_SIZE).unwrap();
2628 assert!(fallback_candidates.len() > 1);
2629
2630 let level_five_only =
2631 encode_member_with_filter_policy(&data, 0, FilterPolicy::None, level_five).unwrap();
2632 let chosen = encode_member_with_filter_policy_candidates(
2633 &data,
2634 0,
2635 FilterPolicy::None,
2636 &fallback_candidates,
2637 )
2638 .unwrap();
2639
2640 assert!(
2641 chosen.len() <= level_five_only.len(),
2642 "candidate fallback should not choose a larger parse: level5={} chosen={}",
2643 level_five_only.len(),
2644 chosen.len()
2645 );
2646
2647 let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2648 let output = decoder
2649 .decode_member(
2650 &chosen,
2651 0,
2652 data.len(),
2653 false,
2654 rars_codec::rar50::DecodeMode::Lz,
2655 )
2656 .unwrap();
2657 assert_eq!(output, data);
2658 }
2659
2660 #[test]
2661 fn auto_x86_filter_ranges_select_dense_opcode_clusters() {
2662 let mut data = vec![0u8; 100_000];
2663 data[1_000] = 0xe8;
2664 data[7_000] = 0xe9;
2665 for pos in [50_000, 50_064, 50_128, 50_192] {
2666 data[pos] = 0xe8;
2667 }
2668 for pos in [70_000, 70_064, 70_128, 70_192] {
2669 data[pos] = 0xe9;
2670 }
2671
2672 let e8_ranges = auto_x86_filter_ranges(&data, false);
2673 assert!(e8_ranges
2674 .iter()
2675 .any(|range| range.start <= 50_000 && range.end >= 50_197));
2676 assert!(!e8_ranges
2677 .iter()
2678 .any(|range| range.start <= 1_000 && range.end >= 1_005));
2679
2680 let e8e9_ranges = auto_x86_filter_ranges(&data, true);
2681 assert!(e8e9_ranges
2682 .iter()
2683 .any(|range| range.start <= 70_000 && range.end >= 70_197));
2684 }
2685
2686 #[test]
2687 fn auto_x86_filter_policy_can_emit_multiple_disjoint_ranges() {
2688 let mut data = vec![0x41u8; 80_000];
2689 for cluster_start in [8_000, 60_000] {
2690 for index in 0..8 {
2691 let pos = cluster_start + index * 64;
2692 data[pos] = 0xe8;
2693 data[pos + 1..pos + 5].copy_from_slice(&(0x2000u32 + index as u32).to_le_bytes());
2694 }
2695 }
2696 let ranges = disjoint_filter_ranges(auto_x86_filter_ranges(&data, false));
2697 let filters: Vec<_> = ranges
2698 .into_iter()
2699 .map(|range| Rar50FilterSpec::range(FilterKind::E8, range))
2700 .collect();
2701
2702 let packed =
2703 encode_member_with_filter_specs(&data, 0, &filters, EncodeOptions::default()).unwrap();
2704 let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2705 let output = decoder
2706 .decode_member(
2707 &packed,
2708 0,
2709 data.len(),
2710 false,
2711 rars_codec::rar50::DecodeMode::Lz,
2712 )
2713 .unwrap();
2714
2715 assert_eq!(filters.len(), 2);
2716 assert_eq!(output, data);
2717 }
2718
2719 #[test]
2720 fn auto_delta_filter_range_skips_container_edges_and_aligns_channels() {
2721 let data = vec![0u8; 512];
2722
2723 let range = auto_delta_filter_range(&data, 3).unwrap();
2724
2725 assert!(range.start >= AUTO_DELTA_EDGE_SKIP);
2726 assert!(range.end <= data.len() - AUTO_DELTA_EDGE_SKIP);
2727 assert_eq!(range.start % 3, 0);
2728 assert_eq!((range.end - range.start) % 3, 0);
2729 assert!(auto_delta_filter_range(&data[..80], 3).is_none());
2730 }
2731
2732 #[test]
2733 fn auto_filter_policy_considers_ranged_delta_candidates() {
2734 let mut data = vec![0x55u8; AUTO_DELTA_EDGE_SKIP];
2735 for sample in 0..256u16 {
2736 let left = sample as u8;
2737 let right = left.wrapping_add(1);
2738 data.extend_from_slice(&[left, right]);
2739 }
2740 data.extend(std::iter::repeat_n(0xaa, AUTO_DELTA_EDGE_SKIP));
2741 let options = EncodeOptions::default();
2742
2743 let plain = encode_lz_member_with_options(&data, 0, options).unwrap();
2744 let ranged = encode_member_with_filter_spec(
2745 &data,
2746 0,
2747 Rar50FilterSpec::range(
2748 FilterKind::Delta { channels: 2 },
2749 auto_delta_filter_range(&data, 2).unwrap(),
2750 ),
2751 options,
2752 )
2753 .unwrap();
2754 let auto = encode_member_with_auto_size_filter(&data, 0, options).unwrap();
2755
2756 assert!(ranged.len() < plain.len());
2757 assert!(auto.len() <= ranged.len());
2758 let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2759 let output = decoder
2760 .decode_member(
2761 &auto,
2762 0,
2763 data.len(),
2764 false,
2765 rars_codec::rar50::DecodeMode::Lz,
2766 )
2767 .unwrap();
2768 assert_eq!(output, data);
2769 }
2770
2771 #[test]
2772 fn explicit_filters_accept_large_members_after_filter_ranges_are_split() {
2773 let data = vec![0u8; 4 * 1024 * 1024 + 1];
2774 let packed = encode_member_with_filter_policy(
2775 &data,
2776 0,
2777 FilterPolicy::Explicit(FilterKind::Delta { channels: 1 }),
2778 EncodeOptions::new(0),
2779 )
2780 .unwrap();
2781 let mut decoder = rars_codec::rar50::Unpack50Decoder::new();
2782
2783 assert_eq!(
2784 decoder
2785 .decode_member(
2786 &packed,
2787 0,
2788 data.len(),
2789 false,
2790 rars_codec::rar50::DecodeMode::Lz
2791 )
2792 .unwrap(),
2793 data
2794 );
2795 }
2796
2797 #[test]
2798 fn solid_reset_policy_chooses_smaller_of_continued_and_fresh_streams() {
2799 let options = EncodeOptions::default();
2800 let first = b"solid reset policy unrelated prefix data\n".repeat(32);
2801 let second = b"second member second member second member\n".repeat(16);
2802 let mut encoder = Unpack50Encoder::with_options(options);
2803 encoder.encode_member(&first, 0).unwrap();
2804
2805 let mut continued = encoder.clone();
2806 let continued_packed = continued.encode_member(&second, 0).unwrap();
2807 let mut fresh = Unpack50Encoder::with_options(options);
2808 let fresh_packed = fresh.encode_member(&second, 0).unwrap();
2809 let expected_fresh = fresh_packed.len() < continued_packed.len();
2810 let expected_len = continued_packed.len().min(fresh_packed.len());
2811
2812 let (packed, solid_continuation) =
2813 encode_with_solid_reset_policy(&mut encoder, &second, 0, options, 1).unwrap();
2814
2815 assert_eq!(packed.len(), expected_len);
2816 assert_eq!(solid_continuation, !expected_fresh);
2817 }
2818
2819 #[test]
2820 #[ignore = "requires local rar command; used for reference-validating experimental RAR5 compressed output"]
2821 fn reference_rar_accepts_internal_literal_only_compressed_member() {
2822 let data = b"RAR5 literal-only compressed reference experiment\n";
2823 let packed = encode_literal_only(data, 0).unwrap();
2824 let name = b"compressed.txt";
2825
2826 let mut archive = Vec::new();
2827 archive.extend_from_slice(RAR50_SIGNATURE);
2828 write_main_header(&mut archive, 0, None, &[]).unwrap();
2829
2830 let mut extra = Vec::new();
2831 write_hash_record(&mut extra, data);
2832 let specific = file_specific(
2833 name,
2834 data.len() as u64,
2835 Some(crc32(data)),
2836 0x20,
2837 None,
2838 1 << 7,
2839 0,
2840 )
2841 .unwrap();
2842 write_block(
2843 &mut archive,
2844 HEAD_FILE,
2845 HFL_EXTRA | HFL_DATA,
2846 Some(packed.len() as u64),
2847 &specific,
2848 &extra,
2849 &packed,
2850 )
2851 .unwrap();
2852 write_block(&mut archive, HEAD_END, 0, None, &[], &[], &[]).unwrap();
2853
2854 let mut path = std::env::temp_dir();
2855 path.push(format!(
2856 "rars-rar50-literal-only-{}.rar",
2857 std::process::id()
2858 ));
2859 fs::write(&path, archive).unwrap();
2860 let output = match Command::new("rar").arg("t").arg(&path).output() {
2861 Ok(output) => output,
2862 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
2863 eprintln!("skipping reference test: local `rar` command is not installed");
2864 return;
2865 }
2866 Err(error) => panic!("failed to run rar: {error}"),
2867 };
2868 if std::env::var_os("RARS_KEEP_REFERENCE_ARCHIVE").is_none() {
2869 let _ = fs::remove_file(&path);
2870 }
2871
2872 assert!(
2873 output.status.success(),
2874 "rar rejected experimental RAR5 compressed output\nstdout:\n{}\nstderr:\n{}",
2875 String::from_utf8_lossy(&output.stdout),
2876 String::from_utf8_lossy(&output.stderr)
2877 );
2878 }
2879
2880 #[test]
2881 #[ignore = "requires local rar command; used for reference-validating experimental RAR5 match output"]
2882 fn reference_rar_accepts_internal_match_compressed_member() {
2883 let data = b"RAR5 match compressed reference experiment\n".repeat(8);
2884 let packed = encode_lz_member(&data, 0).unwrap();
2885 let name = b"compressed.txt";
2886
2887 let mut archive = Vec::new();
2888 archive.extend_from_slice(RAR50_SIGNATURE);
2889 write_main_header(&mut archive, 0, None, &[]).unwrap();
2890
2891 let mut extra = Vec::new();
2892 write_hash_record(&mut extra, &data);
2893 let specific = file_specific(
2894 name,
2895 data.len() as u64,
2896 Some(crc32(&data)),
2897 0x20,
2898 None,
2899 1 << 7,
2900 0,
2901 )
2902 .unwrap();
2903 write_block(
2904 &mut archive,
2905 HEAD_FILE,
2906 HFL_EXTRA | HFL_DATA,
2907 Some(packed.len() as u64),
2908 &specific,
2909 &extra,
2910 &packed,
2911 )
2912 .unwrap();
2913 write_block(&mut archive, HEAD_END, 0, None, &[], &[], &[]).unwrap();
2914
2915 let mut path = std::env::temp_dir();
2916 path.push(format!("rars-rar50-match-{}.rar", std::process::id()));
2917 fs::write(&path, archive).unwrap();
2918 let output = match Command::new("rar").arg("t").arg(&path).output() {
2919 Ok(output) => output,
2920 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
2921 eprintln!("skipping reference test: local `rar` command is not installed");
2922 return;
2923 }
2924 Err(error) => panic!("failed to run rar: {error}"),
2925 };
2926 if std::env::var_os("RARS_KEEP_REFERENCE_ARCHIVE").is_none() {
2927 let _ = fs::remove_file(&path);
2928 }
2929
2930 assert!(
2931 output.status.success(),
2932 "rar rejected experimental RAR5 match output\nstdout:\n{}\nstderr:\n{}",
2933 String::from_utf8_lossy(&output.stdout),
2934 String::from_utf8_lossy(&output.stderr)
2935 );
2936 }
2937
2938 #[test]
2939 fn writer_options_default_targets_rar50_with_store_only_features() {
2940 let options = WriterOptions::default();
2941 assert_eq!(options.target, crate::ArchiveVersion::Rar50);
2942 assert_eq!(options.features, crate::FeatureSet::store_only());
2943 }
2944
2945 #[test]
2946 fn writer_rejects_mixed_member_kinds_without_panicking() {
2947 let stored = [StoredEntry {
2948 name: b"stored.txt",
2949 data: b"stored",
2950 mtime: None,
2951 attributes: 0x20,
2952 host_os: 3,
2953 }];
2954 let compressed = [CompressedEntry {
2955 name: b"compressed.txt",
2956 data: b"compressed compressed compressed",
2957 mtime: None,
2958 attributes: 0x20,
2959 host_os: 3,
2960 }];
2961 let result = Rar50Writer::new(WriterOptions::new(
2962 crate::ArchiveVersion::Rar50,
2963 crate::FeatureSet::store_only(),
2964 ))
2965 .stored_entries(&stored)
2966 .compressed_entries(&compressed)
2967 .finish();
2968
2969 assert!(matches!(
2970 result,
2971 Err(Error::UnsupportedFeature {
2972 version: crate::ArchiveVersion::Rar50,
2973 feature: "RAR 5 mixed stored/compressed writer plan",
2974 })
2975 ));
2976 }
2977}