/src/image/src/codecs/ico/decoder.rs
Line | Count | Source |
1 | | use byteorder_lite::{LittleEndian, ReadBytesExt}; |
2 | | use std::io::{BufRead, Read, Seek, SeekFrom}; |
3 | | use std::{error, fmt}; |
4 | | |
5 | | use crate::color::ColorType; |
6 | | use crate::error::{ |
7 | | DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind, |
8 | | }; |
9 | | use crate::{ImageDecoder, ImageFormat}; |
10 | | |
11 | | use self::InnerDecoder::*; |
12 | | use crate::codecs::bmp::BmpDecoder; |
13 | | use crate::codecs::png::{PngDecoder, PNG_SIGNATURE}; |
14 | | |
15 | | /// Errors that can occur during decoding and parsing an ICO image or one of its enclosed images. |
16 | | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] |
17 | | enum DecoderError { |
18 | | /// The ICO directory is empty |
19 | | NoEntries, |
20 | | /// The number of color planes (0 or 1), or the horizontal coordinate of the hotspot for CUR files too big. |
21 | | IcoEntryTooManyPlanesOrHotspot, |
22 | | /// The bit depth (may be 0 meaning unspecified), or the vertical coordinate of the hotspot for CUR files too big. |
23 | | IcoEntryTooManyBitsPerPixelOrHotspot, |
24 | | |
25 | | /// The entry is in PNG format and specified a length that is shorter than PNG header. |
26 | | PngShorterThanHeader, |
27 | | /// The enclosed PNG is not in RGBA, which is invalid: <https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473>/. |
28 | | PngNotRgba, |
29 | | |
30 | | /// The entry is in BMP format and specified a data size that is not correct for the image and optional mask data. |
31 | | InvalidDataSize, |
32 | | |
33 | | /// The dimensions specified by the entry does not match the dimensions in the header of the enclosed image. |
34 | | ImageEntryDimensionMismatch { |
35 | | /// The mismatched subimage's type |
36 | | format: IcoEntryImageFormat, |
37 | | /// The dimensions specified by the entry |
38 | | entry: (u16, u16), |
39 | | /// The dimensions of the image itself |
40 | | image: (u32, u32), |
41 | | }, |
42 | | } |
43 | | |
44 | | impl fmt::Display for DecoderError { |
45 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
46 | 0 | match self { |
47 | 0 | DecoderError::NoEntries => f.write_str("ICO directory contains no image"), |
48 | | DecoderError::IcoEntryTooManyPlanesOrHotspot => { |
49 | 0 | f.write_str("ICO image entry has too many color planes or too large hotspot value") |
50 | | } |
51 | 0 | DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot => f.write_str( |
52 | 0 | "ICO image entry has too many bits per pixel or too large hotspot value", |
53 | | ), |
54 | | DecoderError::PngShorterThanHeader => { |
55 | 0 | f.write_str("Entry specified a length that is shorter than PNG header!") |
56 | | } |
57 | 0 | DecoderError::PngNotRgba => f.write_str("The PNG is not in RGBA format!"), |
58 | | DecoderError::InvalidDataSize => { |
59 | 0 | f.write_str("ICO image data size did not match expected size") |
60 | | } |
61 | | DecoderError::ImageEntryDimensionMismatch { |
62 | 0 | format, |
63 | 0 | entry, |
64 | 0 | image, |
65 | 0 | } => f.write_fmt(format_args!( |
66 | 0 | "Entry{entry:?} and {format}{image:?} dimensions do not match!" |
67 | | )), |
68 | | } |
69 | 0 | } |
70 | | } |
71 | | |
72 | | impl From<DecoderError> for ImageError { |
73 | 7.40k | fn from(e: DecoderError) -> ImageError { |
74 | 7.40k | ImageError::Decoding(DecodingError::new(ImageFormat::Ico.into(), e)) |
75 | 7.40k | } |
76 | | } |
77 | | |
78 | | impl error::Error for DecoderError {} |
79 | | |
80 | | /// The image formats an ICO may contain |
81 | | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] |
82 | | enum IcoEntryImageFormat { |
83 | | /// PNG in ARGB |
84 | | Png, |
85 | | /// BMP with optional alpha mask |
86 | | Bmp, |
87 | | } |
88 | | |
89 | | impl fmt::Display for IcoEntryImageFormat { |
90 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
91 | 0 | f.write_str(match self { |
92 | 0 | IcoEntryImageFormat::Png => "PNG", |
93 | 0 | IcoEntryImageFormat::Bmp => "BMP", |
94 | | }) |
95 | 0 | } |
96 | | } |
97 | | |
98 | | impl From<IcoEntryImageFormat> for ImageFormat { |
99 | 0 | fn from(val: IcoEntryImageFormat) -> Self { |
100 | 0 | match val { |
101 | 0 | IcoEntryImageFormat::Png => ImageFormat::Png, |
102 | 0 | IcoEntryImageFormat::Bmp => ImageFormat::Bmp, |
103 | | } |
104 | 0 | } |
105 | | } |
106 | | |
107 | | /// An ico decoder |
108 | | pub struct IcoDecoder<R: BufRead + Seek> { |
109 | | selected_entry: DirEntry, |
110 | | inner_decoder: InnerDecoder<R>, |
111 | | } |
112 | | |
113 | | enum InnerDecoder<R: BufRead + Seek> { |
114 | | Bmp(BmpDecoder<R>), |
115 | | Png(Box<PngDecoder<R>>), |
116 | | } |
117 | | |
118 | | #[derive(Clone, Copy, Default)] |
119 | | struct DirEntry { |
120 | | width: u8, |
121 | | height: u8, |
122 | | // We ignore some header fields as they will be replicated in the PNG, BMP and they are not |
123 | | // necessary for determining the best_entry. |
124 | | #[allow(unused)] |
125 | | color_count: u8, |
126 | | // Wikipedia has this to say: |
127 | | // Although Microsoft's technical documentation states that this value must be zero, the icon |
128 | | // encoder built into .NET (System.Drawing.Icon.Save) sets this value to 255. It appears that |
129 | | // the operating system ignores this value altogether. |
130 | | #[allow(unused)] |
131 | | reserved: u8, |
132 | | |
133 | | // We ignore some header fields as they will be replicated in the PNG, BMP and they are not |
134 | | // necessary for determining the best_entry. |
135 | | #[allow(unused)] |
136 | | num_color_planes: u16, |
137 | | bits_per_pixel: u16, |
138 | | |
139 | | image_length: u32, |
140 | | image_offset: u32, |
141 | | } |
142 | | |
143 | | impl<R: BufRead + Seek> IcoDecoder<R> { |
144 | | /// Create a new decoder that decodes from the stream ```r``` |
145 | 7.37k | pub fn new(mut r: R) -> ImageResult<IcoDecoder<R>> { |
146 | 7.37k | let entries = read_entries(&mut r)?; |
147 | 7.18k | let entry = best_entry(entries)?; |
148 | 7.18k | let decoder = entry.decoder(r)?; |
149 | | |
150 | 4.01k | Ok(IcoDecoder { |
151 | 4.01k | selected_entry: entry, |
152 | 4.01k | inner_decoder: decoder, |
153 | 4.01k | }) |
154 | 7.37k | } |
155 | | } |
156 | | |
157 | 7.37k | fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> { |
158 | 7.37k | let _reserved = r.read_u16::<LittleEndian>()?; |
159 | 7.36k | let _type = r.read_u16::<LittleEndian>()?; |
160 | 7.36k | let count = r.read_u16::<LittleEndian>()?; |
161 | 138k | (0..count).map(|_| read_entry(r)).collect() |
162 | 7.37k | } |
163 | | |
164 | 138k | fn read_entry<R: Read>(r: &mut R) -> ImageResult<DirEntry> { |
165 | | Ok(DirEntry { |
166 | 138k | width: r.read_u8()?, |
167 | 138k | height: r.read_u8()?, |
168 | 138k | color_count: r.read_u8()?, |
169 | 138k | reserved: r.read_u8()?, |
170 | | num_color_planes: { |
171 | | // This may be either the number of color planes (0 or 1), or the horizontal coordinate |
172 | | // of the hotspot for CUR files. |
173 | 138k | let num = r.read_u16::<LittleEndian>()?; |
174 | 138k | if num > 256 { |
175 | 28 | return Err(DecoderError::IcoEntryTooManyPlanesOrHotspot.into()); |
176 | 138k | } |
177 | 138k | num |
178 | | }, |
179 | | bits_per_pixel: { |
180 | | // This may be either the bit depth (may be 0 meaning unspecified), |
181 | | // or the vertical coordinate of the hotspot for CUR files. |
182 | 138k | let num = r.read_u16::<LittleEndian>()?; |
183 | 138k | if num > 256 { |
184 | 21 | return Err(DecoderError::IcoEntryTooManyBitsPerPixelOrHotspot.into()); |
185 | 138k | } |
186 | 138k | num |
187 | | }, |
188 | 138k | image_length: r.read_u32::<LittleEndian>()?, |
189 | 138k | image_offset: r.read_u32::<LittleEndian>()?, |
190 | | }) |
191 | 138k | } |
192 | | |
193 | | /// Find the entry with the highest (color depth, size). |
194 | | /// |
195 | | /// If two entries have the same color depth and size, pick the first one. |
196 | | /// While ICO files with multiple identical size and bpp entries are rare, they |
197 | | /// do exist. Since we can't make an educated guess which one is best, picking |
198 | | /// the first one is a reasonable default. |
199 | 7.18k | fn best_entry(entries: Vec<DirEntry>) -> ImageResult<DirEntry> { |
200 | 7.18k | entries |
201 | 7.18k | .into_iter() |
202 | 7.18k | .rev() // ties should pick the first entry, not the last |
203 | 15.6k | .max_by_key(|entry| { |
204 | 15.6k | ( |
205 | 15.6k | entry.bits_per_pixel, |
206 | 15.6k | u32::from(entry.real_width()) * u32::from(entry.real_height()), |
207 | 15.6k | ) |
208 | 15.6k | }) |
209 | 7.18k | .ok_or(DecoderError::NoEntries.into()) |
210 | 7.18k | } |
211 | | |
212 | | impl DirEntry { |
213 | 24.5k | fn real_width(&self) -> u16 { |
214 | 24.5k | match self.width { |
215 | 11.2k | 0 => 256, |
216 | 13.2k | w => u16::from(w), |
217 | | } |
218 | 24.5k | } |
219 | | |
220 | 24.4k | fn real_height(&self) -> u16 { |
221 | 24.4k | match self.height { |
222 | 13.8k | 0 => 256, |
223 | 10.5k | h => u16::from(h), |
224 | | } |
225 | 24.4k | } |
226 | | |
227 | 3.99k | fn matches_dimensions(&self, width: u32, height: u32) -> bool { |
228 | 3.99k | u32::from(self.real_width()) == width.min(256) |
229 | 3.90k | && u32::from(self.real_height()) == height.min(256) |
230 | 3.99k | } |
231 | | |
232 | 14.3k | fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> { |
233 | 14.3k | r.seek(SeekFrom::Start(u64::from(self.image_offset)))?; |
234 | 14.3k | Ok(()) |
235 | 14.3k | } |
236 | | |
237 | 7.18k | fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> { |
238 | 7.18k | self.seek_to_start(r)?; |
239 | | |
240 | | // Read the first 8 bytes to sniff the image. |
241 | 7.18k | let mut signature = [0u8; 8]; |
242 | 7.18k | r.read_exact(&mut signature)?; |
243 | | |
244 | 7.15k | Ok(signature == PNG_SIGNATURE) |
245 | 7.18k | } |
246 | | |
247 | 7.18k | fn decoder<R: BufRead + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> { |
248 | 7.18k | let is_png = self.is_png(&mut r)?; |
249 | 7.15k | self.seek_to_start(&mut r)?; |
250 | | |
251 | 7.15k | if is_png { |
252 | 4.80k | let limits = crate::Limits { |
253 | 4.80k | max_image_width: Some(self.real_width().into()), |
254 | 4.80k | max_image_height: Some(self.real_height().into()), |
255 | 4.80k | max_alloc: Some(256 * 256 * 4 * 2), // width * height * 4 bytes per pixel * safety factor of 2 |
256 | 4.80k | }; |
257 | 4.80k | Ok(Png(Box::new(PngDecoder::with_limits(r, limits)?))) |
258 | | } else { |
259 | 2.35k | Ok(Bmp(BmpDecoder::new_with_ico_format(r)?)) |
260 | | } |
261 | 7.18k | } |
262 | | } |
263 | | |
264 | | impl<R: BufRead + Seek> ImageDecoder for IcoDecoder<R> { |
265 | 19.9k | fn dimensions(&self) -> (u32, u32) { |
266 | 19.9k | match self.inner_decoder { |
267 | 9.31k | Bmp(ref decoder) => decoder.dimensions(), |
268 | 10.6k | Png(ref decoder) => decoder.dimensions(), |
269 | | } |
270 | 19.9k | } |
271 | | |
272 | 15.9k | fn color_type(&self) -> ColorType { |
273 | 15.9k | match self.inner_decoder { |
274 | 7.45k | Bmp(ref decoder) => decoder.color_type(), |
275 | 8.53k | Png(ref decoder) => decoder.color_type(), |
276 | | } |
277 | 15.9k | } |
278 | | |
279 | 3.99k | fn read_image(self, buf: &mut [u8]) -> ImageResult<()> { |
280 | 3.99k | assert_eq!(u64::try_from(buf.len()), Ok(self.total_bytes())); |
281 | 3.99k | match self.inner_decoder { |
282 | 2.13k | Png(decoder) => { |
283 | 2.13k | if self.selected_entry.image_length < PNG_SIGNATURE.len() as u32 { |
284 | 2 | return Err(DecoderError::PngShorterThanHeader.into()); |
285 | 2.13k | } |
286 | | |
287 | | // Check if the image dimensions match the ones in the image data. |
288 | 2.13k | let (width, height) = decoder.dimensions(); |
289 | 2.13k | if !self.selected_entry.matches_dimensions(width, height) { |
290 | 49 | return Err(DecoderError::ImageEntryDimensionMismatch { |
291 | 49 | format: IcoEntryImageFormat::Png, |
292 | 49 | entry: ( |
293 | 49 | self.selected_entry.real_width(), |
294 | 49 | self.selected_entry.real_height(), |
295 | 49 | ), |
296 | 49 | image: (width, height), |
297 | 49 | } |
298 | 49 | .into()); |
299 | 2.08k | } |
300 | | |
301 | | // Embedded PNG images can only be of the 32BPP RGBA format. |
302 | | // https://blogs.msdn.microsoft.com/oldnewthing/20101022-00/?p=12473/ |
303 | 2.08k | if decoder.color_type() != ColorType::Rgba8 { |
304 | 8 | return Err(DecoderError::PngNotRgba.into()); |
305 | 2.07k | } |
306 | | |
307 | 2.07k | decoder.read_image(buf) |
308 | | } |
309 | 1.85k | Bmp(mut decoder) => { |
310 | 1.85k | let (width, height) = decoder.dimensions(); |
311 | 1.85k | if !self.selected_entry.matches_dimensions(width, height) { |
312 | 59 | return Err(DecoderError::ImageEntryDimensionMismatch { |
313 | 59 | format: IcoEntryImageFormat::Bmp, |
314 | 59 | entry: ( |
315 | 59 | self.selected_entry.real_width(), |
316 | 59 | self.selected_entry.real_height(), |
317 | 59 | ), |
318 | 59 | image: (width, height), |
319 | 59 | } |
320 | 59 | .into()); |
321 | 1.80k | } |
322 | | |
323 | | // The ICO decoder needs an alpha channel to apply the AND mask. |
324 | 1.80k | if decoder.color_type() != ColorType::Rgba8 { |
325 | 0 | return Err(ImageError::Unsupported( |
326 | 0 | UnsupportedError::from_format_and_kind( |
327 | 0 | ImageFormat::Bmp.into(), |
328 | 0 | UnsupportedErrorKind::Color(decoder.color_type().into()), |
329 | 0 | ), |
330 | 0 | )); |
331 | 1.80k | } |
332 | | |
333 | 1.80k | decoder.read_image_data(buf)?; |
334 | | |
335 | 524 | let r = decoder.reader(); |
336 | 524 | let image_end = r.stream_position()?; |
337 | 524 | let data_end = u64::from(self.selected_entry.image_offset) |
338 | 524 | + u64::from(self.selected_entry.image_length); |
339 | | |
340 | 524 | let mask_row_bytes = width.div_ceil(32) * 4; |
341 | 524 | let mask_length = u64::from(mask_row_bytes) * u64::from(height); |
342 | | |
343 | | // data_end should be image_end + the mask length (mask_row_bytes * height). |
344 | | // According to |
345 | | // https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483 |
346 | | // the mask is required, but according to Wikipedia |
347 | | // https://en.wikipedia.org/wiki/ICO_(file_format) |
348 | | // the mask is not required. Unfortunately, Wikipedia does not have a citation |
349 | | // for that claim, so we can't be sure which is correct. |
350 | 524 | if data_end >= image_end + mask_length { |
351 | | // If there's an AND mask following the image, read and apply it. |
352 | 172k | for y in 0..height { |
353 | 172k | let mut x = 0; |
354 | 172k | for _ in 0..mask_row_bytes { |
355 | | // Apply the bits of each byte until we reach the end of the row. |
356 | 8.59M | let mask_byte = r.read_u8()?; |
357 | 65.3M | for bit in (0..8).rev() { |
358 | 65.3M | if x >= width { |
359 | 559k | break; |
360 | 64.7M | } |
361 | 64.7M | if mask_byte & (1 << bit) != 0 { |
362 | 18.1M | // Set alpha channel to transparent. |
363 | 18.1M | buf[((height - y - 1) * width + x) as usize * 4 + 3] = 0; |
364 | 46.6M | } |
365 | 64.7M | x += 1; |
366 | | } |
367 | | } |
368 | | } |
369 | | |
370 | 102 | Ok(()) |
371 | 69 | } else if data_end == image_end { |
372 | | // accept images with no mask data |
373 | 17 | Ok(()) |
374 | | } else { |
375 | 52 | Err(DecoderError::InvalidDataSize.into()) |
376 | | } |
377 | | } |
378 | | } |
379 | 3.99k | } |
380 | | |
381 | 3.99k | fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> { |
382 | 3.99k | (*self).read_image(buf) |
383 | 3.99k | } |
384 | | } |
385 | | |
386 | | #[cfg(test)] |
387 | | mod test { |
388 | | use super::*; |
389 | | |
390 | | // Test if BMP images without alpha channel inside ICOs don't panic. |
391 | | // Because the test data is invalid decoding should produce an error. |
392 | | #[test] |
393 | | fn bmp_16_with_missing_alpha_channel() { |
394 | | let data = vec![ |
395 | | 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x0e, 0x04, 0xc3, 0x7e, 0x00, 0x00, 0x00, 0x00, |
396 | | 0x7c, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x01, 0x00, |
397 | | 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
398 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
399 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
400 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
401 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
402 | | 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
403 | | 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, |
404 | | 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xeb, 0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, |
405 | | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, |
406 | | 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, |
407 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0c, |
408 | | 0x00, 0x00, 0x00, 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, |
409 | | 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, |
410 | | 0x4b, 0x4d, 0xe9, 0x87, 0xd3, 0xda, 0xd6, 0x89, 0x81, 0xc5, 0xa4, 0xa1, 0x60, 0x98, |
411 | | 0x31, 0xc7, 0x1d, 0xb6, 0x8f, 0x20, 0xc8, 0x3e, 0xee, 0xd8, 0xe4, 0x8f, 0xee, 0x7b, |
412 | | 0x48, 0x9b, 0x88, 0x25, 0x13, 0xda, 0xa4, 0x13, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x40, |
413 | | 0x16, 0x01, 0xff, 0xff, 0xff, 0xff, 0xe9, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
414 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
415 | | 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, 0x00, 0x00, 0x00, 0xb8, 0x00, |
416 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
417 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x66, 0x64, 0x41, 0x54, 0xa3, 0xa3, |
418 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
419 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
420 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8f, 0xf6, 0xff, 0xff, |
421 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x83, 0x70, 0x61, 0x76, |
422 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, |
423 | | 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, |
424 | | 0x00, 0x00, 0x00, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x62, 0x49, |
425 | | 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, |
426 | | 0x00, 0x00, 0x00, 0xff, 0xff, 0x94, 0xc8, 0x00, 0x02, 0x0c, 0x00, 0xff, 0xff, 0xc6, |
427 | | 0x84, 0x00, 0x2a, 0x75, 0x03, 0xa3, 0x05, 0xfb, 0xe1, 0x6e, 0xe8, 0x27, 0xd6, 0xd3, |
428 | | 0x96, 0xc1, 0xe4, 0x30, 0x0c, 0x05, 0xb9, 0xa3, 0x8b, 0x29, 0xda, 0xa4, 0xf1, 0x4d, |
429 | | 0xf3, 0xb2, 0x98, 0x2b, 0xe6, 0x93, 0x07, 0xf9, 0xca, 0x2b, 0xc2, 0x39, 0x20, 0xba, |
430 | | 0x7c, 0xa0, 0xb1, 0x43, 0xe6, 0xf9, 0xdc, 0xd1, 0xc2, 0x52, 0xdc, 0x41, 0xc1, 0x2f, |
431 | | 0x29, 0xf7, 0x46, 0x32, 0xda, 0x1b, 0x72, 0x8c, 0xe6, 0x2b, 0x01, 0xe5, 0x49, 0x21, |
432 | | 0x89, 0x89, 0xe4, 0x3d, 0xa1, 0xdb, 0x3b, 0x4a, 0x0b, 0x52, 0x86, 0x52, 0x33, 0x9d, |
433 | | 0xb2, 0xcf, 0x4a, 0x86, 0x53, 0xd7, 0xa9, 0x4b, 0xaf, 0x62, 0x06, 0x49, 0x53, 0x00, |
434 | | 0xc3, 0x3f, 0x94, 0x61, 0xaa, 0x17, 0x4d, 0x8d, 0x79, 0x1d, 0x8b, 0x10, 0x00, 0x00, |
435 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x2e, 0x28, 0x40, 0xe5, 0x9f, 0x4b, 0x4d, 0xe9, |
436 | | 0x87, 0xd3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0xc5, 0x00, |
437 | | 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x50, 0x31, 0x00, 0x00, 0x00, 0x00, |
438 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x76, 0x76, 0x01, 0x00, 0x00, 0x00, 0x76, 0x00, |
439 | | 0x00, 0x23, 0x3f, 0x52, 0x41, 0x44, 0x49, 0x41, 0x4e, 0x43, 0x45, 0x61, 0x50, 0x35, |
440 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4d, 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x05, |
441 | | 0x50, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x37, 0x61, |
442 | | ]; |
443 | | |
444 | | let decoder = IcoDecoder::new(std::io::Cursor::new(&data)).unwrap(); |
445 | | let mut buf = vec![0; usize::try_from(decoder.total_bytes()).unwrap()]; |
446 | | assert!(decoder.read_image(&mut buf).is_err()); |
447 | | } |
448 | | } |