compression_codecs/gzip/
header.rs1use 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}