Skip to content

Commit

Permalink
Update limits to be byte based
Browse files Browse the repository at this point in the history
Limiting allocated bytes is more effective and useful than image
size. Especially when incrementaly decoding an image which may not
fit in memory, limiting pixels dosen't make much sense.
  • Loading branch information
birktj committed Apr 23, 2019
1 parent 25110c5 commit d6739d1
Showing 1 changed file with 26 additions and 22 deletions.
48 changes: 26 additions & 22 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ impl OutputInfo {
}

#[derive(Clone, Copy, Debug)]
/// Limits on the resources the `Decoder` is allowed too use
pub struct Limits {
/// max number of pixels: `width * height` (default: 67M = 2<sup>26</sup>)
pub pixels: u64,
/// maximum number of bytes the decoder is allowed to allocate, default is 64M
pub bytes: usize,
}

impl Default for Limits {
fn default() -> Limits {
Limits {
pixels: 1 << 26,
bytes: 1024*1024*64,
}
}
}
Expand All @@ -73,7 +74,7 @@ pub struct Decoder<R: Read> {
r: R,
/// Output transformations
transform: Transformations,
/// Images that are considered too big
/// Limits on resources the Decoder is allowed to use
limits: Limits,
}

Expand All @@ -90,19 +91,19 @@ impl<R: Read> Decoder<R> {
}
}

/// Images that are considered too big
/// Limit resource usage
///
/// ```
/// use std::fs::File;
/// use png::{Decoder, Limits};
/// // This image is 32x32 pixels, so it's more than four pixels in size.
/// // This image is 32x32 pixels, so the deocder will allocate more than four bytes
/// let mut limits = Limits::default();
/// limits.pixels = 4;
/// limits.bytes = 4;
/// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
/// assert!(decoder.read_info().is_err());
/// // This image is 32x32 pixels, so it's exactly 1024 pixels in size.
/// // This image is 32x32 pixels, so the decoder will allocate less than 10K bytes
/// let mut limits = Limits::default();
/// limits.pixels = 1024;
/// limits.bytes = 10*1024;
/// let mut decoder = Decoder::new_with_limits(File::open("tests/pngsuite/basi0g01.png").unwrap(), limits);
/// assert!(decoder.read_info().is_ok());
/// ```
Expand All @@ -112,7 +113,7 @@ impl<R: Read> Decoder<R> {

/// Reads all meta data until the first IDAT chunk
pub fn read_info(self) -> Result<(OutputInfo, Reader<R>), DecodingError> {
let mut r = Reader::new(self.r, StreamingDecoder::new(), self.transform);
let mut r = Reader::new(self.r, StreamingDecoder::new(), self.transform, self.limits);
r.init()?;
let (ct, bits) = r.output_color_type();
let info = {
Expand All @@ -125,12 +126,6 @@ impl<R: Read> Decoder<R> {
line_size: r.output_line_size(info.width),
}
};
let (width, height, pixels) = (info.width as u64, info.height as u64, self.limits.pixels);
if width.checked_mul(height).map(|p| p > pixels).unwrap_or(true) {
// DecodingError::Other is used for backwards compatibility.
// In the next major version, add a variant for this.
return Err(DecodingError::Other(borrow::Cow::Borrowed("pixels limit exceeded")));
}
Ok((info, r))
}
}
Expand Down Expand Up @@ -187,7 +182,8 @@ pub struct Reader<R: Read> {
/// Output transformations
transform: Transformations,
/// Processed line
processed: Vec<u8>
processed: Vec<u8>,
limits: Limits,
}

macro_rules! get_info(
Expand All @@ -198,7 +194,7 @@ macro_rules! get_info(

impl<R: Read> Reader<R> {
/// Creates a new PNG reader
fn new(r: R, d: StreamingDecoder, t: Transformations) -> Reader<R> {
fn new(r: R, d: StreamingDecoder, t: Transformations, limits: Limits) -> Reader<R> {
Reader {
decoder: ReadDecoder {
reader: BufReader::with_capacity(CHUNCK_BUFFER_SIZE, r),
Expand All @@ -211,7 +207,8 @@ impl<R: Read> Reader<R> {
prev: Vec::new(),
current: Vec::new(),
transform: t,
processed: Vec::new()
processed: Vec::new(),
limits,
}
}

Expand Down Expand Up @@ -243,7 +240,7 @@ impl<R: Read> Reader<R> {
self.adam7 = Some(utils::Adam7Iterator::new(info.width, info.height))
}
}
self.allocate_out_buf();
self.allocate_out_buf()?;
self.prev = vec![0; self.rowlen];
Ok(())
}
Expand Down Expand Up @@ -425,9 +422,16 @@ impl<R: Read> Reader<R> {
len + match extra { 0 => 0, _ => 1 }
}

fn allocate_out_buf(&mut self) {
fn allocate_out_buf(&mut self) -> Result<(), DecodingError> {
let width = get_info!(self).width;
self.processed = vec![0; self.line_size(width)]
let bytes = self.limits.bytes;
if bytes < self.line_size(width) {
// FIXME: DecodingError::Other is used for backwards compatibility.
// In the next major version, add a variant for this.
return Err(DecodingError::Other(borrow::Cow::Borrowed("byte limit exceeded")));
}
self.processed = vec![0; self.line_size(width)];
Ok(())
}

/// Returns the next raw row of the image
Expand Down

0 comments on commit d6739d1

Please sign in to comment.