Skip to content

Commit

Permalink
Merge pull request #2008 from fintelia/write-image-validate
Browse files Browse the repository at this point in the history
Make ImageEncoder::write_image assert that dimensions are valid
  • Loading branch information
fintelia authored Sep 20, 2023
2 parents aacfb62 + eb206ea commit f1e34f3
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 12 deletions.
5 changes: 5 additions & 0 deletions src/codecs/avif/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ impl<W: Write> ImageEncoder for AvifEncoder<W> {
height: u32,
color: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64),
data.len() as u64
);

self.set_color(color);
// `ravif` needs strongly typed data so let's convert. We can either use a temporarily
// owned version in our own buffer or zero-copy if possible by using the input buffer.
Expand Down
21 changes: 16 additions & 5 deletions src/codecs/bmp/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
BmpEncoder { writer: w }
}

/// Encodes the image ```image```
/// that has dimensions ```width``` and ```height```
/// and ```ColorType``` ```c```.
/// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`.
///
/// # Panics
///
/// Panics if `width * height * c.bytes_per_pixel() != image.len()`.
pub fn encode(
&mut self,
image: &[u8],
Expand All @@ -35,8 +37,12 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
self.encode_with_palette(image, width, height, c, None)
}

/// Same as ```encode```, but allow a palette to be passed in.
/// The ```palette``` is ignored for color types other than Luma/Luma-with-alpha.
/// Same as `encode`, but allow a palette to be passed in. The `palette` is ignored for color
/// types other than Luma/Luma-with-alpha.
///
/// # Panics
///
/// Panics if `width * height * c.bytes_per_pixel() != image.len()`.
pub fn encode_with_palette(
&mut self,
image: &[u8],
Expand All @@ -55,6 +61,11 @@ impl<'a, W: Write + 'a> BmpEncoder<'a, W> {
)));
}

assert_eq!(
(width as u64 * height as u64).saturating_mul(c.bytes_per_pixel() as u64),
image.len() as u64
);

let bmp_header_size = BITMAPFILEHEADER_SIZE;

let (dib_header_size, written_pixel_size, palette_color_count) =
Expand Down
11 changes: 9 additions & 2 deletions src/codecs/farbfeld.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,16 @@ impl<W: Write> FarbfeldEncoder<W> {
FarbfeldEncoder { w: buffered_writer }
}

