compression_codecs/gzip/
header.rs

1use compression_core::util::PartialBuffer;
2use flate2::Crc;
3use std::io;
4
5#[derive(Debug, Default)]
6struct Flags {
7    _ascii: bool,
8    crc: bool,
9    extra: bool,
10    filename: bool,
11    comment: bool,
12}
13
14#[derive(Debug, Default)]
15pub(super) struct Header {
16    flags: Flags,
17}
18
19#[derive(Debug)]
20enum State {
21    Fixed(PartialBuffer<[u8; 10]>),
22    ExtraLen(PartialBuffer<[u8; 2]>),
23    Extra(usize),
24    Filename,
25    Comment,
26    Crc(PartialBuffer<[u8; 2]>),
27    Done,
28}
29
30impl Default for State {
31    fn default() -> Self {
32        State::Fixed(<_>::default())
33    }
34}
35
36#[derive(Debug, Default)]
37pub(super) struct Parser {
38    state: State,
39    header: Header,
40}
41
42impl Header {
43    fn parse(input: &[u8; 10]) -> io::Result<Self> {
44        if input[0..3] != [0x1f, 0x8b, 0x08] {
45            return Err(io::Error::new(
46                io::ErrorKind::InvalidData,
47                "Invalid gzip header",
48            ));
49        }
50
51        let flag = input[3];
52
53        let flags = Flags {
54            _ascii: (flag & 0b0000_0001) != 0,
55            crc: (flag & 0b0000_0010) != 0,
56            extra: (flag & 0b0000_0100) != 0,
57            filename: (flag & 0b0000_1000) != 0,
58            comment: (flag & 0b0001_0000) != 0,
59        };
60
61        Ok(Header { flags })
62    }
63}
64
65fn consume_input(crc: &mut Crc, n: usize, input: &mut PartialBuffer<&[u8]>) {
66    crc.update(&input.unwritten()[..n]);
67    input.advance(n);
68}
69
70fn consume_cstr(crc: &mut Crc, input: &mut PartialBuffer<&[u8]>) -> Option<()> {
71    if let Some(len) = memchr::memchr(0, input.unwritten()) {
72        consume_input(crc, len + 1, input);
73        Some(())
74    } else {
75        consume_input(crc, input.unwritten().len(), input);
76        None
77    }
78}
79
80impl Parser {
81    pub(super) fn input(
82        &mut self,
83        crc: &mut Crc,
84        input: &mut PartialBuffer<&[u8]>,
85    ) -> io::Result<Option<Header>> {
86        loop {
87            match &mut self.state {
88                State::Fixed(data) => {
89                    data.copy_unwritten_from(input);
90
91                    if data.unwritten().is_empty() {
92                        let data = data.get_mut();
93                        crc.update(data);
94                        self.header = Header::parse(data)?;
95                        self.state = State::ExtraLen(<_>::default());
96                    } else {
97                        break Ok(None);
98                    }
99                }
100
101                State::ExtraLen(data) => {
102                    if !self.header.flags.extra {
103                        self.state = State::Filename;
104                        continue;
105                    }
106
107                    data.copy_unwritten_from(input);
108
109                    if data.unwritten().is_empty() {
110                        let data = data.get_mut();
111                        crc.update(data);
112                        let len = u16::from_le_bytes(*data);
113                        self.state = State::Extra(len.into());
114                    } else {
115                        break Ok(None);
116                    }
117                }
118
119                State::Extra(bytes_to_consume) => {
120                    let n = input.unwritten().len().min(*bytes_to_consume);
121                    *bytes_to_consume -= n;
122                    consume_input(crc, n, input);
123
124                    if *bytes_to_consume == 0 {
125                        self.state = State::Filename;
126                    } else {
127                        break Ok(None);
128                    }
129                }
130
131                State::Filename => {
132                    if !self.header.flags.filename {
133                        self.state = State::Comment;
134                        continue;
135                    }
136
137                    if consume_cstr(crc, input).is_none() {
138                        break Ok(None);
139                    }
140
141                    self.state = State::Comment;
142                }
143
144                State::Comment => {
145                    if !self.header.flags.comment {
146                        self.state = State::Crc(<_>::default());
147                        continue;
148                    }
149
150                    if consume_cstr(crc, input).is_none() {
151                        break Ok(None);
152                    }
153
154                    self.state = State::Crc(<_>::default());
155                }
156
157                State::Crc(data) => {
158                    let header = std::mem::take(&mut self.header);
159
160                    if !self.header.flags.crc {
161                        self.state = State::Done;
162                        break Ok(Some(header));
163                    }
164
165                    data.copy_unwritten_from(input);
166
167                    break if data.unwritten().is_empty() {
168                        let data = data.take().into_inner();
169                        self.state = State::Done;
170                        let checksum = crc.sum().to_le_bytes();
171
172                        if data == checksum[..2] {
173                            Ok(Some(header))
174                        } else {
175                            Err(io::Error::new(
176                                io::ErrorKind::InvalidData,
177                                "CRC computed for header does not match",
178                            ))
179                        }
180                    } else {
181                        Ok(None)
182                    };
183                }
184
185                State::Done => break Err(io::Error::other("parser used after done")),
186            }
187        }
188    }
189}