Coverage Report

Created: 2024-10-16 07:58

/rust/registry/src/index.crates.io-6f17d22bba15001f/miniz_oxide-0.7.4/src/inflate/stream.rs
Line
Count
Source (jump to first uncovered line)
1
//! Extra streaming decompression functionality.
2
//!
3
//! As of now this is mainly intended for use to build a higher-level wrapper.
4
#[cfg(feature = "with-alloc")]
5
use crate::alloc::boxed::Box;
6
use core::{cmp, mem};
7
8
use crate::inflate::core::{decompress, inflate_flags, DecompressorOxide, TINFL_LZ_DICT_SIZE};
9
use crate::inflate::TINFLStatus;
10
use crate::{DataFormat, MZError, MZFlush, MZResult, MZStatus, StreamResult};
11
12
/// Tag that determines reset policy of [InflateState](struct.InflateState.html)
13
pub trait ResetPolicy {
14
    /// Performs reset
15
    fn reset(&self, state: &mut InflateState);
16
}
17
18
/// Resets state, without performing expensive ops (e.g. zeroing buffer)
19
///
20
/// Note that not zeroing buffer can lead to security issues when dealing with untrusted input.
21
pub struct MinReset;
22
23
impl ResetPolicy for MinReset {
24
0
    fn reset(&self, state: &mut InflateState) {
25
0
        state.decompressor().init();
26
0
        state.dict_ofs = 0;
27
0
        state.dict_avail = 0;
28
0
        state.first_call = true;
29
0
        state.has_flushed = false;
30
0
        state.last_status = TINFLStatus::NeedsMoreInput;
31
0
    }
32
}
33
34
/// Resets state and zero memory, continuing to use the same data format.
35
pub struct ZeroReset;
36
37
impl ResetPolicy for ZeroReset {
38
    #[inline]
39
0
    fn reset(&self, state: &mut InflateState) {
40
0
        MinReset.reset(state);
41
0
        state.dict = [0; TINFL_LZ_DICT_SIZE];
42
0
    }
43
}
44
45
/// Full reset of the state, including zeroing memory.
46
///
47
/// Requires to provide new data format.
48
pub struct FullReset(pub DataFormat);
49
50
impl ResetPolicy for FullReset {
51
    #[inline]
52
0
    fn reset(&self, state: &mut InflateState) {
53
0
        ZeroReset.reset(state);
54
0
        state.data_format = self.0;
55
0
    }
56
}
57
58
/// A struct that compbines a decompressor with extra data for streaming decompression.
59
///
60
pub struct InflateState {
61
    /// Inner decompressor struct
62
    decomp: DecompressorOxide,
63
64
    /// Buffer of input bytes for matches.
65
    /// TODO: Could probably do this a bit cleaner with some
66
    /// Cursor-like class.
67
    /// We may also look into whether we need to keep a buffer here, or just one in the
68
    /// decompressor struct.
69
    dict: [u8; TINFL_LZ_DICT_SIZE],
70
    /// Where in the buffer are we currently at?
71
    dict_ofs: usize,
72
    /// How many bytes of data to be flushed is there currently in the buffer?
73
    dict_avail: usize,
74
75
    first_call: bool,
76
    has_flushed: bool,
77
78
    /// Whether the input data is wrapped in a zlib header and checksum.
79
    /// TODO: This should be stored in the decompressor.
80
    data_format: DataFormat,
81
    last_status: TINFLStatus,
82
}
83
84
impl Default for InflateState {
85
0
    fn default() -> Self {
86
0
        InflateState {
87
0
            decomp: DecompressorOxide::default(),
88
0
            dict: [0; TINFL_LZ_DICT_SIZE],
89
0
            dict_ofs: 0,
90
0
            dict_avail: 0,
91
0
            first_call: true,
92
0
            has_flushed: false,
93
0
            data_format: DataFormat::Raw,
94
0
            last_status: TINFLStatus::NeedsMoreInput,
95
0
        }
96
0
    }
97
}
98
impl InflateState {
99
    /// Create a new state.
100
    ///
101
    /// Note that this struct is quite large due to internal buffers, and as such storing it on
102
    /// the stack is not recommended.
103
    ///
104
    /// # Parameters
105
    /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib
106
    /// metadata.
107
0
    pub fn new(data_format: DataFormat) -> InflateState {
108
0
        InflateState {
109
0
            data_format,
110
0
            ..Default::default()
111
0
        }
112
0
    }
113
114
    /// Create a new state on the heap.
115
    ///
116
    /// # Parameters
117
    /// `data_format`: Determines whether the compressed data is assumed to wrapped with zlib
118
    /// metadata.
119
    #[cfg(feature = "with-alloc")]
120
    pub fn new_boxed(data_format: DataFormat) -> Box<InflateState> {
121
        let mut b: Box<InflateState> = Box::default();
122
        b.data_format = data_format;
123
        b
124
    }
125
126
    /// Access the innner decompressor.
127
0
    pub fn decompressor(&mut self) -> &mut DecompressorOxide {
128
0
        &mut self.decomp
129
0
    }
130
131
    /// Return the status of the last call to `inflate` with this `InflateState`.
132
0
    pub const fn last_status(&self) -> TINFLStatus {
133
0
        self.last_status
134
0
    }
135
136
    /// Create a new state using miniz/zlib style window bits parameter.
137
    ///
138
    /// The decompressor does not support different window sizes. As such,
139
    /// any positive (>0) value will set the zlib header flag, while a negative one
140
    /// will not.
141
    #[cfg(feature = "with-alloc")]
142
    pub fn new_boxed_with_window_bits(window_bits: i32) -> Box<InflateState> {
143
        let mut b: Box<InflateState> = Box::default();
144
        b.data_format = DataFormat::from_window_bits(window_bits);
145
        b
146
    }
147
148
    #[inline]
149
    /// Reset the decompressor without re-allocating memory, using the given
150
    /// data format.
151
0
    pub fn reset(&mut self, data_format: DataFormat) {
152
0
        self.reset_as(FullReset(data_format));
153
0
    }
154
155
    #[inline]
156
    /// Resets the state according to specified policy.
157
0
    pub fn reset_as<T: ResetPolicy>(&mut self, policy: T) {
158
0
        policy.reset(self)
159
0
    }
160
}
161
162
/// Try to decompress from `input` to `output` with the given [`InflateState`]
163
///
164
/// # `flush`
165
///
166
/// Generally, the various [`MZFlush`] flags have meaning only on the compression side.  They can be
167
/// supplied here, but the only one that has any semantic meaning is [`MZFlush::Finish`], which is a
168
/// signal that the stream is expected to finish, and failing to do so is an error.  It isn't
169
/// necessary to specify it when the stream ends; you'll still get returned a
170
/// [`MZStatus::StreamEnd`] anyway.  Other values either have no effect or cause errors.  It's
171
/// likely that you'll almost always just want to use [`MZFlush::None`].
172
///
173
/// # Errors
174
///
175
/// Returns [`MZError::Buf`] if the size of the `output` slice is empty or no progress was made due
176
/// to lack of expected input data, or if called with [`MZFlush::Finish`] and input wasn't all
177
/// consumed.
178
///
179
/// Returns [`MZError::Data`] if this or a a previous call failed with an error return from
180
/// [`TINFLStatus`]; probably indicates corrupted data.
181
///
182
/// Returns [`MZError::Stream`] when called with [`MZFlush::Full`] (meaningless on
183
/// decompression), or when called without [`MZFlush::Finish`] after an earlier call with
184
/// [`MZFlush::Finish`] has been made.
185
0
pub fn inflate(
186
0
    state: &mut InflateState,
187
0
    input: &[u8],
188
0
    output: &mut [u8],
189
0
    flush: MZFlush,
190
0
) -> StreamResult {
191
0
    let mut bytes_consumed = 0;
192
0
    let mut bytes_written = 0;
193
0
    let mut next_in = input;
194
0
    let mut next_out = output;
195
0
196
0
    if flush == MZFlush::Full {
197
0
        return StreamResult::error(MZError::Stream);
198
0
    }
199
200
0
    let mut decomp_flags = if state.data_format == DataFormat::Zlib {
201
0
        inflate_flags::TINFL_FLAG_COMPUTE_ADLER32
202
    } else {
203
0
        inflate_flags::TINFL_FLAG_IGNORE_ADLER32
204
    };
205
206
0
    if (state.data_format == DataFormat::Zlib)
207
0
        | (state.data_format == DataFormat::ZLibIgnoreChecksum)
208
0
    {
209
0
        decomp_flags |= inflate_flags::TINFL_FLAG_PARSE_ZLIB_HEADER;
210
0
    }
211
212
0
    let first_call = state.first_call;
213
0
    state.first_call = false;
214
0
    if state.last_status == TINFLStatus::FailedCannotMakeProgress {
215
0
        return StreamResult::error(MZError::Buf);
216
0
    }
217
0
    if (state.last_status as i32) < 0 {
218
0
        return StreamResult::error(MZError::Data);
219
0
    }
220
0
221
0
    if state.has_flushed && (flush != MZFlush::Finish) {
222
0
        return StreamResult::error(MZError::Stream);
223
0
    }
224
0
    state.has_flushed |= flush == MZFlush::Finish;
225
0
226
0
    if (flush == MZFlush::Finish) && first_call {
227
0
        decomp_flags |= inflate_flags::TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
228
0
229
0
        let status = decompress(&mut state.decomp, next_in, next_out, 0, decomp_flags);
230
0
        let in_bytes = status.1;
231
0
        let out_bytes = status.2;
232
0
        let status = status.0;
233
0
234
0
        state.last_status = status;
235
0
236
0
        bytes_consumed += in_bytes;
237
0
        bytes_written += out_bytes;
238
239
0
        let ret_status = {
240
0
            if status == TINFLStatus::FailedCannotMakeProgress {
241
0
                Err(MZError::Buf)
242
0
            } else if (status as i32) < 0 {
243
0
                Err(MZError::Data)
244
0
            } else if status != TINFLStatus::Done {
245
0
                state.last_status = TINFLStatus::Failed;
246
0
                Err(MZError::Buf)
247
            } else {
248
0
                Ok(MZStatus::StreamEnd)
249
            }
250
        };
251
0
        return StreamResult {
252
0
            bytes_consumed,
253
0
            bytes_written,
254
0
            status: ret_status,
255
0
        };
256
0
    }
257
0
258
0
    if flush != MZFlush::Finish {
259
0
        decomp_flags |= inflate_flags::TINFL_FLAG_HAS_MORE_INPUT;
260
0
    }
261
262
0
    if state.dict_avail != 0 {
263
0
        bytes_written += push_dict_out(state, &mut next_out);
264
0
        return StreamResult {
265
0
            bytes_consumed,
266
0
            bytes_written,
267
0
            status: Ok(
268
0
                if (state.last_status == TINFLStatus::Done) && (state.dict_avail == 0) {
269
0
                    MZStatus::StreamEnd
270
                } else {
271
0
                    MZStatus::Ok
272
                },
273
            ),
274
        };
275
0
    }
276
0
277
0
    let status = inflate_loop(
278
0
        state,
279
0
        &mut next_in,
280
0
        &mut next_out,
281
0
        &mut bytes_consumed,
282
0
        &mut bytes_written,
283
0
        decomp_flags,
284
0
        flush,
285
0
    );
286
0
    StreamResult {
287
0
        bytes_consumed,
288
0
        bytes_written,
289
0
        status,
290
0
    }
291
0
}
292
293
0
fn inflate_loop(
294
0
    state: &mut InflateState,
295
0
    next_in: &mut &[u8],
296
0
    next_out: &mut &mut [u8],
297
0
    total_in: &mut usize,
298
0
    total_out: &mut usize,
299
0
    decomp_flags: u32,
300
0
    flush: MZFlush,
301
0
) -> MZResult {
302
0
    let orig_in_len = next_in.len();
303
0
    loop {
304
0
        let status = decompress(
305
0
            &mut state.decomp,
306
0
            next_in,
307
0
            &mut state.dict,
308
0
            state.dict_ofs,
309
0
            decomp_flags,
310
0
        );
311
0
312
0
        let in_bytes = status.1;
313
0
        let out_bytes = status.2;
314
0
        let status = status.0;
315
0
316
0
        state.last_status = status;
317
0
318
0
        *next_in = &next_in[in_bytes..];
319
0
        *total_in += in_bytes;
320
0
321
0
        state.dict_avail = out_bytes;
322
0
        *total_out += push_dict_out(state, next_out);
323
0
324
0
        // The stream was corrupted, and decompression failed.
325
0
        if (status as i32) < 0 {
326
0
            return Err(MZError::Data);
327
0
        }
328
0
329
0
        // The decompressor has flushed all it's data and is waiting for more input, but
330
0
        // there was no more input provided.
331
0
        if (status == TINFLStatus::NeedsMoreInput) && orig_in_len == 0 {
332
0
            return Err(MZError::Buf);
333
0
        }
334
0
335
0
        if flush == MZFlush::Finish {
336
0
            if status == TINFLStatus::Done {
337
                // There is not enough space in the output buffer to flush the remaining
338
                // decompressed data in the internal buffer.
339
0
                return if state.dict_avail != 0 {
340
0
                    Err(MZError::Buf)
341
                } else {
342
0
                    Ok(MZStatus::StreamEnd)
343
                };
344
            // No more space in the output buffer, but we're not done.
345
0
            } else if next_out.is_empty() {
346
0
                return Err(MZError::Buf);
347
0
            }
348
        } else {
349
            // We're not expected to finish, so it's fine if we can't flush everything yet.
350
0
            let empty_buf = next_in.is_empty() || next_out.is_empty();
351
0
            if (status == TINFLStatus::Done) || empty_buf || (state.dict_avail != 0) {
352
0
                return if (status == TINFLStatus::Done) && (state.dict_avail == 0) {
353
                    // No more data left, we're done.
354
0
                    Ok(MZStatus::StreamEnd)
355
                } else {
356
                    // Ok for now, still waiting for more input data or output space.
357
0
                    Ok(MZStatus::Ok)
358
                };
359
0
            }
360
        }
361
    }
362
0
}
363
364
0
fn push_dict_out(state: &mut InflateState, next_out: &mut &mut [u8]) -> usize {
365
0
    let n = cmp::min(state.dict_avail, next_out.len());
366
0
    (next_out[..n]).copy_from_slice(&state.dict[state.dict_ofs..state.dict_ofs + n]);
367
0
    *next_out = &mut mem::take(next_out)[n..];
368
0
    state.dict_avail -= n;
369
0
    state.dict_ofs = (state.dict_ofs + (n)) & (TINFL_LZ_DICT_SIZE - 1);
370
0
    n
371
0
}
372
373
#[cfg(all(test, feature = "with-alloc"))]
374
mod test {
375
    use super::{inflate, InflateState};
376
    use crate::{DataFormat, MZFlush, MZStatus};
377
    use alloc::vec;
378
379
    #[test]
380
    fn test_state() {
381
        let encoded = [
382
            120u8, 156, 243, 72, 205, 201, 201, 215, 81, 168, 202, 201, 76, 82, 4, 0, 27, 101, 4,
383
            19,
384
        ];
385
        let mut out = vec![0; 50];
386
        let mut state = InflateState::new_boxed(DataFormat::Zlib);
387
        let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish);
388
        let status = res.status.expect("Failed to decompress!");
389
        assert_eq!(status, MZStatus::StreamEnd);
390
        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
391
        assert_eq!(res.bytes_consumed, encoded.len());
392
393
        state.reset_as(super::ZeroReset);
394
        out.iter_mut().map(|x| *x = 0).count();
395
        let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish);
