sevenz_rust2/
writer.rs

1mod pack_info;
2mod seq_reader;
3mod unpack_info;
4
5pub use self::seq_reader::*;
6use self::{pack_info::PackInfo, unpack_info::UnpackInfo};
7use crate::{archive::*, encoders, lzma::*, Error, SevenZArchiveEntry};
8use bit_set::BitSet;
9use byteorder::*;
10use crc32fast::Hasher;
11use std::{
12    cell::Cell,
13    fs::File,
14    io::{Read, Seek, Write},
15    path::Path,
16    rc::Rc,
17    sync::Arc,
18};
19
20macro_rules! write_times {
21    //write_i64
22    ($fn_name:tt, $nid:expr, $has_time:tt, $time:tt) => {
23        write_times!($fn_name, $nid, $has_time, $time, write_u64);
24    };
25    ($fn_name:tt, $nid:expr, $has_time:tt, $time:tt, $write_fn:tt) => {
26        fn $fn_name<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
27            let mut num = 0;
28            for entry in self.files.iter() {
29                if entry.$has_time {
30                    num += 1;
31                }
32            }
33            if num > 0 {
34                header.write_u8($nid)?;
35                let mut temp: Vec<u8> = Vec::with_capacity(128);
36                let mut out = &mut temp;
37                if num != self.files.len() {
38                    out.write_u8(0)?;
39                    let mut times = BitSet::with_capacity(self.files.len());
40                    for i in 0..self.files.len() {
41                        if self.files[i].$has_time {
42                            times.insert(i);
43                        }
44                    }
45                    write_bit_set(&mut out, &times)?;
46                } else {
47                    out.write_u8(1)?;
48                }
49                out.write_u8(0)?;
50                for file in self.files.iter() {
51                    if file.$has_time {
52                        out.$write_fn::<LittleEndian>((file.$time).into())?;
53                    }
54                }
55                out.flush()?;
56                write_u64(header, temp.len() as u64)?;
57                header.write_all(&temp)?;
58            }
59            Ok(())
60        }
61    };
62}
63
64type Result<T> = std::result::Result<T, Error>;
65
66/// Writes a 7z file.
67pub struct SevenZWriter<W: Write> {
68    output: W,
69    files: Vec<SevenZArchiveEntry>,
70    content_methods: Arc<Vec<SevenZMethodConfiguration>>,
71    pack_info: PackInfo,
72    unpack_info: UnpackInfo,
73    encrypt_header: bool,
74}
75
76#[cfg(not(target_arch = "wasm32"))]
77impl SevenZWriter<File> {
78    /// Creates a file to write a 7z archive to.
79    pub fn create(path: impl AsRef<Path>) -> Result<Self> {
80        let file = File::create(path.as_ref())
81            .map_err(|e| Error::file_open(e, path.as_ref().to_string_lossy().to_string()))?;
82        Self::new(file)
83    }
84}
85
86impl<W: Write + Seek> SevenZWriter<W> {
87    /// Prepares writer to write a 7z archive to.
88    pub fn new(mut writer: W) -> Result<Self> {
89        writer
90            .seek(std::io::SeekFrom::Start(SIGNATURE_HEADER_SIZE))
91            .map_err(Error::io)?;
92
93        Ok(Self {
94            output: writer,
95            files: Default::default(),
96            content_methods: Arc::new(vec![SevenZMethodConfiguration::new(SevenZMethod::LZMA2)]),
97            pack_info: Default::default(),
98            unpack_info: Default::default(),
99            encrypt_header: true,
100        })
101    }
102
103    /// Sets the default compression methods to use for entry data. Default is LZMA2.
104    pub fn set_content_methods(
105        &mut self,
106        content_methods: Vec<SevenZMethodConfiguration>,
107    ) -> &mut Self {
108        if content_methods.is_empty() {
109            return self;
110        }
111        self.content_methods = Arc::new(content_methods);
112        self
113    }
114
115    /// Whether to enable the encryption of the -header. Default is `true`.
116    pub fn set_encrypt_header(&mut self, enabled: bool) {
117        self.encrypt_header = enabled;
118    }
119
120    /// Adds an archive `entry` with data from `reader`.
121    ///
122    /// # Example
123    /// ```no_run
124    /// use sevenz_rust2::*;
125    /// use std::fs::File;
126    /// use std::path::Path;
127    /// let mut sz = SevenZWriter::create("path/to/dest.7z").expect("create writer ok");
128    /// let src = Path::new("path/to/source.txt");
129    /// let name = "source.txt".to_string();
130    /// let entry = sz.push_archive_entry(
131    ///               SevenZArchiveEntry::from_path(&src, name),
132    ///               Some(File::open(src).unwrap()),
133    ///           )
134    ///           .expect("ok");
135    /// let compressed_size = entry.compressed_size;
136    /// sz.finish().expect("done");
137    /// ```
138    pub fn push_archive_entry<R: Read>(
139        &mut self,
140        mut entry: SevenZArchiveEntry,
141        reader: Option<R>,
142    ) -> Result<&SevenZArchiveEntry> {
143        if !entry.is_directory {
144            if let Some(mut r) = reader {
145                let mut compressed_len = 0;
146                let mut compressed = CompressWrapWriter::new(&mut self.output, &mut compressed_len);
147
148                let mut more_sizes: Vec<Rc<Cell<usize>>> =
149                    Vec::with_capacity(self.content_methods.len() - 1);
150
151                let (crc, size) = {
152                    let mut w = Self::create_writer(
153                        &self.content_methods,
154                        &mut compressed,
155                        &mut more_sizes,
156                    )?;
157                    let mut write_len = 0;
158                    let mut w = CompressWrapWriter::new(&mut w, &mut write_len);
159                    let mut buf = [0u8; 4096];
160                    loop {
161                        match r.read(&mut buf) {
162                            Ok(n) => {
163                                if n == 0 {
164                                    break;
165                                }
166                                w.write_all(&buf[..n]).map_err(|e| {
167                                    Error::io_msg(e, format!("Encode entry:{}", entry.name()))
168                                })?;
169                            }
170                            Err(e) => {
171                                return Err(Error::io_msg(
172                                    e,
173                                    format!("Encode entry:{}", entry.name()),
174                                ));
175                            }
176                        }
177                    }
178                    w.flush()
179                        .map_err(|e| Error::io_msg(e, format!("Encode entry:{}", entry.name())))?;
180                    w.write(&[])
181                        .map_err(|e| Error::io_msg(e, format!("Encode entry:{}", entry.name())))?;
182
183                    (w.crc_value(), write_len)
184                };
185                let compressed_crc = compressed.crc_value();
186                entry.has_stream = true;
187                entry.size = size as u64;
188                entry.crc = crc as u64;
189                entry.has_crc = true;
190                entry.compressed_crc = compressed_crc as u64;
191                entry.compressed_size = compressed_len as u64;
192                self.pack_info
193                    .add_stream(compressed_len as u64, compressed_crc);
194
195                let mut sizes = Vec::with_capacity(more_sizes.len() + 1);
196                sizes.extend(more_sizes.iter().map(|s| s.get() as u64));
197                sizes.push(size as u64);
198
199                self.unpack_info
200                    .add(self.content_methods.clone(), sizes, crc);
201
202                self.files.push(entry);
203                return Ok(self.files.last().unwrap());
204            }
205        }
206        entry.has_stream = false;
207        entry.size = 0;
208        entry.compressed_size = 0;
209        entry.has_crc = false;
210        self.files.push(entry);
211        Ok(self.files.last().unwrap())
212    }
213
214    /// Solid compression - packs `entries` into one pack.
215    ///
216    /// # Panics
217    /// * If `entries`'s length not equals to `reader.reader_len()`
218    pub fn push_archive_entries<R: Read>(
219        &mut self,
220        mut entries: Vec<SevenZArchiveEntry>,
221        reader: SeqReader<SourceReader<R>>,
222    ) -> Result<&mut Self> {
223        let mut r = reader;
224        assert_eq!(r.reader_len(), entries.len());
225        let mut compressed_len = 0;
226        let mut compressed = CompressWrapWriter::new(&mut self.output, &mut compressed_len);
227        let content_methods = &self.content_methods;
228        let mut more_sizes: Vec<Rc<Cell<usize>>> = Vec::with_capacity(content_methods.len() - 1);
229
230        let (crc, size) = {
231            let mut w = Self::create_writer(content_methods, &mut compressed, &mut more_sizes)?;
232            let mut write_len = 0;
233            let mut w = CompressWrapWriter::new(&mut w, &mut write_len);
234            let mut buf = [0u8; 4096];
235            fn entries_names(entries: &[SevenZArchiveEntry]) -> String {
236                let mut names = String::with_capacity(512);
237                for ele in entries.iter() {
238                    names.push_str(&ele.name);
239                    names.push(';');
240                    if names.len() > 512 {
241                        break;
242                    }
243                }
244                names
245            }
246            loop {
247                match r.read(&mut buf) {
248                    Ok(n) => {
249                        if n == 0 {
250                            break;
251                        }
252                        w.write_all(&buf[..n]).map_err(|e| {
253                            Error::io_msg(e, format!("Encode entries:{}", entries_names(&entries)))
254                        })?;
255                    }
256                    Err(e) => {
257                        return Err(Error::io_msg(
258                            e,
259                            format!("Encode entries:{}", entries_names(&entries)),
260                        ));
261                    }
262                }
263            }
264            w.flush().map_err(|e| {
265                let mut names = String::with_capacity(512);
266                for ele in entries.iter() {
267                    names.push_str(&ele.name);
268                    names.push(';');
269                    if names.len() > 512 {
270                        break;
271                    }
272                }
273                Error::io_msg(e, format!("Encode entry:{}", names))
274            })?;
275            w.write(&[]).map_err(|e| {
276                Error::io_msg(e, format!("Encode entry:{}", entries_names(&entries)))
277            })?;
278
279            (w.crc_value(), write_len)
280        };
281        let compressed_crc = compressed.crc_value();
282        let mut sub_stream_crcs = Vec::with_capacity(entries.len());
283        let mut sub_stream_sizes = Vec::with_capacity(entries.len());
284        for i in 0..entries.len() {
285            let entry = &mut entries[i];
286            let ri = &r[i];
287            entry.crc = ri.crc_value() as u64;
288            entry.size = ri.read_count() as u64;
289            sub_stream_crcs.push(entry.crc as u32);
290            sub_stream_sizes.push(entry.size);
291            entry.has_crc = true;
292        }
293
294        self.pack_info
295            .add_stream(compressed_len as u64, compressed_crc);
296
297        let mut sizes = Vec::with_capacity(more_sizes.len() + 1);
298        sizes.extend(more_sizes.iter().map(|s| s.get() as u64));
299        sizes.push(size as u64);
300
301        self.unpack_info.add_multiple(
302            content_methods.clone(),
303            sizes,
304            crc,
305            entries.len() as u64,
306            sub_stream_sizes,
307            sub_stream_crcs,
308        );
309
310        self.files.extend(entries);
311        Ok(self)
312    }
313
314    fn create_writer<'a, O: Write + 'a>(
315        methods: &[SevenZMethodConfiguration],
316        out: O,
317        more_sized: &mut Vec<Rc<Cell<usize>>>,
318    ) -> Result<Box<dyn Write + 'a>> {
319        let mut encoder: Box<dyn Write> = Box::new(out);
320        let mut first = true;
321        for mc in methods.iter() {
322            if !first {
323                let counting = CountingWriter::new(encoder);
324                more_sized.push(counting.counting());
325                encoder = Box::new(encoders::add_encoder(counting, mc)?);
326            } else {
327                let counting = CountingWriter::new(encoder);
328                encoder = Box::new(encoders::add_encoder(counting, mc)?);
329            }
330            first = false;
331        }
332        Ok(encoder)
333    }
334
335    /// Finishes the compression.
336    pub fn finish(mut self) -> std::io::Result<W> {
337        let mut header: Vec<u8> = Vec::with_capacity(64 * 1024);
338        self.write_encoded_header(&mut header)?;
339        let header_pos = self.output.stream_position()?;
340        self.output.write_all(&header)?;
341        let crc32 = crc32fast::hash(&header);
342        let mut hh = [0u8; SIGNATURE_HEADER_SIZE as usize];
343        {
344            let mut hhw = hh.as_mut_slice();
345            //sig
346            hhw.write_all(SEVEN_Z_SIGNATURE)?;
347            //version
348            hhw.write_u8(0)?;
349            hhw.write_u8(2)?;
350            //placeholder for crc: index = 8
351            hhw.write_u32::<LittleEndian>(0)?;
352
353            // start header
354            hhw.write_u64::<LittleEndian>(header_pos - SIGNATURE_HEADER_SIZE)?;
355            hhw.write_u64::<LittleEndian>(0xffffffff & header.len() as u64)?;
356            hhw.write_u32::<LittleEndian>(crc32)?;
357        }
358        let crc32 = crc32fast::hash(&hh[12..]);
359        hh[8..12].copy_from_slice(&crc32.to_le_bytes());
360
361        self.output.seek(std::io::SeekFrom::Start(0))?;
362        self.output.write_all(&hh)?;
363        Ok(self.output)
364    }
365
366    fn write_header<H: Write>(&mut self, header: &mut H) -> std::io::Result<()> {
367        header.write_u8(K_HEADER)?;
368        header.write_u8(K_MAIN_STREAMS_INFO)?;
369        self.write_streams_info(header)?;
370        self.write_files_info(header)?;
371        header.write_u8(K_END)?;
372        Ok(())
373    }
374
375    fn write_encoded_header<H: Write>(&mut self, header: &mut H) -> std::io::Result<()> {
376        let mut raw_header = Vec::with_capacity(64 * 1024);
377        self.write_header(&mut raw_header)?;
378        let mut pack_info = PackInfo::default();
379
380        let position = self.output.stream_position()?;
381        let pos = position - SIGNATURE_HEADER_SIZE;
382        pack_info.pos = pos;
383
384        let mut more_sizes = vec![];
385        let size = raw_header.len() as u64;
386        let crc32 = crc32fast::hash(&raw_header);
387        let mut methods = vec![];
388
389        if self.encrypt_header {
390            for conf in self.content_methods.iter() {
391                if conf.method.id() == SevenZMethod::AES256SHA256.id() {
392                    methods.push(conf.clone());
393                    break;
394                }
395            }
396        }
397
398        methods.push(SevenZMethodConfiguration::new(SevenZMethod::LZMA));
399
400        let methods = Arc::new(methods);
401
402        let mut encoded_data = Vec::with_capacity(size as usize / 2);
403
404        let mut compress_size = 0;
405        let mut compressed = CompressWrapWriter::new(&mut encoded_data, &mut compress_size);
406        {
407            let mut encoder = Self::create_writer(&methods, &mut compressed, &mut more_sizes)
408                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
409            encoder.write_all(&raw_header)?;
410            let _ = encoder.write(&[])?;
411        }
412
413        let compress_crc = compressed.crc_value();
414        let compress_size = *compressed.bytes_written;
415        if compress_size as u64 + 20 >= size {
416            // compression made it worse. Write raw data
417            header.write_all(&raw_header)?;
418            return Ok(());
419        }
420        self.output.write_all(&encoded_data[..compress_size])?;
421
422        pack_info.add_stream(compress_size as u64, compress_crc);
423
424        let mut unpack_info = UnpackInfo::default();
425        let mut sizes = Vec::with_capacity(1 + more_sizes.len());
426        sizes.extend(more_sizes.iter().map(|s| s.get() as u64));
427        sizes.push(size);
428        unpack_info.add(methods, sizes, crc32);
429
430        header.write_u8(K_ENCODED_HEADER)?;
431
432        pack_info.write_to(header)?;
433        unpack_info.write_to(header)?;
434        unpack_info.write_substreams(header)?;
435
436        header.write_u8(K_END)?;
437
438        Ok(())
439    }
440
441    fn write_streams_info<H: Write>(&mut self, header: &mut H) -> std::io::Result<()> {
442        if self.pack_info.len() > 0 {
443            self.pack_info.write_to(header)?;
444            self.unpack_info.write_to(header)?;
445        }
446        self.unpack_info.write_substreams(header)?;
447
448        header.write_u8(K_END)?;
449        Ok(())
450    }
451
452    fn write_files_info<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
453        header.write_u8(K_FILES_INFO)?;
454        write_u64(header, self.files.len() as u64)?;
455        self.write_file_empty_streams(header)?;
456        self.write_file_empty_files(header)?;
457        self.write_file_anti_items(header)?;
458        self.write_file_names(header)?;
459        self.write_file_ctimes(header)?;
460        self.write_file_atimes(header)?;
461        self.write_file_mtimes(header)?;
462        self.write_file_windows_attrs(header)?;
463        header.write_u8(K_END)?;
464        Ok(())
465    }
466
467    fn write_file_empty_streams<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
468        let mut has_empty = false;
469        for entry in self.files.iter() {
470            if !entry.has_stream {
471                has_empty = true;
472                break;
473            }
474        }
475        if has_empty {
476            header.write_u8(K_EMPTY_STREAM)?;
477            let mut bitset = BitSet::with_capacity(self.files.len());
478            for (i, entry) in self.files.iter().enumerate() {
479                if !entry.has_stream {
480                    bitset.insert(i);
481                }
482            }
483            let mut temp: Vec<u8> = Vec::with_capacity(bitset.len() / 8 + 1);
484            write_bit_set(&mut temp, &bitset)?;
485            write_u64(header, temp.len() as u64)?;
486            header.write_all(temp.as_slice())?;
487        }
488        Ok(())
489    }
490
491    fn write_file_empty_files<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
492        let mut has_empty = false;
493        let mut empty_stream_counter = 0;
494        let mut bitset = BitSet::new();
495        for entry in self.files.iter() {
496            if !entry.has_stream {
497                let is_dir = entry.is_directory();
498                has_empty |= !is_dir;
499                if !is_dir {
500                    bitset.insert(empty_stream_counter);
501                }
502                empty_stream_counter += 1;
503            }
504        }
505        if has_empty {
506            header.write_u8(K_EMPTY_FILE)?;
507
508            let mut temp: Vec<u8> = Vec::with_capacity(bitset.len() / 8 + 1);
509            write_bit_set(&mut temp, &bitset)?;
510            write_u64(header, temp.len() as u64)?;
511            header.write_all(&temp)?;
512        }
513        Ok(())
514    }
515
516    fn write_file_anti_items<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
517        let mut has_anti = false;
518        let mut counter = 0;
519        let mut bitset = BitSet::new();
520        for entry in self.files.iter() {
521            if !entry.has_stream {
522                let is_anti = entry.is_anti_item();
523                has_anti |= !is_anti;
524                if !is_anti {
525                    bitset.insert(counter);
526                }
527                counter += 1;
528            }
529        }
530        if has_anti {
531            header.write_u8(K_ANTI)?;
532
533            let mut temp: Vec<u8> = Vec::with_capacity(bitset.len() / 8 + 1);
534            write_bit_set(&mut temp, &bitset)?;
535            write_u64(header, temp.len() as u64)?;
536            header.write_all(temp.as_slice())?;
537        }
538        Ok(())
539    }
540
541    fn write_file_names<H: Write>(&self, header: &mut H) -> std::io::Result<()> {
542        header.write_u8(K_NAME)?;
543        let mut temp: Vec<u8> = Vec::with_capacity(128);
544        let out = &mut temp;
545        out.write_u8(0)?;
546        for file in self.files.iter() {
547            for c in file.name().encode_utf16() {
548                let buf = c.to_le_bytes();
549                out.write_all(&buf)?;
550            }
551            out.write_all(&[0u8; 2])?;
552        }
553        write_u64(header, temp.len() as u64)?;
554        header.write_all(temp.as_slice())?;
555        Ok(())
556    }
557
558    write_times!(
559        write_file_ctimes,
560        K_C_TIME,
561        has_creation_date,
562        creation_date
563    );
564    write_times!(write_file_atimes, K_A_TIME, has_access_date, access_date);
565    write_times!(
566        write_file_mtimes,
567        K_M_TIME,
568        has_last_modified_date,
569        last_modified_date
570    );
571    write_times!(
572        write_file_windows_attrs,
573        K_WIN_ATTRIBUTES,
574        has_windows_attributes,
575        windows_attributes,
576        write_u32
577    );
578}
579
580pub(crate) fn write_u64<W: Write>(header: &mut W, mut value: u64) -> std::io::Result<()> {
581    let mut first = 0;
582    let mut mask = 0x80;
583    let mut i = 0;
584    while i < 8 {
585        if value < (1u64 << (7 * (i + 1))) {
586            first |= value >> (8 * i);
587            break;
588        }
589        first |= mask;
590        mask >>= 1;
591        i += 1;
592    }
593    header.write_u8((first & 0xff) as u8)?;
594    while i > 0 {
595        header.write_u8((value & 0xff) as u8)?;
596        value >>= 8;
597        i -= 1;
598    }
599    Ok(())
600}
601
602fn write_bit_set<W: Write>(mut write: W, bs: &BitSet) -> std::io::Result<()> {
603    let mut cache = 0;
604    let mut shift = 7;
605    for i in 0..bs.get_ref().len() {
606        let set = if bs.contains(i) { 1 } else { 0 };
607        cache |= set << shift;
608        shift -= 1;
609        if shift < 0 {
610            write.write_u8(cache)?;
611            shift = 7;
612            cache = 0;
613        }
614    }
615    if shift != 7 {
616        write.write_u8(cache)?;
617    }
618    Ok(())
619}
620
621struct CompressWrapWriter<'a, W> {
622    writer: W,
623    crc: Hasher,
624    cache: Vec<u8>,
625    bytes_written: &'a mut usize,
626}
627
628impl<'a, W: Write> CompressWrapWriter<'a, W> {
629    pub fn new(writer: W, bytes_written: &'a mut usize) -> Self {
630        Self {
631            writer,
632            crc: Hasher::new(),
633            cache: Vec::with_capacity(8192),
634            bytes_written,
635        }
636    }
637
638    pub fn crc_value(&mut self) -> u32 {
639        let crc = std::mem::replace(&mut self.crc, Hasher::new());
640        crc.finalize()
641    }
642}
643
644impl<W: Write> Write for CompressWrapWriter<'_, W> {
645    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
646        self.cache.resize(buf.len(), Default::default());
647        let len = self.writer.write(buf)?;
648        self.crc.update(&buf[..len]);
649        *self.bytes_written += len;
650        Ok(len)
651    }
652
653    fn flush(&mut self) -> std::io::Result<()> {
654        self.writer.flush()
655    }
656}