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