image/codecs/ico/
decoder.rs

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