rars 0.5.2

High-level Rust API for reading, extracting, writing, and repairing RAR archives.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
//! Assembling a RAR 5 archive in bounded memory.
//!
//! Nothing here holds a member, or the archive, in memory. Members compress
//! into temporary files, and every block size is known before a byte is
//! written, so the archive streams out in one pass.
//!
//! The one thing that has to be read back is the recovery record, which is
//! parity over everything that precedes it. When one is requested the bytes
//! being written are mirrored into a temporary file, and the parity pass reads
//! that instead of the archive it just produced.

use super::compress::{self, CompressPlan, CompressedMember};
use super::headers::{
    block_header_image, encrypted_header_block, encrypted_main_header_block, file_specific,
    header_encryption_keys, header_encryption_password, stored_file_specific, write_end_header,
    write_extra_record, write_file_encryption_record, write_hash_record_with_value,
    write_head_crypt, write_main_header, write_vint, HeaderEncryptionKeys,
};
use super::layout::{resolve_layout, LayoutInputs};
use super::{encrypt_reader_to, validate_file_entry, ArchiveEntry};
use crate::crypto::rar50::Rar50Keys;
use crate::detect::RAR50_SIGNATURE;
use crate::rar50::{
    FHEXTRA_SUBDATA, HEAD_END, HEAD_FILE, HEAD_SERVICE, HFL_DATA, HFL_EXTRA, MHFL_RECOVERY,
    MHFL_SOLID,
};
use crate::recovery::rar5::{
    build_streamed_inline_recovery, choose_recovery_memory_mode, plan_inline_recovery,
    ReadWriteSeek,
};
use crate::streaming::Spool;
use crate::write_progress::ProgressReporter;
use crate::{Error, Result, WriterResources};
use std::io::{Read, Write};

pub(super) struct EnginePlan<'a> {
    pub(super) compress: CompressPlan,
    pub(super) method: u8,
    pub(super) recovery_percent: Option<u64>,
    pub(super) header_encrypted: bool,
    pub(super) progress: Option<ProgressReporter<'a>>,
}

/// A member with its framing settled: the header bytes are final and the
/// payload only has to be copied.
struct PreparedMember {
    header: Vec<u8>,
    payload: Payload,
    payload_len: u64,
}

enum Payload {
    /// Copied straight from the source, which is re-read at write time.
    Stored(crate::EntrySource),
    Packed(Spool),
    /// Encrypted on the way out, so the ciphertext is never stored anywhere.
    Encrypted {
        plain: Box<Payload>,
        keys: Rar50Keys,
        iv: [u8; 16],
    },
}

