Skip to content

Commit 9d87cbb

Browse files
authored
Merge pull request #213 from image-rs/backport-ignore-unknown-extension-blocks
Backport decoder fixes
2 parents e2990d5 + deb37a8 commit 9d87cbb

File tree

8 files changed

+118
-26
lines changed

8 files changed

+118
-26
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: Rust CI
22
on:
33
push:
4-
branches: [ master ]
4+
branches: [ master, "version-0.13" ]
55
pull_request:
6-
branches: [ master ]
6+
branches: [ master, "version-0.13" ]
77
jobs:
88
build:
99
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ rust-version = "1.62"
1616
bench = false
1717

1818
[dependencies]
19-
weezl = "0.1.8"
19+
weezl = "0.1.10"
2020
color_quant = { version = "1.1", optional = true }
2121

2222
[dev-dependencies]

src/reader/decoder.rs

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
use std::borrow::Cow;
22
use std::cmp;
3+
use std::default::Default;
34
use std::error;
45
use std::fmt;
56
use std::io;
67
use std::mem;
7-
use std::default::Default;
88
use std::num::NonZeroUsize;
99

10-
use crate::Repeat;
11-
use crate::MemoryLimit;
1210
use crate::common::{AnyExtension, Block, DisposalMethod, Extension, Frame};
1311
use crate::reader::DecodeOptions;
12+
use crate::MemoryLimit;
13+
use crate::Repeat;
1414

15-
use weezl::{BitOrder, decode::Decoder as LzwDecoder, LzwError, LzwStatus};
15+
use weezl::{decode::Decoder as LzwDecoder, BitOrder, LzwError, LzwStatus};
1616

