easy_archive/archive/
tar.rs

1use crate::{Decode, Encode, File, Fmt, tool::clean};
2use flate2::{Compression, bufread::GzEncoder, read::GzDecoder};
3use std::io::{BufReader, Cursor, Read};
4use tar::Archive;
5
6pub struct Tar;
7
8impl Decode for Tar {
9    fn decode<T: AsRef<[u8]>>(buffer: T) -> Option<Vec<File>> {
10        let mut files = Vec::new();
11        let cur = Cursor::new(buffer);
12        let mut a = Archive::new(cur);
13        for file in a.entries().unwrap() {
14            let mut file = file.unwrap();
15            let path = file.header().path().unwrap().to_string_lossy().to_string();
16            // FIXME: skip PAX
17            if path == "pax_global_header" {
18                continue;
19            }
20            let mut buffer = vec![];
21            file.read_to_end(&mut buffer).expect("failed to read file");
22            let mode = file.header().mode().ok();
23            let is_dir = path.ends_with("/");
24            let path = clean(&path);
25            let mtime = file.header().mtime().ok();
26            files.push(File::new(path, buffer, mode, is_dir, mtime));
27        }
28        Some(files)
29    }
30}
31
32pub struct TarGz;
33impl Decode for TarGz {
34    fn decode<T: AsRef<[u8]>>(buffer: T) -> Option<Vec<File>> {
35        let buffer = buffer.as_ref();
36        let mut decoder = GzDecoder::new(buffer);
37        let mut decompressed = Vec::new();
38        decoder.read_to_end(&mut decompressed).ok()?;
39        Tar::decode(decompressed)
40    }
41}
42
43#[cfg(feature = "liblzma")]
44fn decode_xz2(buffer: &[u8]) -> Option<Vec<File>> {
45    use liblzma::bufread::XzDecoder;
46    let mut dec = XzDecoder::new(buffer);
47    let mut decompressed = vec![];
48    dec.read_to_end(&mut decompressed).ok()?;
49    Tar::decode(decompressed)
50}
51
52#[cfg(feature = "liblzma")]
53/// This is intended to be used by most for encoding data. The `preset`
54/// argument is a number 0-9 indicating the compression level to use, and
55/// normally 6 is a reasonable default.
56fn compress_xz(data: &[u8], preset: u32) -> std::io::Result<Vec<u8>> {
57    use liblzma::write::XzEncoder;
58    use std::io::{Cursor, Write};
59    let cursor = Cursor::new(Vec::new());
60    let mut encoder = XzEncoder::new(cursor, preset);
61    encoder.write_all(data)?;
62    let cursor = encoder.finish()?;
63    Ok(cursor.into_inner())
64}
65
66#[cfg(feature = "lzma-rs")]
67fn decode_lzma_rs(buffer: &[u8]) -> Option<Vec<File>> {
68    let mut cur = Cursor::new(buffer);
69    let mut decomp: Vec<u8> = Vec::new();
70    lzma_rs::xz_decompress(&mut cur, &mut decomp).ok()?;
71    Tar::decode(decomp)
72}
73
74pub struct TarXz;
75impl Decode for TarXz {
76    fn decode<T: AsRef<[u8]>>(buffer: T) -> Option<Vec<File>> {
77        let buffer = buffer.as_ref();
78        #[cfg(feature = "liblzma")]
79        return decode_xz2(buffer);
80        #[allow(unreachable_code)]
81        #[cfg(feature = "lzma-rs")]
82        return decode_lzma_rs(buffer);
83    }
84}
85
86pub struct TarBz;
87impl Decode for TarBz {
88    fn decode<T: AsRef<[u8]>>(buffer: T) -> Option<Vec<File>> {
89        use bzip2_rs::DecoderReader;
90        let cur = Cursor::new(buffer);
91        let reader = BufReader::new(DecoderReader::new(cur));
92        let v: Vec<_> = reader.bytes().map(|i| i.unwrap()).collect();
93        Tar::decode(v)
94    }
95}
96use ruzstd::decoding::StreamingDecoder;
97
98pub struct TarZstd;
99impl Decode for TarZstd {
100    fn decode<T: AsRef<[u8]>>(buffer: T) -> Option<Vec<File>> {
101        let cur = Cursor::new(buffer);
102        let mut decoder = StreamingDecoder::new(cur).unwrap();
103        let mut result = Vec::new();
104        decoder.read_to_end(&mut result).unwrap();
105        Tar::decode(result)
106    }
107}
108
109impl Encode for Tar {
110    fn encode(files: Vec<File>) -> Option<Vec<u8>> {
111        use tar::Header;
112        let mut buffer: Vec<u8> = Vec::new();
113        {
114            let mut builder = tar::Builder::new(&mut buffer);
115
116            for file in files {
117                let mut header = Header::new_gnu();
118                header.set_size(file.buffer.len() as u64);
119                header.set_mode(file.mode.unwrap_or(0o644));
120                header.set_uid(0);
121                header.set_gid(0);
122                header.set_mtime(file.last_modified.unwrap_or(0));
123                header.set_cksum();
124                builder
125                    .append_data(&mut header, &file.path, &file.buffer[..])
126                    .ok()?;
127            }
128
129            builder.finish().ok()?;
130        }
131        Some(buffer)
132    }
133}
134
135impl Encode for TarGz {
136    fn encode(files: Vec<File>) -> Option<Vec<u8>> {
137        let tar = Fmt::Tar.encode(files)?;
138        let mut cursor = Cursor::new(tar);
139        let mut encoder = GzEncoder::new(&mut cursor, Compression::default());
140        let mut compressed = Vec::new();
141        encoder.read_to_end(&mut compressed).ok()?;
142        Some(compressed)
143    }
144}
145impl Encode for TarXz {
146    #[cfg(feature = "liblzma")]
147    fn encode(_files: Vec<File>) -> Option<Vec<u8>> {
148        let tar = Fmt::Tar.encode(_files)?;
149        let xz = compress_xz(&tar, 6);
150        xz.ok()
151    }
152}
153
154impl Encode for TarBz {
155    fn encode(_files: Vec<File>) -> Option<Vec<u8>> {
156        todo!()
157    }
158}
159
160impl Encode for TarZstd {
161    fn encode(files: Vec<File>) -> Option<Vec<u8>> {
162        let tar = Fmt::Tar.encode(files)?;
163        let mut cursor = Cursor::new(tar);
164        let mut v = vec![];
165        let mut encoder = zstd::Encoder::new(&mut v, 6).ok()?;
166        std::io::copy(&mut cursor, &mut encoder).unwrap();
167        encoder.finish().unwrap();
168        Some(v)
169    }
170}