1 use super::{stream::FormatErrorInner, DecodingError, CHUNCK_BUFFER_SIZE};
2 
3 use miniz_oxide::inflate::core::{decompress, inflate_flags, DecompressorOxide};
4 use miniz_oxide::inflate::TINFLStatus;
5 
6 /// Ergonomics wrapper around `miniz_oxide::inflate::stream` for zlib compressed data.
7 pub(super) struct ZlibStream {
8     /// Current decoding state.
9     state: Box<DecompressorOxide>,
10     /// If there has been a call to decompress already.
11     started: bool,
12     /// A buffer of compressed data.
13     /// We use this for a progress guarantee. The data in the input stream is chunked as given by
14     /// the underlying stream buffer. We will not read any more data until the current buffer has
15     /// been fully consumed. The zlib decompression can not fully consume all the data when it is
16     /// in the middle of the stream, it will treat full symbols and maybe the last bytes need to be
17     /// treated in a special way. The exact reason isn't as important but the interface does not
18     /// promise us this. Now, the complication is that the _current_ chunking information of PNG
19     /// alone is not enough to determine this as indeed the compressed stream is the concatenation
20     /// of all consecutive `IDAT`/`fdAT` chunks. We would need to inspect the next chunk header.
21     ///
22     /// Thus, there needs to be a buffer that allows fully clearing a chunk so that the next chunk
23     /// type can be inspected.
24     in_buffer: Vec<u8>,
25     /// The logical start of the `in_buffer`.
26     in_pos: usize,
27     /// Remaining buffered decoded bytes.
28     /// The decoder sometimes wants inspect some already finished bytes for further decoding. So we
29     /// keep a total of 32KB of decoded data available as long as more data may be appended.
30     out_buffer: Vec<u8>,
31     /// The cursor position in the output stream as a buffer index.
32     out_pos: usize,
33 }
34 
35 impl ZlibStream {
new() -> Self36     pub(crate) fn new() -> Self {
37         ZlibStream {
38             state: Box::default(),
39             started: false,
40             in_buffer: Vec::with_capacity(CHUNCK_BUFFER_SIZE),
41             in_pos: 0,
42             out_buffer: vec![0; 2 * CHUNCK_BUFFER_SIZE],
43             out_pos: 0,
44         }
45     }
46 
reset(&mut self)47     pub(crate) fn reset(&mut self) {
48         self.started = false;
49         self.in_buffer.clear();
50         self.out_buffer.clear();
51         self.out_pos = 0;
52         *self.state = DecompressorOxide::default();
53     }
54 
55     /// Fill the decoded buffer as far as possible from `data`.
56     /// On success returns the number of consumed input bytes.
decompress( &mut self, data: &[u8], image_data: &mut Vec<u8>, ) -> Result<usize, DecodingError>57     pub(crate) fn decompress(
58         &mut self,
59         data: &[u8],
60         image_data: &mut Vec<u8>,
61     ) -> Result<usize, DecodingError> {
62         const BASE_FLAGS: u32 = inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER
63             | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF
64             | inflate_flags::TINFL_FLAG_HAS_MORE_INPUT;
65 
66         self.prepare_vec_for_appending();
67 
68         let (status, mut in_consumed, out_consumed) = {
69             let in_data = if self.in_buffer.is_empty() {
70                 data
71             } else {
72                 &self.in_buffer[self.in_pos..]
73             };
74             decompress(
75                 &mut self.state,
76                 in_data,
77                 &mut self.out_buffer.as_mut_slice(),
78                 self.out_pos,
79                 BASE_FLAGS,
80             )
81         };
82 
83         if !self.in_buffer.is_empty() {
84             self.in_pos += in_consumed;
85         }
86 
87         if self.in_buffer.len() == self.in_pos {
88             self.in_buffer.clear();
89             self.in_pos = 0;
90         }
91 
92         if in_consumed == 0 {
93             self.in_buffer.extend_from_slice(data);
94             in_consumed = data.len();
95         }
96 
97         self.started = true;
98         self.out_pos += out_consumed;
99         self.transfer_finished_data(image_data);
100 
101         match status {
102             TINFLStatus::Done | TINFLStatus::HasMoreOutput | TINFLStatus::NeedsMoreInput => {
103                 Ok(in_consumed)
104             }
105             err => Err(DecodingError::Format(
106                 FormatErrorInner::CorruptFlateStream { err }.into(),
107             )),
108         }
109     }
110 
111     /// Called after all consecutive IDAT chunks were handled.
112     ///
113     /// The compressed stream can be split on arbitrary byte boundaries. This enables some cleanup
114     /// within the decompressor and flushing additional data which may have been kept back in case
115     /// more data were passed to it.
finish_compressed_chunks( &mut self, image_data: &mut Vec<u8>, ) -> Result<(), DecodingError>116     pub(crate) fn finish_compressed_chunks(
117         &mut self,
118         image_data: &mut Vec<u8>,
119     ) -> Result<(), DecodingError> {
120         const BASE_FLAGS: u32 = inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER
121             | inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
122 
123         if !self.started {
124             return Ok(());
125         }
126 
127         let tail = self.in_buffer.split_off(0);
128         let tail = &tail[self.in_pos..];
129 
130         let mut start = 0;
131         loop {
132             self.prepare_vec_for_appending();
133 
134             let (status, in_consumed, out_consumed) = {
135                 // TODO: we may be able to avoid the indirection through the buffer here.
136                 // First append all buffered data and then create a cursor on the image_data
137                 // instead.
138                 decompress(
139                     &mut self.state,
140                     &tail[start..],
141                     &mut self.out_buffer.as_mut_slice(),
142                     self.out_pos,
143                     BASE_FLAGS,
144                 )
145             };
146 
147             start += in_consumed;
148             self.out_pos += out_consumed;
149 
150             match status {
151                 TINFLStatus::Done => {
152                     self.out_buffer.truncate(self.out_pos as usize);
153                     image_data.append(&mut self.out_buffer);
154                     return Ok(());
155                 }
156                 TINFLStatus::HasMoreOutput => {
157                     let transferred = self.transfer_finished_data(image_data);
158                     assert!(
159                         transferred > 0 || in_consumed > 0 || out_consumed > 0,
160                         "No more forward progress made in stream decoding."
161                     );
162                 }
163                 err => {
164                     return Err(DecodingError::Format(
165                         FormatErrorInner::CorruptFlateStream { err }.into(),
166                     ));
167                 }
168             }
169         }
170     }
171 
172     /// Resize the vector to allow allocation of more data.
prepare_vec_for_appending(&mut self)173     fn prepare_vec_for_appending(&mut self) {
174         if self.out_buffer.len().saturating_sub(self.out_pos) >= CHUNCK_BUFFER_SIZE {
175             return;
176         }
177 
178         let buffered_len = self.decoding_size(self.out_buffer.len());
179         debug_assert!(self.out_buffer.len() <= buffered_len);
180         self.out_buffer.resize(buffered_len, 0u8);
181     }
182 
decoding_size(&self, len: usize) -> usize183     fn decoding_size(&self, len: usize) -> usize {
184         // Allocate one more chunk size than currently or double the length while ensuring that the
185         // allocation is valid and that any cursor within it will be valid.
186         len
187             // This keeps the buffer size a power-of-two, required by miniz_oxide.
188             .saturating_add(CHUNCK_BUFFER_SIZE.max(len))
189             // Ensure all buffer indices are valid cursor positions.
190             // Note: both cut off and zero extension give correct results.
191             .min(u64::max_value() as usize)
192             // Ensure the allocation request is valid.
193             // TODO: maximum allocation limits?
194             .min(isize::max_value() as usize)
195     }
196 
transfer_finished_data(&mut self, image_data: &mut Vec<u8>) -> usize197     fn transfer_finished_data(&mut self, image_data: &mut Vec<u8>) -> usize {
198         let safe = self.out_pos.saturating_sub(CHUNCK_BUFFER_SIZE);
199         // TODO: allocation limits.
200         image_data.extend(self.out_buffer.drain(..safe));
201         self.out_pos -= safe;
202         safe
203     }
204 }
205