@@ -3,24 +3,19 @@ use image::DynamicImage;
33use libwebp_sys:: * ;
44
55use crate :: shared:: * ;
6+ use internal:: CheckedEncoder ;
67
78/// An encoder for WebP images. It uses the default configuration of libwebp.
89pub struct Encoder < ' a > {
9- image : & ' a [ u8 ] ,
10- layout : PixelLayout ,
11- width : u32 ,
12- height : u32 ,
10+ e : CheckedEncoder < ' a > ,
1311}
1412
1513impl < ' a > Encoder < ' a > {
1614 /// Creates a new encoder from the given image data.
1715 /// The image data must be in the pixel layout of the color parameter.
1816 pub fn new ( image : & ' a [ u8 ] , layout : PixelLayout , width : u32 , height : u32 ) -> Self {
1917 Self {
20- image,
21- layout,
22- width,
23- height,
18+ e : CheckedEncoder :: new ( image, layout, width, height) ,
2419 }
2520 }
2621
@@ -47,20 +42,14 @@ impl<'a> Encoder<'a> {
4742 /// Creates a new encoder from the given image data in the RGB pixel layout.
4843 pub fn from_rgb ( image : & ' a [ u8 ] , width : u32 , height : u32 ) -> Self {
4944 Self {
50- image,
51- layout : PixelLayout :: Rgb ,
52- width,
53- height,
45+ e : CheckedEncoder :: new ( image, PixelLayout :: Rgb , width, height) ,
5446 }
5547 }
5648
5749 /// Creates a new encoder from the given image data in the RGBA pixel layout.
5850 pub fn from_rgba ( image : & ' a [ u8 ] , width : u32 , height : u32 ) -> Self {
5951 Self {
60- image,
61- layout : PixelLayout :: Rgba ,
62- width,
63- height,
52+ e : CheckedEncoder :: new ( image, PixelLayout :: Rgba , width, height) ,
6453 }
6554 }
6655
@@ -89,8 +78,99 @@ impl<'a> Encoder<'a> {
8978
9079 pub fn encode_advanced ( & self , config : & WebPConfig ) -> Result < WebPMemory , WebPEncodingError > {
9180 unsafe {
92- let mut picture = new_picture ( self . image , self . layout , self . width , self . height ) ;
93- encode ( & mut picture, config)
81+ let mut picture = new_picture (
82+ self . e . image ( ) ,
83+ self . e . layout ( ) ,
84+ self . e . width ( ) ,
85+ self . e . height ( ) ,
86+ ) ;
87+ let res = encode ( & mut * picture, config) ;
88+ res
89+ }
90+ }
91+ }
92+
93+ /// This module contains the private CheckedEncoder so that it cannot be constructed directly from the outside.
94+ /// That ensures that the only way to construct one validates the supplied parameters.
95+ mod internal {
96+ use std:: convert:: TryInto ;
97+
98+ use crate :: shared:: PixelLayout ;
99+
100+ /// Validated encoder parameters, guaranteeing `image.len() >= width * height * bytes_per_pixel`.
101+ /// Required for memory safety. Their absence would allow out-of-bounds reads.
102+ pub ( super ) struct CheckedEncoder < ' a > {
103+ image : & ' a [ u8 ] ,
104+ layout : PixelLayout ,
105+ width : u32 ,
106+ height : u32 ,
107+ }
108+
109+ impl < ' a > CheckedEncoder < ' a > {
110+ /// Creates a new instance of `CheckedEncoder`, and performs the necessary bounds checks.
111+ ///
112+ /// This is the only way to create a `CheckedEncoder` exposed outside the `internal` module.
113+ pub ( super ) fn new ( image : & ' a [ u8 ] , layout : PixelLayout , width : u32 , height : u32 ) -> Self {
114+ // TODO: return an error instead of panicking in the next semver-breaking release
115+
116+ if width == 0 || height == 0 {
117+ // TODO: enable this check once this function returns a Result.
118+ // As of v0.3.0 it's possible to contruct an Encoder with width or height 0,
119+ // but encoding will fail and the error is really uninformative:
120+ // "called `Result::unwrap()` on an `Err` value: VP8_ENC_ERROR_BAD_DIMENSION"
121+
122+ //panic!("Width and height must be non-zero.");
123+ }
124+
125+ // We're going to compare the incoming width * height * bpp against the length of a slice.
126+ // The length of a slice is a `usize`, so we are going to do arithmetic in `usize` as well.
127+ //
128+ // On 32-bit and 64-bit platforms these conversions always suceeed and get optimized out.
129+ let width_u: usize = width. try_into ( ) . unwrap ( ) ;
130+ let height_u: usize = height. try_into ( ) . unwrap ( ) ;
131+ let bytes_per_pixel_u: usize = layout. bytes_per_pixel ( ) . into ( ) ;
132+
133+ // If we simply calculate `width * height * bytes_per_pixel`, arithmetic may overflow in release mode
134+ // and make it possible to bypass the length check. So we need either .checked_mul or .saturating_mul here.
135+ //
136+ // Using `saturating_mul` is enough because it's not possible to construct a valid slice of size `usize::MAX` anyway,
137+ // and it makes for simpler code and a nicer error message.
138+ let expected_len = width_u
139+ . saturating_mul ( height_u)
140+ . saturating_mul ( bytes_per_pixel_u) ;
141+ if image. len ( ) < expected_len {
142+ panic ! (
143+ "Image buffer too small. Expected at least {} bytes for a {}x{} image with {:?} layout, got {}." ,
144+ expected_len, width, height, layout, image. len( )
145+ ) ;
146+ }
147+
148+ if image. len ( ) > expected_len {
149+ // TODO: reject this in the future, once this function is made to return Result
150+ }
151+
152+ CheckedEncoder {
153+ image,
154+ layout,
155+ width,
156+ height,
157+ }
158+ }
159+
160+ pub ( super ) fn width ( & self ) -> u32 {
161+ self . width
162+ }
163+
164+ pub ( super ) fn height ( & self ) -> u32 {
165+ self . height
166+ }
167+
168+ pub ( super ) fn layout ( & self ) -> PixelLayout {
169+ self . layout
170+ }
171+
172+ pub ( super ) fn image ( & self ) -> & ' a [ u8 ] {
173+ self . image
94174 }
95175 }
96176}
@@ -141,14 +221,20 @@ mod tests {
141221 use super :: * ;
142222 use crate :: shared;
143223
224+ #[ test]
225+ #[ should_panic]
226+ fn construct_encoder_with_buffer_overflow ( ) {
227+ Encoder :: from_rgb ( & [ ] , 16383 , 16383 ) ;
228+ }
229+
144230 #[ test]
145231 fn test_encoder_new_assigns_fields ( ) {
146- let data = [ 1 , 2 , 3 , 4 , 5 , 6 ] ;
232+ let data = [ 5 ; 18 ] ;
147233 let enc = Encoder :: new ( & data, shared:: PixelLayout :: Rgb , 2 , 3 ) ;
148- assert_eq ! ( enc. image, & data) ;
149- assert_eq ! ( enc. layout, shared:: PixelLayout :: Rgb ) ;
150- assert_eq ! ( enc. width, 2 ) ;
151- assert_eq ! ( enc. height, 3 ) ;
234+ assert_eq ! ( enc. e . image( ) , & data) ;
235+ assert_eq ! ( enc. e . layout( ) , shared:: PixelLayout :: Rgb ) ;
236+ assert_eq ! ( enc. e . width( ) , 2 ) ;
237+ assert_eq ! ( enc. e . height( ) , 3 ) ;
152238 }
153239
154240 #[ test]
@@ -157,12 +243,12 @@ mod tests {
157243 let rgba = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ] ;
158244 let enc_rgb = Encoder :: from_rgb ( & rgb, 2 , 1 ) ;
159245 let enc_rgba = Encoder :: from_rgba ( & rgba, 2 , 1 ) ;
160- assert_eq ! ( enc_rgb. layout, shared:: PixelLayout :: Rgb ) ;
161- assert_eq ! ( enc_rgba. layout, shared:: PixelLayout :: Rgba ) ;
162- assert_eq ! ( enc_rgb. image, & rgb) ;
163- assert_eq ! ( enc_rgba. image, & rgba) ;
164- assert_eq ! ( enc_rgb. width, 2 ) ;
165- assert_eq ! ( enc_rgba. height, 1 ) ;
246+ assert_eq ! ( enc_rgb. e . layout( ) , shared:: PixelLayout :: Rgb ) ;
247+ assert_eq ! ( enc_rgba. e . layout( ) , shared:: PixelLayout :: Rgba ) ;
248+ assert_eq ! ( enc_rgb. e . image( ) , & rgb) ;
249+ assert_eq ! ( enc_rgba. e . image( ) , & rgba) ;
250+ assert_eq ! ( enc_rgb. e . width( ) , 2 ) ;
251+ assert_eq ! ( enc_rgba. e . height( ) , 1 ) ;
166252 }
167253
168254 #[ cfg( feature = "img" ) ]
0 commit comments