1717
/// GIF palettes are RGB
1818
pub const PLTE_CHANNELS: usize = 3;
@@ -219,19 +219,22 @@ impl FrameDecoder {
219219
/// Converts into the given buffer. It must be [`buffer_size()`] bytes large.
220220
///
221221
/// Pixels are always deinterlaced, so update `frame.interlaced` afterwards if you're putting the buffer back into the frame.
222-
pub fn decode_lzw_encoded_frame_into_buffer(&mut self, frame: &Frame<'_>, buf: &mut [u8]) -> Result<(), DecodingError> {
222+
pub fn decode_lzw_encoded_frame_into_buffer(
223+
&mut self,
224+
frame: &Frame<'_>,
225+
buf: &mut [u8],
226+
) -> Result<(), DecodingError> {
223227
let (&min_code_size, mut data) = frame.buffer.split_first().unwrap_or((&2, &[]));
224228
self.lzw_reader.reset(min_code_size)?;
225229
let lzw_reader = &mut self.lzw_reader;
226-
self.pixel_converter.read_into_buffer(frame, buf, &mut move |out| {
227-
loop {
228-
let (bytes_read, bytes_written) = lzw_reader.decode_bytes(data, out)?;
230+
self.pixel_converter
231+
.read_into_buffer(frame, buf, &mut move |out| loop {
232+
let (bytes_read, bytes_written, status) = lzw_reader.decode_bytes(data, out)?;
229233
data = data.get(bytes_read..).unwrap_or_default();
230-
if bytes_written > 0 || bytes_read == 0 || data.is_empty() {
234+
if bytes_written > 0 || matches!(status, LzwStatus::NoProgress) {
231235
return Ok(bytes_written);
232236
}
233-
}
234-
})?;
237+
})?;
235238
Ok(())
236239
}
237240

@@ -285,7 +288,11 @@ impl LzwReader {
285288
self.decoder.as_ref().map_or(true, |e| e.has_ended())
286289
}
287290

288-
pub fn decode_bytes(&mut self, lzw_data: &[u8], decode_buffer: &mut OutputBuffer<'_>) -> io::Result<(usize, usize)> {
291+
pub fn decode_bytes(
292+
&mut self,
293+
lzw_data: &[u8],
294+
decode_buffer: &mut OutputBuffer<'_>,
295+
) -> io::Result<(usize, usize, LzwStatus)> {
289296
let decoder = self.decoder.as_mut().ok_or(io::ErrorKind::Unsupported)?;
290297

291298
let decode_buffer = match decode_buffer {
@@ -296,18 +303,24 @@ impl LzwReader {
296303

297304
let decoded = decoder.decode_bytes(lzw_data, decode_buffer);
298305

299-
match decoded.status {
300-
Ok(LzwStatus::Done | LzwStatus::Ok) => {},
301-
Ok(LzwStatus::NoProgress) => {
306+
let status = match decoded.status {
307+
Ok(ok @ LzwStatus::Done | ok @ LzwStatus::Ok) => ok,
308+
Ok(ok @ LzwStatus::NoProgress) => {
302309
if self.check_for_end_code {
303-
return Err(io::Error::new(io::ErrorKind::InvalidData, "no end code in lzw stream"));
310+
return Err(io::Error::new(
311+
io::ErrorKind::InvalidData,
312+
"no end code in lzw stream",
313+
));
304314
}
305-
},
315+
316+
ok
317+
}
306318
Err(err @ LzwError::InvalidCode) => {
307319
return Err(io::Error::new(io::ErrorKind::InvalidData, err));
308320
}
309-
}
310-
Ok((decoded.consumed_in, decoded.consumed_out))
321+
};
322+
323+
Ok((decoded.consumed_in, decoded.consumed_out, status))
311324
}
312325
}
313326

@@ -633,7 +646,11 @@ impl StreamingDecoder {
633646
self.ext.data.clear();
634647
self.ext.id = AnyExtension(b);
635648
if self.ext.id.into_known().is_none() {
636-
return Err(DecodingError::format("unknown block type encountered"));
649+
if !self.allow_unknown_blocks {
650+
return Err(DecodingError::format(
651+
"unknown extension block encountered",
652+
));
653+
}
637654
}
638655
goto!(ExtensionBlockStart, emit Decoded::BlockStart(Block::Extension))
639656
}
@@ -745,10 +762,11 @@ impl StreamingDecoder {
745762
return goto!(n, DecodeSubBlock(left - n), emit Decoded::Nothing);
746763
}
747764

748-
let (mut consumed, bytes_len) = self.lzw_reader.decode_bytes(&buf[..n], write_into)?;
765+
let (mut consumed, bytes_len, status) =
766+
self.lzw_reader.decode_bytes(&buf[..n], write_into)?;
749767

750768
// skip if can't make progress (decode would fail if check_for_end_code was set)
751-
if consumed == 0 && bytes_len == 0 {
769+
if matches!(status, LzwStatus::NoProgress) {
752770
consumed = n;
753771
}
754772

@@ -762,10 +780,14 @@ impl StreamingDecoder {
762780
// decode next sub-block
763781
goto!(DecodeSubBlock(b as usize))
764782
} else {
765-
let (_, bytes_len) = self.lzw_reader.decode_bytes(&[], write_into)?;
783+
let (_, bytes_len, status) = self.lzw_reader.decode_bytes(&[], write_into)?;
766784

767785
if let Some(bytes_len) = NonZeroUsize::new(bytes_len) {
768786
goto!(0, DecodeSubBlock(0), emit Decoded::BytesDecoded(bytes_len))
787+
} else if matches!(status, LzwStatus::Ok) {
788+
goto!(0, DecodeSubBlock(0), emit Decoded::Nothing)
789+
} else if matches!(status, LzwStatus::Done) {
790+
goto!(0, FrameDecoded)
769791
} else {
770792
goto!(0, FrameDecoded)
771793
}

src/reader/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ impl DecodeOptions {
182182
/// or `Trailer` block. Otherwise, the decoded image will return an error.
183183
/// If an unknown block error is returned from decoding, enabling this
184184
/// setting may allow for a further state of decoding on the next attempt.
185+
///
186+
/// This option also allows unknown extension blocks. The decoder assumes the follow the same
187+
/// block layout, i.e. a sequence of zero-length terminated sub-blocks immediately follow the
188+
/// extension introducer.
185189
pub fn allow_unknown_blocks(&mut self, check: bool) {
186190
self.allow_unknown_blocks = check;
187191
}

tests/decode.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,69 @@ fn check_skip_frame_data_decode_frame_error() {
186186
}
187187
assert!(skipping_decoder.read_next_frame().unwrap().is_none());
188188
}
189+
190+
#[test]
191+
fn check_reset_code_lzw() {
192+
// We had a regression where reset (or clear) codes in the LZW stream was interpreted as a
193+
// truncated image data block due to them neither consuming nor producing any bytes. This
194+
// misinterpretation happened in both the main block and the flush portions of decoding.
195+
let image: [&[u8]; 2] = [
196+
include_bytes!("regression/issue-208-block-type.gif"),
197+
include_bytes!("regression/issue-208-block-type-beta.gif"),
198+
];
199+
200+
for image in image {
201+
let mut options = DecodeOptions::new();
202+
// With skip_frame_decoding we are handling LZW streams differently, returning it.
203+
// Nevertheless the data of these should be equivalent.
204+
options.skip_frame_decoding(true);
205+
let mut skipping_decoder = options.read_info(image).unwrap();
206+
let mut normal_decoder = DecodeOptions::new().read_info(image).unwrap();
207+
208+
while let Some(normal_frame) = normal_decoder.read_next_frame().unwrap() {
209+
let compressed_frame = skipping_decoder.read_next_frame().unwrap().unwrap();
210+
assert_eq!(normal_frame.width, compressed_frame.width);
211+
assert_eq!(normal_frame.height, compressed_frame.height);
212+
assert_eq!(normal_frame.delay, compressed_frame.delay);
213+
assert!(!normal_frame.buffer.is_empty());
214+
assert!(!compressed_frame.buffer.is_empty());
215+
}
216+
217+
assert!(skipping_decoder.read_next_frame().unwrap().is_none());
218+
}
219+
}
220+
221+
#[test]
222+
fn issue_209_exension_block() {
223+
let image: &[u8] = include_bytes!("regression/issue-209-extension-block-type.gif");
224+
225+
{
226+
// Check we can ignore the block with the right settings.
227+
let mut options = DecodeOptions::new();
228+
options.allow_unknown_blocks(true);
229+
230+
let mut normal_decoder = options.read_info(image).unwrap();
231+
while let Some(_) = normal_decoder.read_next_frame().unwrap() {}
232+
}
233+
234+
// TODO: block break syntax would be neat.
235+
(|| {
236+
// Check we surface the error with the right settings
237+
let mut options = DecodeOptions::new();
238+
options.allow_unknown_blocks(false);
239+
let mut normal_decoder = match options.read_info(image) {
240+
// Expectedly hit an unknown block
241+
Err(_e) => return,
242+
Ok(decoder) => decoder,
243+
};
244+
245+
// This one is for future patches if we might surface the error later.
246+
loop {
247+
match normal_decoder.read_next_frame() {
248+
Ok(Some(_)) => {}
249+
Ok(None) => panic!("Fully decoded without hitting an unknown block"),
250+
Err(_e) => return,
251+
}
252+
}
253+
})();
254+
}
116 Bytes
Loading
148 Bytes
Loading
68 Bytes
Loading

0 commit comments

Comments
 (0)