pub(super) fn write_archive(
    entries: &[ArchiveEntry],
    plan: EnginePlan<'_>,
    resources: &WriterResources,
    output: &mut dyn Write,
) -> Result<()> {
    for entry in entries {
        validate_file_entry(&entry.name)?;
    }

    let header_keys = if plan.header_encrypted {
        let password = header_encryption_password(
            entries.iter().filter_map(|entry| entry.password.as_deref()),
        )?;
        Some(header_encryption_keys(password)?)
    } else {
        None
    };

    let sources: Vec<_> = entries.iter().map(|entry| entry.source.clone()).collect();
    let compressed = compress::compress_members(&sources, plan.compress, resources)?;

    let mut members = Vec::with_capacity(entries.len());
    for (entry, member) in entries.iter().zip(compressed) {
        members.push(prepare_member(entry, member, &plan, header_keys.as_ref())?);
    }

    let body_len = members.iter().try_fold(0u64, |total, member| {
        total
            .checked_add(member.header.len() as u64)
            .and_then(|value| value.checked_add(member.payload_len))
            .ok_or(Error::InvalidHeader("RAR 5 archive body size overflows"))
    })?;

    let head_crypt = match &header_keys {
        Some(keys) => {
            let mut block = Vec::new();
            write_head_crypt(&mut block, keys)?;
            block
        }
        None => Vec::new(),
    };

    let mut main_flags = 0;
    if plan.compress.solid {
        main_flags |= MHFL_SOLID;
    }
    if plan.recovery_percent.is_some() {
        main_flags |= MHFL_RECOVERY;
    }

    let layout = resolve_layout(&LayoutInputs {
        header_encrypted: plan.header_encrypted,
        head_crypt_len: head_crypt.len() as u64,
        main_flags,
        volume_number: None,
        archive_metadata: None,
        body_len,
        quick_open_payload_len: None,
        recovery_percent: plan.recovery_percent,
    })?;

    // Only mirror the archive when a recovery record has to read it back.
    let mut mirror = match plan.recovery_percent {
        Some(_) => Some(Spool::create(resources)?),
        None => None,
    };
    {
        let mut sink = Tee {
            output,
            mirror: mirror.as_mut(),
        };

        let main = match &header_keys {
            Some(keys) => {
                encrypted_main_header_block(&keys.keys, main_flags, None, &layout.main_extra)?
            }
            None => {
                let mut main = Vec::new();
                write_main_header(&mut main, main_flags, None, &layout.main_extra)?;
                main
            }
        };
        // The layout predicted this before any of it existed. If the
        // prediction is off, every offset in the locator is off with it.
        debug_assert_eq!(
            main.len() as u64,
            layout.main_header_len,
            "main header size differs from the size its layout was built on"
        );

        sink.write_all(RAR50_SIGNATURE)?;
        sink.write_all(&head_crypt)?;
        sink.write_all(&main)?;

        for member in members {
            sink.write_all(&member.header)?;
            write_payload(member.payload, member.payload_len, &mut sink, resources)?;
        }
    }

    if let Some(recovery_percent) = plan.recovery_percent {
        let mirror = mirror.as_mut().expect("recovery mirrors the archive");
        // The recovery block has to start exactly where the locator in the
        // main header says it does.
        debug_assert_eq!(layout.recovery_prefix_len, Some(mirror.len()));
        debug_assert_eq!(
            layout.recovery_offset,
            Some(mirror.len() - RAR50_SIGNATURE.len() as u64),
            "recovery record is not where the locator points"
        );
        write_recovery_service(
            recovery_percent,
            mirror,
            header_keys.as_ref(),
            resources,
            plan.progress,
            output,
        )?;
    }

    match &header_keys {
        Some(keys) => output.write_all(&encrypted_header_block(
            &keys.keys,
            HEAD_END,
            0,
            None,
            &super::end_header_specific(0),
            &[],
            &[],
        )?)?,
        None => {
            let mut end = Vec::new();
            write_end_header(&mut end, 0)?;
            output.write_all(&end)?;
        }
    }
    Ok(())
}

/// Builds a member's final header and decides how its payload will be written.
fn prepare_member(
    entry: &ArchiveEntry,
    member: CompressedMember,
    plan: &EnginePlan<'_>,
    header_keys: Option<&HeaderEncryptionKeys>,
) -> Result<PreparedMember> {
    let compression_info = compress::member_compression_info(&plan.compress, &member, plan.method)?;
    let plain_len = if member.store {
        member.input_size
    } else {
        member.packed.len()
    };
    let plain = if member.store {
        Payload::Stored(entry.source.clone())
    } else {
        Payload::Packed(member.packed)
    };

    let mut extra = Vec::new();
    let (payload, payload_len, data_crc32, hash) = match entry.password.as_deref() {
        Some(password) => {
            let mut salt = [0u8; 16];
            let mut iv = [0u8; 16];
            getrandom::fill(&mut salt).map_err(|_| {
                Error::InvalidHeader("RAR 5 writer could not generate encryption salt")
            })?;
            getrandom::fill(&mut iv).map_err(|_| {
                Error::InvalidHeader("RAR 5 writer could not generate encryption IV")
            })?;
            let keys = Rar50Keys::derive(password, salt, 0)
                .map_err(crate::rar50::map_rar50_crypto_error)?;
            write_file_encryption_record(&mut extra, salt, iv, keys.password_check_record());
            let crc32 = keys.mac_crc32(member.crc32);
            let hash = keys.mac_hash32(member.hash);
            (
                Payload::Encrypted {
                    plain: Box::new(plain),
                    keys,
                    iv,
                },
                // Encryption pads the payload up to the cipher block size.
                plain_len.div_ceil(16) * 16,
                crc32,
                hash,
            )
        }
        None => (plain, plain_len, member.crc32, member.hash),
    };
    write_hash_record_with_value(&mut extra, hash);

    let specific = file_specific(
        &entry.name,
        member.input_size,
        Some(data_crc32),
        entry.attributes,
        entry.mtime,
        compression_info,
        entry.host_os,
    )?;
    let header = match header_keys {
        Some(keys) => encrypted_header_block(
            &keys.keys,
            HEAD_FILE,
            HFL_EXTRA | HFL_DATA,
            Some(payload_len),
            &specific,
            &extra,
            &[],
        )?,
        None => block_header_image(
            HEAD_FILE,
            HFL_EXTRA | HFL_DATA,
            Some(payload_len),
            &specific,
            &extra,
        )?,
    };

    Ok(PreparedMember {
        header,
        payload,
        payload_len,
    })
}