396
        let status = res.status.expect("Failed to decompress!");
397
        assert_eq!(status, MZStatus::StreamEnd);
398
        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
399
        assert_eq!(res.bytes_consumed, encoded.len());
400
401
        state.reset_as(super::MinReset);
402
        out.iter_mut().map(|x| *x = 0).count();
403
        let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish);
404
        let status = res.status.expect("Failed to decompress!");
405
        assert_eq!(status, MZStatus::StreamEnd);
406
        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
407
        assert_eq!(res.bytes_consumed, encoded.len());
408
        assert_eq!(state.decompressor().adler32(), Some(459605011));
409
410
        // Test state when not computing adler.
411
        state = InflateState::new_boxed(DataFormat::ZLibIgnoreChecksum);
412
        out.iter_mut().map(|x| *x = 0).count();
413
        let res = inflate(&mut state, &encoded, &mut out, MZFlush::Finish);
414
        let status = res.status.expect("Failed to decompress!");
415
        assert_eq!(status, MZStatus::StreamEnd);
416
        assert_eq!(out[..res.bytes_written as usize], b"Hello, zlib!"[..]);
417
        assert_eq!(res.bytes_consumed, encoded.len());
418
        // Not computed, so should be Some(1)
419
        assert_eq!(state.decompressor().adler32(), Some(1));
420
        // Should still have the checksum read from the header file.
421
        assert_eq!(state.decompressor().adler32_header(), Some(459605011))
422
    }
423
}