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