1use crate::detect::{find_archive_start, ArchiveSignature, RAR50_SIGNATURE};
2use crate::error::{Error, Result};
3use crate::io_util::{align16 as checked_align16, read_exact_at, read_u32};
4pub(crate) use crate::source::ArchiveSource;
5use crate::version::ArchiveFamily;
6use rars_crc32::crc32;
7use rars_crypto::rar50::{Rar50Cipher, Rar50Keys};
8use std::fs::File;
9use std::io::{Read, Write};
10use std::ops::Range;
11use std::path::Path;
12use std::sync::Arc;
13
14mod blake2sp;
15mod extract;
16mod write;
17
18pub use extract::extract_volumes_to;
19pub use write::{
20 ArchiveMetadataEntry, CompressedEntry, EncryptedArchiveCommentEntry, EncryptedCompressedEntry,
21 EncryptedStoredEntry, EncryptedStoredEntryWithServices, EncryptedStoredServiceEntry,
22 FilterKind, FilterPolicy, Rar50VolumeWriter, Rar50Writer, StoredEntry, StoredEntryWithServices,
23 StoredServiceEntry, WriterOptions,
24};
25
26const HEAD_MAIN: u64 = 1;
27const HEAD_FILE: u64 = 2;
28const HEAD_SERVICE: u64 = 3;
29const HEAD_CRYPT: u64 = 4;
30const HEAD_END: u64 = 5;
31const REV5_SIGNATURE: &[u8] = b"Rar!\x1aRev";
32
33const HFL_EXTRA: u64 = 0x0001;
34const HFL_DATA: u64 = 0x0002;
35const HFL_SPLIT_BEFORE: u64 = 0x0008;
36const HFL_SPLIT_AFTER: u64 = 0x0010;
37
38const MHFL_VOLUME: u64 = 0x0001;
39const MHFL_VOLUME_NUMBER: u64 = 0x0002;
40const MHFL_SOLID: u64 = 0x0004;
41const MHFL_RECOVERY: u64 = 0x0008;
42const MHFL_LOCKED: u64 = 0x0010;
43
44const FHFL_DIRECTORY: u64 = 0x0001;
45const FHFL_MTIME: u64 = 0x0002;
46const FHFL_CRC32: u64 = 0x0004;
47
48const MHEXTRA_LOCATOR: u64 = 0x01;
49const MHEXTRA_LOCATOR_QUICK_OPEN: u64 = 0x0001;
50const MHEXTRA_LOCATOR_RECOVERY: u64 = 0x0002;
51
52const FHEXTRA_CRYPT: u64 = 0x01;
53const FHEXTRA_HASH: u64 = 0x02;
54const FHEXTRA_REDIR: u64 = 0x05;
55const FHEXTRA_SUBDATA: u64 = 0x07;
56const MHEXTRA_ARCHIVE_METADATA: u64 = 0x02;
57const MHEXTRA_ARCHIVE_METADATA_NAME: u64 = 0x0001;
58const MHEXTRA_ARCHIVE_METADATA_TIME: u64 = 0x0002;
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 block: BlockHeader,
73 pub archive_flags: u64,
74 pub volume_number: Option<u64>,
75 pub extras: Vec<MainExtraRecord>,
76}
77
78impl MainHeader {
79 pub fn is_volume(&self) -> bool {
80 self.archive_flags & MHFL_VOLUME != 0
81 }
82
83 pub fn is_solid(&self) -> bool {
84 self.archive_flags & MHFL_SOLID != 0
85 }
86
87 pub fn has_recovery_record(&self) -> bool {
88 self.archive_flags & MHFL_RECOVERY != 0
89 }
90
91 pub fn is_locked(&self) -> bool {
92 self.archive_flags & MHFL_LOCKED != 0
93 }
94
95 pub fn locator(&self) -> Option<&LocatorRecord> {
96 self.extras.iter().find_map(|record| match record {
97 MainExtraRecord::Locator(locator) => Some(locator),
98 _ => None,
99 })
100 }
101
102 pub fn archive_metadata(&self) -> Option<&ArchiveMetadataRecord> {
103 self.extras.iter().find_map(|record| match record {
104 MainExtraRecord::ArchiveMetadata(metadata) => Some(metadata),
105 _ => None,
106 })
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111#[non_exhaustive]
112pub enum MainExtraRecord {
113 Locator(LocatorRecord),
114 ArchiveMetadata(ArchiveMetadataRecord),
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118#[non_exhaustive]
119pub struct LocatorRecord {
120 pub flags: u64,
121 pub quick_open_offset: Option<u64>,
122 pub recovery_record_offset: Option<u64>,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126#[non_exhaustive]
127pub struct ArchiveMetadataRecord {
128 pub flags: u64,
129 pub name: Option<Vec<u8>>,
130 pub creation_time: Option<u64>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum Block {
136 File(FileHeader),
137 Service(FileHeader),
138 End(BlockHeader),
139 Unknown(BlockHeader),
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
143#[non_exhaustive]
144pub struct BlockHeader {
145 pub header_crc: u32,
146 pub header_size: u64,
147 pub header_type: u64,
148 pub flags: u64,
149 pub extra_area_size: Option<u64>,
150 pub data_size: Option<u64>,
151 pub offset: usize,
152 pub header_range: Range<usize>,
155 pub data_range: Range<usize>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq)]
159#[non_exhaustive]
160pub struct FileHeader {
161 pub block: BlockHeader,
162 pub file_flags: u64,
163 pub unpacked_size: u64,
164 pub attributes: u64,
165 pub mtime: Option<u32>,
166 pub data_crc32: Option<u32>,
167 pub compression_info: u64,
168 pub host_os: u64,
169 pub name: Vec<u8>,
170 pub hash: Option<FileHash>,
171 pub redirection: Option<FileRedirection>,
172 pub service_data: Option<Vec<u8>>,
173 pub encrypted: bool,
174 pub encryption: Option<FileEncryption>,
175 crypto: Option<FileCryptoState>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
179#[non_exhaustive]
180pub struct FileRedirection {
181 pub redirection_type: u64,
182 pub flags: u64,
183 pub target_name: Vec<u8>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq)]
187#[non_exhaustive]
188pub struct FileHash {
189 pub hash_type: u64,
190 pub data: Vec<u8>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194#[non_exhaustive]
195pub struct RecoveryRecord {
196 pub percent: u64,
197 pub payload_size: u64,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201#[non_exhaustive]
202pub struct FileEncryption {
203 pub version: u64,
204 pub flags: u64,
205 pub kdf_count: u8,
206 pub salt: [u8; 16],
207 pub iv: [u8; 16],
208 pub check_value: Option<[u8; 12]>,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212struct FileCryptoState {
213 keys: Rar50Keys,
214 iv: [u8; 16],
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218#[non_exhaustive]
219pub struct Rev5Volume {
220 pub version: u8,
221 pub data_count: u16,
222 pub recovery_count: u16,
223 pub recovery_number: u16,
224 pub payload_crc32: u32,
225 pub payload_size: u64,
226 pub payload: Vec<u8>,
227 pub data_volumes: Vec<Rev5DataVolume>,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231#[non_exhaustive]
232pub struct Rev5VolumeMeta {
233 pub version: u8,
234 pub data_count: u16,
235 pub recovery_count: u16,
236 pub recovery_number: u16,
237 pub payload_crc32: u32,
238 pub payload_size: u64,
239 pub data_volumes: Vec<Rev5DataVolume>,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
243#[non_exhaustive]
244pub struct Rev5DataVolume {
245 pub file_size: u64,
246 pub crc32: u32,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250#[non_exhaustive]
251pub struct CompressionInfo {
252 pub algorithm_version: u8,
253 pub solid: bool,
254 pub method: u8,
255 pub dictionary_power: u8,
256 pub dictionary_fraction: u8,
257 pub rar5_compat: bool,
258 pub dictionary_size: u64,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
262#[non_exhaustive]
263pub struct ExtractedEntryMeta {
264 pub name: Vec<u8>,
265 pub file_time: u32,
266 pub attr: u64,
267 pub host_os: u64,
268 pub is_directory: bool,
269}
270
271impl FileHeader {
272 pub fn name_bytes(&self) -> &[u8] {
273 &self.name
274 }
275
276 pub fn name_lossy(&self) -> String {
280 String::from_utf8_lossy(&self.name).into_owned()
281 }
282
283 pub fn is_split_before(&self) -> bool {
284 self.block.flags & HFL_SPLIT_BEFORE != 0
285 }
286
287 pub fn is_split_after(&self) -> bool {
288 self.block.flags & HFL_SPLIT_AFTER != 0
289 }
290
291 pub fn is_directory(&self) -> bool {
292 self.file_flags & FHFL_DIRECTORY != 0
293 }
294
295 pub fn is_stored(&self) -> bool {
296 compression_method(self.compression_info) == 0
297 }
298
299 pub fn is_redirection(&self) -> bool {
300 self.redirection.is_some()
301 }
302
303 pub fn decoded_compression_info(&self) -> Result<CompressionInfo> {
304 decode_compression_info(self.compression_info)
305 }
306
307 pub fn packed_size(&self) -> u64 {
308 self.block.data_size.unwrap_or(0)
309 }
310
311 pub fn packed_data(&self, archive: &Archive) -> Result<Vec<u8>> {
312 archive.read_range(self.block.data_range.clone())
313 }
314
315 pub fn verify_crc32(&self, data: &[u8]) -> Result<()> {
316 let Some(expected) = self.data_crc32 else {
317 return Ok(());
318 };
319 if self.uses_hash_mac() {
320 return Err(Error::InvalidHeader(
321 "RAR 5 encrypted CRC32 verification needs encryption keys",
322 ));
323 }
324 let actual = crc32(data);
325 if actual == expected {
326 Ok(())
327 } else {
328 Err(Error::Crc32Mismatch { expected, actual })
329 }
330 }
331
332 pub fn verify_hash(&self, data: &[u8]) -> Result<()> {
333 let Some(hash) = &self.hash else {
334 return Ok(());
335 };
336 if self.uses_hash_mac() {
337 return Err(Error::InvalidHeader(
338 "RAR 5 encrypted hash verification needs encryption keys",
339 ));
340 }
341 match hash.hash_type {
342 0 if hash.data.len() == 32 => {
343 let actual = blake2sp::hash(data);
344 if hash.data == actual {
345 Ok(())
346 } else {
347 Err(Error::HashMismatch { hash_type: 0 })
348 }
349 }
350 0 => Err(Error::InvalidHeader(
351 "RAR 5 BLAKE2sp hash record has invalid length",
352 )),
353 _ => Err(Error::UnsupportedFeature {
354 version: crate::version::ArchiveVersion::Rar50,
355 feature: "RAR 5 unknown file hash type",
356 }),
357 }
358 }
359
360 pub fn verify_integrity(&self, data: &[u8]) -> Result<()> {
361 self.verify_crc32(data)?;
362 self.verify_hash(data)
363 }
364
365 fn uses_hash_mac(&self) -> bool {
366 self.encryption
367 .as_ref()
368 .is_some_and(|encryption| encryption.flags & 0x0002 != 0)
369 }
370
371 pub fn recovery_record(&self) -> Result<Option<RecoveryRecord>> {
372 if self.name != b"RR" {
373 return Ok(None);
374 }
375 let Some(data) = &self.service_data else {
376 return Err(Error::InvalidHeader(
377 "RAR 5 recovery service is missing service data",
378 ));
379 };
380 let (percent, len) = read_vint_at(data, 0, data.len())?;
381 if len != data.len() {
382 return Err(Error::InvalidHeader(
383 "RAR 5 recovery service data has trailing bytes",
384 ));
385 }
386 Ok(Some(RecoveryRecord {
387 percent,
388 payload_size: self.packed_size(),
389 }))
390 }
391}
392
393impl Archive {
394 pub fn parse(input: &[u8]) -> Result<Self> {
395 Self::parse_with_options(input, crate::ArchiveReadOptions::default())
396 }
397
398 pub fn parse_owned(input: Vec<u8>) -> Result<Self> {
399 Self::parse_owned_with_options(input, crate::ArchiveReadOptions::default())
400 }
401
402 pub fn parse_with_options(
403 input: &[u8],
404 options: crate::ArchiveReadOptions<'_>,
405 ) -> Result<Self> {
406 let data: Arc<[u8]> = Arc::from(input.to_vec().into_boxed_slice());
407 Self::parse_shared(data, options.password)
408 }
409
410 pub fn parse_owned_with_options(
411 input: Vec<u8>,
412 options: crate::ArchiveReadOptions<'_>,
413 ) -> Result<Self> {
414 Self::parse_shared(Arc::from(input.into_boxed_slice()), options.password)
415 }
416
417 pub fn parse_with_password(input: &[u8], password: Option<&[u8]>) -> Result<Self> {
418 Self::parse_with_options(input, crate::ArchiveReadOptions { password })
419 }
420
421 pub fn parse_owned_with_password(input: Vec<u8>, password: Option<&[u8]>) -> Result<Self> {
422 Self::parse_owned_with_options(input, crate::ArchiveReadOptions { password })
423 }
424
425 pub fn parse_path(path: impl AsRef<Path>) -> Result<Self> {
426 Self::parse_path_with_options(path, crate::ArchiveReadOptions::default())
427 }
428
429 pub fn parse_path_with_options(
430 path: impl AsRef<Path>,
431 options: crate::ArchiveReadOptions<'_>,
432 ) -> Result<Self> {
433 Self::parse_path_with_password(path, options.password)
434 }
435
436 pub fn parse_path_with_password(
437 path: impl AsRef<Path>,
438 password: Option<&[u8]>,
439 ) -> Result<Self> {
440 let path = Arc::new(path.as_ref().to_path_buf());
441 let mut file = File::open(path.as_ref())?;
442 let len = file.metadata()?.len();
443 let scan_len = len.min(128 * 1024) as usize;
444 let mut scan = vec![0; scan_len];
445 file.read_exact(&mut scan)?;
446 let sig = find_archive_start(&scan, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
447 if sig.family != ArchiveFamily::Rar50Plus {
448 return Err(Error::UnsupportedSignature);
449 }
450 let archive_len = usize::try_from(len)
451 .map_err(|_| Error::InvalidHeader("RAR 5 archive size overflows usize"))?
452 .checked_sub(sig.offset)
453 .ok_or(Error::TooShort)?;
454 Self::parse_file_backed(
455 &mut file,
456 archive_len,
457 sig.offset,
458 ArchiveSource::File(path),
459 password,
460 )
461 }
462
463 pub fn parse_path_with_signature(
464 path: impl AsRef<Path>,
465 signature: ArchiveSignature,
466 options: crate::ArchiveReadOptions<'_>,
467 ) -> Result<Self> {
468 Self::parse_path_with_signature_and_password(path, signature, options.password)
469 }
470
471 pub fn parse_path_with_signature_and_password(
472 path: impl AsRef<Path>,
473 signature: ArchiveSignature,
474 password: Option<&[u8]>,
475 ) -> Result<Self> {
476 if signature.family != ArchiveFamily::Rar50Plus {
477 return Err(Error::UnsupportedSignature);
478 }
479 let path = Arc::new(path.as_ref().to_path_buf());
480 let mut file = File::open(path.as_ref())?;
481 let len = file.metadata()?.len();
482 let archive_len = usize::try_from(len)
483 .map_err(|_| Error::InvalidHeader("RAR 5 archive size overflows usize"))?
484 .checked_sub(signature.offset)
485 .ok_or(Error::TooShort)?;
486 Self::parse_file_backed(
487 &mut file,
488 archive_len,
489 signature.offset,
490 ArchiveSource::File(path),
491 password,
492 )
493 }
494
495 fn parse_shared(input: Arc<[u8]>, password: Option<&[u8]>) -> Result<Self> {
496 let sig = find_archive_start(&input, 128 * 1024).ok_or(Error::UnsupportedSignature)?;
497 if sig.family != ArchiveFamily::Rar50Plus {
498 return Err(Error::UnsupportedSignature);
499 }
500 let archive = input.get(sig.offset..).ok_or(Error::TooShort)?;
501 let mut parsed = Self::parse_seekable(
502 archive,
503 sig.offset,
504 ArchiveSource::Memory(Arc::clone(&input)),
505 password,
506 )?;
507 parsed.sfx_offset = sig.offset;
508 Ok(parsed)
509 }
510
511 fn parse_seekable(
512 input: &[u8],
513 sfx_offset: usize,
514 source: ArchiveSource,
515 password: Option<&[u8]>,
516 ) -> Result<Self> {
517 if !input.starts_with(RAR50_SIGNATURE) {
518 return Err(Error::UnsupportedSignature);
519 }
520
521 let archive_len = input.len();
522 let (main, blocks) = parse_archive_blocks(
523 archive_len,
524 password,
525 |offset| parse_block_header_bytes(input, offset, archive_len, sfx_offset),
526 |offset, keys| {
527 parse_encrypted_block_header_bytes(input, offset, archive_len, sfx_offset, keys)
528 },
529 )?;
530
531 Ok(Self {
532 sfx_offset,
533 main,
534 blocks,
535 source,
536 })
537 }
538
539 fn parse_file_backed(
540 file: &mut File,
541 archive_len: usize,
542 sfx_offset: usize,
543 source: ArchiveSource,
544 password: Option<&[u8]>,
545 ) -> Result<Self> {
546 let signature = read_exact_at(file, sfx_offset, RAR50_SIGNATURE.len())?;
547 if signature != RAR50_SIGNATURE {
548 return Err(Error::UnsupportedSignature);
549 }
550
551 let file_cell = std::cell::RefCell::new(file);
552 let (main, blocks) = parse_archive_blocks(
553 archive_len,
554 password,
555 |offset| {
556 read_block_header_at(&mut file_cell.borrow_mut(), offset, archive_len, sfx_offset)
557 },
558 |offset, keys| {
559 read_encrypted_block_header_at(
560 &mut file_cell.borrow_mut(),
561 offset,
562 archive_len,
563 sfx_offset,
564 keys,
565 )
566 },
567 )?;
568
569 Ok(Self {
570 sfx_offset,
571 main,
572 blocks,
573 source,
574 })
575 }
576
577 fn read_range(&self, range: Range<usize>) -> Result<Vec<u8>> {
578 self.source.read_range(range)
579 }
580
581 fn source_len(&self) -> Result<usize> {
582 self.source.len()
583 }
584
585 fn range_reader(&self, range: Range<usize>) -> Result<Box<dyn Read + '_>> {
586 self.source.range_reader(range)
587 }
588
589 fn copy_range_to(&self, range: Range<usize>, writer: &mut dyn Write) -> Result<()> {
590 let source_len = self.source_len()?;
591 if range.start > range.end || range.end > source_len {
592 return Err(Error::InvalidHeader("RAR 5 repair range is out of bounds"));
593 }
594 let mut reader = self.range_reader(range)?;
595 std::io::copy(&mut reader, writer)?;
596 Ok(())
597 }
598
599 pub fn files(&self) -> impl Iterator<Item = &FileHeader> {
600 self.blocks.iter().filter_map(|block| match block {
601 Block::File(file) => Some(file),
602 _ => None,
603 })
604 }
605
606 pub fn services(&self) -> impl Iterator<Item = &FileHeader> {
607 self.blocks.iter().filter_map(|block| match block {
608 Block::Service(service) => Some(service),
609 _ => None,
610 })
611 }
612
613 pub fn archive_comment(&self) -> Result<Option<Vec<u8>>> {
619 self.archive_comment_with_password(None)
620 }
621
622 pub fn archive_comment_with_password(
625 &self,
626 password: Option<&[u8]>,
627 ) -> Result<Option<Vec<u8>>> {
628 for block in &self.blocks {
629 match block {
630 Block::File(_) => return Ok(None),
631 Block::Service(service) if service.name == b"CMT" => {
632 return service.decoded_data_unverified(self, password).map(Some);
633 }
634 _ => {}
635 }
636 }
637 Ok(None)
638 }
639
640 pub fn repair_recovery(&self) -> Result<Vec<u8>> {
641 let mut repaired = Vec::new();
642 self.repair_recovery_to(&mut repaired)?;
643 Ok(repaired)
644 }
645
646 pub fn repair_recovery_to(&self, writer: &mut dyn Write) -> Result<()> {
647 let recovery = self
648 .services()
649 .find(|service| matches!(service.recovery_record(), Ok(Some(_))))
650 .ok_or(Error::InvalidHeader(
651 "RAR 5 archive does not contain an inline recovery record",
652 ))?;
653 let prefix_start = self.sfx_offset;
654 let prefix_end = recovery
655 .block
656 .offset
657 .checked_sub(prefix_start)
658 .and_then(|relative| prefix_start.checked_add(relative))
659 .ok_or(Error::InvalidHeader(
660 "RAR 5 recovery prefix range overflows archive bounds",
661 ))?;
662 let source_len = self.source_len()?;
663 if prefix_end > source_len {
664 return Err(Error::InvalidHeader(
665 "RAR 5 recovery prefix is out of bounds",
666 ));
667 }
668 let recovery_data = recovery
669 .decoded_data_unverified(self, None)
670 .map_err(|error| error.at_entry(recovery.name.clone(), "reading recovery data"))?;
671 let prefix_len = prefix_end
672 .checked_sub(prefix_start)
673 .ok_or(Error::InvalidHeader(
674 "RAR 5 recovery prefix range overflows archive bounds",
675 ))?;
676 let repaired_shards = rars_recovery::rar5::repair_inline_recovery_prefix_shards(
677 prefix_len,
678 &recovery_data,
679 |range| {
680 let start = prefix_start
681 .checked_add(range.start)
682 .ok_or(rars_recovery::rar5::Error::PlanOverflow)?;
683 let end = prefix_start
684 .checked_add(range.end)
685 .ok_or(rars_recovery::rar5::Error::PlanOverflow)?;
686 self.read_range(start..end)
687 .map_err(|_| rars_recovery::rar5::Error::BadRecoveryChunk)
688 },
689 )?;
690
691 self.copy_range_to(0..prefix_start, writer)?;
692 let mut cursor = 0usize;
693 for (range, data) in repaired_shards {
694 if range.start < cursor || range.end > prefix_len || range.len() != data.len() {
695 return Err(Error::InvalidHeader(
696 "RAR 5 recovery shard range is invalid",
697 ));
698 }
699 self.copy_range_to(prefix_start + cursor..prefix_start + range.start, writer)?;
700 writer.write_all(&data)?;
701 cursor = range.end;
702 }
703 self.copy_range_to(prefix_start + cursor..prefix_end, writer)?;
704 self.copy_range_to(prefix_end..source_len, writer)?;
705 Ok(())
706 }
707}
708
709impl Rev5Volume {
710 pub fn parse(input: &[u8]) -> Result<Self> {
711 let (meta, payload_range) = Rev5VolumeMeta::parse_with_payload_range(input)?;
712 let payload = &input[payload_range];
713 let actual_payload_crc = crc32(payload);
714 if actual_payload_crc != meta.payload_crc32 {
715 return Err(Error::Crc32Mismatch {
716 expected: meta.payload_crc32,
717 actual: actual_payload_crc,
718 });
719 }
720
721 Ok(Self {
722 version: meta.version,
723 data_count: meta.data_count,
724 recovery_count: meta.recovery_count,
725 recovery_number: meta.recovery_number,
726 payload_crc32: meta.payload_crc32,
727 payload_size: meta.payload_size,
728 payload: payload.to_vec(),
729 data_volumes: meta.data_volumes,
730 })
731 }
732}
733
734impl Rev5VolumeMeta {
735 pub fn parse(input: &[u8]) -> Result<Self> {
736 Self::parse_with_payload_range(input).map(|(meta, _)| meta)
737 }
738
739 fn parse_with_payload_range(input: &[u8]) -> Result<(Self, Range<usize>)> {
740 if !input.starts_with(REV5_SIGNATURE) {
741 return Err(Error::UnsupportedSignature);
742 }
743 if input.len() < 16 {
744 return Err(Error::TooShort);
745 }
746 let header_crc = read_u32(input, 8)?;
747 let header_size = read_u32(input, 12)? as usize;
748 if header_size <= 5 || header_size > 0x100000 {
749 return Err(Error::InvalidHeader("RAR 5 REV header size is invalid"));
750 }
751 let header_end = 16usize
752 .checked_add(header_size)
753 .ok_or(Error::InvalidHeader("RAR 5 REV header size overflows"))?;
754 if header_end > input.len() {
755 return Err(Error::TooShort);
756 }
757 let actual_header_crc = crc32(&input[12..header_end]);
758 if actual_header_crc != header_crc {
759 return Err(Error::Crc32Mismatch {
760 expected: header_crc,
761 actual: actual_header_crc,
762 });
763 }
764
765 let body = &input[16..header_end];
766 if body.len() < 11 {
767 return Err(Error::TooShort);
768 }
769 let mut reader = SliceReader::new(body, 0, body.len());
770 let version = reader.read_byte()?;
771 if version != 1 {
772 return Err(Error::UnsupportedFeature {
773 version: crate::version::ArchiveVersion::Rar50,
774 feature: "RAR 5 REV version",
775 });
776 }
777 let data_count = reader.read_u16()?;
778 let recovery_count = reader.read_u16()?;
779 let recovery_number = reader.read_u16()?;
780 let payload_crc32 = reader.read_u32()?;
781 let first_recovery_number = u32::from(data_count);
782 let recovery_end = first_recovery_number + u32::from(recovery_count);
783 let recovery_number = u32::from(recovery_number);
784 if recovery_count == 0
785 || recovery_number < first_recovery_number
786 || recovery_number >= recovery_end
787 {
788 return Err(Error::InvalidHeader("RAR 5 REV volume number is invalid"));
789 }
790
791 let expected_table_len = data_count as usize * 12;
792 let expected_table_end =
793 11usize
794 .checked_add(expected_table_len)
795 .ok_or(Error::InvalidHeader(
796 "RAR 5 REV metadata table size overflows",
797 ))?;
798 if body.len() < expected_table_end {
799 return Err(Error::InvalidHeader(
800 "RAR 5 REV metadata table size is invalid",
801 ));
802 }
803 let mut data_volumes = Vec::with_capacity(data_count as usize);
804 for _ in 0..data_count {
805 let file_size = reader.read_u64()?;
806 let crc = reader.read_u32()?;
807 data_volumes.push(Rev5DataVolume {
808 file_size,
809 crc32: crc,
810 });
811 }
812
813 Ok((
814 Self {
815 version,
816 data_count,
817 recovery_count,
818 recovery_number: recovery_number as u16,
819 payload_crc32,
820 payload_size: (input.len() - header_end) as u64,
821 data_volumes,
822 },
823 header_end..input.len(),
824 ))
825 }
826}
827
828impl From<&Rev5Volume> for Rev5VolumeMeta {
829 fn from(volume: &Rev5Volume) -> Self {
830 Self {
831 version: volume.version,
832 data_count: volume.data_count,
833 recovery_count: volume.recovery_count,
834 recovery_number: volume.recovery_number,
835 payload_crc32: volume.payload_crc32,
836 payload_size: volume.payload_size,
837 data_volumes: volume.data_volumes.clone(),
838 }
839 }
840}
841
842impl From<Rev5Volume> for Rev5VolumeMeta {
843 fn from(volume: Rev5Volume) -> Self {
844 Self {
845 version: volume.version,
846 data_count: volume.data_count,
847 recovery_count: volume.recovery_count,
848 recovery_number: volume.recovery_number,
849 payload_crc32: volume.payload_crc32,
850 payload_size: volume.payload_size,
851 data_volumes: volume.data_volumes,
852 }
853 }
854}
855
856pub fn repair_rev5_volumes_to<F>(
857 data_volumes: &[Option<&[u8]>],
858 recovery_volumes: &[Rev5Volume],
859 mut write: F,
860) -> Result<()>
861where
862 F: FnMut(usize, &[u8]) -> Result<()>,
863{
864 let first = recovery_volumes.first().ok_or(Error::InvalidHeader(
865 "RAR 5 REV recovery volume set is empty",
866 ))?;
867 let data_count = usize::from(first.data_count);
868 if data_volumes.len() != data_count {
869 return Err(Error::InvalidHeader(
870 "RAR 5 REV data volume count does not match metadata",
871 ));
872 }
873 if recovery_volumes.iter().any(|rev| {
874 rev.version != first.version
875 || rev.data_count != first.data_count
876 || rev.recovery_count != first.recovery_count
877 || rev.data_volumes != first.data_volumes
878 || rev.payload.len() != first.payload.len()
879 }) {
880 return Err(Error::InvalidHeader(
881 "RAR 5 REV recovery volume metadata differs across files",
882 ));
883 }
884
885 let mut shards = Vec::with_capacity(data_count);
886 for (index, data) in data_volumes.iter().enumerate() {
887 let Some(data) = data else {
888 shards.push(None);
889 continue;
890 };
891 let meta = &first.data_volumes[index];
892 if data.len() as u64 != meta.file_size || crc32(data) != meta.crc32 {
893 shards.push(None);
894 } else {
895 shards.push(Some(*data));
896 }
897 }
898
899 let recovery_rows: Vec<_> = recovery_volumes
900 .iter()
901 .map(|rev| {
902 let row = usize::from(rev.recovery_number)
903 .checked_sub(data_count)
904 .ok_or(Error::InvalidHeader("RAR 5 REV recovery number is invalid"))?;
905 Ok((row, rev.payload.as_slice()))
906 })
907 .collect::<Result<_>>()?;
908 let mut seen_recovery_rows = std::collections::HashSet::with_capacity(recovery_rows.len());
909 if recovery_rows
910 .iter()
911 .any(|(row, _)| !seen_recovery_rows.insert(*row))
912 {
913 return Err(Error::InvalidHeader(
914 "RAR 5 REV recovery volume set contains duplicate recovery rows",
915 ));
916 }
917 let repaired = rars_recovery::rar5::reconstruct_data_shards(&shards, &recovery_rows)?;
918
919 for (index, (mut shard, meta)) in repaired.into_iter().zip(&first.data_volumes).enumerate() {
920 let file_size = usize::try_from(meta.file_size)
921 .map_err(|_| Error::InvalidHeader("RAR 5 REV data volume size overflows usize"))?;
922 if shard.len() < file_size {
923 return Err(Error::InvalidHeader(
924 "RAR 5 REV repaired shard is shorter than data volume size",
925 ));
926 }
927 shard.truncate(file_size);
928 let actual = crc32(&shard);
929 if actual != meta.crc32 {
930 return Err(Error::Crc32Mismatch {
931 expected: meta.crc32,
932 actual,
933 });
934 }
935 write(index, &shard)?;
936 }
937 Ok(())
938}
939
940pub fn repair_inline_recovery_bytes(input: &[u8]) -> Result<Vec<u8>> {
941 if !input.starts_with(RAR50_SIGNATURE) {
942 return Err(Error::UnsupportedSignature);
943 }
944 let repaired =
945 rars_recovery::rar5::repair_inline_recovery_archive(input).map_err(Error::from)?;
946 let parse_target = if repaired == input { input } else { &repaired };
947 let _ = Archive::parse(parse_target)?;
948 Ok(repaired)
949}
950
951fn parse_main_header_bytes(parsed: &ParsedBlockHeader) -> Result<MainHeader> {
952 let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
953 let archive_flags = reader.read_vint()?;
954 let volume_number = if archive_flags & MHFL_VOLUME_NUMBER != 0 {
955 Some(reader.read_vint()?)
956 } else {
957 None
958 };
959 let extras = parse_main_extra_area(&parsed.header, parsed.extra_range.clone())?;
960 Ok(MainHeader {
961 block: parsed.block.clone(),
962 archive_flags,
963 volume_number,
964 extras,
965 })
966}
967
968fn parse_main_extra_area(input: &[u8], range: Range<usize>) -> Result<Vec<MainExtraRecord>> {
969 let mut records = Vec::new();
970 parse_extra_records(input, range, |record_type, data| match record_type {
971 MHEXTRA_LOCATOR => {
972 let mut reader = SliceReader::new(input, data.start, data.end);
973 let flags = reader.read_vint()?;
974 let quick_open_offset = if flags & MHEXTRA_LOCATOR_QUICK_OPEN != 0 {
975 Some(reader.read_vint()?)
976 } else {
977 None
978 };
979 let recovery_record_offset = if flags & MHEXTRA_LOCATOR_RECOVERY != 0 {
980 Some(reader.read_vint()?)
981 } else {
982 None
983 };
984 records.push(MainExtraRecord::Locator(LocatorRecord {
988 flags,
989 quick_open_offset,
990 recovery_record_offset,
991 }));
992 Ok(())
993 }
994 MHEXTRA_ARCHIVE_METADATA => {
995 let mut reader = SliceReader::new(input, data.start, data.end);
996 let flags = reader.read_vint()?;
997 let name = if flags & MHEXTRA_ARCHIVE_METADATA_NAME != 0 {
998 let name_len = usize_from_u64(
999 reader.read_vint()?,
1000 "RAR 5 archive metadata name length overflows usize",
1001 )?;
1002 Some(reader.read_bytes(name_len)?.to_vec())
1003 } else {
1004 None
1005 };
1006 let creation_time = if flags & MHEXTRA_ARCHIVE_METADATA_TIME != 0 {
1007 Some(reader.read_u64()?)
1008 } else {
1009 None
1010 };
1011 if reader.pos != reader.end {
1012 return Err(Error::InvalidHeader(
1013 "RAR 5 archive metadata record has trailing bytes",
1014 ));
1015 }
1016 records.push(MainExtraRecord::ArchiveMetadata(ArchiveMetadataRecord {
1017 flags,
1018 name,
1019 creation_time,
1020 }));
1021 Ok(())
1022 }
1023 _ => Ok(()),
1024 })?;
1025 Ok(records)
1026}
1027
1028fn parse_file_header_bytes(parsed: &ParsedBlockHeader) -> Result<FileHeader> {
1029 let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
1030 let file_flags = reader.read_vint()?;
1031 let unpacked_size = reader.read_vint()?;
1032 let attributes = reader.read_vint()?;
1033 let mtime = if file_flags & FHFL_MTIME != 0 {
1034 Some(reader.read_u32()?)
1035 } else {
1036 None
1037 };
1038 let data_crc32 = if file_flags & FHFL_CRC32 != 0 {
1039 Some(reader.read_u32()?)
1040 } else {
1041 None
1042 };
1043 let compression_info = reader.read_vint()?;
1044 let host_os = reader.read_vint()?;
1045 let name_len = usize_from_u64(
1046 reader.read_vint()?,
1047 "RAR 5 file name length overflows usize",
1048 )?;
1049 let name = reader.read_bytes(name_len)?.to_vec();
1050 let mut file = FileHeader {
1051 block: parsed.block.clone(),
1052 file_flags,
1053 unpacked_size,
1054 attributes,
1055 mtime,
1056 data_crc32,
1057 compression_info,
1058 host_os,
1059 name,
1060 hash: None,
1061 redirection: None,
1062 service_data: None,
1063 encrypted: false,
1064 encryption: None,
1065 crypto: None,
1066 };
1067 parse_file_extra_area(&parsed.header, parsed.extra_range.clone(), &mut file)?;
1068 Ok(file)
1069}
1070
1071fn parse_file_extra_area(input: &[u8], range: Range<usize>, file: &mut FileHeader) -> Result<()> {
1072 if file.block.extra_area_size.is_none() {
1073 return Ok(());
1074 }
1075 parse_extra_records(input, range, |record_type, data| {
1076 match record_type {
1077 FHEXTRA_CRYPT => {
1078 file.encrypted = true;
1079 file.encryption = Some(parse_file_encryption_record(input, data)?);
1080 }
1081 FHEXTRA_HASH => {
1082 let (hash_type, hash_type_len) = read_vint_at(input, data.start, data.end)?;
1083 file.hash = Some(FileHash {
1084 hash_type,
1085 data: input[data.start + hash_type_len..data.end].to_vec(),
1086 });
1087 }
1088 FHEXTRA_REDIR => {
1089 file.redirection = Some(parse_file_redirection_record(input, data)?);
1090 }
1091 FHEXTRA_SUBDATA => {
1092 file.service_data = Some(input[data].to_vec());
1093 }
1094 _ => {}
1095 }
1096 Ok(())
1097 })
1098}
1099
1100fn parse_file_redirection_record(input: &[u8], range: Range<usize>) -> Result<FileRedirection> {
1101 let (redirection_type, type_len) = read_vint_at(input, range.start, range.end)?;
1102 let flags_start = range.start + type_len;
1103 let (flags, flags_len) = read_vint_at(input, flags_start, range.end)?;
1104 let name_len_start = flags_start + flags_len;
1105 let (name_len, name_len_len) = read_vint_at(input, name_len_start, range.end)?;
1106 let name_start = name_len_start + name_len_len;
1107 let name_len = usize::try_from(name_len).map_err(|_| {
1108 Error::InvalidHeader("RAR 5 file redirection target length overflows host address size")
1109 })?;
1110 let name_end = name_start
1111 .checked_add(name_len)
1112 .ok_or(Error::InvalidHeader(
1113 "RAR 5 file redirection target length overflows",
1114 ))?;
1115 if name_end != range.end {
1116 return Err(Error::InvalidHeader(
1117 "RAR 5 file redirection record has trailing bytes",
1118 ));
1119 }
1120 Ok(FileRedirection {
1121 redirection_type,
1122 flags,
1123 target_name: input[name_start..name_end].to_vec(),
1124 })
1125}
1126
1127fn parse_file_encryption_record(input: &[u8], range: Range<usize>) -> Result<FileEncryption> {
1128 let (version, version_len) = read_vint_at(input, range.start, range.end)?;
1129 let flags_pos = range.start + version_len;
1130 let (flags, flags_len) = read_vint_at(input, flags_pos, range.end)?;
1131 let mut pos = flags_pos + flags_len;
1132 if pos >= range.end {
1133 return Err(Error::TooShort);
1134 }
1135 let kdf_count = input[pos];
1136 pos += 1;
1137 let salt = read_array_at::<16>(input, &mut pos, range.end)?;
1138 let iv = read_array_at::<16>(input, &mut pos, range.end)?;
1139 let check_value = if flags & 0x0001 != 0 {
1140 Some(read_array_at::<12>(input, &mut pos, range.end)?)
1141 } else {
1142 None
1143 };
1144 if pos != range.end {
1145 return Err(Error::InvalidHeader(
1146 "RAR 5 file encryption record has trailing bytes",
1147 ));
1148 }
1149 Ok(FileEncryption {
1150 version,
1151 flags,
1152 kdf_count,
1153 salt,
1154 iv,
1155 check_value,
1156 })
1157}
1158
1159fn parse_archive_encryption_header(
1160 parsed: &ParsedBlockHeader,
1161 password: Option<&[u8]>,
1162) -> Result<Rar50Keys> {
1163 let password = password.ok_or(Error::NeedPassword)?;
1164 let mut reader = HeaderReader::new(&parsed.header, parsed.type_specific_range.clone())?;
1165 let version = reader.read_vint()?;
1166 if version != 0 {
1167 return Err(Error::UnsupportedFeature {
1168 version: crate::version::ArchiveVersion::Rar50,
1169 feature: "RAR 5 unknown header encryption version",
1170 });
1171 }
1172 let flags = reader.read_vint()?;
1173 let kdf_count = reader.read_byte()?;
1174 let salt = reader.read_array::<16>()?;
1175 let check_value = if flags & 0x0001 != 0 {
1176 Some(reader.read_array::<12>()?)
1177 } else {
1178 None
1179 };
1180 if reader.pos != reader.range.end {
1181 return Err(Error::InvalidHeader(
1182 "RAR 5 archive encryption header has trailing bytes",
1183 ));
1184 }
1185 let keys = Rar50Keys::derive(password, salt, kdf_count).map_err(map_rar50_crypto_error)?;
1186 if let Some(check_value) = check_value {
1187 keys.check_password(&check_value)
1188 .map_err(map_rar50_crypto_error)?;
1189 }
1190 Ok(keys)
1191}
1192
1193fn attach_file_crypto(file: &mut FileHeader, password: Option<&[u8]>) -> Result<()> {
1194 if !file.encrypted || file.crypto.is_some() {
1195 return Ok(());
1196 }
1197 let Some(password) = password else {
1198 return Ok(());
1199 };
1200 let encryption = file.encryption.as_ref().ok_or(Error::InvalidHeader(
1201 "RAR 5 encrypted file is missing encryption record",
1202 ))?;
1203 if encryption.version != 0 {
1204 return Err(Error::UnsupportedFeature {
1205 version: crate::version::ArchiveVersion::Rar50,
1206 feature: "RAR 5 unknown file encryption version",
1207 });
1208 }
1209 let keys = Rar50Keys::derive(password, encryption.salt, encryption.kdf_count)
1210 .map_err(map_rar50_crypto_error)?;
1211 if let Some(check_value) = encryption.check_value {
1212 keys.check_password(&check_value)
1213 .map_err(map_rar50_crypto_error)?;
1214 }
1215 file.crypto = Some(FileCryptoState {
1216 keys,
1217 iv: encryption.iv,
1218 });
1219 Ok(())
1220}
1221
1222fn map_rar50_crypto_error(error: rars_crypto::rar50::Error) -> Error {
1223 match error {
1224 rars_crypto::rar50::Error::KdfCountTooLarge => Error::UnsupportedFeature {
1225 version: crate::version::ArchiveVersion::Rar50,
1226 feature: "RAR 5 KDF count",
1227 },
1228 rars_crypto::rar50::Error::BadPassword => Error::WrongPasswordOrCorruptData,
1229 rars_crypto::rar50::Error::UnalignedInput => {
1230 Error::InvalidHeader("RAR 5 AES input is not block aligned")
1231 }
1232 other => Error::Rar50Crypto(other),
1233 }
1234}
1235
1236fn read_array_at<const N: usize>(input: &[u8], pos: &mut usize, end: usize) -> Result<[u8; N]> {
1237 if pos.checked_add(N).is_none_or(|next| next > end) {
1238 return Err(Error::TooShort);
1239 }
1240 let mut out = [0; N];
1241 out.copy_from_slice(&input[*pos..*pos + N]);
1242 *pos += N;
1243 Ok(out)
1244}
1245
1246fn parse_archive_blocks<F, G>(
1247 archive_len: usize,
1248 password: Option<&[u8]>,
1249 mut read_block: F,
1250 mut read_encrypted_block: G,
1251) -> Result<(MainHeader, Vec<Block>)>
1252where
1253 F: FnMut(usize) -> Result<ParsedBlockHeader>,
1254 G: FnMut(usize, &Rar50Keys) -> Result<ParsedBlockHeader>,
1255{
1256 let mut pos = RAR50_SIGNATURE.len();
1257 let first = read_block(pos).map_err(|error| error.at_archive_offset(pos))?;
1258 let header_keys = if first.block.header_type == HEAD_CRYPT {
1259 pos = first.next_offset;
1260 Some(parse_archive_encryption_header(&first, password)?)
1261 } else {
1262 None
1263 };
1264
1265 let main_pos = pos;
1266 let main_block;
1267 let first = if let Some(keys) = &header_keys {
1268 main_block =
1269 read_encrypted_block(pos, keys).map_err(|error| error.at_archive_offset(pos))?;
1270 &main_block
1271 } else {
1272 &first
1273 };
1274 if first.block.header_type != HEAD_MAIN {
1275 return Err(Error::InvalidHeader("RAR 5 main header is missing"));
1276 }
1277 let main = parse_main_header_bytes(first).map_err(|error| error.at_archive_offset(main_pos))?;
1278 pos = first.next_offset;
1279
1280 let mut blocks = Vec::new();
1281 while pos < archive_len {
1282 let parsed = if let Some(keys) = &header_keys {
1283 read_encrypted_block(pos, keys).map_err(|error| error.at_archive_offset(pos))?
1284 } else {
1285 read_block(pos).map_err(|error| error.at_archive_offset(pos))?
1286 };
1287 let next = parsed.next_offset;
1288 match parsed.block.header_type {
1289 HEAD_FILE => {
1290 let mut file = parse_file_header_bytes(&parsed)
1291 .map_err(|error| error.at_archive_offset(pos))?;
1292 attach_file_crypto(&mut file, password)
1293 .map_err(|error| error.at_archive_offset(pos))?;
1294 blocks.push(Block::File(file));
1295 }
1296 HEAD_SERVICE => {
1297 let mut service = parse_file_header_bytes(&parsed)
1298 .map_err(|error| error.at_archive_offset(pos))?;
1299 attach_file_crypto(&mut service, password)
1300 .map_err(|error| error.at_archive_offset(pos))?;
1301 blocks.push(Block::Service(service));
1302 }
1303 HEAD_CRYPT => {
1304 return Err(Error::UnsupportedFeature {
1305 version: crate::version::ArchiveVersion::Rar50,
1306 feature: "RAR 5 encrypted headers",
1307 });
1308 }
1309 HEAD_END => {
1310 blocks.push(Block::End(parsed.block));
1311 break;
1312 }
1313 _ => blocks.push(Block::Unknown(parsed.block)),
1314 }
1315 pos = next;
1316 }
1317
1318 Ok((main, blocks))
1319}
1320
1321fn parse_extra_records<F>(input: &[u8], range: Range<usize>, mut handle: F) -> Result<()>
1322where
1323 F: FnMut(u64, Range<usize>) -> Result<()>,
1324{
1325 let mut pos = range.start;
1326 while pos < range.end {
1327 let record_start = pos;
1328 let (record_size, size_len) = read_vint_at(input, pos, range.end)?;
1329 pos += size_len;
1330 let record_payload_len =
1331 usize_from_u64(record_size, "RAR 5 extra record size overflows usize")?;
1332 let record_end = pos
1333 .checked_add(record_payload_len)
1334 .ok_or(Error::InvalidHeader(
1335 "RAR 5 extra record size overflows usize",
1336 ))?;
1337 if record_end > range.end {
1338 return Err(Error::TooShort);
1339 }
1340 let (record_type, type_len) = read_vint_at(input, pos, record_end)?;
1341 let data_start = pos + type_len;
1342 handle(record_type, data_start..record_end)?;
1343 if record_end <= record_start {
1344 return Err(Error::InvalidHeader("RAR 5 extra record does not advance"));
1345 }
1346 pos = record_end;
1347 }
1348 Ok(())
1349}
1350
1351struct ParsedBlockHeader {
1352 block: BlockHeader,
1353 header: Vec<u8>,
1354 type_specific_range: Range<usize>,
1355 extra_range: Range<usize>,
1356 next_offset: usize,
1357}
1358
1359fn parse_block_header_bytes(
1360 input: &[u8],
1361 offset: usize,
1362 archive_len: usize,
1363 sfx_offset: usize,
1364) -> Result<ParsedBlockHeader> {
1365 let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1366 if remaining < 5 {
1367 return Err(Error::TooShort);
1368 }
1369 let header_crc = read_u32(input, offset)?;
1370 let after_crc = offset
1371 .checked_add(4)
1372 .ok_or(Error::InvalidHeader("RAR 5 header offset overflows usize"))?;
1373 let (header_size, header_size_len) = read_vint_at(input, after_crc, archive_len)?;
1374 let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1375 let header_total = 4usize
1376 .checked_add(header_size_len)
1377 .and_then(|size| size.checked_add(header_body_len))
1378 .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1379 if header_total > remaining {
1380 return Err(Error::TooShort);
1381 }
1382 let header_end = offset
1383 .checked_add(header_total)
1384 .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1385 let header = input
1386 .get(offset..header_end)
1387 .ok_or(Error::TooShort)?
1388 .to_vec();
1389 parse_block_header_image(
1390 header,
1391 offset,
1392 archive_len,
1393 sfx_offset,
1394 header_crc,
1395 header_total,
1396 )
1397}
1398
1399fn parse_encrypted_block_header_bytes(
1400 input: &[u8],
1401 offset: usize,
1402 archive_len: usize,
1403 sfx_offset: usize,
1404 keys: &Rar50Keys,
1405) -> Result<ParsedBlockHeader> {
1406 let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1407 if remaining < 32 {
1408 return Err(Error::TooShort);
1409 }
1410 let first = input.get(offset..offset + 32).ok_or(Error::TooShort)?;
1411 let mut iv = [0; 16];
1412 iv.copy_from_slice(&first[..16]);
1413 let mut first_plain = first[16..32].to_vec();
1414 Rar50Cipher::new(keys.key, iv)
1415 .decrypt_in_place(&mut first_plain)
1416 .map_err(map_rar50_crypto_error)?;
1417 let header_crc = read_u32(&first_plain, 0)?;
1418 let (header_size, header_size_len) = read_vint_at(&first_plain, 4, first_plain.len())?;
1419 let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1420 let header_total = 4usize
1421 .checked_add(header_size_len)
1422 .and_then(|size| size.checked_add(header_body_len))
1423 .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1424 let encrypted_len = checked_align16(header_total, "RAR 5 encrypted header size overflows")?;
1425 let disk_header_len = 16usize
1426 .checked_add(encrypted_len)
1427 .ok_or(Error::InvalidHeader(
1428 "RAR 5 encrypted header size overflows",
1429 ))?;
1430 if disk_header_len > remaining {
1431 return Err(Error::TooShort);
1432 }
1433 let encrypted = input
1434 .get(offset + 16..offset + disk_header_len)
1435 .ok_or(Error::TooShort)?;
1436 let mut header = encrypted.to_vec();
1437 Rar50Cipher::new(keys.key, iv)
1438 .decrypt_in_place(&mut header)
1439 .map_err(map_rar50_crypto_error)?;
1440 header.truncate(header_total);
1441
1442 parse_block_header_image(
1443 header,
1444 offset,
1445 archive_len,
1446 sfx_offset,
1447 header_crc,
1448 disk_header_len,
1449 )
1450}
1451
1452fn read_block_header_at(
1453 file: &mut File,
1454 offset: usize,
1455 archive_len: usize,
1456 sfx_offset: usize,
1457) -> Result<ParsedBlockHeader> {
1458 let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1459 if remaining < 5 {
1460 return Err(Error::TooShort);
1461 }
1462 let prefix_len = remaining.min(14);
1463 let prefix = read_exact_at(file, sfx_offset + offset, prefix_len)?;
1464 let header_crc = read_u32(&prefix, 0)?;
1465 let (header_size, header_size_len) = read_vint_at(&prefix, 4, prefix.len())?;
1466 let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1467 let header_total = 4usize
1468 .checked_add(header_size_len)
1469 .and_then(|size| size.checked_add(header_body_len))
1470 .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1471 if header_total > remaining {
1472 return Err(Error::TooShort);
1473 }
1474
1475 let header = read_exact_at(file, sfx_offset + offset, header_total)?;
1476 parse_block_header_image(
1477 header,
1478 offset,
1479 archive_len,
1480 sfx_offset,
1481 header_crc,
1482 header_total,
1483 )
1484}
1485
1486fn read_encrypted_block_header_at(
1487 file: &mut File,
1488 offset: usize,
1489 archive_len: usize,
1490 sfx_offset: usize,
1491 keys: &Rar50Keys,
1492) -> Result<ParsedBlockHeader> {
1493 let remaining = archive_len.checked_sub(offset).ok_or(Error::TooShort)?;
1494 if remaining < 32 {
1495 return Err(Error::TooShort);
1496 }
1497 let first = read_exact_at(file, sfx_offset + offset, 32)?;
1498 let mut iv = [0; 16];
1499 iv.copy_from_slice(&first[..16]);
1500 let mut first_plain = first[16..32].to_vec();
1501 Rar50Cipher::new(keys.key, iv)
1502 .decrypt_in_place(&mut first_plain)
1503 .map_err(map_rar50_crypto_error)?;
1504 let header_crc = read_u32(&first_plain, 0)?;
1505 let (header_size, header_size_len) = read_vint_at(&first_plain, 4, first_plain.len())?;
1506 let header_body_len = usize_from_u64(header_size, "RAR 5 header size overflows usize")?;
1507 let header_total = 4usize
1508 .checked_add(header_size_len)
1509 .and_then(|size| size.checked_add(header_body_len))
1510 .ok_or(Error::InvalidHeader("RAR 5 header size overflows usize"))?;
1511 let encrypted_len = checked_align16(header_total, "RAR 5 encrypted header size overflows")?;
1512 let disk_header_len = 16usize
1513 .checked_add(encrypted_len)
1514 .ok_or(Error::InvalidHeader(
1515 "RAR 5 encrypted header size overflows",
1516 ))?;
1517 if disk_header_len > remaining {
1518 return Err(Error::TooShort);
1519 }
1520 let encrypted = read_exact_at(file, sfx_offset + offset + 16, encrypted_len)?;
1521 let mut header = encrypted;
1522 Rar50Cipher::new(keys.key, iv)
1523 .decrypt_in_place(&mut header)
1524 .map_err(map_rar50_crypto_error)?;
1525 header.truncate(header_total);
1526
1527 parse_block_header_image(
1528 header,
1529 offset,
1530 archive_len,
1531 sfx_offset,
1532 header_crc,
1533 disk_header_len,
1534 )
1535}
1536
1537fn parse_block_header_image(
1538 header: Vec<u8>,
1539 offset: usize,
1540 archive_len: usize,
1541 sfx_offset: usize,
1542 header_crc: u32,
1543 disk_header_len: usize,
1544) -> Result<ParsedBlockHeader> {
1545 let header_total = header.len();
1546 let (decoded_header_size, header_size_len) = read_vint_at(&header, 4, header_total)?;
1547 validate_block_header_crc(&header, header_crc)?;
1548 let type_start = 4 + header_size_len;
1549 let mut reader = SliceReader::new(&header, type_start, header_total);
1550 let header_type = reader.read_vint()?;
1551 let flags = reader.read_vint()?;
1552 let extra_area_size = if flags & HFL_EXTRA != 0 {
1553 Some(reader.read_vint()?)
1554 } else {
1555 None
1556 };
1557 let data_size = if flags & HFL_DATA != 0 {
1558 Some(reader.read_vint()?)
1559 } else {
1560 None
1561 };
1562 let extra_len = extra_area_size
1563 .map(|size| usize_from_u64(size, "RAR 5 extra area size overflows usize"))
1564 .transpose()?
1565 .unwrap_or(0);
1566 if extra_len > header_total.saturating_sub(reader.pos) {
1567 return Err(Error::TooShort);
1568 }
1569 let type_specific_end = header_total - extra_len;
1570 let data_len = data_size
1571 .map(|size| usize_from_u64(size, "RAR 5 data size overflows usize"))
1572 .transpose()?
1573 .unwrap_or(0);
1574 let next_offset = offset
1575 .checked_add(disk_header_len)
1576 .and_then(|pos| pos.checked_add(data_len))
1577 .ok_or(Error::InvalidHeader("RAR 5 data size overflows usize"))?;
1578 if next_offset > archive_len {
1579 return Err(Error::TooShort);
1580 }
1581 let type_specific_start = reader.pos;
1582 let data_start = sfx_offset
1583 .checked_add(offset)
1584 .and_then(|pos| pos.checked_add(disk_header_len))
1585 .ok_or(Error::InvalidHeader("RAR 5 data offset overflows usize"))?;
1586 let data_end = data_start
1587 .checked_add(data_len)
1588 .ok_or(Error::InvalidHeader("RAR 5 data size overflows usize"))?;
1589
1590 Ok(ParsedBlockHeader {
1591 block: BlockHeader {
1592 header_crc,
1593 header_size: decoded_header_size,
1594 header_type,
1595 flags,
1596 extra_area_size,
1597 data_size,
1598 offset: sfx_offset + offset,
1599 header_range: (offset + type_specific_start)..(offset + type_specific_end),
1600 data_range: data_start..data_end,
1601 },
1602 header,
1603 type_specific_range: type_specific_start..type_specific_end,
1604 extra_range: type_specific_end..header_total,
1605 next_offset,
1606 })
1607}
1608
1609fn validate_block_header_crc(header: &[u8], expected: u32) -> Result<()> {
1610 let actual = crc32(header.get(4..).ok_or(Error::TooShort)?);
1611 if actual != expected {
1612 return Err(Error::Crc32Mismatch { expected, actual });
1613 }
1614 Ok(())
1615}
1616
1617struct HeaderReader<'a> {
1618 input: &'a [u8],
1619 range: Range<usize>,
1620 pos: usize,
1621}
1622
1623impl<'a> HeaderReader<'a> {
1624 fn new(input: &'a [u8], range: Range<usize>) -> Result<Self> {
1625 if range.end > input.len() {
1626 return Err(Error::TooShort);
1627 }
1628 Ok(Self {
1629 input,
1630 pos: range.start,
1631 range,
1632 })
1633 }
1634
1635 fn read_vint(&mut self) -> Result<u64> {
1636 let (value, len) = read_vint_at(self.input, self.pos, self.range.end)?;
1637 self.pos += len;
1638 Ok(value)
1639 }
1640
1641 fn read_u32(&mut self) -> Result<u32> {
1642 let value = read_u32(self.input, self.pos)?;
1643 self.pos += 4;
1644 Ok(value)
1645 }
1646
1647 fn read_byte(&mut self) -> Result<u8> {
1648 if self.pos >= self.range.end {
1649 return Err(Error::TooShort);
1650 }
1651 let value = self.input[self.pos];
1652 self.pos += 1;
1653 Ok(value)
1654 }
1655
1656 fn read_array<const N: usize>(&mut self) -> Result<[u8; N]> {
1657 read_array_at::<N>(self.input, &mut self.pos, self.range.end)
1658 }
1659
1660 fn read_bytes(&mut self, len: usize) -> Result<&'a [u8]> {
1661 let end = self
1662 .pos
1663 .checked_add(len)
1664 .ok_or(Error::InvalidHeader("RAR 5 field size overflows usize"))?;
1665 if end > self.range.end {
1666 return Err(Error::TooShort);
1667 }
1668 let bytes = &self.input[self.pos..end];
1669 self.pos = end;
1670 Ok(bytes)
1671 }
1672}
1673
1674struct SliceReader<'a> {
1675 input: &'a [u8],
1676 end: usize,
1677 pos: usize,
1678}
1679
1680impl<'a> SliceReader<'a> {
1681 fn new(input: &'a [u8], pos: usize, end: usize) -> Self {
1682 Self { input, pos, end }
1683 }
1684
1685 fn read_vint(&mut self) -> Result<u64> {
1686 let (value, len) = read_vint_at(self.input, self.pos, self.end)?;
1687 self.pos += len;
1688 Ok(value)
1689 }
1690
1691 fn read_byte(&mut self) -> Result<u8> {
1692 let bytes = self.read_bytes(1)?;
1693 Ok(bytes[0])
1694 }
1695
1696 fn read_u16(&mut self) -> Result<u16> {
1697 let bytes = self.read_bytes(2)?;
1698 Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
1699 }
1700
1701 fn read_u32(&mut self) -> Result<u32> {
1702 let bytes = self.read_bytes(4)?;
1703 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
1704 }
1705
1706 fn read_u64(&mut self) -> Result<u64> {
1707 let bytes = self.read_bytes(8)?;
1708 Ok(u64::from_le_bytes([
1709 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
1710 ]))
1711 }
1712
1713 fn read_bytes(&mut self, len: usize) -> Result<&'a [u8]> {
1714 let end = self
1715 .pos
1716 .checked_add(len)
1717 .ok_or(Error::InvalidHeader("RAR 5 field size overflows usize"))?;
1718 if end > self.end {
1719 return Err(Error::TooShort);
1720 }
1721 let bytes = &self.input[self.pos..end];
1722 self.pos = end;
1723 Ok(bytes)
1724 }
1725}
1726
1727fn read_vint_at(input: &[u8], offset: usize, end: usize) -> Result<(u64, usize)> {
1728 let mut value = 0u64;
1729 let mut shift = 0u32;
1730 for i in 0..10 {
1731 let pos = offset.checked_add(i).ok_or(Error::TooShort)?;
1732 if pos >= end {
1733 return Err(Error::TooShort);
1734 }
1735 let byte = *input.get(pos).ok_or(Error::TooShort)?;
1736 if shift == 63 && byte & 0x7e != 0 {
1737 return Err(Error::InvalidHeader("RAR 5 vint overflows u64"));
1738 }
1739 value = value
1740 .checked_add(((byte & 0x7f) as u64) << shift)
1741 .ok_or(Error::InvalidHeader("RAR 5 vint overflows u64"))?;
1742 if byte & 0x80 == 0 {
1743 return Ok((value, i + 1));
1744 }
1745 shift += 7;
1746 }
1747 Err(Error::InvalidHeader("RAR 5 vint is too long"))
1748}
1749
1750fn usize_from_u64(value: u64, message: &'static str) -> Result<usize> {
1751 usize::try_from(value).map_err(|_| Error::InvalidHeader(message))
1752}
1753
1754fn compression_method(compression_info: u64) -> u64 {
1755 (compression_info >> 7) & 0x07
1756}
1757
1758fn decode_compression_info(raw: u64) -> Result<CompressionInfo> {
1759 let algorithm_version = (raw & 0x3f) as u8;
1760 if algorithm_version > 1 {
1761 return Err(Error::UnsupportedFeature {
1762 version: crate::version::ArchiveVersion::Rar50,
1763 feature: "RAR 5 unknown compression algorithm version",
1764 });
1765 }
1766
1767 let dictionary_power = ((raw >> 10) & 0x1f) as u8;
1768 let dictionary_fraction = ((raw >> 15) & 0x1f) as u8;
1769 let rar5_compat = raw & 0x100000 != 0;
1770 if algorithm_version == 0 && (dictionary_fraction != 0 || rar5_compat) {
1771 return Err(Error::InvalidHeader(
1772 "RAR 5 v0 compression info uses v1 dictionary fields",
1773 ));
1774 }
1775 if algorithm_version == 0 && dictionary_power > 15 {
1776 return Err(Error::InvalidHeader(
1777 "RAR 5 v0 dictionary power exceeds 4 GiB limit",
1778 ));
1779 }
1780
1781 let dictionary_size = if algorithm_version == 1 {
1782 u64::from(dictionary_fraction + 32)
1783 .checked_shl(u32::from(dictionary_power) + 12)
1784 .ok_or(Error::InvalidHeader("RAR 5 dictionary size overflows u64"))?
1785 } else {
1786 (128 * 1024_u64)
1787 .checked_shl(u32::from(dictionary_power))
1788 .ok_or(Error::InvalidHeader("RAR 5 dictionary size overflows u64"))?
1789 };
1790
1791 Ok(CompressionInfo {
1792 algorithm_version,
1793 solid: raw & 0x40 != 0,
1794 method: ((raw >> 7) & 0x07) as u8,
1795 dictionary_power,
1796 dictionary_fraction,
1797 rar5_compat,
1798 dictionary_size,
1799 })
1800}
1801
1802#[cfg(test)]
1803mod tests {
1804 use super::*;
1805
1806 #[test]
1807 fn read_vint_at_honors_logical_end_before_decoding() {
1808 assert_eq!(read_vint_at(&[0x01], 0, 0), Err(Error::TooShort));
1809 assert_eq!(read_vint_at(&[0x81, 0x01], 0, 1), Err(Error::TooShort));
1810 assert_eq!(read_vint_at(&[0x81, 0x01], 0, 2).unwrap(), (129, 2));
1811 }
1812
1813 #[test]
1814 fn read_vint_at_rejects_values_wider_than_u64() {
1815 let max = [0xff; 9].into_iter().chain([0x01]).collect::<Vec<_>>();
1816 assert_eq!(read_vint_at(&max, 0, max.len()).unwrap(), (u64::MAX, 10));
1817
1818 let overflow = [0xff; 9].into_iter().chain([0x02]).collect::<Vec<_>>();
1819 assert_eq!(
1820 read_vint_at(&overflow, 0, overflow.len()),
1821 Err(Error::InvalidHeader("RAR 5 vint overflows u64"))
1822 );
1823 }
1824
1825 #[test]
1826 fn parses_file_redirection_extra_record() {
1827 let input = [1, 1, 6, b't', b'a', b'r', b'g', b'e', b't'];
1828 let record = parse_file_redirection_record(&input, 0..input.len()).unwrap();
1829
1830 assert_eq!(record.redirection_type, 1);
1831 assert_eq!(record.flags, 1);
1832 assert_eq!(record.target_name, b"target");
1833 }
1834
1835 #[test]
1836 fn rejects_file_redirection_record_with_trailing_bytes() {
1837 let input = [1, 0, 3, b'f', b'o', b'o', 0];
1838
1839 assert!(matches!(
1840 parse_file_redirection_record(&input, 0..input.len()),
1841 Err(Error::InvalidHeader(
1842 "RAR 5 file redirection record has trailing bytes"
1843 ))
1844 ));
1845 }
1846
1847 #[test]
1848 fn file_header_name_bytes_preserve_non_utf8_names() {
1849 let file = FileHeader {
1850 block: BlockHeader {
1851 header_crc: 0,
1852 header_size: 0,
1853 header_type: HEAD_FILE,
1854 flags: 0,
1855 extra_area_size: None,
1856 data_size: Some(0),
1857 offset: 0,
1858 header_range: 0..0,
1859 data_range: 0..0,
1860 },
1861 file_flags: 0,
1862 unpacked_size: 0,
1863 attributes: 0,
1864 mtime: None,
1865 data_crc32: None,
1866 compression_info: 0,
1867 host_os: 0,
1868 name: vec![0xff, b'.', b'b', b'i', b'n'],
1869 hash: None,
1870 redirection: None,
1871 service_data: None,
1872 encrypted: false,
1873 encryption: None,
1874 crypto: None,
1875 };
1876
1877 assert_eq!(file.name_bytes(), [0xff, b'.', b'b', b'i', b'n']);
1878 assert_eq!(file.name_lossy(), "\u{fffd}.bin");
1879 }
1880
1881 fn build_archive_with_optional_comment(comment: Option<&[u8]>) -> Archive {
1882 use crate::FeatureSet;
1883 let mut features = FeatureSet::store_only();
1884 features.archive_comment = comment.is_some();
1885 let entries = [crate::rar50::StoredEntry {
1886 name: b"payload.txt",
1887 data: b"payload bytes",
1888 mtime: None,
1889 attributes: 0x20,
1890 host_os: 3,
1891 }];
1892 let bytes = crate::rar50::Rar50Writer::new(crate::rar50::WriterOptions::new(
1893 crate::version::ArchiveVersion::Rar50,
1894 features,
1895 ))
1896 .stored_entries(&entries)
1897 .archive_comment(comment)
1898 .finish()
1899 .unwrap();
1900 Archive::parse(&bytes).unwrap()
1901 }
1902
1903 #[test]
1904 fn archive_comment_returns_none_for_archive_without_a_cmt_service() {
1905 let archive = build_archive_with_optional_comment(None);
1906 assert!(archive.archive_comment().unwrap().is_none());
1907 }
1908
1909 #[test]
1910 fn archive_comment_decodes_the_cmt_service_payload_text() {
1911 let comment_text = b"archive comment from rars unit test\n";
1912 let archive = build_archive_with_optional_comment(Some(comment_text));
1913 let comment = archive.archive_comment().unwrap();
1914 assert_eq!(comment.as_deref(), Some(&comment_text[..]));
1915 }
1916
1917 #[test]
1918 fn archive_comment_ignores_cmt_services_attached_to_files() {
1919 use crate::FeatureSet;
1922 let services = [crate::rar50::StoredServiceEntry {
1923 name: b"CMT",
1924 data: b"per-file comment",
1925 }];
1926 let entry = crate::rar50::StoredEntryWithServices {
1927 entry: crate::rar50::StoredEntry {
1928 name: b"payload.txt",
1929 data: b"payload bytes",
1930 mtime: None,
1931 attributes: 0x20,
1932 host_os: 3,
1933 },
1934 services: &services,
1935 };
1936 let mut features = FeatureSet::store_only();
1937 features.file_comment = true;
1938 let bytes = crate::rar50::Rar50Writer::new(crate::rar50::WriterOptions::new(
1939 crate::version::ArchiveVersion::Rar50,
1940 features,
1941 ))
1942 .stored_entries_with_services(std::slice::from_ref(&entry))
1943 .finish()
1944 .unwrap();
1945 let archive = Archive::parse(&bytes).unwrap();
1946
1947 assert!(archive.archive_comment().unwrap().is_none());
1948 }
1949}