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