| 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::image::{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 | 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 | |
| 72 | impl From<DecoderError> for ImageError { |
| 73 | fn from(e: DecoderError) -> ImageError { |
| 74 | ImageError::Decoding(DecodingError::new(format:ImageFormat::Ico.into(), err:e)) |
| 75 | } |
| 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 | 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 | |
| 98 | impl 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 |
| 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 | 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 | |
| 157 | fn 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 | |
| 164 | fn 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). |
| 194 | fn 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 | |
| 215 | impl 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 | |
| 262 | impl<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)] |
| 385 | mod 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 | |