flate2_expose/gz/
mod.rs

1use std::ffi::CString;
2use std::io::prelude::*;
3use std::time;
4
5use crate::bufreader::BufReader;
6use crate::Compression;
7
8pub static FHCRC: u8 = 1 << 1;
9pub static FEXTRA: u8 = 1 << 2;
10pub static FNAME: u8 = 1 << 3;
11pub static FCOMMENT: u8 = 1 << 4;
12
13pub mod bufread;
14pub mod read;
15pub mod write;
16
17/// A structure representing the header of a gzip stream.
18///
19/// The header can contain metadata about the file that was compressed, if
20/// present.
21#[derive(PartialEq, Clone, Debug, Default)]
22pub struct GzHeader {
23    extra: Option<Vec<u8>>,
24    filename: Option<Vec<u8>>,
25    comment: Option<Vec<u8>>,
26    operating_system: u8,
27    mtime: u32,
28}
29
30impl GzHeader {
31    /// Returns the `filename` field of this gzip stream's header, if present.
32    pub fn filename(&self) -> Option<&[u8]> {
33        self.filename.as_ref().map(|s| &s[..])
34    }
35
36    /// Returns the `extra` field of this gzip stream's header, if present.
37    pub fn extra(&self) -> Option<&[u8]> {
38        self.extra.as_ref().map(|s| &s[..])
39    }
40
41    /// Returns the `comment` field of this gzip stream's header, if present.
42    pub fn comment(&self) -> Option<&[u8]> {
43        self.comment.as_ref().map(|s| &s[..])
44    }
45
46    /// Returns the `operating_system` field of this gzip stream's header.
47    ///
48    /// There are predefined values for various operating systems.
49    /// 255 means that the value is unknown.
50    pub fn operating_system(&self) -> u8 {
51        self.operating_system
52    }
53
54    /// This gives the most recent modification time of the original file being compressed.
55    ///
56    /// The time is in Unix format, i.e., seconds since 00:00:00 GMT, Jan. 1, 1970.
57    /// (Note that this may cause problems for MS-DOS and other systems that use local
58    /// rather than Universal time.) If the compressed data did not come from a file,
59    /// `mtime` is set to the time at which compression started.
60    /// `mtime` = 0 means no time stamp is available.
61    ///
62    /// The usage of `mtime` is discouraged because of Year 2038 problem.
63    pub fn mtime(&self) -> u32 {
64        self.mtime
65    }
66
67    /// Returns the most recent modification time represented by a date-time type.
68    /// Returns `None` if the value of the underlying counter is 0,
69    /// indicating no time stamp is available.
70    ///
71    ///
72    /// The time is measured as seconds since 00:00:00 GMT, Jan. 1 1970.
73    /// See [`mtime`](#method.mtime) for more detail.
74    pub fn mtime_as_datetime(&self) -> Option<time::SystemTime> {
75        if self.mtime == 0 {
76            None
77        } else {
78            let duration = time::Duration::new(u64::from(self.mtime), 0);
79            let datetime = time::UNIX_EPOCH + duration;
80            Some(datetime)
81        }
82    }
83}
84
85/// A builder structure to create a new gzip Encoder.
86///
87/// This structure controls header configuration options such as the filename.
88///
89/// # Examples
90///
91/// ```
92/// use std::io::prelude::*;
93/// # use std::io;
94/// use std::fs::File;
95/// use flate2_expose::GzBuilder;
96/// use flate2_expose::Compression;
97///
98/// // GzBuilder opens a file and writes a sample string using GzBuilder pattern
99///
100/// # fn sample_builder() -> Result<(), io::Error> {
101/// let f = File::create("examples/hello_world.gz")?;
102/// let mut gz = GzBuilder::new()
103///                 .filename("hello_world.txt")
104///                 .comment("test file, please delete")
105///                 .write(f, Compression::default());
106/// gz.write_all(b"hello world")?;
107/// gz.finish()?;
108/// # Ok(())
109/// # }
110/// ```
111#[derive(Debug)]
112pub struct GzBuilder {
113    extra: Option<Vec<u8>>,
114    filename: Option<CString>,
115    comment: Option<CString>,
116    operating_system: Option<u8>,
117    mtime: u32,
118}
119
120impl Default for GzBuilder {
121    fn default() -> Self {
122        Self::new()
123    }
124}
125
126impl GzBuilder {
127    /// Create a new blank builder with no header by default.
128    pub fn new() -> GzBuilder {
129        GzBuilder {
130            extra: None,
131            filename: None,
132            comment: None,
133            operating_system: None,
134            mtime: 0,
135        }
136    }
137
138    /// Configure the `mtime` field in the gzip header.
139    pub fn mtime(mut self, mtime: u32) -> GzBuilder {
140        self.mtime = mtime;
141        self
142    }
143
144    /// Configure the `operating_system` field in the gzip header.
145    pub fn operating_system(mut self, os: u8) -> GzBuilder {
146        self.operating_system = Some(os);
147        self
148    }
149
150    /// Configure the `extra` field in the gzip header.
151    pub fn extra<T: Into<Vec<u8>>>(mut self, extra: T) -> GzBuilder {
152        self.extra = Some(extra.into());
153        self
154    }
155
156    /// Configure the `filename` field in the gzip header.
157    ///
158    /// # Panics
159    ///
160    /// Panics if the `filename` slice contains a zero.
161    pub fn filename<T: Into<Vec<u8>>>(mut self, filename: T) -> GzBuilder {
162        self.filename = Some(CString::new(filename.into()).unwrap());
163        self
164    }
165
166    /// Configure the `comment` field in the gzip header.
167    ///
168    /// # Panics
169    ///
170    /// Panics if the `comment` slice contains a zero.
171    pub fn comment<T: Into<Vec<u8>>>(mut self, comment: T) -> GzBuilder {
172        self.comment = Some(CString::new(comment.into()).unwrap());
173        self
174    }
175
176    /// Consume this builder, creating a writer encoder in the process.
177    ///
178    /// The data written to the returned encoder will be compressed and then
179    /// written out to the supplied parameter `w`.
180    pub fn write<W: Write>(self, w: W, lvl: Compression) -> write::GzEncoder<W> {
181        write::gz_encoder(self.into_header(lvl), w, lvl)
182    }
183
184    /// Consume this builder, creating a reader encoder in the process.
185    ///
186    /// Data read from the returned encoder will be the compressed version of
187    /// the data read from the given reader.
188    pub fn read<R: Read>(self, r: R, lvl: Compression) -> read::GzEncoder<R> {
189        read::gz_encoder(self.buf_read(BufReader::new(r), lvl))
190    }
191
192    /// Consume this builder, creating a reader encoder in the process.
193    ///
194    /// Data read from the returned encoder will be the compressed version of
195    /// the data read from the given reader.
196    pub fn buf_read<R>(self, r: R, lvl: Compression) -> bufread::GzEncoder<R>
197    where
198        R: BufRead,
199    {
200        bufread::gz_encoder(self.into_header(lvl), r, lvl)
201    }
202
203    fn into_header(self, lvl: Compression) -> Vec<u8> {
204        let GzBuilder {
205            extra,
206            filename,
207            comment,
208            operating_system,
209            mtime,
210        } = self;
211        let mut flg = 0;
212        let mut header = vec![0u8; 10];
213        if let Some(v) = extra {
214            flg |= FEXTRA;
215            header.push((v.len() >> 0) as u8);
216            header.push((v.len() >> 8) as u8);
217            header.extend(v);
218        }
219        if let Some(filename) = filename {
220            flg |= FNAME;
221            header.extend(filename.as_bytes_with_nul().iter().map(|x| *x));
222        }
223        if let Some(comment) = comment {
224            flg |= FCOMMENT;
225            header.extend(comment.as_bytes_with_nul().iter().map(|x| *x));
226        }
227        header[0] = 0x1f;
228        header[1] = 0x8b;
229        header[2] = 8;
230        header[3] = flg;
231        header[4] = (mtime >> 0) as u8;
232        header[5] = (mtime >> 8) as u8;
233        header[6] = (mtime >> 16) as u8;
234        header[7] = (mtime >> 24) as u8;
235        header[8] = if lvl.0 >= Compression::best().0 {
236            2
237        } else if lvl.0 <= Compression::fast().0 {
238            4
239        } else {
240            0
241        };
242
243        // Typically this byte indicates what OS the gz stream was created on,
244        // but in an effort to have cross-platform reproducible streams just
245        // default this value to 255. I'm not sure that if we "correctly" set
246        // this it'd do anything anyway...
247        header[9] = operating_system.unwrap_or(255);
248        header
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use std::io::prelude::*;
255
256    use super::{read, write, GzBuilder};
257    use crate::Compression;
258    use rand::{thread_rng, Rng};
259
260    #[test]
261    fn roundtrip() {
262        let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
263        e.write_all(b"foo bar baz").unwrap();
264        let inner = e.finish().unwrap();
265        let mut d = read::GzDecoder::new(&inner[..]);
266        let mut s = String::new();
267        d.read_to_string(&mut s).unwrap();
268        assert_eq!(s, "foo bar baz");
269    }
270
271    #[test]
272    fn roundtrip_zero() {
273        let e = write::GzEncoder::new(Vec::new(), Compression::default());
274        let inner = e.finish().unwrap();
275        let mut d = read::GzDecoder::new(&inner[..]);
276        let mut s = String::new();
277        d.read_to_string(&mut s).unwrap();
278        assert_eq!(s, "");
279    }
280
281    #[test]
282    fn roundtrip_big() {
283        let mut real = Vec::new();
284        let mut w = write::GzEncoder::new(Vec::new(), Compression::default());
285        let v = crate::random_bytes().take(1024).collect::<Vec<_>>();
286        for _ in 0..200 {
287            let to_write = &v[..thread_rng().gen_range(0..v.len())];
288            real.extend(to_write.iter().map(|x| *x));
289            w.write_all(to_write).unwrap();
290        }
291        let result = w.finish().unwrap();
292        let mut r = read::GzDecoder::new(&result[..]);
293        let mut v = Vec::new();
294        r.read_to_end(&mut v).unwrap();
295        assert!(v == real);
296    }
297
298    #[test]
299    fn roundtrip_big2() {
300        let v = crate::random_bytes().take(1024 * 1024).collect::<Vec<_>>();
301        let mut r = read::GzDecoder::new(read::GzEncoder::new(&v[..], Compression::default()));
302        let mut res = Vec::new();
303        r.read_to_end(&mut res).unwrap();
304        assert!(res == v);
305    }
306
307    #[test]
308    fn fields() {
309        let r = vec![0, 2, 4, 6];
310        let e = GzBuilder::new()
311            .filename("foo.rs")
312            .comment("bar")
313            .extra(vec![0, 1, 2, 3])
314            .read(&r[..], Compression::default());
315        let mut d = read::GzDecoder::new(e);
316        assert_eq!(d.header().unwrap().filename(), Some(&b"foo.rs"[..]));
317        assert_eq!(d.header().unwrap().comment(), Some(&b"bar"[..]));
318        assert_eq!(d.header().unwrap().extra(), Some(&b"\x00\x01\x02\x03"[..]));
319        let mut res = Vec::new();
320        d.read_to_end(&mut res).unwrap();
321        assert_eq!(res, vec![0, 2, 4, 6]);
322    }
323
324    #[test]
325    fn keep_reading_after_end() {
326        let mut e = write::GzEncoder::new(Vec::new(), Compression::default());
327        e.write_all(b"foo bar baz").unwrap();
328        let inner = e.finish().unwrap();
329        let mut d = read::GzDecoder::new(&inner[..]);
330        let mut s = String::new();
331        d.read_to_string(&mut s).unwrap();
332        assert_eq!(s, "foo bar baz");
333        d.read_to_string(&mut s).unwrap();
334        assert_eq!(s, "foo bar baz");
335    }
336
337    #[test]
338    fn qc_reader() {
339        ::quickcheck::quickcheck(test as fn(_) -> _);
340
341        fn test(v: Vec<u8>) -> bool {
342            let r = read::GzEncoder::new(&v[..], Compression::default());
343            let mut r = read::GzDecoder::new(r);
344            let mut v2 = Vec::new();
345            r.read_to_end(&mut v2).unwrap();
346            v == v2
347        }
348    }
349
350    #[test]
351    fn flush_after_write() {
352        let mut f = write::GzEncoder::new(Vec::new(), Compression::default());
353        write!(f, "Hello world").unwrap();
354        f.flush().unwrap();
355    }
356
357    use crate::gz::bufread::tests::BlockingCursor;
358    #[test]
359    // test function read_and_forget of Buffer
360    fn blocked_partial_header_read() {
361        // this is a reader which receives data afterwards
362        let mut r = BlockingCursor::new();
363        let data = vec![1, 2, 3];
364
365        match r.write_all(&data) {
366            Ok(()) => {}
367            _ => {
368                panic!("Unexpected result for write_all");
369            }
370        }
371        r.set_position(0);
372
373        // this is unused except for the buffering
374        let mut decoder = read::GzDecoder::new(r);
375        let mut out = Vec::with_capacity(7);
376        match decoder.read(&mut out) {
377            Err(e) => {
378                assert_eq!(e.kind(), std::io::ErrorKind::WouldBlock);
379            }
380            _ => {
381                panic!("Unexpected result for decoder.read");
382            }
383        }
384    }
385}