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(format:ImageFormat::Ico.into(), err: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(data: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: Vec = read_entries(&mut r)?;
147 let entry: DirEntry = best_entry(entries)?;
148 let decoder: InnerDecoder = 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: u16 = r.read_u16::<LittleEndian>()?;
159 let _type: u16 = r.read_u16::<LittleEndian>()?;
160 let count: u16 = 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: DirEntry = entries.pop().ok_or(err:DecoderError::NoEntries)?;
196
197 let mut best_score: (u16, u32) = (
198 best.bits_per_pixel,
199 u32::from(best.real_width()) * u32::from(best.real_height()),
200 );
201
202 for entry: DirEntry in entries {
203 let score: (u16, u32) = (
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}
447