Coverage Report

Created: 2026-01-17 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}