1use std::{error::Error, fmt, time::Duration};
2
3use libwebp_sys::{
4 WEBP_CSP_MODE, WebPAnimDecoder, WebPAnimDecoderDelete, WebPAnimDecoderGetDemuxer,
5 WebPAnimDecoderGetInfo, WebPAnimDecoderGetNext, WebPAnimDecoderHasMoreFrames,
6 WebPAnimDecoderNewInternal, WebPAnimDecoderOptions, WebPAnimDecoderOptionsInitInternal,
7 WebPAnimDecoderReset, WebPAnimInfo, WebPData, WebPDemuxGetFrame, WebPDemuxNextFrame,
8 WebPDemuxReleaseIterator, WebPGetDemuxABIVersion, WebPIterator,
9};
10
11use crate::{
12 inspect::is_animated_webp_fast,
13 model::{AnimationFrame, AnimationInfo, BackgroundColor, CanvasSize, LoopCount},
14};
15
16#[derive(Clone, Debug)]
22pub struct DecodeLimits {
23 pub max_input_bytes: usize,
25 pub max_canvas_pixels: u64,
27 pub max_frame_count: u32,
29 pub max_total_duration: Duration,
31 pub max_frame_rgba_bytes: usize,
33}
34
35impl Default for DecodeLimits {
36 fn default() -> Self {
37 Self {
38 max_input_bytes: 256 * 1024 * 1024,
39 max_canvas_pixels: 100_000_000,
40 max_frame_count: 10_000,
41 max_total_duration: Duration::from_secs(60 * 60),
42 max_frame_rgba_bytes: 400 * 1024 * 1024,
43 }
44 }
45}
46
47impl DecodeLimits {
48 pub const fn for_trusted_input() -> Self {
52 Self {
53 max_input_bytes: usize::MAX,
54 max_canvas_pixels: u64::MAX,
55 max_frame_count: u32::MAX,
56 max_total_duration: Duration::MAX,
57 max_frame_rgba_bytes: usize::MAX,
58 }
59 }
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
64pub enum DecodeError {
65 InputTooLarge {
67 actual: usize,
69 maximum: usize,
71 },
72 NotAnimatedWebp,
74 DecoderOptionsInitialization,
76 DecoderCreation,
78 DecoderInfo,
80 InvalidAnimationInfo,
82 FrameSizeOverflow,
84 LimitExceeded {
86 limit: &'static str,
88 actual: u64,
90 maximum: u64,
92 },
93 FrameDecode,
95 InvalidTimestamp,
97}
98
99impl fmt::Display for DecodeError {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self {
102 Self::InputTooLarge { actual, maximum } => {
103 write!(
104 f,
105 "input is {actual} bytes, exceeding the {maximum}-byte limit"
106 )
107 }
108 Self::NotAnimatedWebp => f.write_str("input is not an animated WebP"),
109 Self::DecoderOptionsInitialization => {
110 f.write_str("failed to initialize WebP decoder options")
111 }
112 Self::DecoderCreation => f.write_str("failed to create WebP animation decoder"),
113 Self::DecoderInfo => f.write_str("failed to read WebP animation information"),
114 Self::InvalidAnimationInfo => f.write_str("WebP animation information is invalid"),
115 Self::FrameSizeOverflow => {
116 f.write_str("WebP animation frame size overflows the host address space")
117 }
118 Self::LimitExceeded {
119 limit,
120 actual,
121 maximum,
122 } => {
123 write!(f, "{limit} is {actual}, exceeding the {maximum} limit")
124 }
125 Self::FrameDecode => f.write_str("failed to decode WebP animation frame"),
126 Self::InvalidTimestamp => f.write_str("WebP animation timestamps are invalid"),
127 }
128 }
129}
130
131impl Error for DecodeError {}
132
133pub struct AnimationDecoder {
135 _input: Vec<u8>,
137 decoder: *mut WebPAnimDecoder,
138 info: AnimationInfo,
139 frame_rgba_bytes: usize,
140 previous_timestamp_ms: i32,
141 total_duration: Duration,
142 max_total_duration: Duration,
143}
144
145impl Drop for AnimationDecoder {
146 fn drop(&mut self) {
147 unsafe {
149 if !self.decoder.is_null() {
150 WebPAnimDecoderDelete(self.decoder);
151 }
152 }
153 }
154}
155
156struct RawDecoderGuard(*mut WebPAnimDecoder);
157
158impl RawDecoderGuard {
159 fn into_raw(mut self) -> *mut WebPAnimDecoder {
160 let decoder = self.0;
161 self.0 = std::ptr::null_mut();
162 decoder
163 }
164}
165
166impl Drop for RawDecoderGuard {
167 fn drop(&mut self) {
168 unsafe {
171 if !self.0.is_null() {
172 WebPAnimDecoderDelete(self.0);
173 }
174 }
175 }
176}
177
178struct DemuxIteratorGuard {
179 iterator: WebPIterator,
180 initialized: bool,
181}
182
183impl DemuxIteratorGuard {
184 fn new() -> Self {
185 Self {
188 iterator: unsafe { std::mem::zeroed() },
189 initialized: false,
190 }
191 }
192
193 fn mark_initialized(&mut self) {
194 self.initialized = true;
195 }
196}
197
198impl Drop for DemuxIteratorGuard {
199 fn drop(&mut self) {
200 if self.initialized {
204 unsafe { WebPDemuxReleaseIterator(&mut self.iterator) };
206 }
207 }
208}
209
210impl AnimationDecoder {
211 pub fn new(input: &[u8], limits: DecodeLimits) -> Result<Self, DecodeError> {
217 if input.len() > limits.max_input_bytes {
218 return Err(DecodeError::InputTooLarge {
219 actual: input.len(),
220 maximum: limits.max_input_bytes,
221 });
222 }
223 if !is_animated_webp_fast(input) {
224 return Err(DecodeError::NotAnimatedWebp);
225 }
226
227 let input = input.to_vec();
228 let mut options: WebPAnimDecoderOptions = unsafe { std::mem::zeroed() };
230 let demux_abi = WebPGetDemuxABIVersion();
231 if unsafe { WebPAnimDecoderOptionsInitInternal(&mut options, demux_abi) } == 0 {
233 return Err(DecodeError::DecoderOptionsInitialization);
234 }
235 options.color_mode = WEBP_CSP_MODE::MODE_RGBA;
236 options.use_threads = 1;
237
238 let data = WebPData {
239 bytes: input.as_ptr(),
240 size: input.len(),
241 };
242 let raw_decoder = unsafe { WebPAnimDecoderNewInternal(&data, &options, demux_abi) };
244 if raw_decoder.is_null() {
245 return Err(DecodeError::DecoderCreation);
246 }
247 let decoder_guard = RawDecoderGuard(raw_decoder);
248
249 let mut raw_info: WebPAnimInfo = unsafe { std::mem::zeroed() };
251 if unsafe { WebPAnimDecoderGetInfo(decoder_guard.0, &mut raw_info) } == 0 {
253 return Err(DecodeError::DecoderInfo);
254 }
255
256 let canvas = CanvasSize {
257 width: raw_info.canvas_width,
258 height: raw_info.canvas_height,
259 };
260 let pixel_count = canvas.pixel_count().ok_or(DecodeError::FrameSizeOverflow)?;
261 enforce_limit("canvas pixels", pixel_count, limits.max_canvas_pixels)?;
262 enforce_limit(
263 "frame count",
264 u64::from(raw_info.frame_count),
265 u64::from(limits.max_frame_count),
266 )?;
267 let frame_rgba_bytes = canvas.rgba_bytes().ok_or(DecodeError::FrameSizeOverflow)?;
268 enforce_limit(
269 "RGBA bytes per frame",
270 u64::try_from(frame_rgba_bytes).unwrap_or(u64::MAX),
271 u64::try_from(limits.max_frame_rgba_bytes).unwrap_or(u64::MAX),
272 )?;
273
274 let loop_count = match raw_info.loop_count {
275 0 => LoopCount::Infinite,
276 value => LoopCount::Finite(
277 std::num::NonZeroU16::new(
278 u16::try_from(value).map_err(|_| DecodeError::InvalidAnimationInfo)?,
279 )
280 .expect("non-zero loop count"),
281 ),
282 };
283 Ok(Self {
284 _input: input,
285 decoder: decoder_guard.into_raw(),
286 info: AnimationInfo {
287 canvas,
288 frame_count: raw_info.frame_count,
289 loop_count,
290 background_color: BackgroundColor {
291 raw: raw_info.bgcolor,
292 },
293 },
294 frame_rgba_bytes,
295 previous_timestamp_ms: 0,
296 total_duration: Duration::ZERO,
297 max_total_duration: limits.max_total_duration,
298 })
299 }
300
301 pub fn info(&self) -> &AnimationInfo {
303 &self.info
304 }
305
306 pub fn frame_durations(&self) -> Result<Vec<Duration>, DecodeError> {
312 let demuxer = unsafe { WebPAnimDecoderGetDemuxer(self.decoder) };
315 if demuxer.is_null() {
316 return Err(DecodeError::DecoderInfo);
317 }
318
319 let mut iterator = DemuxIteratorGuard::new();
320 if unsafe { WebPDemuxGetFrame(demuxer, 1, &mut iterator.iterator) } == 0 {
323 return Err(DecodeError::DecoderInfo);
324 }
325 iterator.mark_initialized();
326
327 let frame_count = usize::try_from(self.info.frame_count)
328 .map_err(|_| DecodeError::InvalidAnimationInfo)?;
329 if u32::try_from(iterator.iterator.num_frames).ok() != Some(self.info.frame_count)
330 || iterator.iterator.frame_num != 1
331 {
332 return Err(DecodeError::InvalidAnimationInfo);
333 }
334
335 let mut durations = Vec::with_capacity(frame_count);
336 let mut total_duration = Duration::ZERO;
337 loop {
338 let actual_count = durations.len().saturating_add(1);
339 if actual_count > frame_count {
340 return Err(DecodeError::InvalidAnimationInfo);
341 }
342 if i32::try_from(actual_count).ok() != Some(iterator.iterator.frame_num) {
343 return Err(DecodeError::InvalidAnimationInfo);
344 }
345
346 let duration_ms = iterator.iterator.duration;
347 if duration_ms < 0 {
348 return Err(DecodeError::InvalidTimestamp);
349 }
350 let duration = Duration::from_millis(
351 u64::try_from(duration_ms).map_err(|_| DecodeError::InvalidTimestamp)?,
352 );
353 total_duration = total_duration
354 .checked_add(duration)
355 .ok_or(DecodeError::InvalidTimestamp)?;
356 let total_duration_ms = u64::try_from(total_duration.as_millis())
357 .map_err(|_| DecodeError::InvalidTimestamp)?;
358 enforce_limit(
359 "total duration in milliseconds",
360 total_duration_ms,
361 self.max_total_duration
362 .as_millis()
363 .try_into()
364 .unwrap_or(u64::MAX),
365 )?;
366 durations.push(duration);
367
368 if unsafe { WebPDemuxNextFrame(&mut iterator.iterator) } == 0 {
372 break;
373 }
374 }
375
376 if durations.len() != frame_count {
377 return Err(DecodeError::InvalidAnimationInfo);
378 }
379 Ok(durations)
380 }
381
382 pub fn reset(&mut self) {
390 unsafe { WebPAnimDecoderReset(self.decoder) };
393 self.previous_timestamp_ms = 0;
394 self.total_duration = Duration::ZERO;
395 }
396
397 pub fn has_more_frames(&self) -> bool {
399 unsafe { WebPAnimDecoderHasMoreFrames(self.decoder) != 0 }
401 }
402
403 pub fn next_frame(&mut self) -> Result<Option<AnimationFrame>, DecodeError> {
405 if !self.has_more_frames() {
406 return Ok(None);
407 }
408
409 let mut rgba = std::ptr::null_mut();
410 let mut timestamp_ms = 0_i32;
411 let ok = unsafe { WebPAnimDecoderGetNext(self.decoder, &mut rgba, &mut timestamp_ms) };
413 if ok == 0 || rgba.is_null() {
414 return Err(DecodeError::FrameDecode);
415 }
416 let duration_ms = timestamp_ms
417 .checked_sub(self.previous_timestamp_ms)
418 .ok_or(DecodeError::InvalidTimestamp)?;
419 let duration = Duration::from_millis(
420 u64::try_from(duration_ms).map_err(|_| DecodeError::InvalidTimestamp)?,
421 );
422 let total_duration = self
423 .total_duration
424 .checked_add(duration)
425 .ok_or(DecodeError::InvalidTimestamp)?;
426 enforce_limit(
427 "total duration in milliseconds",
428 total_duration.as_millis().try_into().unwrap_or(u64::MAX),
429 self.max_total_duration
430 .as_millis()
431 .try_into()
432 .unwrap_or(u64::MAX),
433 )?;
434
435 let rgba = unsafe { std::slice::from_raw_parts(rgba, self.frame_rgba_bytes) }.to_vec();
437 self.previous_timestamp_ms = timestamp_ms;
438 self.total_duration = total_duration;
439
440 Ok(Some(AnimationFrame {
441 rgba,
442 canvas: self.info.canvas,
443 duration,
444 }))
445 }
446}
447
448fn enforce_limit(limit: &'static str, actual: u64, maximum: u64) -> Result<(), DecodeError> {
449 if actual > maximum {
450 return Err(DecodeError::LimitExceeded {
451 limit,
452 actual,
453 maximum,
454 });
455 }
456 Ok(())
457}