1use crate::detect::{find_archive_start, ArchiveSignature, RAR15_SIGNATURE, SFX_SCAN_LIMIT};
2use crate::error::{Error, Result};
3use crate::features::FeatureSet;
4use crate::io_util::{align16 as checked_align16, read_exact_at, read_u16, read_u32};
5pub(crate) use crate::source::ArchiveSource;
6use crate::version::ArchiveFamily;
7use crate::ArchiveVersion;
8use rars_codec::rar13::Unpack15;
9use rars_codec::rar20::Unpack20;
10use rars_codec::rar29::Unpack29;
11use rars_crc32::{crc32, Crc32};
12use rars_crypto::rar15::Rar15Cipher;
13use rars_crypto::rar20::Rar20Cipher;
14use rars_crypto::rar30::{Error as Rar30Error, Rar30Cipher};
15use std::fs::File;
16use std::io::{Read, Write};
17use std::ops::Range;
18use std::path::Path;
19use std::sync::Arc;
20
21mod extract;
22mod write;
23pub use extract::extract_volumes_to;
24use extract::{DecoderSession, DecryptingReader};
25pub use write::{
26 write_compressed_archive, write_compressed_archive_with_comment, write_compressed_volumes,
27 write_rar29_compressed_archive_with_filter_policy, write_stored_archive,
28 write_stored_archive_with_comment, write_stored_volumes, FilterKind, FilterPolicy, FilterSpec,
29};
30
31const MARK_HEAD: u8 = 0x72;
32const MAIN_HEAD: u8 = 0x73;
33const FILE_HEAD: u8 = 0x74;
34const COMM_HEAD: u8 = 0x75;
35const PROTECT_HEAD: u8 = 0x78;
36const NEWSUB_HEAD: u8 = 0x7a;
37const ENDARC_HEAD: u8 = 0x7b;
38
39const LONG_BLOCK: u16 = 0x8000;
40const MHD_VOLUME: u16 = 0x0001;
41const MHD_COMMENT: u16 = 0x0002;
42const MHD_SOLID: u16 = 0x0008;
43const MHD_NEWNUMBERING: u16 = 0x0010;
44const MHD_PROTECT: u16 = 0x0040;
45const MHD_PASSWORD: u16 = 0x0080;
46const MHD_FIRSTVOLUME: u16 = 0x0100;
47const MHD_ENCRYPTVER: u16 = 0x0200;
48
49const FHD_SPLIT_BEFORE: u16 = 0x0001;
50const FHD_SPLIT_AFTER: u16 = 0x0002;
51const FHD_PASSWORD: u16 = 0x0004;
52const FHD_COMMENT: u16 = 0x0008;
53const FHD_SOLID: u16 = 0x0010;
54const FHD_LARGE: u16 = 0x0100;
55const FHD_UNICODE: u16 = 0x0200;
56const FHD_SALT: u16 = 0x0400;
57const FHD_EXTTIME: u16 = 0x1000;
58const FHD_DIRECTORY_MASK: u16 = 0x00e0;
59
60#[derive(Debug, Clone)]
61#[non_exhaustive]
62pub struct Archive {
63 pub sfx_offset: usize,
64 pub main: MainHeader,
65 pub blocks: Vec<Block>,
66 source: ArchiveSource,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq)]
70#[non_exhaustive]
71pub struct MainHeader {
72 pub head_crc: u16,
73 pub flags: u16,
74 pub head_size: u16,
75 pub reserved1: u16,
76 pub reserved2: u32,
77 pub encrypt_version: Option<u8>,
78}
79
80impl MainHeader {
81 pub fn has_archive_comment(&self) -> bool {
82 self.flags & MHD_COMMENT != 0
83 }
84
85 pub fn is_volume(&self) -> bool {
86 self.flags & MHD_VOLUME != 0
87 }
88
89 pub fn is_solid(&self) -> bool {
90 self.flags & MHD_SOLID != 0
91 }
92
93 pub fn uses_new_numbering(&self) -> bool {
94 self.flags & MHD_NEWNUMBERING != 0
95 }
96
97 pub fn has_recovery_record(&self) -> bool {
98 self.flags & MHD_PROTECT != 0
99 }
100
101 pub fn has_encrypted_headers(&self) -> bool {
102 self.flags & MHD_PASSWORD != 0
103 }
104
105 pub fn is_first_volume(&self) -> bool {
106 self.flags & MHD_FIRSTVOLUME != 0
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum Block {
113 File(FileHeader),
114 Comment(CommentHeader),
115 Protect(ProtectHeader),
116 NewSub(NewSubHeader),
117 End(BlockHeader),
118 Unknown(BlockHeader),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122#[non_exhaustive]
123pub struct BlockHeader {
124 pub head_crc: u16,
125 pub head_type: u8,
126 pub flags: u16,
127 pub head_size: u16,
128 pub add_size: Option<u64>,
129 pub offset: usize,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133#[non_exhaustive]
134pub struct FileHeader {
135 pub block: BlockHeader,
136 pub pack_size: u64,
137 pub unp_size: u64,
138 pub host_os: u8,
139 pub file_crc: u32,
140 pub file_time: u32,
141 pub unp_ver: u8,
142 pub method: u8,
143 pub name: Vec<u8>,
144 pub attr: u32,
145 pub salt: Option<[u8; 8]>,
146 pub file_comment: Vec<u8>,
147 pub ext_time: Vec<u8>,
148 pub packed_range: Range<usize>,
149}
150
151#[derive(Debug, Clone, PartialEq, Eq)]
152#[non_exhaustive]
153pub struct NewSubHeader {
154 pub file: FileHeader,
155 pub kind: NewSubKind,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159#[non_exhaustive]
160pub struct CommentHeader {
161 pub block: BlockHeader,
162 pub unp_size: u16,
163 pub unp_ver: u8,
164 pub method: u8,
165 pub comment_crc: u16,
166 pub packed_range: Range<usize>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170#[non_exhaustive]
171pub struct ProtectHeader {
172 pub block: BlockHeader,
173 pub version: u8,
174 pub rec_sectors: u16,
175 pub total_blocks: u32,
176 pub mark: [u8; 8],
177 pub data_range: Range<usize>,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq)]
181#[non_exhaustive]
182pub enum NewSubKind {
183 ArchiveComment,
184 RecoveryRecord,
185 Unknown(Vec<u8>),
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189#[non_exhaustive]
190pub struct WriterOptions {
191 pub target: ArchiveVersion,
192 pub features: FeatureSet,
193 pub compression_level: Option<u8>,
194 pub dictionary_size: Option<usize>,
195}
196
197impl WriterOptions {
198 pub const fn new(target: ArchiveVersion, features: FeatureSet) -> Self {
199 Self {
200 target,
201 features,
202 compression_level: None,
203 dictionary_size: None,
204 }
205 }
206
207 pub const fn with_compression_level(mut self, level: u8) -> Self {
208 self.compression_level = Some(level);
209 self
210 }
211
212 pub const fn with_dictionary_size(mut self, size: usize) -> Self {
213 self.dictionary_size = Some(size);
214 self
215 }
216}
217
218impl Default for WriterOptions {
219 fn default() -> Self {
220 Self {
221 target: ArchiveVersion::Rar15,
222 features: FeatureSet::store_only(),
223 compression_level: None,
224 dictionary_size: None,
225 }
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct StoredEntry<'a> {
231 pub name: &'a [u8],
232 pub data: &'a [u8],
233 pub file_time: u32,
234 pub file_attr: u32,
235 pub host_os: u8,
236 pub password: Option<&'a [u8]>,
237 pub file_comment: Option<&'a [u8]>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub struct FileEntry<'a> {
242 pub name: &'a [u8],
243 pub data: &'a [u8],
244 pub file_time: u32,
245 pub file_attr: u32,
246 pub host_os: u8,
247 pub password: Option<&'a [u8]>,
248 pub file_comment: Option<&'a [u8]>,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
252#[non_exhaustive]
253pub struct ExtractedEntryMeta {
254 pub name: Vec<u8>,
255 pub file_time: u32,
256 pub attr: u32,
257 pub host_os: u8,
258 pub is_directory: bool,
259}
260
261impl FileHeader {
262 pub fn name_bytes(&self) -> &[u8] {
263 &self.name
264 }
265
266 pub fn name_lossy(&self) -> String {
270 String::from_utf8_lossy(&self.name).into_owned()
271 }
272
273 pub fn is_split_before(&self) -> bool {
274 self.block.flags & FHD_SPLIT_BEFORE != 0
275 }
276
277 pub fn is_split_after(&self) -> bool {
278 self.block.flags & FHD_SPLIT_AFTER != 0
279 }
280
281 pub fn is_encrypted(&self) -> bool {
282 self.block.flags & FHD_PASSWORD != 0
283 }
284
285 pub fn is_solid(&self) -> bool {
286 self.block.flags & FHD_SOLID != 0
287 }
288
289 pub fn is_directory(&self) -> bool {
290 self.block.flags & FHD_DIRECTORY_MASK == FHD_DIRECTORY_MASK
291 }
292
293 pub fn has_ext_time(&self) -> bool {
294 self.block.flags & FHD_EXTTIME != 0
295 }
296
297 pub fn has_file_comment(&self) -> bool {
298 self.block.flags & FHD_COMMENT != 0 && !self.file_comment.is_empty()
299 }
300
301 pub fn file_comment(&self) -> Result<Option<Vec<u8>>> {
302 if !self.has_file_comment() {
303 return Ok(None);
304 }
305 let size = read_u16(&self.file_comment, 0)? as usize;
306 let start = 2usize;
307 let end = start
308 .checked_add(size)
309 .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
310 let comment = self.file_comment.get(start..end).ok_or(Error::TooShort)?;
311 Ok(Some(comment.to_vec()))
312 }
313
314 pub fn is_stored(&self) -> bool {
315 self.method == 0x30
316 }
317
318 pub fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
319 archive.read_range(self.packed_range.clone())
320 }
321
322 pub fn write_packed_data(&self, archive: &Archive, out: &mut impl Write) -> Result<()> {
323 archive.copy_range_to(self.packed_range.clone(), out)
324 }
325
326 pub(crate) fn stored_data(&self, archive: &Archive) -> Result<Vec<u8>> {
327 self.stored_data_with_password(archive, None)
328 }
329
330 pub(crate) fn stored_data_with_password(
331 &self,
332 archive: &Archive,
333 password: Option<&[u8]>,
334 ) -> Result<Vec<u8>> {
335 if !self.is_stored() {
336 return Err(self.unsupported_compression());
337 }
338 if !self.is_encrypted() && self.pack_size != self.unp_size {
339 return Err(Error::InvalidHeader(
340 "RAR 1.5 stored file has mismatched packed and unpacked sizes",
341 ));
342 }
343 let mut data = self.packed_data_for_decode(archive, password)?;
344 if self.is_encrypted() {
345 data.truncate(
346 usize::try_from(self.unp_size)
347 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
348 );
349 }
350 Ok(data)
351 }
352
353 pub(crate) fn unpacked_data(&self, archive: &Archive) -> Result<Vec<u8>> {
354 if self.is_stored() {
355 return self.stored_data(archive);
356 }
357 let mut session = DecoderSession::new(false);
358 session.decode_file_data(archive, self)
359 }
360
361 pub(crate) fn unpacked_data_with_rar29(
362 &self,
363 archive: &Archive,
364 decoder: &mut Unpack29,
365 solid: bool,
366 ) -> Result<Vec<u8>> {
367 if self.is_stored() {
368 return self.stored_data(archive);
369 }
370 if self.is_encrypted() {
371 return Err(self.unsupported_encryption());
372 }
373 if self.unp_ver < 29 {
374 return Err(self.unsupported_compression());
375 }
376 let packed = self.packed_data(archive)?;
377 let target = usize::try_from(self.unp_size)
378 .map_err(|_| Error::InvalidHeader("RAR 2.9 unpacked size overflows usize"))?;
379 if solid {
380 decoder.decode_member(&packed, target)
381 } else {
382 decoder.decode_non_solid_member(&packed, target)
383 }
384 .map_err(Into::into)
385 }
386
387 pub(crate) fn unpacked_data_with_unpack15(
388 &self,
389 archive: &Archive,
390 decoder: &mut Unpack15,
391 solid: bool,
392 ) -> Result<Vec<u8>> {
393 if self.is_stored() {
394 return self.stored_data(archive);
395 }
396 if self.is_encrypted() {
397 return Err(self.unsupported_encryption());
398 }
399 if self.unp_ver != 15 {
400 return Err(self.unsupported_compression());
401 }
402 decoder
403 .decode_member(
404 &self.packed_data(archive)?,
405 usize::try_from(self.unp_size)
406 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
407 solid,
408 )
409 .map_err(Into::into)
410 }
411
412 pub(crate) fn unpacked_data_with_unpack20(
413 &self,
414 archive: &Archive,
415 decoder: &mut Unpack20,
416 password: Option<&[u8]>,
417 ) -> Result<Vec<u8>> {
418 if self.is_stored() {
419 return self.stored_data_with_password(archive, password);
420 }
421 if self.unp_ver != 20 && self.unp_ver != 26 {
422 return Err(self.unsupported_compression());
423 }
424 let mut packed = self.packed_reader_for_decode(archive, password)?;
425 let mut out = Vec::new();
426 decoder
427 .decode_member_from_reader(
428 &mut packed,
429 usize::try_from(self.unp_size)
430 .map_err(|_| Error::InvalidHeader("RAR 2.0 unpacked size overflows usize"))?,
431 &mut out,
432 )
433 .map(|_| out)
434 .map_err(Into::into)
435 }
436
437 fn packed_data_for_decode(
438 &self,
439 archive: &Archive,
440 password: Option<&[u8]>,
441 ) -> Result<Vec<u8>> {
442 let mut reader = self.packed_reader_for_decode(archive, password)?;
443 let mut data = Vec::new();
444 reader.read_to_end(&mut data)?;
445 Ok(data)
446 }
447
448 pub(super) fn packed_reader_for_decode<'a>(
449 &self,
450 archive: &'a Archive,
451 password: Option<&[u8]>,
452 ) -> Result<Box<dyn Read + 'a>> {
453 let reader = archive.range_reader(self.packed_range.clone())?;
454 if !self.is_encrypted() {
455 return Ok(reader);
456 }
457 let Some(password) = password else {
458 return Err(Error::NeedPassword);
459 };
460 if (self.unp_ver == 20 || self.unp_ver == 26 || self.unp_ver >= 29)
461 && !self.packed_range.len().is_multiple_of(16)
462 {
463 return Err(Error::InvalidHeader(
464 "RAR encrypted payload is not block aligned",
465 ));
466 }
467 Ok(Box::new(DecryptingReader::new(
468 reader,
469 self.unp_ver,
470 password,
471 self.salt,
472 )?))
473 }
474
475 pub fn verify_crc32(&self, data: &[u8]) -> Result<()> {
476 let actual = crc32(data);
477 if actual == self.file_crc {
478 Ok(())
479 } else {
480 Err(Error::Crc32Mismatch {
481 expected: self.file_crc,
482 actual,
483 })
484 }
485 }
486
487 pub fn metadata(&self) -> ExtractedEntryMeta {
488 ExtractedEntryMeta {
489 name: self.name.clone(),
490 file_time: self.file_time,
491 attr: self.attr,
492 host_os: self.host_os,
493 is_directory: self.is_directory(),
494 }
495 }
496
497 pub fn write_to(
498 &self,
499 archive: &Archive,
500 password: Option<&[u8]>,
501 out: &mut impl Write,
502 ) -> Result<()> {
503 if self.is_directory() {
504 return Ok(());
505 }
506 let mut session = DecoderSession::new_with_password(false, password);
507 session.write_file_to(archive, self, out)
508 }
509
510 fn write_stored_to(
511 &self,
512 archive: &Archive,
513 password: Option<&[u8]>,
514 out: &mut impl Write,
515 ) -> Result<()> {
516 if !self.is_stored() {
517 return Err(self.unsupported_compression());
518 }
519 if !self.is_encrypted() && self.pack_size != self.unp_size {
520 return Err(Error::InvalidHeader(
521 "RAR 1.5 stored file has mismatched packed and unpacked sizes",
522 ));
523 }
524 let mut reader = self
525 .packed_reader_for_decode(archive, password)
526 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
527 let expected_len = usize::try_from(self.unp_size)
528 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?;
529 let mut crc = Crc32::new();
530 let mut crc_writer = CrcWriter {
531 inner: out,
532 crc: &mut crc,
533 };
534 let copied = std::io::copy(
535 &mut reader.by_ref().take(expected_len as u64),
536 &mut crc_writer,
537 )?;
538 if copied != expected_len as u64 {
539 return Err(self.map_encrypted_payload_error(
540 password,
541 Error::InvalidHeader("RAR 1.5 stored file ended before unpacked size"),
542 ));
543 }
544 let actual = crc.finish();
545 if actual == self.file_crc {
546 Ok(())
547 } else {
548 Err(self.map_encrypted_payload_error(
549 password,
550 Error::Crc32Mismatch {
551 expected: self.file_crc,
552 actual,
553 },
554 ))
555 }
556 }
557
558 fn map_encrypted_payload_error(&self, password: Option<&[u8]>, error: Error) -> Error {
559 if !self.is_encrypted() || password.is_none() {
560 return error;
561 }
562 match error {
563 Error::NeedPassword => Error::NeedPassword,
564 Error::UnsupportedSignature
565 | Error::UnsupportedVersion(_)
566 | Error::UnsupportedFeature { .. }
567 | Error::Rar50BufferedDecodeLimitExceeded { .. }
568 | Error::UnsupportedFamilyFeature { .. }
569 | Error::UnsupportedCompression { .. }
570 | Error::UnsupportedEncryption { .. }
571 | Error::TooShort
572 | Error::Io(_)
573 | Error::AtArchiveOffset { .. }
574 | Error::AtEntry { .. } => error,
575 Error::InvalidHeader(_)
576 | Error::Codec(_)
577 | Error::Rar3Recovery(_)
578 | Error::Rar5Recovery(_)
579 | Error::Rar20Crypto(_)
580 | Error::Rar30Crypto(_)
581 | Error::Rar50Crypto(_)
582 | Error::CrcMismatch { .. }
583 | Error::Crc32Mismatch { .. }
584 | Error::HashMismatch { .. }
585 | Error::WrongPasswordOrCorruptData => Error::WrongPasswordOrCorruptData,
586 }
587 }
588
589 fn entry_error(&self, operation: &'static str, error: Error) -> Error {
590 if matches!(
591 error,
592 Error::NeedPassword | Error::WrongPasswordOrCorruptData
593 ) {
594 return error;
595 }
596 error.at_entry(self.name.clone(), operation)
597 }
598
599 fn crc_result(&self, actual: u32, password: Option<&[u8]>) -> Result<()> {
600 if actual == self.file_crc {
601 Ok(())
602 } else {
603 Err(self.map_encrypted_payload_error(
604 password,
605 Error::Crc32Mismatch {
606 expected: self.file_crc,
607 actual,
608 },
609 ))
610 }
611 }
612
613 fn write_rar29_to(
614 &self,
615 archive: &Archive,
616 decoder: &mut Unpack29,
617 out: &mut impl Write,
618 ) -> Result<()> {
619 if self.is_stored() {
620 return self.write_stored_to(archive, None, out);
621 }
622 if self.is_encrypted() {
623 return Err(self.unsupported_encryption());
624 }
625 if self.unp_ver < 29 {
626 return Err(self.unsupported_compression());
627 }
628
629 let mut packed = archive.range_reader(self.packed_range.clone())?;
630 let mut crc = Crc32::new();
631 let mut crc_writer = CrcWriter {
632 inner: out,
633 crc: &mut crc,
634 };
635 decoder
636 .decode_member_from_reader(
637 &mut packed,
638 usize::try_from(self.unp_size)
639 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
640 &mut crc_writer,
641 )
642 .map_err(Error::from)?;
643 let actual = crc.finish();
644 if actual == self.file_crc {
645 Ok(())
646 } else {
647 Err(Error::Crc32Mismatch {
648 expected: self.file_crc,
649 actual,
650 })
651 }
652 }
653
654 fn write_unpack15_to(
655 &self,
656 archive: &Archive,
657 decoder: &mut Unpack15,
658 solid: bool,
659 password: Option<&[u8]>,
660 out: &mut impl Write,
661 ) -> Result<()> {
662 if self.is_stored() {
663 return self.write_stored_to(archive, password, out);
664 }
665 if self.unp_ver != 15 {
666 return Err(self.unsupported_compression());
667 }
668
669 let mut input = self
670 .packed_reader_for_decode(archive, password)
671 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
672 self.write_unpack15_decoded(decoder, solid, &mut input, out, password)
673 .map_err(|error| self.map_encrypted_payload_error(password, error))
674 }
675
676 fn write_unpack15_decoded(
677 &self,
678 decoder: &mut Unpack15,
679 solid: bool,
680 input: &mut impl Read,
681 out: &mut impl Write,
682 password: Option<&[u8]>,
683 ) -> Result<()> {
684 let mut crc = Crc32::new();
685 let mut crc_writer = CrcWriter {
686 inner: out,
687 crc: &mut crc,
688 };
689 decoder
690 .decode_member_from_reader(
691 input,
692 usize::try_from(self.unp_size)
693 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
694 solid,
695 &mut crc_writer,
696 )
697 .map_err(Error::from)?;
698 let actual = crc.finish();
699 self.crc_result(actual, password)
700 }
701
702 fn write_unpack20_to(
703 &self,
704 archive: &Archive,
705 decoder: &mut Unpack20,
706 password: Option<&[u8]>,
707 out: &mut impl Write,
708 ) -> Result<()> {
709 if self.is_stored() {
710 return self.write_stored_to(archive, password, out);
711 }
712 if self.unp_ver != 20 && self.unp_ver != 26 {
713 return Err(self.unsupported_compression());
714 }
715
716 let mut crc = Crc32::new();
717 let mut crc_writer = CrcWriter {
718 inner: out,
719 crc: &mut crc,
720 };
721 let target = usize::try_from(self.unp_size)
722 .map_err(|_| Error::InvalidHeader("RAR 2.0 unpacked size overflows usize"))?;
723 let mut packed = self
724 .packed_reader_for_decode(archive, password)
725 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
726 decoder
727 .decode_member_from_reader(&mut packed, target, &mut crc_writer)
728 .map_err(Error::from)
729 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
730 let actual = crc.finish();
731 self.crc_result(actual, password)
732 }
733
734 fn unsupported_compression(&self) -> Error {
735 Error::UnsupportedCompression {
736 family: "RAR 1.5-4.x",
737 unpack_version: self.unp_ver,
738 method: self.method,
739 }
740 }
741
742 fn unsupported_encryption(&self) -> Error {
743 Error::UnsupportedEncryption {
744 family: "RAR 1.5-4.x",
745 unpack_version: self.unp_ver,
746 }
747 }
748}
749
750impl NewSubHeader {
751 pub fn name_bytes(&self) -> &[u8] {
752 self.file.name_bytes()
753 }
754
755 pub fn name_lossy(&self) -> String {
759 self.file.name_lossy()
760 }
761}
762
763impl CommentHeader {
764 fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
765 archive.read_range(self.packed_range.clone())
766 }
767
768 fn unpacked_data(&self, archive: &Archive) -> Result<Vec<u8>> {
769 let target = usize::from(self.unp_size);
770 let data = if self.method == 0x30 {
771 let data = self.packed_data(archive)?;
772 if data.len() != target {
773 return Err(Error::InvalidHeader(
774 "RAR 1.5 stored comment has mismatched packed and unpacked sizes",
775 ));
776 }
777 data
778 } else if self.unp_ver == 15 {
779 Unpack15::default().decode_member(&self.packed_data(archive)?, target, false)?
780 } else {
781 return Err(Error::UnsupportedCompression {
782 family: "RAR 1.5 comment",
783 unpack_version: self.unp_ver,
784 method: self.method,
785 });
786 };
787 let actual = (crc32(&data) & 0xffff) as u16;
788 if actual == self.comment_crc {
789 Ok(data)
790 } else {
791 Err(Error::CrcMismatch {
792 expected: self.comment_crc,
793 actual,
794 })
795 }
796 }
797}
798
799impl Archive {
800 pub fn parse(input: &[u8]) -> Result<Self> {
801 Self::parse_with_options(input, crate::ArchiveReadOptions::default())
802 }
803
804 pub fn parse_owned(input: Vec<u8>) -> Result<Self> {
805 Self::parse_owned_with_options(input, crate::ArchiveReadOptions::default())
806 }
807
808 pub fn parse_with_options(
809 input: &[u8],
810 options: crate::ArchiveReadOptions<'_>,
811 ) -> Result<Self> {
812 let data: Arc<[u8]> = Arc::from(input.to_vec().into_boxed_slice());
813 Self::parse_shared(data, options.password)
814 }
815
816 pub fn parse_owned_with_options(
817 input: Vec<u8>,
818 options: crate::ArchiveReadOptions<'_>,
819 ) -> Result<Self> {
820 Self::parse_shared(Arc::from(input.into_boxed_slice()), options.password)
821 }
822
823 pub fn parse_with_password(input: &[u8], password: Option<&[u8]>) -> Result<Self> {
824 Self::parse_with_options(
825 input,
826 crate::ArchiveReadOptions::with_optional_password(password),
827 )
828 }
829
830 pub fn parse_owned_with_password(input: Vec<u8>, password: Option<&[u8]>) -> Result<Self> {
831 Self::parse_owned_with_options(
832 input,
833 crate::ArchiveReadOptions::with_optional_password(password),
834 )
835 }
836
837 pub fn parse_path(path: impl AsRef<Path>) -> Result<Self> {
838 Self::parse_path_with_options(path, crate::ArchiveReadOptions::default())
839 }
840
841 pub fn parse_path_with_options(
842 path: impl AsRef<Path>,
843 options: crate::ArchiveReadOptions<'_>,
844 ) -> Result<Self> {
845 Self::parse_path_with_password(path, options.password)
846 }
847
848 pub fn parse_path_with_password(
849 path: impl AsRef<Path>,
850 password: Option<&[u8]>,
851 ) -> Result<Self> {
852 let path = Arc::new(path.as_ref().to_path_buf());
853 let mut file = File::open(path.as_ref())?;
854 let len = file.metadata()?.len();
855 let scan_len = len.min(SFX_SCAN_LIMIT as u64) as usize;
856 let mut scan = vec![0; scan_len];
857 file.read_exact(&mut scan)?;
858 let sig = find_archive_start(&scan, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
859 if sig.family != ArchiveFamily::Rar15To40 {
860 return Err(Error::UnsupportedSignature);
861 }
862 Self::parse_seekable(file, len, sig.offset, ArchiveSource::File(path), password)
863 }
864
865 pub fn parse_path_with_signature(
866 path: impl AsRef<Path>,
867 signature: ArchiveSignature,
868 options: crate::ArchiveReadOptions<'_>,
869 ) -> Result<Self> {
870 Self::parse_path_with_signature_and_password(path, signature, options.password)
871 }
872
873 pub fn parse_path_with_signature_and_password(
874 path: impl AsRef<Path>,
875 signature: ArchiveSignature,
876 password: Option<&[u8]>,
877 ) -> Result<Self> {
878 if signature.family != ArchiveFamily::Rar15To40 {
879 return Err(Error::UnsupportedSignature);
880 }
881 let path = Arc::new(path.as_ref().to_path_buf());
882 let file = File::open(path.as_ref())?;
883 let len = file.metadata()?.len();
884 Self::parse_seekable(
885 file,
886 len,
887 signature.offset,
888 ArchiveSource::File(path),
889 password,
890 )
891 }
892
893 fn parse_shared(input: Arc<[u8]>, password: Option<&[u8]>) -> Result<Self> {
894 let sig = find_archive_start(&input, SFX_SCAN_LIMIT).ok_or(Error::UnsupportedSignature)?;
895 if sig.family != ArchiveFamily::Rar15To40 {
896 return Err(Error::UnsupportedSignature);
897 }
898
899 let archive = &input[sig.offset..];
900 if !archive.starts_with(RAR15_SIGNATURE) {
901 return Err(Error::UnsupportedSignature);
902 }
903
904 let marker = parse_block_header(archive, 0)?;
905 if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
906 return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
907 }
908
909 let main_block = parse_block_header(archive, marker.head_size as usize)?;
910 if main_block.head_type != MAIN_HEAD {
911 return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
912 }
913 let main = parse_main_header(archive, &main_block)?;
914 let mut pos = main_block.offset + main_block.head_size as usize;
915 let mut blocks = Vec::new();
916 let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
917
918 while pos < archive.len() {
919 if archive.len() - pos < 7 {
920 break;
921 }
922 let (block, header, total) = if main.has_encrypted_headers() {
923 let password = password.ok_or(Error::NeedPassword)?;
924 let encrypted = decrypt_encrypted_header_at(
925 archive,
926 pos,
927 password,
928 &mut encrypted_header_ciphers,
929 )?;
930 (encrypted.block, encrypted.header, encrypted.total_size)
931 } else {
932 let block = parse_block_header(archive, pos)?;
933 let total = block_total_size(&block)?;
934 let header = archive[pos..pos + block.head_size as usize].to_vec();
935 (block, header, total)
936 };
937 match block.head_type {
938 FILE_HEAD => {
939 let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
940 let total = file_block_total_size(&block, total, file.pack_size)?;
941 let next = checked_block_next(&block, total, archive.len())?;
942 file.block.offset = block.offset;
943 file.packed_range =
944 packed_range(sig.offset, block.offset, total, file.pack_size)?;
945 blocks.push(Block::File(file));
946 pos = next;
947 }
948 NEWSUB_HEAD => {
949 let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
950 let total = file_block_total_size(&block, total, file.pack_size)?;
951 let next = checked_block_next(&block, total, archive.len())?;
952 file.block.offset = block.offset;
953 file.packed_range =
954 packed_range(sig.offset, block.offset, total, file.pack_size)?;
955 let kind = classify_new_sub(&file.name);
956 blocks.push(Block::NewSub(NewSubHeader { file, kind }));
957 pos = next;
958 }
959 COMM_HEAD => {
960 let next = checked_block_next(&block, total, archive.len())?;
961 let mut comment = parse_comment_header(&header, relative_block(&block))?;
962 comment.block.offset = block.offset;
963 comment.packed_range =
964 sig.offset + block.offset + 13..sig.offset + block.offset + total;
965 blocks.push(Block::Comment(comment));
966 pos = next;
967 }
968 PROTECT_HEAD => {
969 let next = checked_block_next(&block, total, archive.len())?;
970 let protect = parse_protect_header(&header, &block, sig.offset, total)?;
971 blocks.push(Block::Protect(protect));
972 pos = next;
973 }
974 ENDARC_HEAD => {
975 let _next = checked_block_next(&block, total, archive.len())?;
976 blocks.push(Block::End(block));
977 break;
978 }
979 _ => {
980 let next = checked_block_next(&block, total, archive.len())?;
981 blocks.push(Block::Unknown(block));
982 pos = next;
983 }
984 }
985 }
986
987 Ok(Self {
988 sfx_offset: sig.offset,
989 main,
990 blocks,
991 source: ArchiveSource::Memory(input),
992 })
993 }
994
995 fn parse_seekable(
996 mut file: File,
997 file_len: u64,
998 sfx_offset: usize,
999 source: ArchiveSource,
1000 password: Option<&[u8]>,
1001 ) -> Result<Self> {
1002 let marker = read_block_header_at(&mut file, file_len, sfx_offset, 0)?;
1003 if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
1004 return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
1005 }
1006
1007 let main_block =
1008 read_block_header_at(&mut file, file_len, sfx_offset, marker.head_size as usize)?;
1009 if main_block.head_type != MAIN_HEAD {
1010 return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
1011 }
1012 let main_header = read_exact_at(
1013 &mut file,
1014 sfx_offset + main_block.offset,
1015 main_block.head_size as usize,
1016 )?;
1017 let main = parse_main_header(&main_header, &relative_block(&main_block))?;
1018 let mut pos = main_block.offset + main_block.head_size as usize;
1019 let mut blocks = Vec::new();
1020 let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
1021
1022 while (sfx_offset + pos) as u64 + 7 <= file_len {
1023 let (block, header, total) = if main.has_encrypted_headers() {
1024 let password = password.ok_or(Error::NeedPassword)?;
1025 let encrypted = read_encrypted_header_at(
1026 &mut file,
1027 file_len,
1028 sfx_offset,
1029 pos,
1030 password,
1031 &mut encrypted_header_ciphers,
1032 )?;
1033 (encrypted.block, encrypted.header, encrypted.total_size)
1034 } else {
1035 let block = read_block_header_at(&mut file, file_len, sfx_offset, pos)?;
1036 let total = block_total_size(&block)?;
1037 let header = read_exact_at(&mut file, sfx_offset + pos, block.head_size as usize)?;
1038 (block, header, total)
1039 };
1040 match block.head_type {
1041 FILE_HEAD => {
1042 let mut file_header =
1043 parse_file_like_header(&header, relative_block(&block), 0)?;
1044 let total = file_block_total_size(&block, total, file_header.pack_size)?;
1045 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1046 file_header.block.offset = block.offset;
1047 file_header.packed_range =
1048 packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1049 blocks.push(Block::File(file_header));
1050 pos = next;
1051 }
1052 NEWSUB_HEAD => {
1053 let mut file_header =
1054 parse_file_like_header(&header, relative_block(&block), 0)?;
1055 let total = file_block_total_size(&block, total, file_header.pack_size)?;
1056 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1057 file_header.block.offset = block.offset;
1058 file_header.packed_range =
1059 packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1060 let kind = classify_new_sub(&file_header.name);
1061 blocks.push(Block::NewSub(NewSubHeader {
1062 file: file_header,
1063 kind,
1064 }));
1065 pos = next;
1066 }
1067 COMM_HEAD => {
1068 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1069 let mut comment = parse_comment_header(&header, relative_block(&block))?;
1070 comment.block.offset = block.offset;
1071 comment.packed_range =
1072 sfx_offset + block.offset + 13..sfx_offset + block.offset + total;
1073 blocks.push(Block::Comment(comment));
1074 pos = next;
1075 }
1076 PROTECT_HEAD => {
1077 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1078 let protect = parse_protect_header(&header, &block, sfx_offset, total)?;
1079 blocks.push(Block::Protect(protect));
1080 pos = next;
1081 }
1082 ENDARC_HEAD => {
1083 let _next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1084 blocks.push(Block::End(block));
1085 break;
1086 }
1087 _ => {
1088 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1089 blocks.push(Block::Unknown(block));
1090 pos = next;
1091 }
1092 }
1093 }
1094
1095 Ok(Self {
1096 sfx_offset,
1097 main,
1098 blocks,
1099 source,
1100 })
1101 }
1102
1103 fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>> {
1104 self.source.read_range(range)
1105 }
1106
1107 fn copy_range_to(&self, range: Range<usize>, out: &mut impl Write) -> Result<()> {
1108 self.source.copy_range_to(range, out)
1109 }
1110
1111 fn range_reader(&self, range: Range<usize>) -> Result<Box<dyn Read + '_>> {
1112 self.source.range_reader(range)
1113 }
1114
1115 pub fn files(&self) -> impl Iterator<Item = &FileHeader> {
1116 self.blocks.iter().filter_map(|block| match block {
1117 Block::File(file) => Some(file),
1118 _ => None,
1119 })
1120 }
1121
1122 pub fn new_subs(&self) -> impl Iterator<Item = &NewSubHeader> {
1123 self.blocks.iter().filter_map(|block| match block {
1124 Block::NewSub(sub) => Some(sub),
1125 _ => None,
1126 })
1127 }
1128
1129 pub fn protect_records(&self) -> impl Iterator<Item = &ProtectHeader> {
1130 self.blocks.iter().filter_map(|block| match block {
1131 Block::Protect(protect) => Some(protect),
1132 _ => None,
1133 })
1134 }
1135
1136 fn source_bytes(&self) -> Result<Vec<u8>> {
1137 self.source.bytes()
1138 }
1139
1140 pub fn repair_protect_head(&self) -> Result<Vec<u8>> {
1141 if let Some(recovery) = self
1142 .new_subs()
1143 .find(|sub| sub.kind == NewSubKind::RecoveryRecord)
1144 {
1145 return repair_newsub_recovery_bytes(
1146 &self.source_bytes()?,
1147 self.sfx_offset,
1148 self,
1149 recovery,
1150 );
1151 }
1152 let protect = self.protect_records().next().ok_or(Error::InvalidHeader(
1153 "RAR 2.x archive does not contain a PROTECT_HEAD recovery record",
1154 ))?;
1155 repair_protect_head_bytes(&self.source_bytes()?, self.sfx_offset, protect)
1156 }
1157
1158 pub fn extract_to<F>(&self, options: crate::ArchiveReadOptions<'_>, mut open: F) -> Result<()>
1160 where
1161 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1162 {
1163 let password = options.password;
1164 let mut session = DecoderSession::new_with_password(self.main.is_solid(), password);
1165 for file in self.files() {
1166 if file.is_split_before() || file.is_split_after() {
1167 return Err(Error::InvalidHeader(
1168 "RAR 1.5 split entry requires multivolume extraction",
1169 ));
1170 }
1171 let meta = file.metadata();
1172 if meta.is_directory {
1173 let _ = open(&meta)?;
1174 continue;
1175 }
1176 let mut writer = open(&meta)?;
1177 if file.is_stored() {
1178 file.write_stored_to(self, password, &mut writer)
1179 .map_err(|error| file.entry_error("extracting", error))?;
1180 } else {
1181 session
1182 .write_file_to(self, file, &mut writer)
1183 .map_err(|error| file.entry_error("extracting", error))?;
1184 }
1185 }
1186 Ok(())
1187 }
1188
1189 #[cfg(feature = "parallel")]
1190 pub fn extract_to_parallel_buffered<F>(
1191 &self,
1192 options: crate::ArchiveReadOptions<'_>,
1193 mut open: F,
1194 ) -> Result<()>
1195 where
1196 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1197 {
1198 if self.main.is_solid()
1199 || self
1200 .files()
1201 .any(|file| file.is_solid() || file.is_split_before() || file.is_split_after())
1202 {
1203 return self.extract_to(options, open);
1204 }
1205
1206 let password = options.password;
1207 let files: Vec<_> = self.files().collect();
1208 if files.len() < 2 {
1209 return self.extract_to(options, open);
1210 }
1211 let entries = crate::parallel::map_collect(files, |file| {
1212 decode_parallel_entry(self, file, password)
1213 })?;
1214 for entry in entries {
1215 write_parallel_entry(entry, &mut open)?;
1216 }
1217 Ok(())
1218 }
1219
1220 pub fn archive_comment(&self) -> Result<Option<Vec<u8>>> {
1221 if let Some(comment) = self.blocks.iter().find_map(|block| match block {
1222 Block::Comment(comment) => Some(comment),
1223 _ => None,
1224 }) {
1225 return comment.unpacked_data(self).map(Some);
1226 }
1227
1228 let Some(comment) = self
1229 .new_subs()
1230 .find(|sub| sub.kind == NewSubKind::ArchiveComment)
1231 else {
1232 return Ok(None);
1233 };
1234 let data = comment.file.unpacked_data(self)?;
1235 comment.file.verify_crc32(&data)?;
1236 Ok(Some(data))
1237 }
1238}
1239
1240#[cfg(feature = "parallel")]
1241enum ParallelExtractedEntry {
1242 Directory(ExtractedEntryMeta),
1243 File {
1244 meta: ExtractedEntryMeta,
1245 data: Vec<u8>,
1246 },
1247}
1248
1249#[cfg(feature = "parallel")]
1250fn decode_parallel_entry(
1251 archive: &Archive,
1252 file: &FileHeader,
1253 password: Option<&[u8]>,
1254) -> Result<ParallelExtractedEntry> {
1255 if file.is_split_before() || file.is_split_after() {
1256 return Err(Error::InvalidHeader(
1257 "RAR 1.5 split entry requires multivolume extraction",
1258 ));
1259 }
1260 let meta = file.metadata();
1261 if meta.is_directory {
1262 return Ok(ParallelExtractedEntry::Directory(meta));
1263 }
1264 let mut data = Vec::new();
1265 if file.is_stored() {
1266 file.write_stored_to(archive, password, &mut data)
1267 .map_err(|error| file.entry_error("extracting", error))?;
1268 } else {
1269 let mut session = DecoderSession::new_with_password(false, password);
1270 session
1271 .write_file_to(archive, file, &mut data)
1272 .map_err(|error| file.entry_error("extracting", error))?;
1273 }
1274 Ok(ParallelExtractedEntry::File { meta, data })
1275}
1276
1277#[cfg(feature = "parallel")]
1278fn write_parallel_entry<F>(entry: ParallelExtractedEntry, open: &mut F) -> Result<()>
1279where
1280 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1281{
1282 match entry {
1283 ParallelExtractedEntry::Directory(meta) => {
1284 let _ = open(&meta)?;
1285 }
1286 ParallelExtractedEntry::File { meta, data } => {
1287 let mut writer = open(&meta)?;
1288 writer.write_all(&data)?;
1289 }
1290 }
1291 Ok(())
1292}
1293
1294fn classify_new_sub(name: &[u8]) -> NewSubKind {
1295 match name {
1296 b"CMT" => NewSubKind::ArchiveComment,
1297 b"RR" => NewSubKind::RecoveryRecord,
1298 _ => NewSubKind::Unknown(name.to_vec()),
1299 }
1300}
1301
1302fn parse_main_header(input: &[u8], block: &BlockHeader) -> Result<MainHeader> {
1303 if block.head_size < 13 {
1304 return Err(Error::InvalidHeader("RAR 1.5 main header is too short"));
1305 }
1306 let start = block.offset;
1307 let head_end = start + block.head_size as usize;
1308 if head_end > input.len() {
1309 return Err(Error::TooShort);
1310 }
1311
1312 let encrypt_version = if block.flags & MHD_ENCRYPTVER != 0 {
1313 Some(*input.get(start + 13).ok_or(Error::TooShort)?)
1314 } else {
1315 None
1316 };
1317
1318 Ok(MainHeader {
1319 head_crc: block.head_crc,
1320 flags: block.flags,
1321 head_size: block.head_size,
1322 reserved1: read_u16(input, start + 7)?,
1323 reserved2: read_u32(input, start + 9)?,
1324 encrypt_version,
1325 })
1326}
1327
1328fn parse_comment_header(input: &[u8], block: BlockHeader) -> Result<CommentHeader> {
1329 if block.head_size < 13 {
1330 return Err(Error::InvalidHeader("RAR 1.5 comment header is too short"));
1331 }
1332 let start = block.offset;
1333 Ok(CommentHeader {
1334 block,
1335 unp_size: read_u16(input, start + 7)?,
1336 unp_ver: *input.get(start + 9).ok_or(Error::TooShort)?,
1337 method: *input.get(start + 10).ok_or(Error::TooShort)?,
1338 comment_crc: read_u16(input, start + 11)?,
1339 packed_range: 0..0,
1340 })
1341}
1342
1343fn parse_protect_header(
1344 input: &[u8],
1345 block: &BlockHeader,
1346 archive_offset: usize,
1347 total_size: usize,
1348) -> Result<ProtectHeader> {
1349 if block.head_size != 26 {
1350 return Err(Error::InvalidHeader(
1351 "RAR 2.x recovery header size is invalid",
1352 ));
1353 }
1354 let add_size = block.add_size.ok_or(Error::InvalidHeader(
1355 "RAR 2.x recovery header is missing data size",
1356 ))?;
1357 let rec_sectors = read_u16(input, 12)?;
1358 let total_blocks = read_u32(input, 14)?;
1359 let expected_add_size = u64::from(total_blocks)
1360 .checked_mul(2)
1361 .and_then(|size| size.checked_add(u64::from(rec_sectors) * 512))
1362 .ok_or(Error::InvalidHeader("RAR 2.x recovery data size overflows"))?;
1363 if add_size != expected_add_size {
1364 return Err(Error::InvalidHeader(
1365 "RAR 2.x recovery data size does not match header",
1366 ));
1367 }
1368 let mark: [u8; 8] = input
1369 .get(18..26)
1370 .ok_or(Error::TooShort)?
1371 .try_into()
1372 .expect("RAR protect mark size");
1373 let data_start = archive_offset
1374 .checked_add(block.offset)
1375 .and_then(|offset| offset.checked_add(block.head_size as usize))
1376 .ok_or(Error::InvalidHeader(
1377 "RAR 2.x recovery data range overflows",
1378 ))?;
1379 let data_end = archive_offset
1380 .checked_add(block.offset)
1381 .and_then(|offset| offset.checked_add(total_size))
1382 .ok_or(Error::InvalidHeader(
1383 "RAR 2.x recovery data range overflows",
1384 ))?;
1385 Ok(ProtectHeader {
1386 block: block.clone(),
1387 version: *input.get(11).ok_or(Error::TooShort)?,
1388 rec_sectors,
1389 total_blocks,
1390 mark,
1391 data_range: data_start..data_end,
1392 })
1393}
1394
1395fn repair_protect_head_bytes(
1396 source: &[u8],
1397 sfx_offset: usize,
1398 protect: &ProtectHeader,
1399) -> Result<Vec<u8>> {
1400 if protect.rec_sectors == 0 {
1401 return Err(Error::InvalidHeader(
1402 "RAR 2.x recovery record has no parity sectors",
1403 ));
1404 }
1405 if protect.mark != *b"Protect!" {
1406 return Err(Error::InvalidHeader("RAR 2.x recovery mark is invalid"));
1407 }
1408 let protected_start = sfx_offset;
1409 let protected_len = usize::try_from(protect.total_blocks)
1410 .ok()
1411 .and_then(|blocks| blocks.checked_mul(512))
1412 .ok_or(Error::InvalidHeader(
1413 "RAR 2.x protected sector size overflows",
1414 ))?;
1415 let protected_end = protected_start
1416 .checked_add(protected_len)
1417 .ok_or(Error::InvalidHeader(
1418 "RAR 2.x protected sector range overflows",
1419 ))?;
1420 if protected_end > source.len() {
1421 return Err(Error::InvalidHeader(
1422 "RAR 2.x protected sector range is invalid",
1423 ));
1424 }
1425 let recovery_data = source
1426 .get(protect.data_range.clone())
1427 .ok_or(Error::TooShort)?;
1428 let declared_blocks = usize::try_from(protect.total_blocks).map_err(|_| {
1429 Error::InvalidHeader("RAR 2.x recovery protected sector count overflows usize")
1430 })?;
1431 let tag_len = declared_blocks
1432 .checked_mul(2)
1433 .ok_or(Error::InvalidHeader("RAR 2.x recovery tag size overflows"))?;
1434 let parity_len =
1435 usize::from(protect.rec_sectors)
1436 .checked_mul(512)
1437 .ok_or(Error::InvalidHeader(
1438 "RAR 2.x recovery parity size overflows",
1439 ))?;
1440 if recovery_data.len() != tag_len + parity_len {
1441 return Err(Error::InvalidHeader(
1442 "RAR 2.x recovery data size is invalid",
1443 ));
1444 }
1445 let tags = &recovery_data[..tag_len];
1446 let parity = &recovery_data[tag_len..];
1447 let repairable_blocks = declared_blocks.min(protect.block.offset / 512);
1451
1452 let mut damaged = Vec::new();
1453 for index in 0..repairable_blocks {
1454 let sector_start = protected_start + index * 512;
1455 let sector = &source[sector_start..sector_start + 512];
1456 let actual = (!crc32(sector) & 0xffff) as u16;
1457 let expected = read_u16(tags, index * 2)?;
1458 if actual != expected {
1459 damaged.push(index);
1460 }
1461 }
1462 if damaged.is_empty() {
1463 return Ok(source.to_vec());
1464 }
1465 if damaged.len() > usize::from(protect.rec_sectors) {
1466 return Err(Error::InvalidHeader(
1467 "RAR 2.x recovery damage exceeds parity sector count",
1468 ));
1469 }
1470
1471 let mut used_slots = vec![false; usize::from(protect.rec_sectors)];
1472 for &index in &damaged {
1473 let slot = index % usize::from(protect.rec_sectors);
1474 if used_slots[slot] {
1475 return Err(Error::InvalidHeader(
1476 "RAR 2.x recovery cannot repair multiple sectors in the same parity group",
1477 ));
1478 }
1479 used_slots[slot] = true;
1480 }
1481
1482 let mut repaired = source.to_vec();
1483 for &missing_index in &damaged {
1484 let slot = missing_index % usize::from(protect.rec_sectors);
1485 let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1486 for index in (slot..repairable_blocks).step_by(usize::from(protect.rec_sectors)) {
1487 if index == missing_index {
1488 continue;
1489 }
1490 let sector_start = protected_start + index * 512;
1491 for (out, byte) in sector
1492 .iter_mut()
1493 .zip(&repaired[sector_start..sector_start + 512])
1494 {
1495 *out ^= *byte;
1496 }
1497 }
1498 let sector_start = protected_start + missing_index * 512;
1499 repaired[sector_start..sector_start + 512].copy_from_slice(§or);
1500 let actual = (!crc32(§or) & 0xffff) as u16;
1501 let expected = u16::from_le_bytes(
1502 tags[missing_index * 2..missing_index * 2 + 2]
1503 .try_into()
1504 .unwrap(),
1505 );
1506 if actual != expected {
1507 return Err(Error::CrcMismatch { expected, actual });
1508 }
1509 }
1510 Ok(repaired)
1511}
1512
1513fn repair_newsub_recovery_bytes(
1514 source: &[u8],
1515 sfx_offset: usize,
1516 archive: &Archive,
1517 recovery: &NewSubHeader,
1518) -> Result<Vec<u8>> {
1519 let recovery_data = newsub_recovery_data(archive, recovery)?;
1520 let expected_unpacked = usize::try_from(recovery.file.unp_size)
1521 .map_err(|_| Error::InvalidHeader("RAR 3.x recovery unpacked size overflows usize"))?;
1522 if recovery_data.len() != expected_unpacked {
1523 return Err(Error::InvalidHeader(
1524 "RAR 3.x recovery data size does not match unpacked size",
1525 ));
1526 }
1527 let protected_start = sfx_offset;
1528 let protected_end =
1529 sfx_offset
1530 .checked_add(recovery.file.block.offset)
1531 .ok_or(Error::InvalidHeader(
1532 "RAR 3.x recovery protected range overflows",
1533 ))?;
1534 if protected_end > source.len() || protected_start > protected_end {
1535 return Err(Error::InvalidHeader(
1536 "RAR 3.x recovery protected range is invalid",
1537 ));
1538 }
1539 let protected_len = protected_end - protected_start;
1540 let protected_sectors = protected_len.div_ceil(512);
1541 if protected_sectors == 0 {
1542 return Err(Error::InvalidHeader(
1543 "RAR 3.x recovery record has no protected sectors",
1544 ));
1545 }
1546 let tag_len = protected_sectors
1547 .checked_mul(2)
1548 .ok_or(Error::InvalidHeader("RAR 3.x recovery tag size overflows"))?;
1549 if recovery_data.len() <= tag_len || !(recovery_data.len() - tag_len).is_multiple_of(512) {
1550 return Err(Error::InvalidHeader(
1551 "RAR 3.x recovery data size is invalid",
1552 ));
1553 }
1554 let parity_sectors = (recovery_data.len() - tag_len) / 512;
1555 if parity_sectors == 0 {
1556 return Err(Error::InvalidHeader(
1557 "RAR 3.x recovery record has no parity sectors",
1558 ));
1559 }
1560 let tags = &recovery_data[..tag_len];
1561 let parity = &recovery_data[tag_len..];
1562
1563 let mut damaged = Vec::new();
1564 for index in 0..protected_sectors {
1565 let sector = protected_sector(source, protected_start, protected_len, index)?;
1566 let actual = (!crc32(§or) & 0xffff) as u16;
1567 let expected = read_u16(tags, index * 2)?;
1568 if actual != expected {
1569 damaged.push(index);
1570 }
1571 }
1572 if damaged.is_empty() {
1573 return Ok(source.to_vec());
1574 }
1575 if damaged.len() > parity_sectors {
1576 return Err(Error::InvalidHeader(
1577 "RAR 3.x recovery damage exceeds parity sector count",
1578 ));
1579 }
1580
1581 let mut used_slots = vec![false; parity_sectors];
1582 for &index in &damaged {
1583 let slot = index % parity_sectors;
1584 if used_slots[slot] {
1585 return Err(Error::InvalidHeader(
1586 "RAR 3.x recovery cannot repair multiple sectors in the same parity group",
1587 ));
1588 }
1589 used_slots[slot] = true;
1590 }
1591
1592 let mut repaired = source.to_vec();
1593 for &missing_index in &damaged {
1594 let slot = missing_index % parity_sectors;
1595 let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1596 for index in (slot..protected_sectors).step_by(parity_sectors) {
1597 if index == missing_index {
1598 continue;
1599 }
1600 let other = protected_sector(&repaired, protected_start, protected_len, index)?;
1601 for (out, byte) in sector.iter_mut().zip(other) {
1602 *out ^= byte;
1603 }
1604 }
1605 let actual = (!crc32(§or) & 0xffff) as u16;
1606 let expected = u16::from_le_bytes(
1607 tags[missing_index * 2..missing_index * 2 + 2]
1608 .try_into()
1609 .unwrap(),
1610 );
1611 if actual != expected {
1612 return Err(Error::CrcMismatch { expected, actual });
1613 }
1614 let sector_start = protected_start + missing_index * 512;
1615 let write_len = 512.min(protected_end - sector_start);
1616 repaired[sector_start..sector_start + write_len].copy_from_slice(§or[..write_len]);
1617 }
1618
1619 Ok(repaired)
1620}
1621
1622fn newsub_recovery_data(archive: &Archive, recovery: &NewSubHeader) -> Result<Vec<u8>> {
1623 if recovery.file.is_encrypted() {
1624 return Err(Error::UnsupportedFeature {
1625 version: ArchiveVersion::Rar30,
1626 feature: "encrypted RAR 3.x NEWSUB recovery record",
1627 });
1628 }
1629 if recovery.file.method == 0x30 {
1630 if recovery.file.pack_size != recovery.file.unp_size {
1631 return Err(Error::InvalidHeader(
1632 "RAR 3.x recovery record packed size does not match unpacked size",
1633 ));
1634 }
1635 return recovery.file.stored_data(archive);
1636 }
1637 let mut session = DecoderSession::new(false);
1638 session.decode_file_data(archive, &recovery.file)
1639}
1640
1641fn protected_sector(
1642 source: &[u8],
1643 protected_start: usize,
1644 protected_len: usize,
1645 index: usize,
1646) -> Result<[u8; 512]> {
1647 let sector_offset = index.checked_mul(512).ok_or(Error::InvalidHeader(
1648 "RAR 3.x recovery sector offset overflows",
1649 ))?;
1650 if sector_offset >= protected_len {
1651 return Err(Error::InvalidHeader(
1652 "RAR 3.x recovery sector offset is invalid",
1653 ));
1654 }
1655 let sector_start = protected_start
1656 .checked_add(sector_offset)
1657 .ok_or(Error::InvalidHeader(
1658 "RAR 3.x recovery sector range overflows",
1659 ))?;
1660 let available = 512.min(protected_len - sector_offset);
1661 let mut sector = [0u8; 512];
1662 sector[..available].copy_from_slice(
1663 source
1664 .get(sector_start..sector_start + available)
1665 .ok_or(Error::TooShort)?,
1666 );
1667 Ok(sector)
1668}
1669
1670pub fn repair_rev3_volumes_to<F>(
1671 data_volumes: &[Option<&[u8]>],
1672 recovery_count: usize,
1673 recovery_volumes: &[(usize, &[u8])],
1674 mut write: F,
1675) -> Result<()>
1676where
1677 F: FnMut(usize, &[u8]) -> Result<()>,
1678{
1679 for (index, bytes) in rars_recovery::rar3::reconstruct_data_volumes(
1680 data_volumes,
1681 recovery_count,
1682 recovery_volumes,
1683 )
1684 .map_err(Error::from)?
1685 .into_iter()
1686 .enumerate()
1687 {
1688 let bytes = truncate_repaired_rev3_volume(bytes)?;
1689 write(index, &bytes)?;
1690 }
1691 Ok(())
1692}
1693
1694fn truncate_repaired_rev3_volume(mut bytes: Vec<u8>) -> Result<Vec<u8>> {
1695 let Ok(archive) = Archive::parse(&bytes) else {
1696 return Ok(bytes);
1697 };
1698 let Some(end) = archive.blocks.iter().find_map(|block| match block {
1699 Block::End(end) => Some(end),
1700 _ => None,
1701 }) else {
1702 return Ok(bytes);
1703 };
1704 let end_pos = archive
1705 .sfx_offset
1706 .checked_add(end.offset)
1707 .and_then(|offset| offset.checked_add(block_total_size(end).ok()?))
1708 .ok_or(Error::InvalidHeader(
1709 "RAR 3 repaired volume end offset overflows",
1710 ))?;
1711 if end_pos < bytes.len() && bytes[end_pos..].iter().all(|&byte| byte == 0) {
1712 bytes.truncate(end_pos);
1713 }
1714 Ok(bytes)
1715}
1716
1717struct EncryptedHeader {
1718 block: BlockHeader,
1719 header: Vec<u8>,
1720 total_size: usize,
1721}
1722
1723#[derive(Default)]
1724struct EncryptedHeaderCipherCache {
1725 salt: Option<[u8; 8]>,
1726 cipher: Option<Rar30Cipher>,
1727}
1728
1729impl EncryptedHeaderCipherCache {
1730 fn cipher(&mut self, password: &[u8], salt: [u8; 8]) -> Result<Rar30Cipher> {
1731 if self.salt != Some(salt) {
1732 self.salt = Some(salt);
1733 self.cipher =
1734 Some(Rar30Cipher::new(password, Some(salt)).map_err(map_rar30_crypto_error)?);
1735 }
1736 Ok(self
1737 .cipher
1738 .as_ref()
1739 .expect("RAR 3 encrypted header cipher cache initialized")
1740 .clone())
1741 }
1742}
1743
1744fn map_rar30_crypto_error(error: Rar30Error) -> Error {
1745 Error::from(error)
1746}
1747
1748fn decrypt_encrypted_header_at(
1749 archive: &[u8],
1750 offset: usize,
1751 password: &[u8],
1752 cipher_cache: &mut EncryptedHeaderCipherCache,
1753) -> Result<EncryptedHeader> {
1754 let salt = read_header_salt(archive, offset)?;
1755 let first_ciphertext = archive
1756 .get(offset + 8..offset + 24)
1757 .ok_or(Error::TooShort)?;
1758 let mut cipher = cipher_cache.cipher(password, salt)?;
1759 let mut first_block = [0u8; 16];
1760 first_block.copy_from_slice(first_ciphertext);
1761 cipher
1762 .decrypt_in_place(&mut first_block)
1763 .map_err(map_rar30_crypto_error)?;
1764 let head_size = read_u16(&first_block, 5)? as usize;
1765 if head_size < 7 {
1766 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1767 }
1768 let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1769 let encrypted_start = offset
1770 .checked_add(8)
1771 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1772 let encrypted_end = encrypted_start
1773 .checked_add(encrypted_header_size)
1774 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1775 let encrypted_rest = archive
1776 .get(offset + 24..encrypted_end)
1777 .ok_or(Error::TooShort)?;
1778 let mut header = Vec::with_capacity(encrypted_header_size);
1779 header.extend_from_slice(&first_block);
1780 header.extend_from_slice(encrypted_rest);
1781 cipher
1782 .decrypt_in_place(&mut header[16..])
1783 .map_err(map_rar30_crypto_error)?;
1784 header.truncate(head_size);
1785
1786 let mut block = parse_block_header(&header, 0)?;
1787 block.offset = offset;
1788 let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1789 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1790 let total_size = 8usize
1791 .checked_add(encrypted_header_size)
1792 .and_then(|size| size.checked_add(payload_size))
1793 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1794 Ok(EncryptedHeader {
1795 block,
1796 header,
1797 total_size,
1798 })
1799}
1800
1801fn read_encrypted_header_at(
1802 file: &mut File,
1803 file_len: u64,
1804 archive_offset: usize,
1805 offset: usize,
1806 password: &[u8],
1807 cipher_cache: &mut EncryptedHeaderCipherCache,
1808) -> Result<EncryptedHeader> {
1809 let absolute = archive_offset
1810 .checked_add(offset)
1811 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1812 if absolute as u64 + 24 > file_len {
1813 return Err(Error::TooShort);
1814 }
1815 let first = read_exact_at(file, absolute, 24)?;
1816 let salt = read_header_salt(&first, 0)?;
1817 let mut cipher = cipher_cache.cipher(password, salt)?;
1818 let mut first_block = [0u8; 16];
1819 first_block.copy_from_slice(&first[8..24]);
1820 cipher
1821 .decrypt_in_place(&mut first_block)
1822 .map_err(map_rar30_crypto_error)?;
1823 let head_size = read_u16(&first_block, 5)? as usize;
1824 if head_size < 7 {
1825 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1826 }
1827 let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1828 let encrypted_start = absolute
1829 .checked_add(8)
1830 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1831 if encrypted_start as u64 + encrypted_header_size as u64 > file_len {
1832 return Err(Error::TooShort);
1833 }
1834 let encrypted_rest = read_exact_at(file, encrypted_start + 16, encrypted_header_size - 16)?;
1835 let mut header = Vec::with_capacity(encrypted_header_size);
1836 header.extend_from_slice(&first_block);
1837 header.extend_from_slice(&encrypted_rest);
1838 cipher
1839 .decrypt_in_place(&mut header[16..])
1840 .map_err(map_rar30_crypto_error)?;
1841 header.truncate(head_size);
1842
1843 let mut block = parse_block_header(&header, 0)?;
1844 block.offset = offset;
1845 let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1846 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1847 let total_size = 8usize
1848 .checked_add(encrypted_header_size)
1849 .and_then(|size| size.checked_add(payload_size))
1850 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1851 Ok(EncryptedHeader {
1852 block,
1853 header,
1854 total_size,
1855 })
1856}
1857
1858fn read_header_salt(input: &[u8], offset: usize) -> Result<[u8; 8]> {
1859 input
1860 .get(offset..offset + 8)
1861 .ok_or(Error::TooShort)
1862 .map(|salt| salt.try_into().expect("RAR 3 salt size"))
1863}
1864
1865fn parse_file_like_header(
1866 input: &[u8],
1867 block: BlockHeader,
1868 archive_offset: usize,
1869) -> Result<FileHeader> {
1870 if block.head_size < 32 {
1871 return Err(Error::InvalidHeader("RAR 1.5 file header is too short"));
1872 }
1873 if block.flags & LONG_BLOCK == 0 {
1874 return Err(Error::InvalidHeader(
1875 "RAR 1.5 file header is missing packed data size",
1876 ));
1877 }
1878
1879 let start = block.offset;
1880 let head_end = start + block.head_size as usize;
1881 if head_end > input.len() {
1882 return Err(Error::TooShort);
1883 }
1884
1885 let pack_low = read_u32(input, start + 7)? as u64;
1886 let unp_low = read_u32(input, start + 11)? as u64;
1887 let host_os = input[start + 15];
1888 let file_crc = read_u32(input, start + 16)?;
1889 let file_time = read_u32(input, start + 20)?;
1890 let unp_ver = input[start + 24];
1891 let method = input[start + 25];
1892 let name_size = read_u16(input, start + 26)? as usize;
1893 let attr = read_u32(input, start + 28)?;
1894 let mut pos = start + 32;
1895
1896 let (pack_size, unp_size) = if block.flags & FHD_LARGE != 0 {
1897 let high_pack = read_u32(input, pos)? as u64;
1898 let high_unp = read_u32(input, pos + 4)? as u64;
1899 pos += 8;
1900 ((high_pack << 32) | pack_low, (high_unp << 32) | unp_low)
1901 } else {
1902 (pack_low, unp_low)
1903 };
1904
1905 let name_end = pos
1906 .checked_add(name_size)
1907 .ok_or(Error::InvalidHeader("RAR 1.5 file name size overflows"))?;
1908 if name_end > head_end {
1909 return Err(Error::InvalidHeader(
1910 "RAR 1.5 file name extends beyond header",
1911 ));
1912 }
1913 let name = decode_file_name(&input[pos..name_end], block.flags);
1914 pos = name_end;
1915
1916 let salt = if block.flags & FHD_SALT != 0 {
1917 let salt_end = pos
1918 .checked_add(8)
1919 .ok_or(Error::InvalidHeader("RAR 1.5 salt size overflows"))?;
1920 if salt_end > head_end {
1921 return Err(Error::InvalidHeader(
1922 "RAR 1.5 salt extends beyond file header",
1923 ));
1924 }
1925 let salt_bytes = input.get(pos..salt_end).ok_or(Error::TooShort)?;
1926 pos = salt_end;
1927 Some(
1928 salt_bytes
1929 .try_into()
1930 .expect("RAR 1.5 salt slice has fixed length"),
1931 )
1932 } else {
1933 None
1934 };
1935
1936 let file_comment = if block.flags & FHD_COMMENT != 0 {
1937 if pos + 2 <= head_end {
1938 let comment_len = read_u16(input, pos)? as usize;
1939 let comment_total = comment_len
1940 .checked_add(2)
1941 .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1942 let comment_end = pos
1943 .checked_add(comment_total)
1944 .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1945 if comment_end <= head_end {
1946 let comment = input[pos..comment_end].to_vec();
1947 pos = comment_end;
1948 comment
1949 } else {
1950 Vec::new()
1951 }
1952 } else {
1953 Vec::new()
1954 }
1955 } else {
1956 Vec::new()
1957 };
1958
1959 let ext_time = if block.flags & FHD_EXTTIME != 0 {
1960 input[pos..head_end].to_vec()
1961 } else {
1962 Vec::new()
1963 };
1964 let data_start = head_end;
1965 let data_len = usize::try_from(pack_size)
1966 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
1967 let data_end = data_start
1968 .checked_add(data_len)
1969 .ok_or(Error::InvalidHeader(
1970 "RAR 1.5 packed file size overflows usize",
1971 ))?;
1972 Ok(FileHeader {
1973 block,
1974 pack_size,
1975 unp_size,
1976 host_os,
1977 file_crc,
1978 file_time,
1979 unp_ver,
1980 method,
1981 name,
1982 attr,
1983 salt,
1984 file_comment,
1985 ext_time,
1986 packed_range: archive_offset + data_start..archive_offset + data_end,
1987 })
1988}
1989
1990fn decode_file_name(raw: &[u8], flags: u16) -> Vec<u8> {
1991 if flags & FHD_UNICODE == 0 {
1992 return raw.to_vec();
1993 }
1994
1995 let Some(zero_pos) = raw.iter().position(|byte| *byte == 0) else {
1996 return raw.to_vec();
1997 };
1998 if zero_pos + 1 >= raw.len() {
1999 return raw[..zero_pos].to_vec();
2000 }
2001
2002 let fallback = &raw[..zero_pos];
2003 let high_byte = raw[zero_pos + 1];
2004 let encoded = &raw[zero_pos + 2..];
2005 let mut pos = 0usize;
2006 let mut flag_byte = 0u8;
2007 let mut flag_bits = 0u8;
2008 let mut dst_pos = 0usize;
2009 let mut units = Vec::new();
2010
2011 while pos < encoded.len() {
2012 if flag_bits == 0 {
2013 flag_byte = encoded[pos];
2014 pos += 1;
2015 flag_bits = 8;
2016 }
2017 let mode = flag_byte >> 6;
2018 flag_byte <<= 2;
2019 flag_bits -= 2;
2020
2021 match mode {
2022 0 => {
2023 let Some(&low) = encoded.get(pos) else {
2024 return raw.to_vec();
2025 };
2026 pos += 1;
2027 units.push(u16::from(low));
2028 dst_pos += 1;
2029 }
2030 1 => {
2031 let Some(&low) = encoded.get(pos) else {
2032 return raw.to_vec();
2033 };
2034 pos += 1;
2035 units.push((u16::from(high_byte) << 8) | u16::from(low));
2036 dst_pos += 1;
2037 }
2038 2 => {
2039 let Some((&low, &high)) = encoded.get(pos).zip(encoded.get(pos + 1)) else {
2040 return raw.to_vec();
2041 };
2042 pos += 2;
2043 units.push((u16::from(high) << 8) | u16::from(low));
2044 dst_pos += 1;
2045 }
2046 3 => {
2047 let Some(&length_byte) = encoded.get(pos) else {
2048 return raw.to_vec();
2049 };
2050 pos += 1;
2051 let (count, correction, high) = if length_byte & 0x80 != 0 {
2052 let Some(&correction) = encoded.get(pos) else {
2053 return raw.to_vec();
2054 };
2055 pos += 1;
2056 ((length_byte & 0x7f) as usize + 2, correction, high_byte)
2057 } else {
2058 (length_byte as usize + 2, 0, 0)
2059 };
2060 for _ in 0..count {
2061 let low = fallback
2062 .get(dst_pos)
2063 .copied()
2064 .unwrap_or(b'?')
2065 .wrapping_add(correction);
2066 units.push((u16::from(high) << 8) | u16::from(low));
2067 dst_pos += 1;
2068 }
2069 }
2070 _ => unreachable!("2-bit filename mode"),
2071 }
2072 }
2073
2074 char::decode_utf16(units)
2075 .map(|unit| unit.unwrap_or(char::REPLACEMENT_CHARACTER))
2076 .collect::<String>()
2077 .into_bytes()
2078}
2079
2080fn read_block_header_at(
2081 file: &mut File,
2082 file_len: u64,
2083 archive_offset: usize,
2084 offset: usize,
2085) -> Result<BlockHeader> {
2086 let absolute = archive_offset
2087 .checked_add(offset)
2088 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
2089 if absolute as u64 + 7 > file_len {
2090 return Err(Error::TooShort);
2091 }
2092 let base = read_exact_at(file, absolute, 7)?;
2093 let head_size = read_u16(&base, 5)? as usize;
2094 if head_size < 7 {
2095 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2096 }
2097 if absolute as u64 + head_size as u64 > file_len {
2098 return Err(Error::TooShort);
2099 }
2100 let header = if head_size == 7 {
2101 base
2102 } else {
2103 read_exact_at(file, absolute, head_size)?
2104 };
2105 let mut block = parse_block_header(&header, 0)?;
2106 block.offset = offset;
2107 Ok(block)
2108}
2109
2110fn relative_block(block: &BlockHeader) -> BlockHeader {
2111 let mut relative = block.clone();
2112 relative.offset = 0;
2113 relative
2114}
2115
2116struct CrcWriter<'a, W: Write + ?Sized> {
2117 inner: &'a mut W,
2118 crc: &'a mut Crc32,
2119}
2120
2121impl<W: Write + ?Sized> Write for CrcWriter<'_, W> {
2122 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2123 let written = self.inner.write(buf)?;
2124 self.crc.update(&buf[..written]);
2125 Ok(written)
2126 }
2127
2128 fn flush(&mut self) -> std::io::Result<()> {
2129 self.inner.flush()
2130 }
2131}
2132
2133fn parse_block_header(input: &[u8], offset: usize) -> Result<BlockHeader> {
2134 if input.len() < offset + 7 {
2135 return Err(Error::TooShort);
2136 }
2137 let head_crc = read_u16(input, offset)?;
2138 let head_type = input[offset + 2];
2139 let flags = read_u16(input, offset + 3)?;
2140 let head_size = read_u16(input, offset + 5)?;
2141 if head_size < 7 {
2142 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2143 }
2144 let add_size = if flags & LONG_BLOCK != 0 {
2145 Some(read_u32(input, offset + 7)? as u64)
2146 } else {
2147 None
2148 };
2149 if offset + head_size as usize > input.len() {
2150 return Err(Error::TooShort);
2151 }
2152 if head_type != MARK_HEAD && should_validate_header_crc(head_type) {
2153 let header_end = header_crc_end(input, offset, head_type, flags, head_size)?;
2154 let actual = (crc32(&input[offset + 2..header_end]) & 0xffff) as u16;
2155 if actual != head_crc {
2156 return Err(Error::CrcMismatch {
2157 expected: head_crc,
2158 actual,
2159 });
2160 }
2161 }
2162 validate_legacy_auth_block_size(head_type, head_size)?;
2163
2164 Ok(BlockHeader {
2165 head_crc,
2166 head_type,
2167 flags,
2168 head_size,
2169 add_size,
2170 offset,
2171 })
2172}
2173
2174fn header_crc_end(
2175 input: &[u8],
2176 offset: usize,
2177 head_type: u8,
2178 flags: u16,
2179 head_size: u16,
2180) -> Result<usize> {
2181 let full_end = offset + head_size as usize;
2182 let fixed_end = match head_type {
2183 MAIN_HEAD if flags & MHD_COMMENT != 0 => Some(offset + 13),
2184 COMM_HEAD => Some(offset + 13),
2185 FILE_HEAD if flags & FHD_COMMENT != 0 => Some(file_header_comment_crc_end(input, offset)?),
2186 _ => None,
2187 };
2188 Ok(fixed_end.unwrap_or(full_end).min(full_end))
2189}
2190
2191fn file_header_comment_crc_end(input: &[u8], offset: usize) -> Result<usize> {
2192 if input.len() < offset + 32 {
2193 return Err(Error::TooShort);
2194 }
2195 let flags = read_u16(input, offset + 3)?;
2196 let name_size = read_u16(input, offset + 26)? as usize;
2197 let mut end = offset + 32;
2198 if flags & FHD_LARGE != 0 {
2199 end = end
2200 .checked_add(8)
2201 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2202 }
2203 end = end
2204 .checked_add(name_size)
2205 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2206 if flags & FHD_SALT != 0 {
2207 end = end
2208 .checked_add(8)
2209 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2210 }
2211 Ok(end)
2212}
2213
2214fn should_validate_header_crc(head_type: u8) -> bool {
2215 !matches!(head_type, 0x76 | 0x79)
2218}
2219
2220fn validate_legacy_auth_block_size(head_type: u8, head_size: u16) -> Result<()> {
2221 let minimum = match head_type {
2222 0x76 => 21,
2223 0x79 => 182,
2224 _ => return Ok(()),
2225 };
2226 if head_size < minimum {
2227 return Err(Error::InvalidHeader(
2228 "RAR legacy authenticity block is too short",
2229 ));
2230 }
2231 Ok(())
2232}
2233
2234fn block_total_size(block: &BlockHeader) -> Result<usize> {
2235 let total = block.head_size as u64 + block.add_size.unwrap_or(0);
2236 usize::try_from(total).map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2237}
2238
2239fn file_block_total_size(
2240 block: &BlockHeader,
2241 default_total: usize,
2242 pack_size: u64,
2243) -> Result<usize> {
2244 let low_payload_size = usize::try_from(block.add_size.unwrap_or(0))
2245 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2246 let header_prefix = default_total
2247 .checked_sub(low_payload_size)
2248 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2249 let pack_size = usize::try_from(pack_size)
2250 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2251 header_prefix
2252 .checked_add(pack_size)
2253 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2254}
2255
2256fn checked_block_next(block: &BlockHeader, total: usize, archive_len: usize) -> Result<usize> {
2257 let next = block
2258 .offset
2259 .checked_add(total)
2260 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2261 if next > archive_len {
2262 return Err(Error::TooShort);
2263 }
2264 Ok(next)
2265}
2266
2267fn checked_file_block_next(
2268 sfx_offset: usize,
2269 block: &BlockHeader,
2270 total: usize,
2271 file_len: u64,
2272) -> Result<usize> {
2273 let next = block
2274 .offset
2275 .checked_add(total)
2276 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2277 let absolute_next = sfx_offset
2278 .checked_add(next)
2279 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2280 if absolute_next as u64 > file_len {
2281 return Err(Error::TooShort);
2282 }
2283 Ok(next)
2284}
2285
2286fn packed_range(
2287 archive_offset: usize,
2288 block_offset: usize,
2289 total: usize,
2290 pack_size: u64,
2291) -> Result<Range<usize>> {
2292 let pack_size = usize::try_from(pack_size)
2293 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2294 let block_end = archive_offset
2295 .checked_add(block_offset)
2296 .and_then(|start| start.checked_add(total))
2297 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2298 let block_start = block_end
2299 .checked_sub(pack_size)
2300 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2301 Ok(block_start..block_end)
2302}
2303
2304#[cfg(test)]
2305mod tests {
2306 use super::*;
2307
2308 fn test_write_main_header(out: &mut Vec<u8>, flags: u16) {
2309 let start = out.len();
2310 out.extend_from_slice(&0u16.to_le_bytes());
2311 out.push(MAIN_HEAD);
2312 out.extend_from_slice(&flags.to_le_bytes());
2313 out.extend_from_slice(&13u16.to_le_bytes());
2314 out.extend_from_slice(&0u16.to_le_bytes());
2315 out.extend_from_slice(&0u32.to_le_bytes());
2316 test_write_header_crc(out, start);
2317 }
2318
2319 fn test_write_header_crc(out: &mut [u8], start: usize) {
2320 let crc = (crc32(&out[start + 2..]) & 0xffff) as u16;
2321 out[start..start + 2].copy_from_slice(&crc.to_le_bytes());
2322 }
2323
2324 fn legacy_auth_block(head_type: u8, head_size: u16) -> Vec<u8> {
2325 let mut block = vec![0; head_size as usize];
2326 block[2] = head_type;
2327 block[5..7].copy_from_slice(&head_size.to_le_bytes());
2328 block
2329 }
2330
2331 #[test]
2332 fn rejects_too_short_legacy_auth_blocks_even_without_crc_check() {
2333 assert!(matches!(
2334 parse_block_header(&legacy_auth_block(0x76, 20), 0),
2335 Err(Error::InvalidHeader(
2336 "RAR legacy authenticity block is too short"
2337 ))
2338 ));
2339 assert!(matches!(
2340 parse_block_header(&legacy_auth_block(0x79, 181), 0),
2341 Err(Error::InvalidHeader(
2342 "RAR legacy authenticity block is too short"
2343 ))
2344 ));
2345 }
2346
2347 #[test]
2348 fn accepts_minimum_legacy_auth_block_sizes_with_bad_crc() {
2349 assert_eq!(
2350 parse_block_header(&legacy_auth_block(0x76, 21), 0)
2351 .unwrap()
2352 .head_size,
2353 21
2354 );
2355 assert_eq!(
2356 parse_block_header(&legacy_auth_block(0x79, 182), 0)
2357 .unwrap()
2358 .head_size,
2359 182
2360 );
2361 }
2362
2363 #[test]
2364 fn parses_fhd_large_high_size_fields_from_file_header() {
2365 let name = b"large.bin";
2366 let head_size = 32 + 8 + name.len();
2367 let mut header = Vec::new();
2368 header.extend_from_slice(&0u16.to_le_bytes());
2369 header.push(FILE_HEAD);
2370 header.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2371 header.extend_from_slice(&(head_size as u16).to_le_bytes());
2372 header.extend_from_slice(&0x89ab_cdefu32.to_le_bytes());
2373 header.extend_from_slice(&0x7654_3210u32.to_le_bytes());
2374 header.push(3);
2375 header.extend_from_slice(&0x1234_5678u32.to_le_bytes());
2376 header.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2377 header.push(29);
2378 header.push(0x35);
2379 header.extend_from_slice(&(name.len() as u16).to_le_bytes());
2380 header.extend_from_slice(&0x20u32.to_le_bytes());
2381 header.extend_from_slice(&1u32.to_le_bytes());
2382 header.extend_from_slice(&2u32.to_le_bytes());
2383 header.extend_from_slice(name);
2384
2385 let block = BlockHeader {
2386 head_crc: 0,
2387 head_type: FILE_HEAD,
2388 flags: LONG_BLOCK | FHD_LARGE,
2389 head_size: head_size as u16,
2390 add_size: Some(0x89ab_cdef),
2391 offset: 0,
2392 };
2393 let file = parse_file_like_header(&header, block, 0).unwrap();
2394
2395 assert_eq!(file.pack_size, 0x0000_0001_89ab_cdef);
2396 assert_eq!(file.unp_size, 0x0000_0002_7654_3210);
2397 assert_eq!(file.name, name);
2398 assert_eq!(
2399 file.packed_range,
2400 head_size..head_size + 0x0000_0001_89ab_cdefusize
2401 );
2402 }
2403
2404 #[test]
2405 fn fhd_large_archive_extent_uses_high_packed_size_without_underflowing() {
2406 let name = b"large-zero-low.bin";
2407 let head_size = 32 + 8 + name.len();
2408 let mut archive = Vec::from(RAR15_SIGNATURE);
2409 test_write_main_header(&mut archive, 0);
2410
2411 let start = archive.len();
2412 archive.extend_from_slice(&0u16.to_le_bytes());
2413 archive.push(FILE_HEAD);
2414 archive.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2415 archive.extend_from_slice(&(head_size as u16).to_le_bytes());
2416 archive.extend_from_slice(&0u32.to_le_bytes());
2417 archive.extend_from_slice(&0u32.to_le_bytes());
2418 archive.push(3);
2419 archive.extend_from_slice(&0u32.to_le_bytes());
2420 archive.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2421 archive.push(29);
2422 archive.push(0x35);
2423 archive.extend_from_slice(&(name.len() as u16).to_le_bytes());
2424 archive.extend_from_slice(&0x20u32.to_le_bytes());
2425 archive.extend_from_slice(&1u32.to_le_bytes());
2426 archive.extend_from_slice(&1u32.to_le_bytes());
2427 archive.extend_from_slice(name);
2428 test_write_header_crc(&mut archive, start);
2429
2430 assert!(matches!(Archive::parse(&archive), Err(Error::TooShort)));
2431 }
2432
2433 fn block_header_with(flags: u16) -> BlockHeader {
2434 BlockHeader {
2435 head_crc: 0,
2436 head_type: FILE_HEAD,
2437 flags,
2438 head_size: 32,
2439 add_size: None,
2440 offset: 0,
2441 }
2442 }
2443
2444 fn file_header_with(flags: u16) -> FileHeader {
2445 FileHeader {
2446 block: block_header_with(flags),
2447 pack_size: 0,
2448 unp_size: 0,
2449 host_os: 0,
2450 file_crc: 0,
2451 file_time: 0,
2452 unp_ver: 29,
2453 method: 0x30,
2454 name: b"entry".to_vec(),
2455 attr: 0,
2456 salt: None,
2457 file_comment: Vec::new(),
2458 ext_time: Vec::new(),
2459 packed_range: 0..0,
2460 }
2461 }
2462
2463 fn main_header_with(flags: u16) -> MainHeader {
2464 MainHeader {
2465 head_crc: 0,
2466 flags,
2467 head_size: 13,
2468 reserved1: 0,
2469 reserved2: 0,
2470 encrypt_version: None,
2471 }
2472 }
2473
2474 #[test]
2475 fn main_header_predicates_match_each_flag_bit() {
2476 let cases = [
2477 (MHD_VOLUME, MainHeader::is_volume as fn(&MainHeader) -> bool),
2478 (MHD_COMMENT, MainHeader::has_archive_comment),
2479 (MHD_SOLID, MainHeader::is_solid),
2480 (MHD_NEWNUMBERING, MainHeader::uses_new_numbering),
2481 (MHD_PROTECT, MainHeader::has_recovery_record),
2482 (MHD_PASSWORD, MainHeader::has_encrypted_headers),
2483 (MHD_FIRSTVOLUME, MainHeader::is_first_volume),
2484 ];
2485 let zero = main_header_with(0);
2486 for (bit, predicate) in cases {
2487 assert!(
2488 !predicate(&zero),
2489 "expected false when bit {bit:#x} is clear"
2490 );
2491 let one = main_header_with(bit);
2492 assert!(predicate(&one), "expected true when bit {bit:#x} is set");
2493 }
2494 }
2495
2496 #[test]
2497 fn file_header_flag_predicates_track_each_bit() {
2498 let cases = [
2499 (
2500 FHD_SPLIT_BEFORE,
2501 FileHeader::is_split_before as fn(&FileHeader) -> bool,
2502 ),
2503 (FHD_SPLIT_AFTER, FileHeader::is_split_after),
2504 (FHD_PASSWORD, FileHeader::is_encrypted),
2505 (FHD_SOLID, FileHeader::is_solid),
2506 (FHD_EXTTIME, FileHeader::has_ext_time),
2507 ];
2508 let zero = file_header_with(0);
2509 for (bit, predicate) in cases {
2510 assert!(
2511 !predicate(&zero),
2512 "expected false on FileHeader for bit {bit:#x}"
2513 );
2514 let one = file_header_with(bit);
2515 assert!(
2516 predicate(&one),
2517 "expected true on FileHeader for bit {bit:#x}"
2518 );
2519 }
2520
2521 let directory = file_header_with(FHD_DIRECTORY_MASK);
2522 assert!(directory.is_directory());
2523 assert!(!file_header_with(0).is_directory());
2524
2525 let stored = file_header_with(0);
2526 assert!(stored.is_stored());
2527 let mut packed = file_header_with(0);
2528 packed.method = 0x33;
2529 assert!(!packed.is_stored());
2530 }
2531
2532 #[test]
2533 fn file_header_comment_extracts_payload_after_two_byte_size_prefix() {
2534 let mut without_flag = file_header_with(0);
2535 without_flag.file_comment = vec![3, 0, b'a', b'b', b'c'];
2536 assert!(!without_flag.has_file_comment());
2537 assert_eq!(without_flag.file_comment().unwrap(), None);
2538
2539 let mut with_flag = file_header_with(FHD_COMMENT);
2540 with_flag.file_comment = vec![3, 0, b'h', b'e', b'y'];
2541 assert!(with_flag.has_file_comment());
2542 assert_eq!(with_flag.file_comment().unwrap().unwrap(), b"hey");
2543
2544 let mut empty_flagged = file_header_with(FHD_COMMENT);
2545 empty_flagged.file_comment.clear();
2546 assert!(!empty_flagged.has_file_comment());
2547
2548 let mut truncated = file_header_with(FHD_COMMENT);
2549 truncated.file_comment = vec![10, 0, b'a'];
2550 assert!(matches!(truncated.file_comment(), Err(Error::TooShort)));
2551 }
2552
2553 #[test]
2554 fn file_header_name_metadata_and_crc_helpers_describe_entry() {
2555 let mut header = file_header_with(0);
2556 header.name = b"r\xc3\xa9sum\xc3\xa9.txt".to_vec();
2557 header.file_crc = crc32(b"hello");
2558 header.attr = 0x20;
2559 header.host_os = 3;
2560 header.file_time = 0x5a21_0000;
2561
2562 assert_eq!(header.name_bytes(), b"r\xc3\xa9sum\xc3\xa9.txt");
2563 assert_eq!(header.name_lossy(), "résumé.txt");
2564 let meta = header.metadata();
2565 assert_eq!(meta.name, header.name);
2566 assert_eq!(meta.attr, 0x20);
2567 assert_eq!(meta.host_os, 3);
2568 assert_eq!(meta.file_time, 0x5a21_0000);
2569 assert!(!meta.is_directory);
2570
2571 header.verify_crc32(b"hello").unwrap();
2572 match header.verify_crc32(b"different") {
2573 Err(Error::Crc32Mismatch { expected, actual }) => {
2574 assert_eq!(expected, header.file_crc);
2575 assert_ne!(actual, expected);
2576 }
2577 other => panic!("expected Crc32Mismatch, got {other:?}"),
2578 }
2579
2580 let directory = file_header_with(FHD_DIRECTORY_MASK);
2581 assert!(directory.metadata().is_directory);
2582
2583 let mut garbage = file_header_with(0);
2585 garbage.name = vec![0xff, 0xfe, b'/', 0x80, b'x'];
2586 let lossy = garbage.name_lossy();
2587 assert!(lossy.ends_with("/\u{fffd}x"), "got {lossy:?}");
2588 }
2589
2590 #[test]
2591 fn newsub_header_name_lossy_delegates_to_inner_file_header() {
2592 let mut file = file_header_with(0);
2593 file.name = b"CMT".to_vec();
2594 let sub = NewSubHeader {
2595 file,
2596 kind: NewSubKind::ArchiveComment,
2597 };
2598 assert_eq!(sub.name_lossy(), "CMT");
2599 }
2600
2601 #[test]
2602 fn writer_options_constructor_and_default_match_documented_targets() {
2603 let default = WriterOptions::default();
2604 assert_eq!(default.target, ArchiveVersion::Rar15);
2605 assert_eq!(default.features, FeatureSet::store_only());
2606
2607 let explicit = WriterOptions::new(ArchiveVersion::Rar20, FeatureSet::store_only());
2608 assert_eq!(explicit.target, ArchiveVersion::Rar20);
2609 assert_eq!(explicit.features, FeatureSet::store_only());
2610 }
2611
2612 fn stored_archive_bytes(name: &[u8], data: &[u8]) -> Vec<u8> {
2613 write_stored_archive(
2614 &[StoredEntry {
2615 name,
2616 data,
2617 file_time: 0,
2618 file_attr: 0x20,
2619 host_os: 3,
2620 password: None,
2621 file_comment: None,
2622 }],
2623 WriterOptions::default(),
2624 )
2625 .unwrap()
2626 }
2627
2628 #[test]
2629 fn archive_parse_owned_consumes_buffer_without_changing_dispatch() {
2630 let bytes = stored_archive_bytes(b"owned.txt", b"hello rar15 owned");
2631 let archive = Archive::parse_owned(bytes.clone()).unwrap();
2632 assert_eq!(archive.files().count(), 1);
2633 let file = archive.files().next().unwrap();
2634 assert_eq!(file.name, b"owned.txt");
2635
2636 let with_options =
2638 Archive::parse_owned_with_options(bytes.clone(), crate::ArchiveReadOptions::default())
2639 .unwrap();
2640 assert_eq!(with_options.files().count(), 1);
2641
2642 let no_password = Archive::parse_owned_with_password(bytes, None).unwrap();
2643 assert_eq!(no_password.files().count(), 1);
2644 }
2645
2646 #[test]
2647 fn archive_parse_owned_with_password_unlocks_encrypted_archive() {
2648 let mut features = FeatureSet::store_only();
2649 features.file_encryption = true;
2650 let bytes = write_stored_archive(
2651 &[StoredEntry {
2652 name: b"locked.txt",
2653 data: b"encrypted owned payload",
2654 file_time: 0,
2655 file_attr: 0x20,
2656 host_os: 3,
2657 password: Some(b"pw"),
2658 file_comment: None,
2659 }],
2660 WriterOptions {
2661 target: ArchiveVersion::Rar20,
2662 features,
2663 compression_level: None,
2664 dictionary_size: None,
2665 },
2666 )
2667 .unwrap();
2668
2669 let archive = Archive::parse_owned_with_password(bytes, Some(b"pw")).unwrap();
2670 let file = archive.files().next().unwrap();
2671 assert!(file.is_encrypted());
2672 }
2673
2674 #[test]
2675 fn file_header_write_packed_data_streams_through_writer() {
2676 let payload = b"write_packed_data direct dump";
2677 let bytes = stored_archive_bytes(b"dump.bin", payload);
2678 let archive = Archive::parse(&bytes).unwrap();
2679 let file = archive.files().next().unwrap();
2680
2681 let mut sink = Vec::new();
2682 file.write_packed_data(&archive, &mut sink).unwrap();
2683 assert_eq!(sink, payload);
2684 }
2685
2686 #[test]
2687 fn file_header_unsupported_compression_describes_method_and_unpack_version() {
2688 let mut header = file_header_with(0);
2689 header.method = 0x33;
2690 header.unp_ver = 26;
2691 let err = header.unsupported_compression();
2692 assert!(matches!(
2693 err,
2694 Error::UnsupportedCompression {
2695 family: "RAR 1.5-4.x",
2696 unpack_version: 26,
2697 method: 0x33,
2698 }
2699 ));
2700 }
2701
2702 #[test]
2703 fn file_header_unsupported_encryption_describes_unpack_version() {
2704 let mut header = file_header_with(FHD_PASSWORD);
2705 header.unp_ver = 36;
2706 let err = header.unsupported_encryption();
2707 assert!(matches!(
2708 err,
2709 Error::UnsupportedEncryption {
2710 family: "RAR 1.5-4.x",
2711 unpack_version: 36,
2712 }
2713 ));
2714 }
2715
2716 #[test]
2717 fn crc_writer_flush_propagates_to_inner_writer() {
2718 struct FlushSpy {
2719 data: Vec<u8>,
2720 flushed: usize,
2721 }
2722 impl Write for FlushSpy {
2723 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2724 self.data.extend_from_slice(buf);
2725 Ok(buf.len())
2726 }
2727 fn flush(&mut self) -> std::io::Result<()> {
2728 self.flushed += 1;
2729 Ok(())
2730 }
2731 }
2732 let mut inner = FlushSpy {
2733 data: Vec::new(),
2734 flushed: 0,
2735 };
2736 let mut crc = Crc32::new();
2737 let mut writer = CrcWriter {
2738 inner: &mut inner,
2739 crc: &mut crc,
2740 };
2741 writer.write_all(b"hi").unwrap();
2742 writer.flush().unwrap();
2743 assert_eq!(inner.data, b"hi");
2744 assert_eq!(inner.flushed, 1);
2745 }
2746
2747 #[test]
2748 fn parse_main_header_rejects_block_size_below_minimum() {
2749 let block = BlockHeader {
2750 head_crc: 0,
2751 head_type: MAIN_HEAD,
2752 flags: 0,
2753 head_size: 12,
2754 add_size: None,
2755 offset: 0,
2756 };
2757 let err = parse_main_header(&[0u8; 32], &block).unwrap_err();
2758 assert_eq!(
2759 err,
2760 Error::InvalidHeader("RAR 1.5 main header is too short")
2761 );
2762 }
2763
2764 #[test]
2765 fn parse_main_header_rejects_block_extending_past_input_buffer() {
2766 let block = BlockHeader {
2767 head_crc: 0,
2768 head_type: MAIN_HEAD,
2769 flags: 0,
2770 head_size: 13,
2771 add_size: None,
2772 offset: 8,
2773 };
2774 let err = parse_main_header(&[0u8; 16], &block).unwrap_err();
2776 assert_eq!(err, Error::TooShort);
2777 }
2778
2779 #[test]
2780 fn parse_main_header_reads_encrypt_version_when_flag_is_set() {
2781 let mut input = vec![0u8; 14];
2784 input[13] = 0x29;
2785 let block = BlockHeader {
2786 head_crc: 0,
2787 head_type: MAIN_HEAD,
2788 flags: MHD_ENCRYPTVER,
2789 head_size: 14,
2790 add_size: None,
2791 offset: 0,
2792 };
2793 let main = parse_main_header(&input, &block).unwrap();
2794 assert_eq!(main.encrypt_version, Some(0x29));
2795 }
2796
2797 #[test]
2798 fn decode_file_name_returns_raw_when_unicode_flag_is_clear() {
2799 let raw = b"plain.txt";
2800 let decoded = decode_file_name(raw, 0);
2801 assert_eq!(decoded, raw);
2802 }
2803
2804 #[test]
2805 fn decode_file_name_returns_raw_when_unicode_marker_is_missing() {
2806 let raw = b"no-zero-marker";
2809 let decoded = decode_file_name(raw, FHD_UNICODE);
2810 assert_eq!(decoded, raw);
2811 }
2812
2813 #[test]
2814 fn decode_file_name_returns_fallback_when_no_encoded_payload_follows_zero() {
2815 let mut raw = b"fallback".to_vec();
2818 raw.push(0);
2819 let decoded = decode_file_name(&raw, FHD_UNICODE);
2820 assert_eq!(decoded, b"fallback");
2821 }
2822
2823 #[test]
2824 fn decode_file_name_decodes_mode_zero_low_byte_only_units() {
2825 let mut raw = b"orig".to_vec();
2829 raw.push(0); raw.push(0); raw.push(0b00_00_00_00); raw.extend_from_slice(b"abcd");
2833 let decoded = decode_file_name(&raw, FHD_UNICODE);
2834 assert_eq!(decoded, b"abcd");
2835 }
2836
2837 #[test]
2838 fn decode_file_name_decodes_mode_two_full_two_byte_units() {
2839 let mut raw = b"".to_vec();
2842 raw.push(0); raw.push(0); raw.push(0b10_10_00_00);
2846 raw.extend_from_slice(&[0x48, 0x00]);
2848 raw.extend_from_slice(&[0x69, 0x00]);
2850 let decoded = decode_file_name(&raw, FHD_UNICODE);
2851 assert!(decoded.starts_with(b"Hi"), "got {decoded:?}");
2854 }
2855}