image/codecs/ico/
decoder.rs

1use byteorder::{LittleEndian, ReadBytesExt};
2use std::io::{self, Cursor, Read, Seek, SeekFrom};
3use std::marker::PhantomData;
4use std::{error, fmt, mem};
5
6use crate::color::ColorType;
7use crate::error::{
8    DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind,
9};
10use crate::image::{self, ImageDecoder, ImageFormat};
11
12use self::InnerDecoder::*;
13use crate::codecs::bmp::BmpDecoder;
14use 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)]
18enum 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
45impl 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
74impl From<DecoderError> for ImageError {
75    fn from(e: DecoderError) -> ImageError {
76        ImageError::Decoding(DecodingError::new(ImageFormat::Ico.into(), e))
77    }
78}
79
80impl error::Error for DecoderError {}
81
82/// The image formats an ICO may contain
83#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
84enum IcoEntryImageFormat {
85    /// PNG in ARGB
86    Png,
87    /// BMP with optional alpha mask
88    Bmp,
89}
90
91impl fmt::Display for IcoEntryImageFormat {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.write_str(match self {
94            IcoEntryImageFormat::Png => "PNG",
95            IcoEntryImageFormat::Bmp => "BMP",
96        })
97    }
98}
99
100impl 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
110pub struct IcoDecoder<R: Read> {
111    selected_entry: DirEntry,
112    inner_decoder: InnerDecoder<R>,
113}
114
115enum InnerDecoder<R: Read> {
116    Bmp(BmpDecoder<R>),
117    Png(Box<PngDecoder<R>>),
118}
119
120#[derive(Clone, Copy, Default)]
121struct 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
145impl<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 = read_entries(&mut r)?;
149        let entry = best_entry(entries)?;
150        let decoder = entry.decoder(r)?;
151
152        Ok(IcoDecoder {
153            selected_entry: entry,
154            inner_decoder: decoder,
155        })
156    }
157}
158
159fn read_entries<R: Read>(r: &mut R) -> ImageResult<Vec<DirEntry>> {
160    let _reserved = r.read_u16::<LittleEndian>()?;
161    let _type = r.read_u16::<LittleEndian>()?;
162    let count = r.read_u16::<LittleEndian>()?;
163    (0..count).map(|_| read_entry(r)).collect()
164}
165
166fn 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).
196fn best_entry(mut entries: Vec<DirEntry>) -> ImageResult<DirEntry> {
197    let mut best = entries.pop().ok_or(DecoderError::NoEntries)?;
198
199    let mut best_score = (
200        best.bits_per_pixel,
201        u32::from(best.real_width()) * u32::from(best.real_height()),
202    );
203
204    for entry in entries {
205        let score = (
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
217impl 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>>`
265pub struct IcoReader<R>(Cursor<Vec<u8>>, PhantomData<R>);
266impl<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(buf, self.0.get_mut());
273            Ok(buf.len())
274        } else {
275            self.0.read_to_end(buf)
276        }
277    }
278}
279
280impl<'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)]
408mod 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}