diff --git a/src/codecs/ico/decoder.rs b/src/codecs/ico/decoder.rs index 297b8db28e..b19f2ae4b8 100644 --- a/src/codecs/ico/decoder.rs +++ b/src/codecs/ico/decoder.rs @@ -6,6 +6,7 @@ use crate::color::ColorType; use crate::error::{ DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, }; +use crate::io::image_reader_type::SpecCompliance; use crate::io::{ DecodedAnimationAttributes, DecodedImageAttributes, DecoderPreparedImage, FormatAttributes, }; @@ -113,6 +114,7 @@ pub struct IcoDecoder { selected_entry: DirEntry, reader_offset: u64, inner_decoder: InnerDecoder, + spec_strictness: SpecCompliance, } enum InnerDecoder { @@ -147,7 +149,15 @@ struct DirEntry { impl IcoDecoder { /// Create a new decoder that decodes from the stream ```r``` - pub fn new(mut r: R) -> ImageResult> { + pub fn new(r: R) -> ImageResult> { + Self::with_spec_compliance(r, SpecCompliance::default()) + } + + /// Create a new decoder with the given spec compliance mode. + pub(crate) fn with_spec_compliance( + mut r: R, + spec: SpecCompliance, + ) -> ImageResult> { let reader_offset = r.stream_position()?; let entries = read_entries(&mut r)?; let entry = best_entry(entries)?; @@ -157,6 +167,7 @@ impl IcoDecoder { selected_entry: entry, reader_offset, inner_decoder: decoder, + spec_strictness: spec, }) } } @@ -261,12 +272,7 @@ impl DirEntry { self.seek_to_start(&mut r, reader_offset)?; if is_png { - let limits = crate::Limits { - max_image_width: Some(self.real_width().into()), - max_image_height: Some(self.real_height().into()), - max_alloc: Some(256 * 256 * 4 * 2), // width * height * 4 bytes per pixel * safety factor of 2 - }; - Ok(Png(Box::new(PngDecoder::with_limits(r, limits)))) + Ok(Png(Box::new(PngDecoder::new(r)))) } else { Ok(Bmp(BmpDecoder::new_with_ico_format(r)?)) } @@ -307,7 +313,9 @@ impl ImageDecoder for IcoDecoder { let layout = decoder.prepare_image()?; // Check if the image dimensions match the ones in the image data. let (width, height) = layout.layout.dimensions(); - if !self.selected_entry.matches_dimensions(width, height) { + if self.spec_strictness == SpecCompliance::Strict + && !self.selected_entry.matches_dimensions(width, height) + { return Err(DecoderError::ImageEntryDimensionMismatch { format: IcoEntryImageFormat::Png, entry: ( @@ -330,7 +338,9 @@ impl ImageDecoder for IcoDecoder { Bmp(decoder) => { let layout = decoder.prepare_image()?; let (width, height) = layout.layout.dimensions(); - if !self.selected_entry.matches_dimensions(width, height) { + if self.spec_strictness == SpecCompliance::Strict + && !self.selected_entry.matches_dimensions(width, height) + { return Err(DecoderError::ImageEntryDimensionMismatch { format: IcoEntryImageFormat::Bmp, entry: ( @@ -537,6 +547,34 @@ mod test { assert!(decoder.read_image(&mut buf).is_err()); } + #[test] + fn dimension_mismatch_strict_vs_lenient() { + // Minimal 2x2 32-bit BMP inside an ICO where the directory entry says 3x3. + let data = vec![ + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03, 0x03, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + + let mut decoder = + IcoDecoder::with_spec_compliance(std::io::Cursor::new(&data), SpecCompliance::Lenient) + .unwrap(); + let bytes = decoder.prepare_image().unwrap().total_bytes(); + let mut buf = vec![0; usize::try_from(bytes).unwrap()]; + assert!(decoder.read_image(&mut buf).is_ok()); + + let mut decoder = + IcoDecoder::with_spec_compliance(std::io::Cursor::new(&data), SpecCompliance::Strict) + .unwrap(); + let bytes = decoder.prepare_image().unwrap().total_bytes(); + let mut buf = vec![0; usize::try_from(bytes).unwrap()]; + assert!(decoder.read_image(&mut buf).is_err()); + } + // Verify that the AND mask is ignored for 32bpp BMP images in ICO files. #[test] fn bmp_32bpp_and_mask_ignored() { diff --git a/src/io/image_reader_type.rs b/src/io/image_reader_type.rs index 6b33a6be75..663894be81 100644 --- a/src/io/image_reader_type.rs +++ b/src/io/image_reader_type.rs @@ -220,7 +220,10 @@ impl<'a, R: 'a + BufRead + Seek> ImageReaderOptions { spec_compliance, )?), #[cfg(feature = "ico")] - ImageFormat::Ico => Box::new(ico::IcoDecoder::new(reader)?), + ImageFormat::Ico => Box::new(ico::IcoDecoder::with_spec_compliance( + reader, + spec_compliance, + )?), #[cfg(feature = "hdr")] ImageFormat::Hdr => Box::new(hdr::HdrDecoder::with_spec_compliance( reader,