1#[cfg(any(feature = "jpeg", feature = "zstd", feature = "webp"))]
4use std::io::Cursor;
5use std::io::Read;
6#[cfg(feature = "jpeg")]
7use std::panic::{self, AssertUnwindSafe};
8
9use crate::error::{Error, Result};
10use crate::header::ByteOrder;
11use tiff_core::{Compression, Predictor};
12
13#[derive(Debug, Clone, Copy)]
14#[cfg_attr(not(any(feature = "jpeg", feature = "webp")), allow(dead_code))]
15pub(crate) struct ImageDecodeLayout {
16 pub width: usize,
17 pub height: usize,
18 pub samples_per_pixel: usize,
19}
20
21pub fn decompress(
23 compression: u16,
24 data: &[u8],
25 index: usize,
26 _jpeg_tables: Option<&[u8]>,
27 decoded_len_limit: usize,
28) -> Result<Vec<u8>> {
29 decompress_with_layout(
30 compression,
31 data,
32 index,
33 _jpeg_tables,
34 decoded_len_limit,
35 None,
36 )
37}
38
39pub(crate) fn decompress_with_layout(
40 compression: u16,
41 data: &[u8],
42 index: usize,
43 _jpeg_tables: Option<&[u8]>,
44 decoded_len_limit: usize,
45 expected_layout: Option<ImageDecodeLayout>,
46) -> Result<Vec<u8>> {
47 let _ = expected_layout;
49 match Compression::from_code(compression) {
50 Some(Compression::None) => {
51 if data.len() > decoded_len_limit {
52 return Err(decoded_block_too_large(
53 index,
54 "uncompressed",
55 decoded_len_limit,
56 ));
57 }
58 Ok(data.to_vec())
59 }
60 Some(Compression::Deflate | Compression::DeflateOld) => {
61 decompress_deflate(data, index, decoded_len_limit)
62 }
63 Some(Compression::Lzw) => decompress_lzw(data, index, decoded_len_limit),
64 Some(Compression::PackBits) => decompress_packbits(data, index, decoded_len_limit),
65 Some(Compression::Lerc) => Err(Error::UnsupportedCompression(compression)),
66 #[cfg(feature = "jpeg")]
67 Some(Compression::OldJpeg) => Err(Error::UnsupportedCompression(compression)),
68 #[cfg(feature = "jpeg")]
69 Some(Compression::Jpeg) => decompress_jpeg(
70 data,
71 index,
72 _jpeg_tables,
73 decoded_len_limit,
74 expected_layout,
75 ),
76 #[cfg(not(feature = "jpeg"))]
77 Some(Compression::OldJpeg | Compression::Jpeg) => {
78 Err(Error::UnsupportedCompression(compression))
79 }
80 #[cfg(feature = "zstd")]
81 Some(Compression::Zstd) => decompress_zstd(data, index, decoded_len_limit),
82 #[cfg(not(feature = "zstd"))]
83 Some(Compression::Zstd) => Err(Error::UnsupportedCompression(compression)),
84 #[cfg(feature = "webp")]
85 Some(Compression::WebP) => decompress_webp(data, index, decoded_len_limit, expected_layout),
86 #[cfg(not(feature = "webp"))]
87 Some(Compression::WebP) => Err(Error::UnsupportedCompression(compression)),
88 None => Err(Error::UnsupportedCompression(compression)),
89 }
90}
91
92pub fn fix_endianness_and_predict(
94 row: &mut [u8],
95 bit_depth: u16,
96 samples: u16,
97 byte_order: ByteOrder,
98 predictor: u16,
99) -> Result<()> {
100 fix_endianness_and_predict_with_scratch(
101 row,
102 bit_depth,
103 samples,
104 byte_order,
105 predictor,
106 &mut Vec::new(),
107 )
108}
109
110pub(crate) fn fix_endianness_and_predict_with_scratch(
113 row: &mut [u8],
114 bit_depth: u16,
115 samples: u16,
116 byte_order: ByteOrder,
117 predictor: u16,
118 scratch: &mut Vec<u8>,
119) -> Result<()> {
120 match Predictor::from_code(predictor) {
121 Some(Predictor::None) => {
122 fix_endianness(row, byte_order, bit_depth);
123 Ok(())
124 }
125 Some(Predictor::Horizontal) => {
126 fix_endianness(row, byte_order, bit_depth);
127 reverse_horizontal_predictor(row, bit_depth, samples);
128 Ok(())
129 }
130 Some(Predictor::FloatingPoint) => match bit_depth {
131 16 | 32 | 64 => {
132 scratch.clear();
133 scratch.extend_from_slice(row);
134 match bit_depth {
135 16 => predict_f16(scratch, row, samples),
136 32 => predict_f32(scratch, row, samples),
137 _ => predict_f64(scratch, row, samples),
138 }
139 Ok(())
140 }
141 _ => Err(Error::UnsupportedPredictor(3)),
142 },
143 None => Err(Error::UnsupportedPredictor(predictor)),
144 }
145}
146
147fn decompress_deflate(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
148 use flate2::read::ZlibDecoder;
149
150 let decoder = ZlibDecoder::new(data);
151 read_bounded_to_end(decoder, index, "deflate", decoded_len_limit)
152}
153
154fn decompress_lzw(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
155 use weezl::decode::Configuration;
156 use weezl::{BitOrder, LzwStatus};
157
158 let mut decoder = Configuration::with_tiff_size_switch(BitOrder::Msb, 8)
159 .with_yield_on_full_buffer(true)
160 .build();
161 let probe_limit = decoded_len_probe_limit(index, "LZW", decoded_len_limit)?;
162 let mut out = Vec::with_capacity(decoded_len_limit.min(8192));
163 let mut input_offset = 0usize;
164 let mut scratch = [0u8; 8192];
165
166 loop {
167 let remaining = probe_limit.saturating_sub(out.len());
168 if remaining == 0 {
169 return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
170 }
171
172 let output_len = remaining.min(scratch.len());
173 let result = decoder.decode_bytes(&data[input_offset..], &mut scratch[..output_len]);
174 input_offset += result.consumed_in;
175 out.extend_from_slice(&scratch[..result.consumed_out]);
176 if out.len() > decoded_len_limit {
177 return Err(decoded_block_too_large(index, "LZW", decoded_len_limit));
178 }
179
180 match result.status {
181 Err(e) => {
182 return Err(Error::DecompressionFailed {
183 index,
184 reason: format!("LZW: {e}"),
185 })
186 }
187 Ok(LzwStatus::Done) => return Ok(out),
188 Ok(LzwStatus::Ok) => {
189 if result.consumed_in == 0 && result.consumed_out == 0 {
190 return Err(Error::DecompressionFailed {
191 index,
192 reason: "LZW: decoder made no progress".into(),
193 });
194 }
195 }
196 Ok(LzwStatus::NoProgress) => {
197 if result.consumed_out == output_len {
198 continue;
199 }
200 return Err(Error::DecompressionFailed {
201 index,
202 reason: "LZW: stream ended before end marker".into(),
203 });
204 }
205 }
206 }
207}
208
209fn decompress_packbits(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
210 let probe_limit = decoded_len_probe_limit(index, "PackBits", decoded_len_limit)?;
211 let mut out = Vec::new();
212 let mut cursor = 0usize;
213
214 while cursor < data.len() {
215 let header = data[cursor] as i8;
216 cursor += 1;
217
218 if header >= 0 {
219 let count = header as usize + 1;
220 let end = cursor + count;
221 if end > data.len() {
222 return Err(Error::DecompressionFailed {
223 index,
224 reason: "PackBits literal run is truncated".into(),
225 });
226 }
227 append_bounded_bytes(
228 &mut out,
229 &data[cursor..end],
230 index,
231 "PackBits",
232 decoded_len_limit,
233 probe_limit,
234 )?;
235 cursor = end;
236 } else if header != -128 {
237 if cursor >= data.len() {
238 return Err(Error::DecompressionFailed {
239 index,
240 reason: "PackBits repeat run is truncated".into(),
241 });
242 }
243 let count = (1i16 - header as i16) as usize;
244 let byte = data[cursor];
245 cursor += 1;
246 append_bounded_repeat(
247 &mut out,
248 byte,
249 count,
250 index,
251 "PackBits",
252 decoded_len_limit,
253 probe_limit,
254 )?;
255 }
256 }
257
258 Ok(out)
259}
260
261#[cfg(feature = "jpeg")]
262fn decompress_jpeg(
263 data: &[u8],
264 index: usize,
265 jpeg_tables: Option<&[u8]>,
266 decoded_len_limit: usize,
267 expected_layout: Option<ImageDecodeLayout>,
268) -> Result<Vec<u8>> {
269 let stream = merge_jpeg_stream(jpeg_tables, data);
270 panic::catch_unwind(AssertUnwindSafe(|| {
271 let mut decoder = jpeg_decoder::Decoder::new(Cursor::new(stream));
272 decoder.set_max_decoding_buffer_size(decoded_len_limit);
273 decoder.read_info()?;
274 validate_jpeg_metadata(&decoder, decoded_len_limit, expected_layout)?;
275 decoder.decode()
276 }))
277 .map_err(|payload| Error::DecompressionFailed {
278 index,
279 reason: format!(
280 "JPEG decoder panicked: {}",
281 panic_payload_message(payload.as_ref())
282 ),
283 })?
284 .map_err(|e| Error::DecompressionFailed {
285 index,
286 reason: format!("JPEG: {e}"),
287 })
288}
289
290#[cfg(feature = "jpeg")]
291fn validate_jpeg_metadata<R: std::io::Read>(
292 decoder: &jpeg_decoder::Decoder<R>,
293 decoded_len_limit: usize,
294 expected_layout: Option<ImageDecodeLayout>,
295) -> std::result::Result<(), jpeg_decoder::Error> {
296 let info = decoder.info().ok_or_else(|| {
297 jpeg_decoder::Error::Format("JPEG metadata missing after read_info".into())
298 })?;
299 let decoded_len = usize::from(info.width)
300 .checked_mul(usize::from(info.height))
301 .and_then(|pixels| pixels.checked_mul(info.pixel_format.pixel_bytes()))
302 .ok_or_else(|| jpeg_decoder::Error::Format("JPEG decoded size overflow".into()))?;
303 if decoded_len > decoded_len_limit {
304 return Err(jpeg_decoder::Error::Format(format!(
305 "JPEG decoded size {decoded_len} exceeds TIFF block budget {decoded_len_limit}"
306 )));
307 }
308 if let Some(expected) = expected_layout {
309 if usize::from(info.width) != expected.width || usize::from(info.height) != expected.height
310 {
311 return Err(jpeg_decoder::Error::Format(format!(
312 "JPEG dimensions {}x{} do not match TIFF block {}x{}",
313 info.width, info.height, expected.width, expected.height
314 )));
315 }
316 if info.pixel_format.pixel_bytes() != expected.samples_per_pixel {
317 return Err(jpeg_decoder::Error::Format(format!(
318 "JPEG channel count {} does not match TIFF block channel count {}",
319 info.pixel_format.pixel_bytes(),
320 expected.samples_per_pixel
321 )));
322 }
323 }
324 Ok(())
325}
326
327#[cfg(feature = "zstd")]
328fn decompress_zstd(data: &[u8], index: usize, decoded_len_limit: usize) -> Result<Vec<u8>> {
329 let decoder = ruzstd::decoding::StreamingDecoder::new(Cursor::new(data)).map_err(|error| {
330 Error::DecompressionFailed {
331 index,
332 reason: format!("ZSTD: {error}"),
333 }
334 })?;
335 read_bounded_to_end(decoder, index, "ZSTD", decoded_len_limit)
336}
337
338#[cfg(feature = "webp")]
339fn decompress_webp(
340 data: &[u8],
341 index: usize,
342 decoded_len_limit: usize,
343 expected_layout: Option<ImageDecodeLayout>,
344) -> Result<Vec<u8>> {
345 let mut decoder = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
346 image_webp::WebPDecoder::new(Cursor::new(data))
347 }))
348 .map_err(|payload| Error::DecompressionFailed {
349 index,
350 reason: format!(
351 "WebP decoder panicked: {}",
352 webp_panic_payload_message(payload.as_ref())
353 ),
354 })?
355 .map_err(|error| Error::DecompressionFailed {
356 index,
357 reason: format!("WebP: {error}"),
358 })?;
359
360 if decoder.is_animated() {
361 return Err(Error::DecompressionFailed {
362 index,
363 reason: "WebP: animated payloads are not valid TIFF blocks".into(),
364 });
365 }
366 if let Some(expected) = expected_layout {
367 let (actual_width, actual_height) = decoder.dimensions();
368 if actual_width as usize != expected.width || actual_height as usize != expected.height {
369 return Err(Error::DecompressionFailed {
370 index,
371 reason: format!(
372 "WebP dimensions {actual_width}x{actual_height} do not match TIFF block {}x{}",
373 expected.width, expected.height
374 ),
375 });
376 }
377 let actual_samples = if decoder.has_alpha() { 4 } else { 3 };
378 if actual_samples != expected.samples_per_pixel {
379 return Err(Error::DecompressionFailed {
380 index,
381 reason: format!(
382 "WebP channel count {actual_samples} does not match TIFF block channel count {}",
383 expected.samples_per_pixel
384 ),
385 });
386 }
387 }
388
389 let output_len = decoder
390 .output_buffer_size()
391 .ok_or_else(|| Error::DecompressionFailed {
392 index,
393 reason: "WebP output size overflows usize".into(),
394 })?;
395 if output_len > decoded_len_limit {
396 return Err(decoded_block_too_large(index, "WebP", decoded_len_limit));
397 }
398
399 let mut out = vec![0u8; output_len];
400 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
401 decoder.read_image(&mut out)
402 }))
403 .map_err(|payload| Error::DecompressionFailed {
404 index,
405 reason: format!(
406 "WebP decoder panicked: {}",
407 webp_panic_payload_message(payload.as_ref())
408 ),
409 })?
410 .map_err(|error| Error::DecompressionFailed {
411 index,
412 reason: format!("WebP: {error}"),
413 })?;
414 Ok(out)
415}
416
417#[cfg(feature = "webp")]
418fn webp_panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
419 if let Some(message) = payload.downcast_ref::<&'static str>() {
420 (*message).to_string()
421 } else if let Some(message) = payload.downcast_ref::<String>() {
422 message.clone()
423 } else {
424 "unknown panic payload".into()
425 }
426}
427
428fn read_bounded_to_end<R: Read>(
429 reader: R,
430 index: usize,
431 codec: &'static str,
432 decoded_len_limit: usize,
433) -> Result<Vec<u8>> {
434 let probe_limit = decoded_len_probe_limit(index, codec, decoded_len_limit)?;
435 let mut reader = reader.take(probe_limit as u64);
436 let mut out = Vec::with_capacity(decoded_len_limit.min(64 * 1024));
437 let mut scratch = vec![0u8; probe_limit.min(64 * 1024)];
438
439 loop {
440 let remaining = probe_limit.saturating_sub(out.len());
441 if remaining == 0 {
442 break;
443 }
444 let read_len = remaining.min(scratch.len());
445 let bytes_read =
446 reader
447 .read(&mut scratch[..read_len])
448 .map_err(|e| Error::DecompressionFailed {
449 index,
450 reason: format!("{codec}: {e}"),
451 })?;
452 if bytes_read == 0 {
453 break;
454 }
455 out.extend_from_slice(&scratch[..bytes_read]);
456 if out.len() > decoded_len_limit {
457 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
458 }
459 }
460
461 Ok(out)
462}
463
464fn decoded_len_probe_limit(
465 index: usize,
466 codec: &'static str,
467 decoded_len_limit: usize,
468) -> Result<usize> {
469 decoded_len_limit
470 .checked_add(1)
471 .ok_or_else(|| Error::DecompressionFailed {
472 index,
473 reason: format!("{codec}: TIFF block budget is too large to probe safely"),
474 })
475}
476
477fn decoded_block_too_large(index: usize, codec: &'static str, decoded_len_limit: usize) -> Error {
478 Error::DecompressionFailed {
479 index,
480 reason: format!("{codec}: decoded block exceeds TIFF block budget {decoded_len_limit}"),
481 }
482}
483
484fn append_bounded_bytes(
485 out: &mut Vec<u8>,
486 bytes: &[u8],
487 index: usize,
488 codec: &'static str,
489 decoded_len_limit: usize,
490 probe_limit: usize,
491) -> Result<()> {
492 let remaining = probe_limit.saturating_sub(out.len());
493 let copy_len = bytes.len().min(remaining);
494 out.extend_from_slice(&bytes[..copy_len]);
495 if copy_len < bytes.len() || out.len() > decoded_len_limit {
496 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
497 }
498 Ok(())
499}
500
501fn append_bounded_repeat(
502 out: &mut Vec<u8>,
503 byte: u8,
504 count: usize,
505 index: usize,
506 codec: &'static str,
507 decoded_len_limit: usize,
508 probe_limit: usize,
509) -> Result<()> {
510 let remaining = probe_limit.saturating_sub(out.len());
511 let copy_len = count.min(remaining);
512 out.resize(out.len() + copy_len, byte);
513 if copy_len < count || out.len() > decoded_len_limit {
514 return Err(decoded_block_too_large(index, codec, decoded_len_limit));
515 }
516 Ok(())
517}
518
519#[cfg(feature = "jpeg")]
520fn merge_jpeg_stream(jpeg_tables: Option<&[u8]>, scan_data: &[u8]) -> Vec<u8> {
521 if jpeg_tables.is_none() {
522 return scan_data.to_vec();
523 }
524
525 let tables = jpeg_tables.unwrap_or_default();
526 let table_body = match tables.strip_suffix(&[0xff, 0xd9]) {
527 Some(without_eoi) => without_eoi,
528 None => tables,
529 };
530 let scan_body = match scan_data.strip_prefix(&[0xff, 0xd8]) {
531 Some(without_soi) => without_soi,
532 None => scan_data,
533 };
534
535 let mut merged = Vec::with_capacity(table_body.len() + scan_body.len() + 2);
536 if table_body.starts_with(&[0xff, 0xd8]) {
537 merged.extend_from_slice(table_body);
538 } else {
539 merged.extend_from_slice(&[0xff, 0xd8]);
540 merged.extend_from_slice(table_body);
541 }
542 merged.extend_from_slice(scan_body);
543 if !merged.ends_with(&[0xff, 0xd9]) {
544 merged.extend_from_slice(&[0xff, 0xd9]);
545 }
546 merged
547}
548
549#[cfg(feature = "jpeg")]
550fn panic_payload_message(payload: &(dyn std::any::Any + Send)) -> String {
551 if let Some(message) = payload.downcast_ref::<&'static str>() {
552 (*message).to_string()
553 } else if let Some(message) = payload.downcast_ref::<String>() {
554 message.clone()
555 } else {
556 "unknown panic payload".into()
557 }
558}
559
560fn fix_endianness(buf: &mut [u8], byte_order: ByteOrder, bit_depth: u16) {
561 let host_is_little_endian = cfg!(target_endian = "little");
562 let data_is_little_endian = matches!(byte_order, ByteOrder::LittleEndian);
563 if host_is_little_endian == data_is_little_endian {
564 return;
565 }
566
567 let chunk = match bit_depth {
568 0..=8 => 1,
569 9..=16 => 2,
570 17..=32 => 4,
571 _ => 8,
572 };
573 if chunk == 1 {
574 return;
575 }
576
577 for value in buf.chunks_exact_mut(chunk) {
578 value.reverse();
579 }
580}
581
582fn reverse_horizontal_predictor(buf: &mut [u8], bit_depth: u16, samples: u16) {
583 let bytes_per_value = match bit_depth {
584 0..=8 => 1,
585 9..=16 => 2,
586 17..=32 => 4,
587 _ => 8,
588 };
589 let lookback = usize::from(samples) * bytes_per_value;
590
591 match bytes_per_value {
592 1 => {
593 for index in lookback..buf.len() {
594 buf[index] = buf[index].wrapping_add(buf[index - lookback]);
595 }
596 }
597 2 => {
598 for index in (lookback..buf.len()).step_by(2) {
599 let current = u16::from_ne_bytes(buf[index..index + 2].try_into().unwrap());
600 let previous = u16::from_ne_bytes(
601 buf[index - lookback..index - lookback + 2]
602 .try_into()
603 .unwrap(),
604 );
605 buf[index..index + 2]
606 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
607 }
608 }
609 4 => {
610 for index in (lookback..buf.len()).step_by(4) {
611 let current = u32::from_ne_bytes(buf[index..index + 4].try_into().unwrap());
612 let previous = u32::from_ne_bytes(
613 buf[index - lookback..index - lookback + 4]
614 .try_into()
615 .unwrap(),
616 );
617 buf[index..index + 4]
618 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
619 }
620 }
621 _ => {
622 for index in (lookback..buf.len()).step_by(8) {
623 let current = u64::from_ne_bytes(buf[index..index + 8].try_into().unwrap());
624 let previous = u64::from_ne_bytes(
625 buf[index - lookback..index - lookback + 8]
626 .try_into()
627 .unwrap(),
628 );
629 buf[index..index + 8]
630 .copy_from_slice(¤t.wrapping_add(previous).to_ne_bytes());
631 }
632 }
633 }
634}
635
636fn predict_f16(input: &mut [u8], output: &mut [u8], samples: u16) {
637 let samples = usize::from(samples);
638 for i in samples..input.len() {
639 input[i] = input[i].wrapping_add(input[i - samples]);
640 }
641 for (i, chunk) in output.chunks_mut(2).enumerate() {
642 chunk.copy_from_slice(&u16::to_ne_bytes(u16::from_be_bytes([
643 input[i],
644 input[input.len() / 2 + i],
645 ])));
646 }
647}
648
649fn predict_f32(input: &mut [u8], output: &mut [u8], samples: u16) {
650 let samples = usize::from(samples);
651 for i in samples..input.len() {
652 input[i] = input[i].wrapping_add(input[i - samples]);
653 }
654 for (i, chunk) in output.chunks_mut(4).enumerate() {
655 chunk.copy_from_slice(&u32::to_ne_bytes(u32::from_be_bytes([
656 input[i],
657 input[input.len() / 4 + i],
658 input[input.len() / 2 + i],
659 input[input.len() / 4 * 3 + i],
660 ])));
661 }
662}
663
664fn predict_f64(input: &mut [u8], output: &mut [u8], samples: u16) {
665 let samples = usize::from(samples);
666 for i in samples..input.len() {
667 input[i] = input[i].wrapping_add(input[i - samples]);
668 }
669 for (i, chunk) in output.chunks_mut(8).enumerate() {
670 chunk.copy_from_slice(&u64::to_ne_bytes(u64::from_be_bytes([
671 input[i],
672 input[input.len() / 8 + i],
673 input[input.len() / 8 * 2 + i],
674 input[input.len() / 8 * 3 + i],
675 input[input.len() / 8 * 4 + i],
676 input[input.len() / 8 * 5 + i],
677 input[input.len() / 8 * 6 + i],
678 input[input.len() / 8 * 7 + i],
679 ])));
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use std::path::Path;
686
687 #[cfg(not(feature = "jpeg"))]
688 use super::decompress;
689 #[cfg(feature = "jpeg")]
690 use super::{decompress, merge_jpeg_stream};
691 use super::{decompress_lzw, decompress_packbits, fix_endianness_and_predict};
692 use crate::header::ByteOrder;
693 use std::io::Write;
694 use tiff_core::Compression;
695
696 #[test]
697 fn horizontal_predictor_restores_u16_rows() {
698 let mut row = vec![1, 0, 1, 0, 2, 0];
699 fix_endianness_and_predict(&mut row, 16, 1, ByteOrder::LittleEndian, 2).unwrap();
700 assert_eq!(row, vec![1, 0, 2, 0, 4, 0]);
701 }
702
703 #[test]
704 fn packbits_decoder_rejects_truncated_repeat_run() {
705 let err = decompress_packbits(&[0xff], 0, 1).unwrap_err();
706 assert!(err.to_string().contains("PackBits"));
707 }
708
709 #[test]
710 fn deflate_decoder_rejects_blocks_that_exceed_budget() {
711 let payload = [0x2a; 128];
712 let mut encoder =
713 flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default());
714 encoder.write_all(&payload).unwrap();
715 let compressed = encoder.finish().unwrap();
716
717 let err =
718 decompress(Compression::Deflate.to_code(), &compressed, 0, None, 127).unwrap_err();
719 assert!(err.to_string().contains("block budget"));
720 }
721
722 #[test]
723 fn lzw_decoder_rejects_blocks_that_exceed_budget() {
724 let payload = [0x2a; 128];
725 let compressed = weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
726 .encode(&payload)
727 .unwrap();
728
729 let err = decompress_lzw(&compressed, 0, 127).unwrap_err();
730 assert!(err.to_string().contains("block budget"));
731 }
732
733 #[test]
734 fn packbits_decoder_rejects_blocks_that_exceed_budget() {
735 let err = decompress_packbits(&[0x81, 0x2a], 0, 127).unwrap_err();
736 assert!(err.to_string().contains("block budget"));
737 }
738
739 #[test]
740 fn uncompressed_decoder_rejects_blocks_that_exceed_budget() {
741 let err =
742 decompress(Compression::None.to_code(), &[1, 2, 3, 4, 5], 0, None, 4).unwrap_err();
743 assert!(err.to_string().contains("block budget"));
744 }
745
746 #[test]
747 fn lzw_real_cog_tile_requires_repeated_trailer_bytes() {
748 let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
749 .join("../testdata/interoperability/gdal/gcore/data/cog/byte_little_endian_golden.tif");
750 let bytes = std::fs::read(fixture).unwrap();
751
752 let without_trailer = &bytes[570..570 + 1223];
753 let with_trailer = &bytes[570..570 + 1227];
754
755 assert!(decompress_lzw(without_trailer, 0, 1_000_000).is_ok());
756 assert!(decompress_lzw(with_trailer, 0, 1_000_000).is_ok());
757 }
758
759 #[cfg(feature = "zstd")]
760 #[test]
761 fn zstd_decoder_rejects_blocks_that_exceed_budget() {
762 let payload = [0x2a; 128];
763 let compressed = ruzstd::encoding::compress_to_vec(
764 &payload[..],
765 ruzstd::encoding::CompressionLevel::Fastest,
766 );
767
768 let err = decompress(Compression::Zstd.to_code(), &compressed, 0, None, 127).unwrap_err();
769 assert!(err.to_string().contains("block budget"));
770 }
771
772 #[cfg(feature = "jpeg")]
773 #[test]
774 fn merges_jpeg_tables_with_abbreviated_scan() {
775 let merged = merge_jpeg_stream(
776 Some(&[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0xff, 0xd9]),
777 &[0xff, 0xda, 0x00, 0x08, 0x00],
778 );
779 assert_eq!(&merged[..6], &[0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43]);
780 assert!(merged.ends_with(&[0xff, 0xd9]));
781 }
782
783 #[cfg(feature = "jpeg")]
784 #[test]
785 fn jpeg_decoder_rejects_frame_sizes_that_exceed_tiff_budget() {
786 let mut jpeg = vec![
787 0xff, 0xd8, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x14, 0x00, 0x14, 0x01, 0x01, 0x11,
788 0x00, 0xff, 0xc4, 0x00, 0x17, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
789 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x04, 0x06, 0xff, 0xc4,
790 0x00, 0x2a, 0x10, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x05, 0x05, 0x00, 0x00, 0x00,
791 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x11, 0x03, 0x04, 0x00, 0x18, 0x31, 0x41,
792 0x13, 0x21, 0x51, 0x71, 0x05, 0x22, 0x61, 0x91, 0xb1, 0x14, 0x42, 0x62, 0xc1, 0xf0,
793 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x75, 0xc5, 0xb7, 0xd2,
794 0x31, 0x4a, 0x75, 0x51, 0xe0, 0x65, 0xf2, 0x19, 0xd8, 0x8d, 0x7d, 0xfe, 0x71, 0x19,
795 0x2b, 0x94, 0x54, 0x2c, 0x33, 0x38, 0x20, 0x2f, 0x7d, 0xf5, 0xd2, 0x40, 0x18, 0x6b,
796 0xdc, 0x3d, 0xa0, 0x44, 0x15, 0xc9, 0x2c, 0xa1, 0xc8, 0x5c, 0xa4, 0x2c, 0xed, 0xcc,
797 0x74, 0x83, 0xcb, 0xaf, 0x59, 0xc2, 0xaf, 0x0f, 0x02, 0xb3, 0x2e, 0x57, 0xfc, 0x79,
798 0x15, 0x9f, 0x58, 0xee, 0x3f, 0x7b, 0xe0, 0x59, 0x95, 0x84, 0x26, 0x56, 0xac, 0xc2,
799 0x62, 0xa0, 0x8c, 0xa4, 0x91, 0xc9, 0x44, 0xed, 0xa4, 0x9e, 0x9a, 0x08, 0xc1, 0x8a,
800 0x54, 0x9d, 0x41, 0xe3, 0xa4, 0xe8, 0x65, 0x01, 0xe7, 0xdc, 0xff, 0x00, 0x6d, 0x8d,
801 0x2f, 0x89, 0x5b, 0x50, 0xbe, 0xb9, 0x4a, 0x0d, 0x4c, 0x53, 0x51, 0x01, 0x8a, 0x31,
802 0x9a, 0x92, 0x22, 0x5a, 0x49, 0xe7, 0xda, 0x37, 0xeb, 0x8c, 0xc5, 0xc7, 0x0a, 0xd5,
803 0x87, 0x0a, 0x85, 0x30, 0xc7, 0xee, 0x69, 0x27, 0x40, 0x77, 0x3e, 0xbf, 0x18, 0x99,
804 0xae, 0x1c, 0xb6, 0xc0, 0x0d, 0x02, 0xf9, 0x47, 0xb0, 0x81, 0x8f, 0xff, 0xd9,
805 ];
806 jpeg[7] = 0x9b;
807 jpeg[8] = 0x43;
808 jpeg[9] = 0xee;
809 jpeg[10] = 0x23;
810
811 let error = decompress(Compression::Jpeg.to_code(), &jpeg, 0, None, 20 * 20).unwrap_err();
812 assert!(error.to_string().contains("block budget"));
813 }
814}