Skip to content

Commit f953f6a

Browse files
Support ICO decoding mid stream (#2895)
* Support ICO decoding mid stream * Offset reader approach * Address most feedback * Track offset separately
1 parent b654e0d commit f953f6a

File tree

2 files changed

+35
-10
lines changed

2 files changed

+35
-10
lines changed

src/codecs/ico/decoder.rs

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use byteorder_lite::ReadBytesExt;
2-
use std::io::{BufRead, Read, Seek, SeekFrom};
2+
use std::io::{BufRead, Read, Seek};
33
use std::{error, fmt};
44

55
use crate::color::ColorType;
66
use crate::error::{
77
DecodingError, ImageError, ImageResult, UnsupportedError, UnsupportedErrorKind,
88
};
9+
use crate::utils::seek_start_with_offset;
910
use crate::{ImageDecoder, ImageFormat};
1011

1112
use self::InnerDecoder::*;
@@ -107,6 +108,7 @@ impl From<IcoEntryImageFormat> for ImageFormat {
107108
/// An ico decoder
108109
pub struct IcoDecoder<R: BufRead + Seek> {
109110
selected_entry: DirEntry,
111+
reader_offset: u64,
110112
inner_decoder: InnerDecoder<R>,
111113
}
112114

@@ -143,12 +145,14 @@ struct DirEntry {
143145
impl<R: BufRead + Seek> IcoDecoder<R> {
144146
/// Create a new decoder that decodes from the stream ```r```
145147
pub fn new(mut r: R) -> ImageResult<IcoDecoder<R>> {
148+
let reader_offset = r.stream_position()?;
146149
let entries = read_entries(&mut r)?;
147150
let entry = best_entry(entries)?;
148-
let decoder = entry.decoder(r)?;
151+
let decoder = entry.decoder(r, reader_offset)?;
149152

150153
Ok(IcoDecoder {
151154
selected_entry: entry,
155+
reader_offset,
152156
inner_decoder: decoder,
153157
})
154158
}
@@ -230,13 +234,13 @@ impl DirEntry {
230234
&& u32::from(self.real_height()) == height.min(256)
231235
}
232236

233-
fn seek_to_start<R: Read + Seek>(&self, r: &mut R) -> ImageResult<()> {
234-
r.seek(SeekFrom::Start(u64::from(self.image_offset)))?;
237+
fn seek_to_start<R: Read + Seek>(&self, r: &mut R, reader_offset: u64) -> ImageResult<()> {
238+
seek_start_with_offset(r, reader_offset, u64::from(self.image_offset))?;
235239
Ok(())
236240
}
237241

238-
fn is_png<R: Read + Seek>(&self, r: &mut R) -> ImageResult<bool> {
239-
self.seek_to_start(r)?;
242+
fn is_png<R: Read + Seek>(&self, r: &mut R, reader_offset: u64) -> ImageResult<bool> {
243+
self.seek_to_start(r, reader_offset)?;
240244

241245
// Read the first 8 bytes to sniff the image.
242246
let mut signature = [0u8; 8];
@@ -245,9 +249,13 @@ impl DirEntry {
245249
Ok(signature == PNG_SIGNATURE)
246250
}
247251

248-
fn decoder<R: BufRead + Seek>(&self, mut r: R) -> ImageResult<InnerDecoder<R>> {
249-
let is_png = self.is_png(&mut r)?;
250-
self.seek_to_start(&mut r)?;
252+
fn decoder<R: BufRead + Seek>(
253+
&self,
254+
mut r: R,
255+
reader_offset: u64,
256+
) -> ImageResult<InnerDecoder<R>> {
257+
let is_png = self.is_png(&mut r, reader_offset)?;
258+
self.seek_to_start(&mut r, reader_offset)?;
251259

252260
if is_png {
253261
let limits = crate::Limits {
@@ -335,7 +343,8 @@ impl<R: BufRead + Seek> ImageDecoder for IcoDecoder<R> {
335343

336344
let r = decoder.reader();
337345
let image_end = r.stream_position()?;
338-
let data_end = u64::from(self.selected_entry.image_offset)
346+
let data_end = self.reader_offset
347+
+ u64::from(self.selected_entry.image_offset)
339348
+ u64::from(self.selected_entry.image_length);
340349

341350
let mask_row_bytes = width.div_ceil(32) * 4;

src/utils/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,22 @@ pub(crate) fn is_integer<T: num_traits::NumCast + num_traits::Zero>() -> bool {
156156
<T as num_traits::NumCast>::from(0.5).unwrap().is_zero()
157157
}
158158

159+
/// This is equivalent to `r.seek(SeekFrom::Start(reader_offset + offset))`,
160+
/// but correctly handles the case where `reader_offset + offset` would overflow.
161+
pub(crate) fn seek_start_with_offset<R: std::io::Read + std::io::Seek>(
162+
r: &mut R,
163+
reader_offset: u64,
164+
offset: u64,
165+
) -> std::io::Result<u64> {
166+
let Some(offset) = reader_offset.checked_add(offset) else {
167+
return Err(std::io::Error::new(
168+
std::io::ErrorKind::InvalidInput,
169+
"offset overflow",
170+
));
171+
};
172+
r.seek(std::io::SeekFrom::Start(offset))
173+
}
174+
159175
#[cfg(test)]
160176
mod test {
161177
#[test]

0 commit comments

Comments
 (0)