1use std::{error::Error, fmt, time::Duration};
2
3use libwebp_sys::{
4 WEBP_CSP_MODE, WebPAnimDecoder, WebPAnimDecoderDelete, WebPAnimDecoderGetInfo,
5 WebPAnimDecoderGetNext, WebPAnimDecoderHasMoreFrames, WebPAnimDecoderNewInternal,
6 WebPAnimDecoderOptions, WebPAnimDecoderOptionsInitInternal, WebPAnimInfo, WebPData,
7 WebPGetDemuxABIVersion,
8};
9
10use crate::{
11 inspect::is_animated_webp_fast,
12 model::{AnimationFrame, AnimationInfo, BackgroundColor, CanvasSize, LoopCount},
13};
14
15#[derive(Clone, Debug)]
21pub struct DecodeLimits {
22 pub max_input_bytes: usize,
24 pub max_canvas_pixels: u64,
26 pub max_frame_count: u32,
28 pub max_total_duration: Duration,
30 pub max_frame_rgba_bytes: usize,
32}
33
34impl Default for DecodeLimits {
35 fn default() -> Self {
36 Self {
37 max_input_bytes: 256 * 1024 * 1024,
38 max_canvas_pixels: 100_000_000,
39 max_frame_count: 10_000,
40 max_total_duration: Duration::from_secs(60 * 60),
41 max_frame_rgba_bytes: 400 * 1024 * 1024,
42 }
43 }
44}
45
46impl DecodeLimits {
47 pub const fn for_trusted_input() -> Self {
51 Self {
52 max_input_bytes: usize::MAX,
53 max_canvas_pixels: u64::MAX,
54 max_frame_count: u32::MAX,
55 max_total_duration: Duration::MAX,
56 max_frame_rgba_bytes: usize::MAX,
57 }
58 }
59}
60
61#[derive(Clone, Debug, Eq, PartialEq)]
63pub enum DecodeError {
64 InputTooLarge {
66 actual: usize,
68 maximum: usize,
70 },
71 NotAnimatedWebp,
73 DecoderOptionsInitialization,
75 DecoderCreation,
77 DecoderInfo,
79 InvalidAnimationInfo,
81 FrameSizeOverflow,
83 LimitExceeded {
85 limit: &'static str,
87 actual: u64,
89 maximum: u64,
91 },
92 FrameDecode,
94 InvalidTimestamp,
96}
97
98impl fmt::Display for DecodeError {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 Self::InputTooLarge { actual, maximum } => {
102 write!(
103 f,
104 "input is {actual} bytes, exceeding the {maximum}-byte limit"
105 )
106 }
107 Self::NotAnimatedWebp => f.write_str("input is not an animated WebP"),
108 Self::DecoderOptionsInitialization => {
109 f.write_str("failed to initialize WebP decoder options")
110 }
111 Self::DecoderCreation => f.write_str("failed to create WebP animation decoder"),
112 Self::DecoderInfo => f.write_str("failed to read WebP animation information"),
113 Self::InvalidAnimationInfo => f.write_str("WebP animation information is invalid"),
114 Self::FrameSizeOverflow => {
115 f.write_str("WebP animation frame size overflows the host address space")
116 }
117 Self::LimitExceeded {
118 limit,
119 actual,
120 maximum,
121 } => {
122 write!(f, "{limit} is {actual}, exceeding the {maximum} limit")
123 }
124 Self::FrameDecode => f.write_str("failed to decode WebP animation frame"),
125 Self::InvalidTimestamp => f.write_str("WebP animation timestamps are invalid"),
126 }
127 }
128}
129
130impl Error for DecodeError {}
131
132pub struct AnimationDecoder {
134 _input: Vec<u8>,
136 decoder: *mut WebPAnimDecoder,
137 info: AnimationInfo,
138 frame_rgba_bytes: usize,
139 previous_timestamp_ms: i32,
140 total_duration: Duration,
141 max_total_duration: Duration,
142}
143
144impl Drop for AnimationDecoder {
145 fn drop(&mut self) {
146 unsafe {
148 if !self.decoder.is_null() {
149 WebPAnimDecoderDelete(self.decoder);
150 }
151 }
152 }
153}
154
155impl AnimationDecoder {
156 pub fn new(input: &[u8], limits: DecodeLimits) -> Result<Self, DecodeError> {
162 if input.len() > limits.max_input_bytes {
163 return Err(DecodeError::InputTooLarge {
164 actual: input.len(),
165 maximum: limits.max_input_bytes,
166 });
167 }
168 if !is_animated_webp_fast(input) {
169 return Err(DecodeError::NotAnimatedWebp);
170 }
171
172 let input = input.to_vec();
173 let mut options: WebPAnimDecoderOptions = unsafe { std::mem::zeroed() };
175 let demux_abi = WebPGetDemuxABIVersion();
176 if unsafe { WebPAnimDecoderOptionsInitInternal(&mut options, demux_abi) } == 0 {
178 return Err(DecodeError::DecoderOptionsInitialization);
179 }
180 options.color_mode = WEBP_CSP_MODE::MODE_RGBA;
181 options.use_threads = 1;
182
183 let data = WebPData {
184 bytes: input.as_ptr(),
185 size: input.len(),
186 };
187 let decoder = unsafe { WebPAnimDecoderNewInternal(&data, &options, demux_abi) };
189 if decoder.is_null() {
190 return Err(DecodeError::DecoderCreation);
191 }
192
193 let mut raw_info: WebPAnimInfo = unsafe { std::mem::zeroed() };
195 if unsafe { WebPAnimDecoderGetInfo(decoder, &mut raw_info) } == 0 {
197 unsafe { WebPAnimDecoderDelete(decoder) };
199 return Err(DecodeError::DecoderInfo);
200 }
201
202 let canvas = CanvasSize {
203 width: raw_info.canvas_width,
204 height: raw_info.canvas_height,
205 };
206 let pixel_count = canvas.pixel_count().ok_or(DecodeError::FrameSizeOverflow)?;
207 enforce_limit("canvas pixels", pixel_count, limits.max_canvas_pixels)?;
208 enforce_limit(
209 "frame count",
210 u64::from(raw_info.frame_count),
211 u64::from(limits.max_frame_count),
212 )?;
213 let frame_rgba_bytes = canvas.rgba_bytes().ok_or(DecodeError::FrameSizeOverflow)?;
214 enforce_limit(
215 "RGBA bytes per frame",
216 u64::try_from(frame_rgba_bytes).unwrap_or(u64::MAX),
217 u64::try_from(limits.max_frame_rgba_bytes).unwrap_or(u64::MAX),
218 )?;
219
220 let loop_count = match raw_info.loop_count {
221 0 => LoopCount::Infinite,
222 value => LoopCount::Finite(
223 std::num::NonZeroU16::new(
224 u16::try_from(value).map_err(|_| DecodeError::InvalidAnimationInfo)?,
225 )
226 .expect("non-zero loop count"),
227 ),
228 };
229 Ok(Self {
230 _input: input,
231 decoder,
232 info: AnimationInfo {
233 canvas,
234 frame_count: raw_info.frame_count,
235 loop_count,
236 background_color: BackgroundColor {
237 raw: raw_info.bgcolor,
238 },
239 },
240 frame_rgba_bytes,
241 previous_timestamp_ms: 0,
242 total_duration: Duration::ZERO,
243 max_total_duration: limits.max_total_duration,
244 })
245 }
246
247 pub fn info(&self) -> &AnimationInfo {
249 &self.info
250 }
251
252 pub fn has_more_frames(&self) -> bool {
254 unsafe { WebPAnimDecoderHasMoreFrames(self.decoder) != 0 }
256 }
257
258 pub fn next_frame(&mut self) -> Result<Option<AnimationFrame>, DecodeError> {
260 if !self.has_more_frames() {
261 return Ok(None);
262 }
263
264 let mut rgba = std::ptr::null_mut();
265 let mut timestamp_ms = 0_i32;
266 let ok = unsafe { WebPAnimDecoderGetNext(self.decoder, &mut rgba, &mut timestamp_ms) };
268 if ok == 0 || rgba.is_null() {
269 return Err(DecodeError::FrameDecode);
270 }
271 let duration_ms = timestamp_ms
272 .checked_sub(self.previous_timestamp_ms)
273 .ok_or(DecodeError::InvalidTimestamp)?;
274 let duration = Duration::from_millis(
275 u64::try_from(duration_ms).map_err(|_| DecodeError::InvalidTimestamp)?,
276 );
277 let total_duration = self
278 .total_duration
279 .checked_add(duration)
280 .ok_or(DecodeError::InvalidTimestamp)?;
281 enforce_limit(
282 "total duration in milliseconds",
283 total_duration.as_millis().try_into().unwrap_or(u64::MAX),
284 self.max_total_duration
285 .as_millis()
286 .try_into()
287 .unwrap_or(u64::MAX),
288 )?;
289
290 let rgba = unsafe { std::slice::from_raw_parts(rgba, self.frame_rgba_bytes) }.to_vec();
292 self.previous_timestamp_ms = timestamp_ms;
293 self.total_duration = total_duration;
294
295 Ok(Some(AnimationFrame {
296 rgba,
297 canvas: self.info.canvas,
298 duration,
299 }))
300 }
301}
302
303fn enforce_limit(limit: &'static str, actual: u64, maximum: u64) -> Result<(), DecodeError> {
304 if actual > maximum {
305 return Err(DecodeError::LimitExceeded {
306 limit,
307 actual,
308 maximum,
309 });
310 }
311 Ok(())
312}