Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/codecs/ico/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use byteorder_lite::ReadBytesExt;
use byteorder_lite::{LittleEndian, ReadBytesExt};
use std::io::{BufRead, Read, Seek};
use std::{error, fmt};

Expand Down Expand Up @@ -175,8 +175,25 @@ impl<R: BufRead + Seek> IcoDecoder<R> {
fn read_entries<R: Read>(r: &mut R, spec: SpecCompliance) -> ImageResult<Vec<DirEntry>> {
let mut header = [0u8; 6];
r.read_exact(&mut header)?;
// header[0..2] = reserved, header[2..4] = type, header[4..6] = count
let count = u16::from_le_bytes(header[4..6].try_into().unwrap());
let mut header = header.as_slice();

let reserved = header.read_u16::<LittleEndian>()?;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use split_first_chunk here to instead of an IO-facing library. Alternatively, but only since this is uniformly u16, you might turn header into [[u8; 2]; 3] and read it with as_flattened? Lots of alternatives that do not involve an IO error for a statically sized array and constant condition.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I just copied what TGA is doing. Should we not do that there too?

I also considered the u16 approach, but didn't like it because it makes it really easy to mess up endian. E.g.

let mut header = [0_u16; 3];
reader.read_exact(bytemuck::cast_slice_mut(&mut header))?;
let [reserved, id_type, entries] = header; // only correct on LE systems

But I agree with you that the IO interface is a little overkill. Maybe we could just have a 2 or 3 util methods like this:

// Panics when OOB
fn read_u16<Endian>(slice: &[u8], offset: usize) -> u16;

I saw this pattern in Chrome, so that might be an idea.

let id_type = header.read_u16::<LittleEndian>()?;
let count = header.read_u16::<LittleEndian>()?;

if spec == SpecCompliance::Strict && reserved != 0 {
return Err(ImageError::Decoding(DecodingError::new(
ImageFormat::Ico.into(),
format!("Reserved field must be 0, but found 0x{reserved:X}"),
)));
}
if spec == SpecCompliance::Strict && !matches!(id_type, 1 | 2) {
return Err(ImageError::Decoding(DecodingError::new(
ImageFormat::Ico.into(),
format!("Invalid header type. Expected 1 (ICO) or 2 (CUR), but found {id_type}"),
)));
}

(0..count).map(|_| read_entry(r, spec)).collect()
}

Expand Down
Loading