1use crate::detect::{find_archive_start, ArchiveSignature, RAR15_SIGNATURE};
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::UnsupportedFamilyFeature { .. }
568 | Error::UnsupportedCompression { .. }
569 | Error::UnsupportedEncryption { .. }
570 | Error::TooShort
571 | Error::Io(_)
572 | Error::AtArchiveOffset { .. }
573 | Error::AtEntry { .. } => error,
574 Error::InvalidHeader(_)
575 | Error::Codec(_)
576 | Error::Rar3Recovery(_)
577 | Error::Rar5Recovery(_)
578 | Error::Rar20Crypto(_)
579 | Error::Rar30Crypto(_)
580 | Error::Rar50Crypto(_)
581 | Error::CrcMismatch { .. }
582 | Error::Crc32Mismatch { .. }
583 | Error::HashMismatch { .. }
584 | Error::WrongPasswordOrCorruptData => Error::WrongPasswordOrCorruptData,
585 }
586 }
587
588 fn entry_error(&self, operation: &'static str, error: Error) -> Error {
589 if matches!(
590 error,
591 Error::NeedPassword | Error::WrongPasswordOrCorruptData
592 ) {
593 return error;
594 }
595 error.at_entry(self.name.clone(), operation)
596 }
597
598 fn crc_result(&self, actual: u32, password: Option<&[u8]>) -> Result<()> {
599 if actual == self.file_crc {
600 Ok(())
601 } else {
602 Err(self.map_encrypted_payload_error(
603 password,
604 Error::Crc32Mismatch {
605 expected: self.file_crc,
606 actual,
607 },
608 ))
609 }
610 }
611
612 fn write_rar29_to(
613 &self,
614 archive: &Archive,
615 decoder: &mut Unpack29,
616 out: &mut impl Write,
617 ) -> Result<()> {
618 if self.is_stored() {
619 return self.write_stored_to(archive, None, out);
620 }
621 if self.is_encrypted() {
622 return Err(self.unsupported_encryption());
623 }
624 if self.unp_ver < 29 {
625 return Err(self.unsupported_compression());
626 }
627
628 let mut packed = archive.range_reader(self.packed_range.clone())?;
629 let mut crc = Crc32::new();
630 let mut crc_writer = CrcWriter {
631 inner: out,
632 crc: &mut crc,
633 };
634 decoder
635 .decode_member_from_reader(
636 &mut packed,
637 usize::try_from(self.unp_size)
638 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
639 &mut crc_writer,
640 )
641 .map_err(Error::from)?;
642 let actual = crc.finish();
643 if actual == self.file_crc {
644 Ok(())
645 } else {
646 Err(Error::Crc32Mismatch {
647 expected: self.file_crc,
648 actual,
649 })
650 }
651 }
652
653 fn write_unpack15_to(
654 &self,
655 archive: &Archive,
656 decoder: &mut Unpack15,
657 solid: bool,
658 password: Option<&[u8]>,
659 out: &mut impl Write,
660 ) -> Result<()> {
661 if self.is_stored() {
662 return self.write_stored_to(archive, password, out);
663 }
664 if self.unp_ver != 15 {
665 return Err(self.unsupported_compression());
666 }
667
668 let mut input = self
669 .packed_reader_for_decode(archive, password)
670 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
671 self.write_unpack15_decoded(decoder, solid, &mut input, out, password)
672 .map_err(|error| self.map_encrypted_payload_error(password, error))
673 }
674
675 fn write_unpack15_decoded(
676 &self,
677 decoder: &mut Unpack15,
678 solid: bool,
679 input: &mut impl Read,
680 out: &mut impl Write,
681 password: Option<&[u8]>,
682 ) -> Result<()> {
683 let mut crc = Crc32::new();
684 let mut crc_writer = CrcWriter {
685 inner: out,
686 crc: &mut crc,
687 };
688 decoder
689 .decode_member_from_reader(
690 input,
691 usize::try_from(self.unp_size)
692 .map_err(|_| Error::InvalidHeader("RAR 1.5 unpacked size overflows usize"))?,
693 solid,
694 &mut crc_writer,
695 )
696 .map_err(Error::from)?;
697 let actual = crc.finish();
698 self.crc_result(actual, password)
699 }
700
701 fn write_unpack20_to(
702 &self,
703 archive: &Archive,
704 decoder: &mut Unpack20,
705 password: Option<&[u8]>,
706 out: &mut impl Write,
707 ) -> Result<()> {
708 if self.is_stored() {
709 return self.write_stored_to(archive, password, out);
710 }
711 if self.unp_ver != 20 && self.unp_ver != 26 {
712 return Err(self.unsupported_compression());
713 }
714
715 let mut crc = Crc32::new();
716 let mut crc_writer = CrcWriter {
717 inner: out,
718 crc: &mut crc,
719 };
720 let target = usize::try_from(self.unp_size)
721 .map_err(|_| Error::InvalidHeader("RAR 2.0 unpacked size overflows usize"))?;
722 let mut packed = self
723 .packed_reader_for_decode(archive, password)
724 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
725 decoder
726 .decode_member_from_reader(&mut packed, target, &mut crc_writer)
727 .map_err(Error::from)
728 .map_err(|error| self.map_encrypted_payload_error(password, error))?;
729 let actual = crc.finish();
730 self.crc_result(actual, password)
731 }
732
733 fn unsupported_compression(&self) -> Error {
734 Error::UnsupportedCompression {
735 family: "RAR 1.5-4.x",
736 unpack_version: self.unp_ver,
737 method: self.method,
738 }
739 }
740
741 fn unsupported_encryption(&self) -> Error {
742 Error::UnsupportedEncryption {
743 family: "RAR 1.5-4.x",
744 unpack_version: self.unp_ver,
745 }
746 }
747}
748
749impl NewSubHeader {
750 pub fn name_bytes(&self) -> &[u8] {
751 self.file.name_bytes()
752 }
753
754 pub fn name_lossy(&self) -> String {
758 self.file.name_lossy()
759 }
760}
761
762impl CommentHeader {
763 fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
764 archive.read_range(self.packed_range.clone())
765 }
766
767 fn unpacked_data(&self, archive: &Archive) -> Result<Vec<u8>> {
768 let target = usize::from(self.unp_size);
769 let data = if self.method == 0x30 {
770 let data = self.packed_data(archive)?;
771 if data.len() != target {
772 return Err(Error::InvalidHeader(
773 "RAR 1.5 stored comment has mismatched packed and unpacked sizes",
774 ));
775 }
776 data
777 } else if self.unp_ver == 15 {
778 Unpack15::default().decode_member(&self.packed_data(archive)?, target, false)?
779 } else {
780 return Err(Error::UnsupportedCompression {
781 family: "RAR 1.5 comment",
782 unpack_version: self.unp_ver,
783 method: self.method,
784 });
785 };
786 let actual = (crc32(&data) & 0xffff) as u16;
787 if actual == self.comment_crc {
788 Ok(data)
789 } else {
790 Err(Error::CrcMismatch {
791 expected: self.comment_crc,
792 actual,
793 })
794 }
795 }
796}
797
798impl Archive {
799 pub fn parse(input: &[u8]) -> Result<Self> {
800 Self::parse_with_options(input, crate::ArchiveReadOptions::default())
801 }
802
803 pub fn parse_owned(input: Vec<u8>) -> Result<Self> {
804 Self::parse_owned_with_options(input, crate::ArchiveReadOptions::default())
805 }
806
807 pub fn parse_with_options(
808 input: &[u8],
809 options: crate::ArchiveReadOptions<'_>,
810 ) -> Result<Self> {
811 let data: Arc<[u8]> = Arc::from(input.to_vec().into_boxed_slice());
812 Self::parse_shared(data, options.password)
813 }
814
815 pub fn parse_owned_with_options(
816 input: Vec<u8>,
817 options: crate::ArchiveReadOptions<'_>,
818 ) -> Result<Self> {
819 Self::parse_shared(Arc::from(input.into_boxed_slice()), options.password)
820 }
821
822 pub fn parse_with_password(input: &[u8], password: Option<&[u8]>) -> Result<Self> {
823 Self::parse_with_options(input, crate::ArchiveReadOptions { password })
824 }
825
826 pub fn parse_owned_with_password(input: Vec<u8>, password: Option<&[u8]>) -> Result<Self> {
827 Self::parse_owned_with_options(input, crate::ArchiveReadOptions { password })
828 }
829
830 pub fn parse_path(path: impl AsRef<Path>) -> Result<Self> {
831 Self::parse_path_with_options(path, crate::ArchiveReadOptions::default())
832 }
833
834 pub fn parse_path_with_options(
835 path: impl AsRef<Path>,
836 options: crate::ArchiveReadOptions<'_>,
837 ) -> Result<Self> {
838 Self::parse_path_with_password(path, options.password)
839 }
840
841 pub fn parse_path_with_password(
842 path: impl AsRef<Path>,
843 password: Option<&[u8]>,
844 ) -> Result<Self> {
845 let path = Arc::new(path.as_ref().to_path_buf());
846 let mut file = File::open(path.as_ref())?;
847 let len = file.metadata()?.len();
848 let scan_len = len.min(128 * 1024) as usize;
849 let mut scan = vec![0; scan_len];
850 file.read_exact(&mut scan)?;
851 let sig = find_archive_start(&scan, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
852 if sig.family != ArchiveFamily::Rar15To40 {
853 return Err(Error::UnsupportedSignature);
854 }
855 Self::parse_seekable(file, len, sig.offset, ArchiveSource::File(path), password)
856 }
857
858 pub fn parse_path_with_signature(
859 path: impl AsRef<Path>,
860 signature: ArchiveSignature,
861 options: crate::ArchiveReadOptions<'_>,
862 ) -> Result<Self> {
863 Self::parse_path_with_signature_and_password(path, signature, options.password)
864 }
865
866 pub fn parse_path_with_signature_and_password(
867 path: impl AsRef<Path>,
868 signature: ArchiveSignature,
869 password: Option<&[u8]>,
870 ) -> Result<Self> {
871 if signature.family != ArchiveFamily::Rar15To40 {
872 return Err(Error::UnsupportedSignature);
873 }
874 let path = Arc::new(path.as_ref().to_path_buf());
875 let file = File::open(path.as_ref())?;
876 let len = file.metadata()?.len();
877 Self::parse_seekable(
878 file,
879 len,
880 signature.offset,
881 ArchiveSource::File(path),
882 password,
883 )
884 }
885
886 fn parse_shared(input: Arc<[u8]>, password: Option<&[u8]>) -> Result<Self> {
887 let sig = find_archive_start(&input, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
888 if sig.family != ArchiveFamily::Rar15To40 {
889 return Err(Error::UnsupportedSignature);
890 }
891
892 let archive = &input[sig.offset..];
893 if !archive.starts_with(RAR15_SIGNATURE) {
894 return Err(Error::UnsupportedSignature);
895 }
896
897 let marker = parse_block_header(archive, 0)?;
898 if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
899 return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
900 }
901
902 let main_block = parse_block_header(archive, marker.head_size as usize)?;
903 if main_block.head_type != MAIN_HEAD {
904 return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
905 }
906 let main = parse_main_header(archive, &main_block)?;
907 let mut pos = main_block.offset + main_block.head_size as usize;
908 let mut blocks = Vec::new();
909 let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
910
911 while pos < archive.len() {
912 if archive.len() - pos < 7 {
913 break;
914 }
915 let (block, header, total) = if main.has_encrypted_headers() {
916 let password = password.ok_or(Error::NeedPassword)?;
917 let encrypted = decrypt_encrypted_header_at(
918 archive,
919 pos,
920 password,
921 &mut encrypted_header_ciphers,
922 )?;
923 (encrypted.block, encrypted.header, encrypted.total_size)
924 } else {
925 let block = parse_block_header(archive, pos)?;
926 let total = block_total_size(&block)?;
927 let header = archive[pos..pos + block.head_size as usize].to_vec();
928 (block, header, total)
929 };
930 match block.head_type {
931 FILE_HEAD => {
932 let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
933 let total = file_block_total_size(&block, total, file.pack_size)?;
934 let next = checked_block_next(&block, total, archive.len())?;
935 file.block.offset = block.offset;
936 file.packed_range =
937 packed_range(sig.offset, block.offset, total, file.pack_size)?;
938 blocks.push(Block::File(file));
939 pos = next;
940 }
941 NEWSUB_HEAD => {
942 let mut file = parse_file_like_header(&header, relative_block(&block), 0)?;
943 let total = file_block_total_size(&block, total, file.pack_size)?;
944 let next = checked_block_next(&block, total, archive.len())?;
945 file.block.offset = block.offset;
946 file.packed_range =
947 packed_range(sig.offset, block.offset, total, file.pack_size)?;
948 let kind = classify_new_sub(&file.name);
949 blocks.push(Block::NewSub(NewSubHeader { file, kind }));
950 pos = next;
951 }
952 COMM_HEAD => {
953 let next = checked_block_next(&block, total, archive.len())?;
954 let mut comment = parse_comment_header(&header, relative_block(&block))?;
955 comment.block.offset = block.offset;
956 comment.packed_range =
957 sig.offset + block.offset + 13..sig.offset + block.offset + total;
958 blocks.push(Block::Comment(comment));
959 pos = next;
960 }
961 PROTECT_HEAD => {
962 let next = checked_block_next(&block, total, archive.len())?;
963 let protect = parse_protect_header(&header, &block, sig.offset, total)?;
964 blocks.push(Block::Protect(protect));
965 pos = next;
966 }
967 ENDARC_HEAD => {
968 let _next = checked_block_next(&block, total, archive.len())?;
969 blocks.push(Block::End(block));
970 break;
971 }
972 _ => {
973 let next = checked_block_next(&block, total, archive.len())?;
974 blocks.push(Block::Unknown(block));
975 pos = next;
976 }
977 }
978 }
979
980 Ok(Self {
981 sfx_offset: sig.offset,
982 main,
983 blocks,
984 source: ArchiveSource::Memory(input),
985 })
986 }
987
988 fn parse_seekable(
989 mut file: File,
990 file_len: u64,
991 sfx_offset: usize,
992 source: ArchiveSource,
993 password: Option<&[u8]>,
994 ) -> Result<Self> {
995 let marker = read_block_header_at(&mut file, file_len, sfx_offset, 0)?;
996 if marker.head_type != MARK_HEAD || marker.head_size != RAR15_SIGNATURE.len() as u16 {
997 return Err(Error::InvalidHeader("RAR 1.5 marker block is invalid"));
998 }
999
1000 let main_block =
1001 read_block_header_at(&mut file, file_len, sfx_offset, marker.head_size as usize)?;
1002 if main_block.head_type != MAIN_HEAD {
1003 return Err(Error::InvalidHeader("RAR 1.5 main header is missing"));
1004 }
1005 let main_header = read_exact_at(
1006 &mut file,
1007 sfx_offset + main_block.offset,
1008 main_block.head_size as usize,
1009 )?;
1010 let main = parse_main_header(&main_header, &relative_block(&main_block))?;
1011 let mut pos = main_block.offset + main_block.head_size as usize;
1012 let mut blocks = Vec::new();
1013 let mut encrypted_header_ciphers = EncryptedHeaderCipherCache::default();
1014
1015 while (sfx_offset + pos) as u64 + 7 <= file_len {
1016 let (block, header, total) = if main.has_encrypted_headers() {
1017 let password = password.ok_or(Error::NeedPassword)?;
1018 let encrypted = read_encrypted_header_at(
1019 &mut file,
1020 file_len,
1021 sfx_offset,
1022 pos,
1023 password,
1024 &mut encrypted_header_ciphers,
1025 )?;
1026 (encrypted.block, encrypted.header, encrypted.total_size)
1027 } else {
1028 let block = read_block_header_at(&mut file, file_len, sfx_offset, pos)?;
1029 let total = block_total_size(&block)?;
1030 let header = read_exact_at(&mut file, sfx_offset + pos, block.head_size as usize)?;
1031 (block, header, total)
1032 };
1033 match block.head_type {
1034 FILE_HEAD => {
1035 let mut file_header =
1036 parse_file_like_header(&header, relative_block(&block), 0)?;
1037 let total = file_block_total_size(&block, total, file_header.pack_size)?;
1038 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1039 file_header.block.offset = block.offset;
1040 file_header.packed_range =
1041 packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1042 blocks.push(Block::File(file_header));
1043 pos = next;
1044 }
1045 NEWSUB_HEAD => {
1046 let mut file_header =
1047 parse_file_like_header(&header, relative_block(&block), 0)?;
1048 let total = file_block_total_size(&block, total, file_header.pack_size)?;
1049 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1050 file_header.block.offset = block.offset;
1051 file_header.packed_range =
1052 packed_range(sfx_offset, block.offset, total, file_header.pack_size)?;
1053 let kind = classify_new_sub(&file_header.name);
1054 blocks.push(Block::NewSub(NewSubHeader {
1055 file: file_header,
1056 kind,
1057 }));
1058 pos = next;
1059 }
1060 COMM_HEAD => {
1061 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1062 let mut comment = parse_comment_header(&header, relative_block(&block))?;
1063 comment.block.offset = block.offset;
1064 comment.packed_range =
1065 sfx_offset + block.offset + 13..sfx_offset + block.offset + total;
1066 blocks.push(Block::Comment(comment));
1067 pos = next;
1068 }
1069 PROTECT_HEAD => {
1070 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1071 let protect = parse_protect_header(&header, &block, sfx_offset, total)?;
1072 blocks.push(Block::Protect(protect));
1073 pos = next;
1074 }
1075 ENDARC_HEAD => {
1076 let _next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1077 blocks.push(Block::End(block));
1078 break;
1079 }
1080 _ => {
1081 let next = checked_file_block_next(sfx_offset, &block, total, file_len)?;
1082 blocks.push(Block::Unknown(block));
1083 pos = next;
1084 }
1085 }
1086 }
1087
1088 Ok(Self {
1089 sfx_offset,
1090 main,
1091 blocks,
1092 source,
1093 })
1094 }
1095
1096 fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>> {
1097 self.source.read_range(range)
1098 }
1099
1100 fn copy_range_to(&self, range: Range<usize>, out: &mut impl Write) -> Result<()> {
1101 self.source.copy_range_to(range, out)
1102 }
1103
1104 fn range_reader(&self, range: Range<usize>) -> Result<Box<dyn Read + '_>> {
1105 self.source.range_reader(range)
1106 }
1107
1108 pub fn files(&self) -> impl Iterator<Item = &FileHeader> {
1109 self.blocks.iter().filter_map(|block| match block {
1110 Block::File(file) => Some(file),
1111 _ => None,
1112 })
1113 }
1114
1115 pub fn new_subs(&self) -> impl Iterator<Item = &NewSubHeader> {
1116 self.blocks.iter().filter_map(|block| match block {
1117 Block::NewSub(sub) => Some(sub),
1118 _ => None,
1119 })
1120 }
1121
1122 pub fn protect_records(&self) -> impl Iterator<Item = &ProtectHeader> {
1123 self.blocks.iter().filter_map(|block| match block {
1124 Block::Protect(protect) => Some(protect),
1125 _ => None,
1126 })
1127 }
1128
1129 fn source_bytes(&self) -> Result<Vec<u8>> {
1130 self.source.bytes()
1131 }
1132
1133 pub fn repair_protect_head(&self) -> Result<Vec<u8>> {
1134 if let Some(recovery) = self
1135 .new_subs()
1136 .find(|sub| sub.kind == NewSubKind::RecoveryRecord)
1137 {
1138 return repair_newsub_recovery_bytes(
1139 &self.source_bytes()?,
1140 self.sfx_offset,
1141 self,
1142 recovery,
1143 );
1144 }
1145 let protect = self.protect_records().next().ok_or(Error::InvalidHeader(
1146 "RAR 2.x archive does not contain a PROTECT_HEAD recovery record",
1147 ))?;
1148 repair_protect_head_bytes(&self.source_bytes()?, self.sfx_offset, protect)
1149 }
1150
1151 pub fn extract_to<F>(&self, options: crate::ArchiveReadOptions<'_>, mut open: F) -> Result<()>
1153 where
1154 F: FnMut(&ExtractedEntryMeta) -> Result<Box<dyn Write>>,
1155 {
1156 let password = options.password;
1157 let mut session = DecoderSession::new_with_password(self.main.is_solid(), password);
1158 for file in self.files() {
1159 if file.is_split_before() || file.is_split_after() {
1160 return Err(Error::InvalidHeader(
1161 "RAR 1.5 split entry requires multivolume extraction",
1162 ));
1163 }
1164 let meta = file.metadata();
1165 if meta.is_directory {
1166 let _ = open(&meta)?;
1167 continue;
1168 }
1169 let mut writer = open(&meta)?;
1170 if file.is_stored() {
1171 file.write_stored_to(self, password, &mut writer)
1172 .map_err(|error| file.entry_error("extracting", error))?;
1173 } else {
1174 session
1175 .write_file_to(self, file, &mut writer)
1176 .map_err(|error| file.entry_error("extracting", error))?;
1177 }
1178 }
1179 Ok(())
1180 }
1181
1182 pub fn archive_comment(&self) -> Result<Option<Vec<u8>>> {
1183 if let Some(comment) = self.blocks.iter().find_map(|block| match block {
1184 Block::Comment(comment) => Some(comment),
1185 _ => None,
1186 }) {
1187 return comment.unpacked_data(self).map(Some);
1188 }
1189
1190 let Some(comment) = self
1191 .new_subs()
1192 .find(|sub| sub.kind == NewSubKind::ArchiveComment)
1193 else {
1194 return Ok(None);
1195 };
1196 let data = comment.file.unpacked_data(self)?;
1197 comment.file.verify_crc32(&data)?;
1198 Ok(Some(data))
1199 }
1200}
1201
1202fn classify_new_sub(name: &[u8]) -> NewSubKind {
1203 match name {
1204 b"CMT" => NewSubKind::ArchiveComment,
1205 b"RR" => NewSubKind::RecoveryRecord,
1206 _ => NewSubKind::Unknown(name.to_vec()),
1207 }
1208}
1209
1210fn parse_main_header(input: &[u8], block: &BlockHeader) -> Result<MainHeader> {
1211 if block.head_size < 13 {
1212 return Err(Error::InvalidHeader("RAR 1.5 main header is too short"));
1213 }
1214 let start = block.offset;
1215 let head_end = start + block.head_size as usize;
1216 if head_end > input.len() {
1217 return Err(Error::TooShort);
1218 }
1219
1220 let encrypt_version = if block.flags & MHD_ENCRYPTVER != 0 {
1221 Some(*input.get(start + 13).ok_or(Error::TooShort)?)
1222 } else {
1223 None
1224 };
1225
1226 Ok(MainHeader {
1227 head_crc: block.head_crc,
1228 flags: block.flags,
1229 head_size: block.head_size,
1230 reserved1: read_u16(input, start + 7)?,
1231 reserved2: read_u32(input, start + 9)?,
1232 encrypt_version,
1233 })
1234}
1235
1236fn parse_comment_header(input: &[u8], block: BlockHeader) -> Result<CommentHeader> {
1237 if block.head_size < 13 {
1238 return Err(Error::InvalidHeader("RAR 1.5 comment header is too short"));
1239 }
1240 let start = block.offset;
1241 Ok(CommentHeader {
1242 block,
1243 unp_size: read_u16(input, start + 7)?,
1244 unp_ver: *input.get(start + 9).ok_or(Error::TooShort)?,
1245 method: *input.get(start + 10).ok_or(Error::TooShort)?,
1246 comment_crc: read_u16(input, start + 11)?,
1247 packed_range: 0..0,
1248 })
1249}
1250
1251fn parse_protect_header(
1252 input: &[u8],
1253 block: &BlockHeader,
1254 archive_offset: usize,
1255 total_size: usize,
1256) -> Result<ProtectHeader> {
1257 if block.head_size != 26 {
1258 return Err(Error::InvalidHeader(
1259 "RAR 2.x recovery header size is invalid",
1260 ));
1261 }
1262 let add_size = block.add_size.ok_or(Error::InvalidHeader(
1263 "RAR 2.x recovery header is missing data size",
1264 ))?;
1265 let rec_sectors = read_u16(input, 12)?;
1266 let total_blocks = read_u32(input, 14)?;
1267 let expected_add_size = u64::from(total_blocks)
1268 .checked_mul(2)
1269 .and_then(|size| size.checked_add(u64::from(rec_sectors) * 512))
1270 .ok_or(Error::InvalidHeader("RAR 2.x recovery data size overflows"))?;
1271 if add_size != expected_add_size {
1272 return Err(Error::InvalidHeader(
1273 "RAR 2.x recovery data size does not match header",
1274 ));
1275 }
1276 let mark: [u8; 8] = input
1277 .get(18..26)
1278 .ok_or(Error::TooShort)?
1279 .try_into()
1280 .expect("RAR protect mark size");
1281 let data_start = archive_offset
1282 .checked_add(block.offset)
1283 .and_then(|offset| offset.checked_add(block.head_size as usize))
1284 .ok_or(Error::InvalidHeader(
1285 "RAR 2.x recovery data range overflows",
1286 ))?;
1287 let data_end = archive_offset
1288 .checked_add(block.offset)
1289 .and_then(|offset| offset.checked_add(total_size))
1290 .ok_or(Error::InvalidHeader(
1291 "RAR 2.x recovery data range overflows",
1292 ))?;
1293 Ok(ProtectHeader {
1294 block: block.clone(),
1295 version: *input.get(11).ok_or(Error::TooShort)?,
1296 rec_sectors,
1297 total_blocks,
1298 mark,
1299 data_range: data_start..data_end,
1300 })
1301}
1302
1303fn repair_protect_head_bytes(
1304 source: &[u8],
1305 sfx_offset: usize,
1306 protect: &ProtectHeader,
1307) -> Result<Vec<u8>> {
1308 if protect.rec_sectors == 0 {
1309 return Err(Error::InvalidHeader(
1310 "RAR 2.x recovery record has no parity sectors",
1311 ));
1312 }
1313 if protect.mark != *b"Protect!" {
1314 return Err(Error::InvalidHeader("RAR 2.x recovery mark is invalid"));
1315 }
1316 let protected_start = sfx_offset;
1317 let protected_len = usize::try_from(protect.total_blocks)
1318 .ok()
1319 .and_then(|blocks| blocks.checked_mul(512))
1320 .ok_or(Error::InvalidHeader(
1321 "RAR 2.x protected sector size overflows",
1322 ))?;
1323 let protected_end = protected_start
1324 .checked_add(protected_len)
1325 .ok_or(Error::InvalidHeader(
1326 "RAR 2.x protected sector range overflows",
1327 ))?;
1328 if protected_end > source.len() {
1329 return Err(Error::InvalidHeader(
1330 "RAR 2.x protected sector range is invalid",
1331 ));
1332 }
1333 let recovery_data = source
1334 .get(protect.data_range.clone())
1335 .ok_or(Error::TooShort)?;
1336 let declared_blocks = usize::try_from(protect.total_blocks).map_err(|_| {
1337 Error::InvalidHeader("RAR 2.x recovery protected sector count overflows usize")
1338 })?;
1339 let tag_len = declared_blocks
1340 .checked_mul(2)
1341 .ok_or(Error::InvalidHeader("RAR 2.x recovery tag size overflows"))?;
1342 let parity_len =
1343 usize::from(protect.rec_sectors)
1344 .checked_mul(512)
1345 .ok_or(Error::InvalidHeader(
1346 "RAR 2.x recovery parity size overflows",
1347 ))?;
1348 if recovery_data.len() != tag_len + parity_len {
1349 return Err(Error::InvalidHeader(
1350 "RAR 2.x recovery data size is invalid",
1351 ));
1352 }
1353 let tags = &recovery_data[..tag_len];
1354 let parity = &recovery_data[tag_len..];
1355 let repairable_blocks = declared_blocks.min(protect.block.offset / 512);
1359
1360 let mut damaged = Vec::new();
1361 for index in 0..repairable_blocks {
1362 let sector_start = protected_start + index * 512;
1363 let sector = &source[sector_start..sector_start + 512];
1364 let actual = (!crc32(sector) & 0xffff) as u16;
1365 let expected = read_u16(tags, index * 2)?;
1366 if actual != expected {
1367 damaged.push(index);
1368 }
1369 }
1370 if damaged.is_empty() {
1371 return Ok(source.to_vec());
1372 }
1373 if damaged.len() > usize::from(protect.rec_sectors) {
1374 return Err(Error::InvalidHeader(
1375 "RAR 2.x recovery damage exceeds parity sector count",
1376 ));
1377 }
1378
1379 let mut used_slots = vec![false; usize::from(protect.rec_sectors)];
1380 for &index in &damaged {
1381 let slot = index % usize::from(protect.rec_sectors);
1382 if used_slots[slot] {
1383 return Err(Error::InvalidHeader(
1384 "RAR 2.x recovery cannot repair multiple sectors in the same parity group",
1385 ));
1386 }
1387 used_slots[slot] = true;
1388 }
1389
1390 let mut repaired = source.to_vec();
1391 for &missing_index in &damaged {
1392 let slot = missing_index % usize::from(protect.rec_sectors);
1393 let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1394 for index in (slot..repairable_blocks).step_by(usize::from(protect.rec_sectors)) {
1395 if index == missing_index {
1396 continue;
1397 }
1398 let sector_start = protected_start + index * 512;
1399 for (out, byte) in sector
1400 .iter_mut()
1401 .zip(&repaired[sector_start..sector_start + 512])
1402 {
1403 *out ^= *byte;
1404 }
1405 }
1406 let sector_start = protected_start + missing_index * 512;
1407 repaired[sector_start..sector_start + 512].copy_from_slice(§or);
1408 let actual = (!crc32(§or) & 0xffff) as u16;
1409 let expected = u16::from_le_bytes(
1410 tags[missing_index * 2..missing_index * 2 + 2]
1411 .try_into()
1412 .unwrap(),
1413 );
1414 if actual != expected {
1415 return Err(Error::CrcMismatch { expected, actual });
1416 }
1417 }
1418 Ok(repaired)
1419}
1420
1421fn repair_newsub_recovery_bytes(
1422 source: &[u8],
1423 sfx_offset: usize,
1424 archive: &Archive,
1425 recovery: &NewSubHeader,
1426) -> Result<Vec<u8>> {
1427 let recovery_data = newsub_recovery_data(archive, recovery)?;
1428 let expected_unpacked = usize::try_from(recovery.file.unp_size)
1429 .map_err(|_| Error::InvalidHeader("RAR 3.x recovery unpacked size overflows usize"))?;
1430 if recovery_data.len() != expected_unpacked {
1431 return Err(Error::InvalidHeader(
1432 "RAR 3.x recovery data size does not match unpacked size",
1433 ));
1434 }
1435 let protected_start = sfx_offset;
1436 let protected_end =
1437 sfx_offset
1438 .checked_add(recovery.file.block.offset)
1439 .ok_or(Error::InvalidHeader(
1440 "RAR 3.x recovery protected range overflows",
1441 ))?;
1442 if protected_end > source.len() || protected_start > protected_end {
1443 return Err(Error::InvalidHeader(
1444 "RAR 3.x recovery protected range is invalid",
1445 ));
1446 }
1447 let protected_len = protected_end - protected_start;
1448 let protected_sectors = protected_len.div_ceil(512);
1449 if protected_sectors == 0 {
1450 return Err(Error::InvalidHeader(
1451 "RAR 3.x recovery record has no protected sectors",
1452 ));
1453 }
1454 let tag_len = protected_sectors
1455 .checked_mul(2)
1456 .ok_or(Error::InvalidHeader("RAR 3.x recovery tag size overflows"))?;
1457 if recovery_data.len() <= tag_len || (recovery_data.len() - tag_len) % 512 != 0 {
1458 return Err(Error::InvalidHeader(
1459 "RAR 3.x recovery data size is invalid",
1460 ));
1461 }
1462 let parity_sectors = (recovery_data.len() - tag_len) / 512;
1463 if parity_sectors == 0 {
1464 return Err(Error::InvalidHeader(
1465 "RAR 3.x recovery record has no parity sectors",
1466 ));
1467 }
1468 let tags = &recovery_data[..tag_len];
1469 let parity = &recovery_data[tag_len..];
1470
1471 let mut damaged = Vec::new();
1472 for index in 0..protected_sectors {
1473 let sector = protected_sector(source, protected_start, protected_len, index)?;
1474 let actual = (!crc32(§or) & 0xffff) as u16;
1475 let expected = read_u16(tags, index * 2)?;
1476 if actual != expected {
1477 damaged.push(index);
1478 }
1479 }
1480 if damaged.is_empty() {
1481 return Ok(source.to_vec());
1482 }
1483 if damaged.len() > parity_sectors {
1484 return Err(Error::InvalidHeader(
1485 "RAR 3.x recovery damage exceeds parity sector count",
1486 ));
1487 }
1488
1489 let mut used_slots = vec![false; parity_sectors];
1490 for &index in &damaged {
1491 let slot = index % parity_sectors;
1492 if used_slots[slot] {
1493 return Err(Error::InvalidHeader(
1494 "RAR 3.x recovery cannot repair multiple sectors in the same parity group",
1495 ));
1496 }
1497 used_slots[slot] = true;
1498 }
1499
1500 let mut repaired = source.to_vec();
1501 for &missing_index in &damaged {
1502 let slot = missing_index % parity_sectors;
1503 let mut sector = parity[slot * 512..slot * 512 + 512].to_vec();
1504 for index in (slot..protected_sectors).step_by(parity_sectors) {
1505 if index == missing_index {
1506 continue;
1507 }
1508 let other = protected_sector(&repaired, protected_start, protected_len, index)?;
1509 for (out, byte) in sector.iter_mut().zip(other) {
1510 *out ^= byte;
1511 }
1512 }
1513 let actual = (!crc32(§or) & 0xffff) as u16;
1514 let expected = u16::from_le_bytes(
1515 tags[missing_index * 2..missing_index * 2 + 2]
1516 .try_into()
1517 .unwrap(),
1518 );
1519 if actual != expected {
1520 return Err(Error::CrcMismatch { expected, actual });
1521 }
1522 let sector_start = protected_start + missing_index * 512;
1523 let write_len = 512.min(protected_end - sector_start);
1524 repaired[sector_start..sector_start + write_len].copy_from_slice(§or[..write_len]);
1525 }
1526
1527 Ok(repaired)
1528}
1529
1530fn newsub_recovery_data(archive: &Archive, recovery: &NewSubHeader) -> Result<Vec<u8>> {
1531 if recovery.file.is_encrypted() {
1532 return Err(Error::UnsupportedFeature {
1533 version: ArchiveVersion::Rar30,
1534 feature: "encrypted RAR 3.x NEWSUB recovery record",
1535 });
1536 }
1537 if recovery.file.method == 0x30 {
1538 if recovery.file.pack_size != recovery.file.unp_size {
1539 return Err(Error::InvalidHeader(
1540 "RAR 3.x recovery record packed size does not match unpacked size",
1541 ));
1542 }
1543 return recovery.file.stored_data(archive);
1544 }
1545 let mut session = DecoderSession::new(false);
1546 session.decode_file_data(archive, &recovery.file)
1547}
1548
1549fn protected_sector(
1550 source: &[u8],
1551 protected_start: usize,
1552 protected_len: usize,
1553 index: usize,
1554) -> Result<[u8; 512]> {
1555 let sector_offset = index.checked_mul(512).ok_or(Error::InvalidHeader(
1556 "RAR 3.x recovery sector offset overflows",
1557 ))?;
1558 if sector_offset >= protected_len {
1559 return Err(Error::InvalidHeader(
1560 "RAR 3.x recovery sector offset is invalid",
1561 ));
1562 }
1563 let sector_start = protected_start
1564 .checked_add(sector_offset)
1565 .ok_or(Error::InvalidHeader(
1566 "RAR 3.x recovery sector range overflows",
1567 ))?;
1568 let available = 512.min(protected_len - sector_offset);
1569 let mut sector = [0u8; 512];
1570 sector[..available].copy_from_slice(
1571 source
1572 .get(sector_start..sector_start + available)
1573 .ok_or(Error::TooShort)?,
1574 );
1575 Ok(sector)
1576}
1577
1578pub fn repair_rev3_volumes_to<F>(
1579 data_volumes: &[Option<&[u8]>],
1580 recovery_count: usize,
1581 recovery_volumes: &[(usize, &[u8])],
1582 mut write: F,
1583) -> Result<()>
1584where
1585 F: FnMut(usize, &[u8]) -> Result<()>,
1586{
1587 for (index, bytes) in rars_recovery::rar3::reconstruct_data_volumes(
1588 data_volumes,
1589 recovery_count,
1590 recovery_volumes,
1591 )
1592 .map_err(Error::from)?
1593 .into_iter()
1594 .enumerate()
1595 {
1596 let bytes = truncate_repaired_rev3_volume(bytes)?;
1597 write(index, &bytes)?;
1598 }
1599 Ok(())
1600}
1601
1602fn truncate_repaired_rev3_volume(mut bytes: Vec<u8>) -> Result<Vec<u8>> {
1603 let Ok(archive) = Archive::parse(&bytes) else {
1604 return Ok(bytes);
1605 };
1606 let Some(end) = archive.blocks.iter().find_map(|block| match block {
1607 Block::End(end) => Some(end),
1608 _ => None,
1609 }) else {
1610 return Ok(bytes);
1611 };
1612 let end_pos = archive
1613 .sfx_offset
1614 .checked_add(end.offset)
1615 .and_then(|offset| offset.checked_add(block_total_size(end).ok()?))
1616 .ok_or(Error::InvalidHeader(
1617 "RAR 3 repaired volume end offset overflows",
1618 ))?;
1619 if end_pos < bytes.len() && bytes[end_pos..].iter().all(|&byte| byte == 0) {
1620 bytes.truncate(end_pos);
1621 }
1622 Ok(bytes)
1623}
1624
1625struct EncryptedHeader {
1626 block: BlockHeader,
1627 header: Vec<u8>,
1628 total_size: usize,
1629}
1630
1631#[derive(Default)]
1632struct EncryptedHeaderCipherCache {
1633 salt: Option<[u8; 8]>,
1634 cipher: Option<Rar30Cipher>,
1635}
1636
1637impl EncryptedHeaderCipherCache {
1638 fn cipher(&mut self, password: &[u8], salt: [u8; 8]) -> Result<Rar30Cipher> {
1639 if self.salt != Some(salt) {
1640 self.salt = Some(salt);
1641 self.cipher =
1642 Some(Rar30Cipher::new(password, Some(salt)).map_err(map_rar30_crypto_error)?);
1643 }
1644 Ok(self
1645 .cipher
1646 .as_ref()
1647 .expect("RAR 3 encrypted header cipher cache initialized")
1648 .clone())
1649 }
1650}
1651
1652fn map_rar30_crypto_error(error: Rar30Error) -> Error {
1653 Error::from(error)
1654}
1655
1656fn decrypt_encrypted_header_at(
1657 archive: &[u8],
1658 offset: usize,
1659 password: &[u8],
1660 cipher_cache: &mut EncryptedHeaderCipherCache,
1661) -> Result<EncryptedHeader> {
1662 let salt = read_header_salt(archive, offset)?;
1663 let first_ciphertext = archive
1664 .get(offset + 8..offset + 24)
1665 .ok_or(Error::TooShort)?;
1666 let mut cipher = cipher_cache.cipher(password, salt)?;
1667 let mut first_block = [0u8; 16];
1668 first_block.copy_from_slice(first_ciphertext);
1669 cipher
1670 .decrypt_in_place(&mut first_block)
1671 .map_err(map_rar30_crypto_error)?;
1672 let head_size = read_u16(&first_block, 5)? as usize;
1673 if head_size < 7 {
1674 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1675 }
1676 let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1677 let encrypted_start = offset
1678 .checked_add(8)
1679 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1680 let encrypted_end = encrypted_start
1681 .checked_add(encrypted_header_size)
1682 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1683 let encrypted_rest = archive
1684 .get(offset + 24..encrypted_end)
1685 .ok_or(Error::TooShort)?;
1686 let mut header = Vec::with_capacity(encrypted_header_size);
1687 header.extend_from_slice(&first_block);
1688 header.extend_from_slice(encrypted_rest);
1689 cipher
1690 .decrypt_in_place(&mut header[16..])
1691 .map_err(map_rar30_crypto_error)?;
1692 header.truncate(head_size);
1693
1694 let mut block = parse_block_header(&header, 0)?;
1695 block.offset = offset;
1696 let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1697 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1698 let total_size = 8usize
1699 .checked_add(encrypted_header_size)
1700 .and_then(|size| size.checked_add(payload_size))
1701 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1702 Ok(EncryptedHeader {
1703 block,
1704 header,
1705 total_size,
1706 })
1707}
1708
1709fn read_encrypted_header_at(
1710 file: &mut File,
1711 file_len: u64,
1712 archive_offset: usize,
1713 offset: usize,
1714 password: &[u8],
1715 cipher_cache: &mut EncryptedHeaderCipherCache,
1716) -> Result<EncryptedHeader> {
1717 let absolute = archive_offset
1718 .checked_add(offset)
1719 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1720 if absolute as u64 + 24 > file_len {
1721 return Err(Error::TooShort);
1722 }
1723 let first = read_exact_at(file, absolute, 24)?;
1724 let salt = read_header_salt(&first, 0)?;
1725 let mut cipher = cipher_cache.cipher(password, salt)?;
1726 let mut first_block = [0u8; 16];
1727 first_block.copy_from_slice(&first[8..24]);
1728 cipher
1729 .decrypt_in_place(&mut first_block)
1730 .map_err(map_rar30_crypto_error)?;
1731 let head_size = read_u16(&first_block, 5)? as usize;
1732 if head_size < 7 {
1733 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
1734 }
1735 let encrypted_header_size = checked_align16(head_size, "RAR 1.5 block size overflows usize")?;
1736 let encrypted_start = absolute
1737 .checked_add(8)
1738 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1739 if encrypted_start as u64 + encrypted_header_size as u64 > file_len {
1740 return Err(Error::TooShort);
1741 }
1742 let encrypted_rest = read_exact_at(file, encrypted_start + 16, encrypted_header_size - 16)?;
1743 let mut header = Vec::with_capacity(encrypted_header_size);
1744 header.extend_from_slice(&first_block);
1745 header.extend_from_slice(&encrypted_rest);
1746 cipher
1747 .decrypt_in_place(&mut header[16..])
1748 .map_err(map_rar30_crypto_error)?;
1749 header.truncate(head_size);
1750
1751 let mut block = parse_block_header(&header, 0)?;
1752 block.offset = offset;
1753 let payload_size = usize::try_from(block.add_size.unwrap_or(0))
1754 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1755 let total_size = 8usize
1756 .checked_add(encrypted_header_size)
1757 .and_then(|size| size.checked_add(payload_size))
1758 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
1759 Ok(EncryptedHeader {
1760 block,
1761 header,
1762 total_size,
1763 })
1764}
1765
1766fn read_header_salt(input: &[u8], offset: usize) -> Result<[u8; 8]> {
1767 input
1768 .get(offset..offset + 8)
1769 .ok_or(Error::TooShort)
1770 .map(|salt| salt.try_into().expect("RAR 3 salt size"))
1771}
1772
1773fn parse_file_like_header(
1774 input: &[u8],
1775 block: BlockHeader,
1776 archive_offset: usize,
1777) -> Result<FileHeader> {
1778 if block.head_size < 32 {
1779 return Err(Error::InvalidHeader("RAR 1.5 file header is too short"));
1780 }
1781 if block.flags & LONG_BLOCK == 0 {
1782 return Err(Error::InvalidHeader(
1783 "RAR 1.5 file header is missing packed data size",
1784 ));
1785 }
1786
1787 let start = block.offset;
1788 let head_end = start + block.head_size as usize;
1789 if head_end > input.len() {
1790 return Err(Error::TooShort);
1791 }
1792
1793 let pack_low = read_u32(input, start + 7)? as u64;
1794 let unp_low = read_u32(input, start + 11)? as u64;
1795 let host_os = input[start + 15];
1796 let file_crc = read_u32(input, start + 16)?;
1797 let file_time = read_u32(input, start + 20)?;
1798 let unp_ver = input[start + 24];
1799 let method = input[start + 25];
1800 let name_size = read_u16(input, start + 26)? as usize;
1801 let attr = read_u32(input, start + 28)?;
1802 let mut pos = start + 32;
1803
1804 let (pack_size, unp_size) = if block.flags & FHD_LARGE != 0 {
1805 let high_pack = read_u32(input, pos)? as u64;
1806 let high_unp = read_u32(input, pos + 4)? as u64;
1807 pos += 8;
1808 ((high_pack << 32) | pack_low, (high_unp << 32) | unp_low)
1809 } else {
1810 (pack_low, unp_low)
1811 };
1812
1813 let name_end = pos
1814 .checked_add(name_size)
1815 .ok_or(Error::InvalidHeader("RAR 1.5 file name size overflows"))?;
1816 if name_end > head_end {
1817 return Err(Error::InvalidHeader(
1818 "RAR 1.5 file name extends beyond header",
1819 ));
1820 }
1821 let name = decode_file_name(&input[pos..name_end], block.flags);
1822 pos = name_end;
1823
1824 let salt = if block.flags & FHD_SALT != 0 {
1825 let salt_end = pos
1826 .checked_add(8)
1827 .ok_or(Error::InvalidHeader("RAR 1.5 salt size overflows"))?;
1828 if salt_end > head_end {
1829 return Err(Error::InvalidHeader(
1830 "RAR 1.5 salt extends beyond file header",
1831 ));
1832 }
1833 let salt_bytes = input.get(pos..salt_end).ok_or(Error::TooShort)?;
1834 pos = salt_end;
1835 Some(
1836 salt_bytes
1837 .try_into()
1838 .expect("RAR 1.5 salt slice has fixed length"),
1839 )
1840 } else {
1841 None
1842 };
1843
1844 let file_comment = if block.flags & FHD_COMMENT != 0 {
1845 if pos + 2 <= head_end {
1846 let comment_len = read_u16(input, pos)? as usize;
1847 let comment_total = comment_len
1848 .checked_add(2)
1849 .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1850 let comment_end = pos
1851 .checked_add(comment_total)
1852 .ok_or(Error::InvalidHeader("RAR 1.5 file comment size overflows"))?;
1853 if comment_end <= head_end {
1854 let comment = input[pos..comment_end].to_vec();
1855 pos = comment_end;
1856 comment
1857 } else {
1858 Vec::new()
1859 }
1860 } else {
1861 Vec::new()
1862 }
1863 } else {
1864 Vec::new()
1865 };
1866
1867 let ext_time = if block.flags & FHD_EXTTIME != 0 {
1868 input[pos..head_end].to_vec()
1869 } else {
1870 Vec::new()
1871 };
1872 let data_start = head_end;
1873 let data_len = usize::try_from(pack_size)
1874 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
1875 let data_end = data_start
1876 .checked_add(data_len)
1877 .ok_or(Error::InvalidHeader(
1878 "RAR 1.5 packed file size overflows usize",
1879 ))?;
1880 Ok(FileHeader {
1881 block,
1882 pack_size,
1883 unp_size,
1884 host_os,
1885 file_crc,
1886 file_time,
1887 unp_ver,
1888 method,
1889 name,
1890 attr,
1891 salt,
1892 file_comment,
1893 ext_time,
1894 packed_range: archive_offset + data_start..archive_offset + data_end,
1895 })
1896}
1897
1898fn decode_file_name(raw: &[u8], flags: u16) -> Vec<u8> {
1899 if flags & FHD_UNICODE == 0 {
1900 return raw.to_vec();
1901 }
1902
1903 let Some(zero_pos) = raw.iter().position(|byte| *byte == 0) else {
1904 return raw.to_vec();
1905 };
1906 if zero_pos + 1 >= raw.len() {
1907 return raw[..zero_pos].to_vec();
1908 }
1909
1910 let fallback = &raw[..zero_pos];
1911 let high_byte = raw[zero_pos + 1];
1912 let encoded = &raw[zero_pos + 2..];
1913 let mut pos = 0usize;
1914 let mut flag_byte = 0u8;
1915 let mut flag_bits = 0u8;
1916 let mut dst_pos = 0usize;
1917 let mut units = Vec::new();
1918
1919 while pos < encoded.len() {
1920 if flag_bits == 0 {
1921 flag_byte = encoded[pos];
1922 pos += 1;
1923 flag_bits = 8;
1924 }
1925 let mode = flag_byte >> 6;
1926 flag_byte <<= 2;
1927 flag_bits -= 2;
1928
1929 match mode {
1930 0 => {
1931 let Some(&low) = encoded.get(pos) else {
1932 return raw.to_vec();
1933 };
1934 pos += 1;
1935 units.push(u16::from(low));
1936 dst_pos += 1;
1937 }
1938 1 => {
1939 let Some(&low) = encoded.get(pos) else {
1940 return raw.to_vec();
1941 };
1942 pos += 1;
1943 units.push((u16::from(high_byte) << 8) | u16::from(low));
1944 dst_pos += 1;
1945 }
1946 2 => {
1947 let Some((&low, &high)) = encoded.get(pos).zip(encoded.get(pos + 1)) else {
1948 return raw.to_vec();
1949 };
1950 pos += 2;
1951 units.push((u16::from(high) << 8) | u16::from(low));
1952 dst_pos += 1;
1953 }
1954 3 => {
1955 let Some(&length_byte) = encoded.get(pos) else {
1956 return raw.to_vec();
1957 };
1958 pos += 1;
1959 let (count, correction, high) = if length_byte & 0x80 != 0 {
1960 let Some(&correction) = encoded.get(pos) else {
1961 return raw.to_vec();
1962 };
1963 pos += 1;
1964 ((length_byte & 0x7f) as usize + 2, correction, high_byte)
1965 } else {
1966 (length_byte as usize + 2, 0, 0)
1967 };
1968 for _ in 0..count {
1969 let low = fallback
1970 .get(dst_pos)
1971 .copied()
1972 .unwrap_or(b'?')
1973 .wrapping_add(correction);
1974 units.push((u16::from(high) << 8) | u16::from(low));
1975 dst_pos += 1;
1976 }
1977 }
1978 _ => unreachable!("2-bit filename mode"),
1979 }
1980 }
1981
1982 char::decode_utf16(units)
1983 .map(|unit| unit.unwrap_or(char::REPLACEMENT_CHARACTER))
1984 .collect::<String>()
1985 .into_bytes()
1986}
1987
1988fn read_block_header_at(
1989 file: &mut File,
1990 file_len: u64,
1991 archive_offset: usize,
1992 offset: usize,
1993) -> Result<BlockHeader> {
1994 let absolute = archive_offset
1995 .checked_add(offset)
1996 .ok_or(Error::InvalidHeader("RAR 1.5 block offset overflows usize"))?;
1997 if absolute as u64 + 7 > file_len {
1998 return Err(Error::TooShort);
1999 }
2000 let base = read_exact_at(file, absolute, 7)?;
2001 let head_size = read_u16(&base, 5)? as usize;
2002 if head_size < 7 {
2003 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2004 }
2005 if absolute as u64 + head_size as u64 > file_len {
2006 return Err(Error::TooShort);
2007 }
2008 let header = if head_size == 7 {
2009 base
2010 } else {
2011 read_exact_at(file, absolute, head_size)?
2012 };
2013 let mut block = parse_block_header(&header, 0)?;
2014 block.offset = offset;
2015 Ok(block)
2016}
2017
2018fn relative_block(block: &BlockHeader) -> BlockHeader {
2019 let mut relative = block.clone();
2020 relative.offset = 0;
2021 relative
2022}
2023
2024struct CrcWriter<'a, W: Write + ?Sized> {
2025 inner: &'a mut W,
2026 crc: &'a mut Crc32,
2027}
2028
2029impl<W: Write + ?Sized> Write for CrcWriter<'_, W> {
2030 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2031 let written = self.inner.write(buf)?;
2032 self.crc.update(&buf[..written]);
2033 Ok(written)
2034 }
2035
2036 fn flush(&mut self) -> std::io::Result<()> {
2037 self.inner.flush()
2038 }
2039}
2040
2041fn parse_block_header(input: &[u8], offset: usize) -> Result<BlockHeader> {
2042 if input.len() < offset + 7 {
2043 return Err(Error::TooShort);
2044 }
2045 let head_crc = read_u16(input, offset)?;
2046 let head_type = input[offset + 2];
2047 let flags = read_u16(input, offset + 3)?;
2048 let head_size = read_u16(input, offset + 5)?;
2049 if head_size < 7 {
2050 return Err(Error::InvalidHeader("RAR 1.5 block header is too short"));
2051 }
2052 let add_size = if flags & LONG_BLOCK != 0 {
2053 Some(read_u32(input, offset + 7)? as u64)
2054 } else {
2055 None
2056 };
2057 if offset + head_size as usize > input.len() {
2058 return Err(Error::TooShort);
2059 }
2060 if head_type != MARK_HEAD && should_validate_header_crc(head_type) {
2061 let header_end = header_crc_end(input, offset, head_type, flags, head_size)?;
2062 let actual = (crc32(&input[offset + 2..header_end]) & 0xffff) as u16;
2063 if actual != head_crc {
2064 return Err(Error::CrcMismatch {
2065 expected: head_crc,
2066 actual,
2067 });
2068 }
2069 }
2070 validate_legacy_auth_block_size(head_type, head_size)?;
2071
2072 Ok(BlockHeader {
2073 head_crc,
2074 head_type,
2075 flags,
2076 head_size,
2077 add_size,
2078 offset,
2079 })
2080}
2081
2082fn header_crc_end(
2083 input: &[u8],
2084 offset: usize,
2085 head_type: u8,
2086 flags: u16,
2087 head_size: u16,
2088) -> Result<usize> {
2089 let full_end = offset + head_size as usize;
2090 let fixed_end = match head_type {
2091 MAIN_HEAD if flags & MHD_COMMENT != 0 => Some(offset + 13),
2092 COMM_HEAD => Some(offset + 13),
2093 FILE_HEAD if flags & FHD_COMMENT != 0 => Some(file_header_comment_crc_end(input, offset)?),
2094 _ => None,
2095 };
2096 Ok(fixed_end.unwrap_or(full_end).min(full_end))
2097}
2098
2099fn file_header_comment_crc_end(input: &[u8], offset: usize) -> Result<usize> {
2100 if input.len() < offset + 32 {
2101 return Err(Error::TooShort);
2102 }
2103 let flags = read_u16(input, offset + 3)?;
2104 let name_size = read_u16(input, offset + 26)? as usize;
2105 let mut end = offset + 32;
2106 if flags & FHD_LARGE != 0 {
2107 end = end
2108 .checked_add(8)
2109 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2110 }
2111 end = end
2112 .checked_add(name_size)
2113 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2114 if flags & FHD_SALT != 0 {
2115 end = end
2116 .checked_add(8)
2117 .ok_or(Error::InvalidHeader("RAR 1.5 file header size overflows"))?;
2118 }
2119 Ok(end)
2120}
2121
2122fn should_validate_header_crc(head_type: u8) -> bool {
2123 !matches!(head_type, 0x76 | 0x79)
2126}
2127
2128fn validate_legacy_auth_block_size(head_type: u8, head_size: u16) -> Result<()> {
2129 let minimum = match head_type {
2130 0x76 => 21,
2131 0x79 => 182,
2132 _ => return Ok(()),
2133 };
2134 if head_size < minimum {
2135 return Err(Error::InvalidHeader(
2136 "RAR legacy authenticity block is too short",
2137 ));
2138 }
2139 Ok(())
2140}
2141
2142fn block_total_size(block: &BlockHeader) -> Result<usize> {
2143 let total = block.head_size as u64 + block.add_size.unwrap_or(0);
2144 usize::try_from(total).map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2145}
2146
2147fn file_block_total_size(
2148 block: &BlockHeader,
2149 default_total: usize,
2150 pack_size: u64,
2151) -> Result<usize> {
2152 let low_payload_size = usize::try_from(block.add_size.unwrap_or(0))
2153 .map_err(|_| Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2154 let header_prefix = default_total
2155 .checked_sub(low_payload_size)
2156 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2157 let pack_size = usize::try_from(pack_size)
2158 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2159 header_prefix
2160 .checked_add(pack_size)
2161 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))
2162}
2163
2164fn checked_block_next(block: &BlockHeader, total: usize, archive_len: usize) -> Result<usize> {
2165 let next = block
2166 .offset
2167 .checked_add(total)
2168 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2169 if next > archive_len {
2170 return Err(Error::TooShort);
2171 }
2172 Ok(next)
2173}
2174
2175fn checked_file_block_next(
2176 sfx_offset: usize,
2177 block: &BlockHeader,
2178 total: usize,
2179 file_len: u64,
2180) -> Result<usize> {
2181 let next = block
2182 .offset
2183 .checked_add(total)
2184 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2185 let absolute_next = sfx_offset
2186 .checked_add(next)
2187 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2188 if absolute_next as u64 > file_len {
2189 return Err(Error::TooShort);
2190 }
2191 Ok(next)
2192}
2193
2194fn packed_range(
2195 archive_offset: usize,
2196 block_offset: usize,
2197 total: usize,
2198 pack_size: u64,
2199) -> Result<Range<usize>> {
2200 let pack_size = usize::try_from(pack_size)
2201 .map_err(|_| Error::InvalidHeader("RAR 1.5 packed file size overflows usize"))?;
2202 let block_end = archive_offset
2203 .checked_add(block_offset)
2204 .and_then(|start| start.checked_add(total))
2205 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2206 let block_start = block_end
2207 .checked_sub(pack_size)
2208 .ok_or(Error::InvalidHeader("RAR 1.5 block size overflows usize"))?;
2209 Ok(block_start..block_end)
2210}
2211
2212#[cfg(test)]
2213mod tests {
2214 use super::*;
2215
2216 fn test_write_main_header(out: &mut Vec<u8>, flags: u16) {
2217 let start = out.len();
2218 out.extend_from_slice(&0u16.to_le_bytes());
2219 out.push(MAIN_HEAD);
2220 out.extend_from_slice(&flags.to_le_bytes());
2221 out.extend_from_slice(&13u16.to_le_bytes());
2222 out.extend_from_slice(&0u16.to_le_bytes());
2223 out.extend_from_slice(&0u32.to_le_bytes());
2224 test_write_header_crc(out, start);
2225 }
2226
2227 fn test_write_header_crc(out: &mut [u8], start: usize) {
2228 let crc = (crc32(&out[start + 2..]) & 0xffff) as u16;
2229 out[start..start + 2].copy_from_slice(&crc.to_le_bytes());
2230 }
2231
2232 fn legacy_auth_block(head_type: u8, head_size: u16) -> Vec<u8> {
2233 let mut block = vec![0; head_size as usize];
2234 block[2] = head_type;
2235 block[5..7].copy_from_slice(&head_size.to_le_bytes());
2236 block
2237 }
2238
2239 #[test]
2240 fn rejects_too_short_legacy_auth_blocks_even_without_crc_check() {
2241 assert!(matches!(
2242 parse_block_header(&legacy_auth_block(0x76, 20), 0),
2243 Err(Error::InvalidHeader(
2244 "RAR legacy authenticity block is too short"
2245 ))
2246 ));
2247 assert!(matches!(
2248 parse_block_header(&legacy_auth_block(0x79, 181), 0),
2249 Err(Error::InvalidHeader(
2250 "RAR legacy authenticity block is too short"
2251 ))
2252 ));
2253 }
2254
2255 #[test]
2256 fn accepts_minimum_legacy_auth_block_sizes_with_bad_crc() {
2257 assert_eq!(
2258 parse_block_header(&legacy_auth_block(0x76, 21), 0)
2259 .unwrap()
2260 .head_size,
2261 21
2262 );
2263 assert_eq!(
2264 parse_block_header(&legacy_auth_block(0x79, 182), 0)
2265 .unwrap()
2266 .head_size,
2267 182
2268 );
2269 }
2270
2271 #[test]
2272 fn parses_fhd_large_high_size_fields_from_file_header() {
2273 let name = b"large.bin";
2274 let head_size = 32 + 8 + name.len();
2275 let mut header = Vec::new();
2276 header.extend_from_slice(&0u16.to_le_bytes());
2277 header.push(FILE_HEAD);
2278 header.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2279 header.extend_from_slice(&(head_size as u16).to_le_bytes());
2280 header.extend_from_slice(&0x89ab_cdefu32.to_le_bytes());
2281 header.extend_from_slice(&0x7654_3210u32.to_le_bytes());
2282 header.push(3);
2283 header.extend_from_slice(&0x1234_5678u32.to_le_bytes());
2284 header.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2285 header.push(29);
2286 header.push(0x35);
2287 header.extend_from_slice(&(name.len() as u16).to_le_bytes());
2288 header.extend_from_slice(&0x20u32.to_le_bytes());
2289 header.extend_from_slice(&1u32.to_le_bytes());
2290 header.extend_from_slice(&2u32.to_le_bytes());
2291 header.extend_from_slice(name);
2292
2293 let block = BlockHeader {
2294 head_crc: 0,
2295 head_type: FILE_HEAD,
2296 flags: LONG_BLOCK | FHD_LARGE,
2297 head_size: head_size as u16,
2298 add_size: Some(0x89ab_cdef),
2299 offset: 0,
2300 };
2301 let file = parse_file_like_header(&header, block, 0).unwrap();
2302
2303 assert_eq!(file.pack_size, 0x0000_0001_89ab_cdef);
2304 assert_eq!(file.unp_size, 0x0000_0002_7654_3210);
2305 assert_eq!(file.name, name);
2306 assert_eq!(
2307 file.packed_range,
2308 head_size..head_size + 0x0000_0001_89ab_cdefusize
2309 );
2310 }
2311
2312 #[test]
2313 fn fhd_large_archive_extent_uses_high_packed_size_without_underflowing() {
2314 let name = b"large-zero-low.bin";
2315 let head_size = 32 + 8 + name.len();
2316 let mut archive = Vec::from(RAR15_SIGNATURE);
2317 test_write_main_header(&mut archive, 0);
2318
2319 let start = archive.len();
2320 archive.extend_from_slice(&0u16.to_le_bytes());
2321 archive.push(FILE_HEAD);
2322 archive.extend_from_slice(&(LONG_BLOCK | FHD_LARGE).to_le_bytes());
2323 archive.extend_from_slice(&(head_size as u16).to_le_bytes());
2324 archive.extend_from_slice(&0u32.to_le_bytes());
2325 archive.extend_from_slice(&0u32.to_le_bytes());
2326 archive.push(3);
2327 archive.extend_from_slice(&0u32.to_le_bytes());
2328 archive.extend_from_slice(&0x5a21_0000u32.to_le_bytes());
2329 archive.push(29);
2330 archive.push(0x35);
2331 archive.extend_from_slice(&(name.len() as u16).to_le_bytes());
2332 archive.extend_from_slice(&0x20u32.to_le_bytes());
2333 archive.extend_from_slice(&1u32.to_le_bytes());
2334 archive.extend_from_slice(&1u32.to_le_bytes());
2335 archive.extend_from_slice(name);
2336 test_write_header_crc(&mut archive, start);
2337
2338 assert!(matches!(Archive::parse(&archive), Err(Error::TooShort)));
2339 }
2340
2341 fn block_header_with(flags: u16) -> BlockHeader {
2342 BlockHeader {
2343 head_crc: 0,
2344 head_type: FILE_HEAD,
2345 flags,
2346 head_size: 32,
2347 add_size: None,
2348 offset: 0,
2349 }
2350 }
2351
2352 fn file_header_with(flags: u16) -> FileHeader {
2353 FileHeader {
2354 block: block_header_with(flags),
2355 pack_size: 0,
2356 unp_size: 0,
2357 host_os: 0,
2358 file_crc: 0,
2359 file_time: 0,
2360 unp_ver: 29,
2361 method: 0x30,
2362 name: b"entry".to_vec(),
2363 attr: 0,
2364 salt: None,
2365 file_comment: Vec::new(),
2366 ext_time: Vec::new(),
2367 packed_range: 0..0,
2368 }
2369 }
2370
2371 fn main_header_with(flags: u16) -> MainHeader {
2372 MainHeader {
2373 head_crc: 0,
2374 flags,
2375 head_size: 13,
2376 reserved1: 0,
2377 reserved2: 0,
2378 encrypt_version: None,
2379 }
2380 }
2381
2382 #[test]
2383 fn main_header_predicates_match_each_flag_bit() {
2384 let cases = [
2385 (MHD_VOLUME, MainHeader::is_volume as fn(&MainHeader) -> bool),
2386 (MHD_COMMENT, MainHeader::has_archive_comment),
2387 (MHD_SOLID, MainHeader::is_solid),
2388 (MHD_NEWNUMBERING, MainHeader::uses_new_numbering),
2389 (MHD_PROTECT, MainHeader::has_recovery_record),
2390 (MHD_PASSWORD, MainHeader::has_encrypted_headers),
2391 (MHD_FIRSTVOLUME, MainHeader::is_first_volume),
2392 ];
2393 let zero = main_header_with(0);
2394 for (bit, predicate) in cases {
2395 assert!(
2396 !predicate(&zero),
2397 "expected false when bit {bit:#x} is clear"
2398 );
2399 let one = main_header_with(bit);
2400 assert!(predicate(&one), "expected true when bit {bit:#x} is set");
2401 }
2402 }
2403
2404 #[test]
2405 fn file_header_flag_predicates_track_each_bit() {
2406 let cases = [
2407 (
2408 FHD_SPLIT_BEFORE,
2409 FileHeader::is_split_before as fn(&FileHeader) -> bool,
2410 ),
2411 (FHD_SPLIT_AFTER, FileHeader::is_split_after),
2412 (FHD_PASSWORD, FileHeader::is_encrypted),
2413 (FHD_SOLID, FileHeader::is_solid),
2414 (FHD_EXTTIME, FileHeader::has_ext_time),
2415 ];
2416 let zero = file_header_with(0);
2417 for (bit, predicate) in cases {
2418 assert!(
2419 !predicate(&zero),
2420 "expected false on FileHeader for bit {bit:#x}"
2421 );
2422 let one = file_header_with(bit);
2423 assert!(
2424 predicate(&one),
2425 "expected true on FileHeader for bit {bit:#x}"
2426 );
2427 }
2428
2429 let directory = file_header_with(FHD_DIRECTORY_MASK);
2430 assert!(directory.is_directory());
2431 assert!(!file_header_with(0).is_directory());
2432
2433 let stored = file_header_with(0);
2434 assert!(stored.is_stored());
2435 let mut packed = file_header_with(0);
2436 packed.method = 0x33;
2437 assert!(!packed.is_stored());
2438 }
2439
2440 #[test]
2441 fn file_header_comment_extracts_payload_after_two_byte_size_prefix() {
2442 let mut without_flag = file_header_with(0);
2443 without_flag.file_comment = vec![3, 0, b'a', b'b', b'c'];
2444 assert!(!without_flag.has_file_comment());
2445 assert_eq!(without_flag.file_comment().unwrap(), None);
2446
2447 let mut with_flag = file_header_with(FHD_COMMENT);
2448 with_flag.file_comment = vec![3, 0, b'h', b'e', b'y'];
2449 assert!(with_flag.has_file_comment());
2450 assert_eq!(with_flag.file_comment().unwrap().unwrap(), b"hey");
2451
2452 let mut empty_flagged = file_header_with(FHD_COMMENT);
2453 empty_flagged.file_comment.clear();
2454 assert!(!empty_flagged.has_file_comment());
2455
2456 let mut truncated = file_header_with(FHD_COMMENT);
2457 truncated.file_comment = vec![10, 0, b'a'];
2458 assert!(matches!(truncated.file_comment(), Err(Error::TooShort)));
2459 }
2460
2461 #[test]
2462 fn file_header_name_metadata_and_crc_helpers_describe_entry() {
2463 let mut header = file_header_with(0);
2464 header.name = b"r\xc3\xa9sum\xc3\xa9.txt".to_vec();
2465 header.file_crc = crc32(b"hello");
2466 header.attr = 0x20;
2467 header.host_os = 3;
2468 header.file_time = 0x5a21_0000;
2469
2470 assert_eq!(header.name_bytes(), b"r\xc3\xa9sum\xc3\xa9.txt");
2471 assert_eq!(header.name_lossy(), "résumé.txt");
2472 let meta = header.metadata();
2473 assert_eq!(meta.name, header.name);
2474 assert_eq!(meta.attr, 0x20);
2475 assert_eq!(meta.host_os, 3);
2476 assert_eq!(meta.file_time, 0x5a21_0000);
2477 assert!(!meta.is_directory);
2478
2479 header.verify_crc32(b"hello").unwrap();
2480 match header.verify_crc32(b"different") {
2481 Err(Error::Crc32Mismatch { expected, actual }) => {
2482 assert_eq!(expected, header.file_crc);
2483 assert_ne!(actual, expected);
2484 }
2485 other => panic!("expected Crc32Mismatch, got {other:?}"),
2486 }
2487
2488 let directory = file_header_with(FHD_DIRECTORY_MASK);
2489 assert!(directory.metadata().is_directory);
2490
2491 let mut garbage = file_header_with(0);
2493 garbage.name = vec![0xff, 0xfe, b'/', 0x80, b'x'];
2494 let lossy = garbage.name_lossy();
2495 assert!(lossy.ends_with("/\u{fffd}x"), "got {lossy:?}");
2496 }
2497
2498 #[test]
2499 fn newsub_header_name_lossy_delegates_to_inner_file_header() {
2500 let mut file = file_header_with(0);
2501 file.name = b"CMT".to_vec();
2502 let sub = NewSubHeader {
2503 file,
2504 kind: NewSubKind::ArchiveComment,
2505 };
2506 assert_eq!(sub.name_lossy(), "CMT");
2507 }
2508
2509 #[test]
2510 fn writer_options_constructor_and_default_match_documented_targets() {
2511 let default = WriterOptions::default();
2512 assert_eq!(default.target, ArchiveVersion::Rar15);
2513 assert_eq!(default.features, FeatureSet::store_only());
2514
2515 let explicit = WriterOptions::new(ArchiveVersion::Rar20, FeatureSet::store_only());
2516 assert_eq!(explicit.target, ArchiveVersion::Rar20);
2517 assert_eq!(explicit.features, FeatureSet::store_only());
2518 }
2519
2520 fn stored_archive_bytes(name: &[u8], data: &[u8]) -> Vec<u8> {
2521 write_stored_archive(
2522 &[StoredEntry {
2523 name,
2524 data,
2525 file_time: 0,
2526 file_attr: 0x20,
2527 host_os: 3,
2528 password: None,
2529 file_comment: None,
2530 }],
2531 WriterOptions::default(),
2532 )
2533 .unwrap()
2534 }
2535
2536 #[test]
2537 fn archive_parse_owned_consumes_buffer_without_changing_dispatch() {
2538 let bytes = stored_archive_bytes(b"owned.txt", b"hello rar15 owned");
2539 let archive = Archive::parse_owned(bytes.clone()).unwrap();
2540 assert_eq!(archive.files().count(), 1);
2541 let file = archive.files().next().unwrap();
2542 assert_eq!(file.name, b"owned.txt");
2543
2544 let with_options =
2546 Archive::parse_owned_with_options(bytes.clone(), crate::ArchiveReadOptions::default())
2547 .unwrap();
2548 assert_eq!(with_options.files().count(), 1);
2549
2550 let no_password = Archive::parse_owned_with_password(bytes, None).unwrap();
2551 assert_eq!(no_password.files().count(), 1);
2552 }
2553
2554 #[test]
2555 fn archive_parse_owned_with_password_unlocks_encrypted_archive() {
2556 let mut features = FeatureSet::store_only();
2557 features.file_encryption = true;
2558 let bytes = write_stored_archive(
2559 &[StoredEntry {
2560 name: b"locked.txt",
2561 data: b"encrypted owned payload",
2562 file_time: 0,
2563 file_attr: 0x20,
2564 host_os: 3,
2565 password: Some(b"pw"),
2566 file_comment: None,
2567 }],
2568 WriterOptions {
2569 target: ArchiveVersion::Rar20,
2570 features,
2571 compression_level: None,
2572 dictionary_size: None,
2573 },
2574 )
2575 .unwrap();
2576
2577 let archive = Archive::parse_owned_with_password(bytes, Some(b"pw")).unwrap();
2578 let file = archive.files().next().unwrap();
2579 assert!(file.is_encrypted());
2580 }
2581
2582 #[test]
2583 fn file_header_write_packed_data_streams_through_writer() {
2584 let payload = b"write_packed_data direct dump";
2585 let bytes = stored_archive_bytes(b"dump.bin", payload);
2586 let archive = Archive::parse(&bytes).unwrap();
2587 let file = archive.files().next().unwrap();
2588
2589 let mut sink = Vec::new();
2590 file.write_packed_data(&archive, &mut sink).unwrap();
2591 assert_eq!(sink, payload);
2592 }
2593
2594 #[test]
2595 fn file_header_unsupported_compression_describes_method_and_unpack_version() {
2596 let mut header = file_header_with(0);
2597 header.method = 0x33;
2598 header.unp_ver = 26;
2599 let err = header.unsupported_compression();
2600 assert!(matches!(
2601 err,
2602 Error::UnsupportedCompression {
2603 family: "RAR 1.5-4.x",
2604 unpack_version: 26,
2605 method: 0x33,
2606 }
2607 ));
2608 }
2609
2610 #[test]
2611 fn file_header_unsupported_encryption_describes_unpack_version() {
2612 let mut header = file_header_with(FHD_PASSWORD);
2613 header.unp_ver = 36;
2614 let err = header.unsupported_encryption();
2615 assert!(matches!(
2616 err,
2617 Error::UnsupportedEncryption {
2618 family: "RAR 1.5-4.x",
2619 unpack_version: 36,
2620 }
2621 ));
2622 }
2623
2624 #[test]
2625 fn crc_writer_flush_propagates_to_inner_writer() {
2626 struct FlushSpy {
2627 data: Vec<u8>,
2628 flushed: usize,
2629 }
2630 impl Write for FlushSpy {
2631 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
2632 self.data.extend_from_slice(buf);
2633 Ok(buf.len())
2634 }
2635 fn flush(&mut self) -> std::io::Result<()> {
2636 self.flushed += 1;
2637 Ok(())
2638 }
2639 }
2640 let mut inner = FlushSpy {
2641 data: Vec::new(),
2642 flushed: 0,
2643 };
2644 let mut crc = Crc32::new();
2645 let mut writer = CrcWriter {
2646 inner: &mut inner,
2647 crc: &mut crc,
2648 };
2649 writer.write_all(b"hi").unwrap();
2650 writer.flush().unwrap();
2651 assert_eq!(inner.data, b"hi");
2652 assert_eq!(inner.flushed, 1);
2653 }
2654
2655 #[test]
2656 fn parse_main_header_rejects_block_size_below_minimum() {
2657 let block = BlockHeader {
2658 head_crc: 0,
2659 head_type: MAIN_HEAD,
2660 flags: 0,
2661 head_size: 12,
2662 add_size: None,
2663 offset: 0,
2664 };
2665 let err = parse_main_header(&[0u8; 32], &block).unwrap_err();
2666 assert_eq!(
2667 err,
2668 Error::InvalidHeader("RAR 1.5 main header is too short")
2669 );
2670 }
2671
2672 #[test]
2673 fn parse_main_header_rejects_block_extending_past_input_buffer() {
2674 let block = BlockHeader {
2675 head_crc: 0,
2676 head_type: MAIN_HEAD,
2677 flags: 0,
2678 head_size: 13,
2679 add_size: None,
2680 offset: 8,
2681 };
2682 let err = parse_main_header(&[0u8; 16], &block).unwrap_err();
2684 assert_eq!(err, Error::TooShort);
2685 }
2686
2687 #[test]
2688 fn parse_main_header_reads_encrypt_version_when_flag_is_set() {
2689 let mut input = vec![0u8; 14];
2692 input[13] = 0x29;
2693 let block = BlockHeader {
2694 head_crc: 0,
2695 head_type: MAIN_HEAD,
2696 flags: MHD_ENCRYPTVER,
2697 head_size: 14,
2698 add_size: None,
2699 offset: 0,
2700 };
2701 let main = parse_main_header(&input, &block).unwrap();
2702 assert_eq!(main.encrypt_version, Some(0x29));
2703 }
2704
2705 #[test]
2706 fn decode_file_name_returns_raw_when_unicode_flag_is_clear() {
2707 let raw = b"plain.txt";
2708 let decoded = decode_file_name(raw, 0);
2709 assert_eq!(decoded, raw);
2710 }
2711
2712 #[test]
2713 fn decode_file_name_returns_raw_when_unicode_marker_is_missing() {
2714 let raw = b"no-zero-marker";
2717 let decoded = decode_file_name(raw, FHD_UNICODE);
2718 assert_eq!(decoded, raw);
2719 }
2720
2721 #[test]
2722 fn decode_file_name_returns_fallback_when_no_encoded_payload_follows_zero() {
2723 let mut raw = b"fallback".to_vec();
2726 raw.push(0);
2727 let decoded = decode_file_name(&raw, FHD_UNICODE);
2728 assert_eq!(decoded, b"fallback");
2729 }
2730
2731 #[test]
2732 fn decode_file_name_decodes_mode_zero_low_byte_only_units() {
2733 let mut raw = b"orig".to_vec();
2737 raw.push(0); raw.push(0); raw.push(0b00_00_00_00); raw.extend_from_slice(b"abcd");
2741 let decoded = decode_file_name(&raw, FHD_UNICODE);
2742 assert_eq!(decoded, b"abcd");
2743 }
2744
2745 #[test]
2746 fn decode_file_name_decodes_mode_two_full_two_byte_units() {
2747 let mut raw = b"".to_vec();
2750 raw.push(0); raw.push(0); raw.push(0b10_10_00_00);
2754 raw.extend_from_slice(&[0x48, 0x00]);
2756 raw.extend_from_slice(&[0x69, 0x00]);
2758 let decoded = decode_file_name(&raw, FHD_UNICODE);
2759 assert!(decoded.starts_with(b"Hi"), "got {decoded:?}");
2762 }
2763}