fn write_payload(
    payload: Payload,
    payload_len: u64,
    output: &mut dyn Write,
    resources: &WriterResources,
) -> Result<()> {
    match payload {
        Payload::Stored(source) => {
            let mut reader = source.open()?;
            let copied = std::io::copy(&mut reader.by_ref().take(payload_len), output)?;
            if copied != payload_len {
                return Err(Error::InvalidHeader(
                    "entry source size changed while writing",
                ));
            }
            let mut trailing = [0u8; 1];
            if reader.read(&mut trailing)? != 0 {
                return Err(Error::InvalidHeader(
                    "entry source size changed while writing",
                ));
            }
            Ok(())
        }
        Payload::Packed(mut packed) => {
            packed.copy_to(output)?;
            Ok(())
        }
        Payload::Encrypted { plain, keys, iv } => {
            const ENCRYPT_CHUNK: usize = 64 * 1024;
            let _permit = resources.acquire(ENCRYPT_CHUNK as u64, 0)?;
            match *plain {
                Payload::Stored(source) => {
                    let mut reader = source.open()?;
                    let len = source.len()?;
                    encrypt_reader_to(&mut *reader, len, output, &keys, iv, ENCRYPT_CHUNK)
                }
                Payload::Packed(mut packed) => {
                    let len = packed.len();
                    packed.rewind()?;
                    encrypt_reader_to(&mut packed, len, output, &keys, iv, ENCRYPT_CHUNK)
                }
                Payload::Encrypted { .. } => Err(Error::InvalidHeader(
                    "RAR 5 payload cannot be encrypted twice",
                )),
            }
        }
    }
}

/// Computes the recovery record over `prefix` and writes its service block.
fn write_recovery_service(
    recovery_percent: u64,
    prefix: &mut Spool,
    header_keys: Option<&HeaderEncryptionKeys>,
    resources: &WriterResources,
    progress: Option<ProgressReporter<'_>>,
    output: &mut dyn Write,
) -> Result<()> {
    let prefix_len = prefix.len();
    let plan = plan_inline_recovery(prefix_len, recovery_percent)?;
    let (mode, required) = choose_recovery_memory_mode(plan, resources.memory_limit())?;
    let _permit = resources.acquire(required, 0)?;

    let mut scratch = match mode {
        crate::recovery::rar5::RecoveryMemoryMode::Striped { .. } => {
            Some(Spool::create(resources)?)
        }
        crate::recovery::rar5::RecoveryMemoryMode::Resident => None,
    };
    let mut payload = Spool::create(resources)?;
    prefix.rewind()?;
    let built = build_streamed_inline_recovery(
        prefix,
        prefix_len,
        recovery_percent,
        mode,
        scratch
            .as_mut()
            .map(|scratch| scratch as &mut dyn ReadWriteSeek),
        &mut payload,
        progress,
        1,
    )?;

    debug_assert_eq!(built.plan.payload_size(), Ok(built.payload_len));

    let mut service_data = Vec::new();
    write_vint(&mut service_data, recovery_percent);
    let mut extra = Vec::new();
    write_extra_record(&mut extra, FHEXTRA_SUBDATA, &service_data);
    let specific = stored_file_specific(
        b"RR",
        built.payload_len,
        Some(built.payload_crc32),
        0,
        None,
        0,
    )?;
    let header = match header_keys {
        Some(keys) => encrypted_header_block(
            &keys.keys,
            HEAD_SERVICE,
            HFL_EXTRA | HFL_DATA,
            Some(built.payload_len),
            &specific,
            &extra,
            &[],
        )?,
        None => block_header_image(
            HEAD_SERVICE,
            HFL_EXTRA | HFL_DATA,
            Some(built.payload_len),
            &specific,
            &extra,
        )?,
    };
    output.write_all(&header)?;
    payload.copy_to(output)?;
    Ok(())
}

/// Writes to the archive and, when a recovery record is coming, keeps a copy
/// for the parity pass to read.
struct Tee<'a> {
    output: &'a mut dyn Write,
    mirror: Option<&'a mut Spool>,
}

impl Write for Tee<'_> {
    fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
        self.output.write_all(buffer)?;
        if let Some(mirror) = self.mirror.as_mut() {
            mirror.write_all(buffer)?;
        }
        Ok(buffer.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.output.flush()
    }
}