/// Encodes the image ```data``` (native endian)
/// that has dimensions ```width``` and ```height```
/// Encodes the image `data` (native endian) that has dimensions `width` and `height`.
///
/// # Panics
///
/// Panics if `width * height * 8 != data.len()`.
pub fn encode(self, data: &[u8], width: u32, height: u32) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(8),
data.len() as u64
);
self.encode_impl(data, width, height)?;
Ok(())
}
Expand Down
5 changes: 5 additions & 0 deletions src/codecs/ico/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ impl<W: Write> ImageEncoder for IcoEncoder<W> {
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

let image = IcoFrame::as_png(buf, width, height, color_type)?;
self.encode_images(&[image])
}
Expand Down
9 changes: 9 additions & 0 deletions src/codecs/jpeg/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,13 +436,22 @@ impl<W: Write> JpegEncoder<W> {
/// and ```ColorType``` ```c```
///
/// The Image in encoded with subsampling ratio 4:2:2
///
/// # Panics
///
/// Panics if `width * height * color_type.bytes_per_pixel() != image.len()`.
pub fn encode(
&mut self,
image: &[u8],
width: u32,
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
image.len() as u64
);

match color_type {
ColorType::L8 => {
let image: ImageBuffer<Luma<_>, _> =
Expand Down
10 changes: 7 additions & 3 deletions src/codecs/openexr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,20 @@ where
{
/// Writes the complete image.
///
/// Returns an Error if it has an invalid length.
/// Assumes the writer is buffered. In most cases,
/// you should wrap your writer in a `BufWriter` for best performance.
/// Assumes the writer is buffered. In most cases, you should wrap your writer in a `BufWriter`
/// for best performance.
fn write_image(
self,
buf: &[u8],
width: u32,
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

write_buffer(self.0, buf, width, height, color_type)
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/codecs/png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,11 @@ impl<W: Write> ImageEncoder for PngEncoder<W> {
use byteorder::{BigEndian, ByteOrder, NativeEndian};
use ColorType::*;

assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

// PNG images are big endian. For 16 bit per channel and larger types,
// the buffer may need to be reordered to big endian per the
// contract of `write_image`.
Expand Down
5 changes: 5 additions & 0 deletions src/codecs/pnm/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ impl<W: Write> ImageEncoder for PnmEncoder<W> {
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

self.encode(buf, width, height, color_type)
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/codecs/qoi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ impl<W: Write> ImageEncoder for QoiEncoder<W> {
)));
}

assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

// Encode data in QOI
let data = qoi::encode_to_vec(buf, width, height).map_err(encoding_error)?;

Expand Down
9 changes: 9 additions & 0 deletions src/codecs/tga/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,22 @@ impl<W: Write> TgaEncoder<W> {
///
/// The dimensions of the image must be between 0 and 65535 (inclusive) or
/// an error will be returned.
///
/// # Panics
///
/// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`.
pub fn encode(
mut self,
buf: &[u8],
width: u32,
height: u32,
color_type: ColorType,
) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color_type.bytes_per_pixel() as u64),
buf.len() as u64
);

// Validate dimensions.
let width = u16::try_from(width)
.map_err(|_| ImageError::from(EncoderError::WidthInvalid(width)))?;
Expand Down
9 changes: 9 additions & 0 deletions src/codecs/tiff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,16 @@ impl<W: Write + Seek> TiffEncoder<W> {
/// Encodes the image `image` that has dimensions `width` and `height` and `ColorType` `c`.
///
/// 16-bit types assume the buffer is native endian.
///
/// # Panics
///
/// Panics if `width * height * color_type.bytes_per_pixel() != data.len()`.
pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64),
data.len() as u64
);

let mut encoder =
tiff::encoder::TiffEncoder::new(self.w).map_err(ImageError::from_tiff_encode)?;
match color {
Expand Down
22 changes: 20 additions & 2 deletions src/codecs/webp/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,16 @@ impl<W: Write> WebPEncoder<W> {
/// Encode image data with the indicated color type.
///
/// The encoder requires image data be Rgb8 or Rgba8.
///
/// # Panics
///
/// Panics if `width * height * color.bytes_per_pixel() != data.len()`.
pub fn encode(self, data: &[u8], width: u32, height: u32, color: ColorType) -> ImageResult<()> {
assert_eq!(
(width as u64 * height as u64).saturating_mul(color.bytes_per_pixel() as u64),
data.len() as u64
);

if let WebPQuality(Quality::Lossless) = self.quality {
self.encode_lossless(data, width, height, color)
} else {
Expand Down Expand Up @@ -804,15 +813,24 @@ mod native_tests {

fn fuzz_webp_no_panic(data: Vec<u8>, width: u8, height: u8, quality: u8) -> bool {
// Check random (usually invalid) parameters do not panic.

if data.len() < width as usize * height as usize * 4 {
return true;
}

let mut buffer = Vec::<u8>::new();
for color in [ColorType::Rgb8, ColorType::Rgba8] {
for webp_quality in [WebPQuality::lossless(), #[allow(deprecated)] WebPQuality::lossy(quality)] {
buffer.clear();
#[allow(deprecated)]
let encoder = WebPEncoder::new_with_quality(&mut buffer, webp_quality);
// Ignore errors.
let _ = encoder
.write_image(&data, width as u32, height as u32, color);
let _ = encoder.write_image(
&data[..width as usize * height as usize * color.bytes_per_pixel() as usize],
width as u32,
height as u32,
color,
);
}
}
true
Expand Down
4 changes: 4 additions & 0 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,10 @@ pub trait ImageEncoder {
///
/// See also `ImageDecoder::read_image` which reads byte buffers into
/// native endian.
///
/// # Panics
///
/// Panics if `width * height * color_type.bytes_per_pixel() != buf.len()`.
fn write_image(
self,
buf: &[u8],
Expand Down

0 comments on commit f1e34f3

Please sign in to comment.