1use zpdf_core::{Error, ParseLimits, PdfDict, PdfName, PdfObject, Result};
2
3pub(crate) struct DecodeBudget {
6 remaining: u64,
7 total_consumed: u64,
8}
9
10impl DecodeBudget {
11 pub(crate) fn new(limit: u64) -> Self {
12 Self {
13 remaining: limit,
14 total_consumed: 0,
15 }
16 }
17
18 pub(crate) fn reserve(&mut self, bytes: u64) -> Result<()> {
20 if bytes > self.remaining {
21 return Err(Error::StreamDecode(format!(
22 "decoded stream exceeds budget: {} bytes already consumed, {} more requested, {} limit",
23 self.total_consumed, bytes, self.total_consumed.saturating_add(self.remaining)
24 )));
25 }
26 self.remaining = self.remaining.saturating_sub(bytes);
27 self.total_consumed = self.total_consumed.saturating_add(bytes);
28 Ok(())
29 }
30}
31
32pub fn decode_stream_with_limits(
37 data: &[u8],
38 dict: &PdfDict,
39 limits: &ParseLimits,
40) -> Result<Vec<u8>> {
41 let filters = match dict.get("Filter") {
42 Some(PdfObject::Name(n)) => vec![n.clone()],
43 Some(PdfObject::Array(arr)) => arr
44 .iter()
45 .map(|obj| match obj {
46 PdfObject::Name(n) => Ok(n.clone()),
47 _ => Err(Error::TypeMismatch {
48 expected: "Name",
49 actual: obj.type_name(),
50 }),
51 })
52 .collect::<Result<Vec<_>>>()?,
53 Some(_) => {
54 return Err(Error::TypeMismatch {
55 expected: "Name or Array",
56 actual: "other",
57 })
58 }
59 None => {
60 if data.len() as u64 > limits.max_decoded_stream_bytes {
61 return Err(Error::StreamSizeLimit(limits.max_decoded_stream_bytes));
62 }
63 return Ok(data.to_vec());
64 }
65 };
66
67 let decode_parms = extract_decode_parms(dict, filters.len());
68
69 let mut budget = DecodeBudget::new(limits.max_decoded_stream_bytes);
71
72 let mut current = data.to_vec();
74 for (i, filter) in filters.iter().enumerate() {
75 let params = decode_parms[i].as_ref();
76
77 let decoded = apply_filter(filter, ¤t, params, limits, &mut budget)?;
79
80 current = if let Some(p) = params {
82 let predictor = p.get_i64("Predictor").unwrap_or(1);
83 if predictor == 2 || predictor >= 10 {
84 apply_predictor(&decoded, p, &mut budget)?
85 } else {
86 decoded
89 }
90 } else {
91 decoded
92 };
93 }
94
95 Ok(current)
96}
97
98pub fn decode_stream(data: &[u8], dict: &PdfDict) -> Result<Vec<u8>> {
107 decode_stream_with_limits(data, dict, &ParseLimits::default())
108}
109
110fn extract_decode_parms(dict: &PdfDict, filter_count: usize) -> Vec<Option<PdfDict>> {
111 match dict.get("DecodeParms").or_else(|| dict.get("DP")) {
112 Some(PdfObject::Dict(d)) => {
113 let mut v = vec![None; filter_count];
114 if !v.is_empty() {
115 v[0] = Some(d.clone());
116 }
117 v
118 }
119 Some(PdfObject::Array(arr)) => arr
120 .iter()
121 .map(|obj| match obj {
122 PdfObject::Dict(d) => Some(d.clone()),
123 _ => None,
124 })
125 .chain(std::iter::repeat(None))
126 .take(filter_count)
127 .collect(),
128 _ => vec![None; filter_count],
129 }
130}
131
132fn apply_predictor(data: &[u8], params: &PdfDict, budget: &mut DecodeBudget) -> Result<Vec<u8>> {
133 let predictor = params.get_i64("Predictor").unwrap_or(1) as u32;
134 if predictor == 1 {
135 return Ok(data.to_vec());
136 }
137
138 budget.reserve(data.len() as u64)?;
140
141 const MAX_COLORS: usize = 256; const MAX_BPC: usize = 32; const MAX_COLUMNS: usize = 1 << 16; let colors = params
148 .get_i64("Colors")
149 .unwrap_or(1)
150 .clamp(1, MAX_COLORS as i64) as usize;
151 let bpc = params
152 .get_i64("BitsPerComponent")
153 .unwrap_or(8)
154 .clamp(1, MAX_BPC as i64) as usize;
155 let columns = params
156 .get_i64("Columns")
157 .unwrap_or(1)
158 .clamp(1, MAX_COLUMNS as i64) as usize;
159
160 if predictor == 2 {
161 decode_tiff_predictor(data, colors, bpc, columns)
162 } else if predictor >= 10 {
163 decode_png_predictor(data, colors, bpc, columns)
164 } else {
165 Ok(data.to_vec())
166 }
167}
168
169fn decode_tiff_predictor(
170 data: &[u8],
171 colors: usize,
172 bpc: usize,
173 columns: usize,
174) -> Result<Vec<u8>> {
175 if bpc != 8 {
178 return Ok(data.to_vec());
180 }
181
182 if colors == 0 || columns == 0 {
184 return Err(Error::StreamDecode(
185 "TIFF predictor: colors and columns must be non-zero".into(),
186 ));
187 }
188
189 let row_bytes = columns
191 .checked_mul(colors)
192 .ok_or_else(|| Error::StreamDecode("TIFF predictor: columns * colors overflow".into()))?;
193
194 if data.is_empty() {
197 return Ok(data.to_vec());
198 }
199
200 let mut output = data.to_vec();
201 for row_start in (0..output.len()).step_by(row_bytes) {
202 let row_end = (row_start + row_bytes).min(output.len());
203 for i in (row_start + colors)..row_end {
204 output[i] = output[i].wrapping_add(output[i - colors]);
205 }
206 }
207 Ok(output)
208}
209
210fn decode_png_predictor(data: &[u8], colors: usize, bpc: usize, columns: usize) -> Result<Vec<u8>> {
211 if colors == 0 || columns == 0 {
213 return Err(Error::StreamDecode(
214 "PNG predictor: colors and columns must be non-zero".into(),
215 ));
216 }
217 if bpc == 0 || bpc > 16 {
218 return Err(Error::StreamDecode(
219 "PNG predictor: bits per component must be 1-16".into(),
220 ));
221 }
222
223 let bits_per_row = colors
225 .checked_mul(bpc)
226 .and_then(|v| v.checked_mul(columns))
227 .ok_or_else(|| Error::StreamDecode("PNG predictor: row computation overflow".into()))?;
228 let row_bytes = bits_per_row.div_ceil(8);
229
230 let bits_per_pixel = colors
231 .checked_mul(bpc)
232 .ok_or_else(|| Error::StreamDecode("PNG predictor: pixel computation overflow".into()))?;
233 let bpp = bits_per_pixel.div_ceil(8); let stride = row_bytes
236 .checked_add(1)
237 .ok_or_else(|| Error::StreamDecode("PNG predictor: stride overflow".into()))?; if stride == 0 {
241 return Err(Error::StreamDecode(
242 "PNG predictor: stride cannot be zero".into(),
243 ));
244 }
245
246 if !data.len().is_multiple_of(stride) && !data.is_empty() {
247 tracing::debug!(
249 "PNG predictor: data length {} not multiple of stride {stride}",
250 data.len()
251 );
252 }
253
254 let num_rows = data.len().div_ceil(stride);
255 let output_size = num_rows
256 .checked_mul(row_bytes)
257 .ok_or_else(|| Error::StreamDecode("PNG predictor: output size overflow".into()))?;
258 let mut output = Vec::with_capacity(output_size);
259 let mut prev_row = vec![0u8; row_bytes];
260
261 let mut pos = 0;
262 while pos < data.len() {
263 let filter_type = data[pos];
264 pos += 1;
265
266 let available = (data.len() - pos).min(row_bytes);
267 let cur = &data[pos..pos + available];
268
269 let mut row = vec![0u8; row_bytes];
270 row[..available].copy_from_slice(cur);
271
272 match filter_type {
273 0 => {} 1 => {
275 for i in bpp..row_bytes {
277 row[i] = row[i].wrapping_add(row[i - bpp]);
278 }
279 }
280 2 => {
281 for i in 0..row_bytes {
283 row[i] = row[i].wrapping_add(prev_row[i]);
284 }
285 }
286 3 => {
287 for i in 0..row_bytes {
289 let left = if i >= bpp { row[i - bpp] as u16 } else { 0 };
290 let above = prev_row[i] as u16;
291 row[i] = row[i].wrapping_add(((left + above) / 2) as u8);
292 }
293 }
294 4 => {
295 for i in 0..row_bytes {
297 let left = if i >= bpp { row[i - bpp] as i32 } else { 0 };
298 let above = prev_row[i] as i32;
299 let upper_left = if i >= bpp {
300 prev_row[i - bpp] as i32
301 } else {
302 0
303 };
304 row[i] = row[i].wrapping_add(paeth(left, above, upper_left));
305 }
306 }
307 _ => {
308 tracing::debug!("PNG predictor: unknown filter type {filter_type}");
309 }
310 }
311
312 output.extend_from_slice(&row);
313 prev_row.copy_from_slice(&row);
314 pos += available;
315 }
316
317 Ok(output)
318}
319
320fn paeth(a: i32, b: i32, c: i32) -> u8 {
321 let p = a + b - c;
322 let pa = (p - a).abs();
323 let pb = (p - b).abs();
324 let pc = (p - c).abs();
325 if pa <= pb && pa <= pc {
326 a as u8
327 } else if pb <= pc {
328 b as u8
329 } else {
330 c as u8
331 }
332}
333
334fn apply_filter(
335 filter: &PdfName,
336 data: &[u8],
337 params: Option<&PdfDict>,
338 limits: &ParseLimits,
339 budget: &mut DecodeBudget,
340) -> Result<Vec<u8>> {
341 match filter.as_str() {
342 "FlateDecode" | "Fl" => decode_flate(data, budget),
343 "LZWDecode" | "LZW" => {
344 let early_change = params
346 .and_then(|p| p.get_i64("EarlyChange").ok())
347 .unwrap_or(1);
348 lzw_decode(data, early_change, budget)
349 }
350 "ASCIIHexDecode" | "AHx" => decode_ascii_hex(data, budget),
351 "ASCII85Decode" | "A85" => decode_ascii85(data, budget),
352 "RunLengthDecode" | "RL" => decode_run_length(data, budget),
353 "DCTDecode" | "DCT" => decode_dct(data, limits.max_image_pixels, budget),
354 "CCITTFaxDecode" | "CCF" => {
355 let ccitt_params = crate::ccitt::CcittParams::from_dict(params);
356 crate::ccitt::decode(data, &ccitt_params, budget)
358 }
359 "JBIG2Decode" => {
360 let jbig2_params = crate::jbig2::Jbig2Params::from_dict(params);
361 crate::jbig2::decode(data, &jbig2_params, budget)
363 }
364 "JPXDecode" => {
369 budget.reserve(data.len() as u64)?;
370 Ok(data.to_vec())
371 }
372 other => Err(Error::UnsupportedFilter(other.to_string())),
373 }
374}
375
376fn lzw_decode(data: &[u8], early_change: i64, budget: &mut DecodeBudget) -> Result<Vec<u8>> {
384 const CLEAR: u32 = 256;
385 const EOD: u32 = 257;
386
387 let early: u32 = if early_change == 0 { 0 } else { 1 };
389
390 let mut table: Vec<Vec<u8>> = Vec::with_capacity(4096);
393 let reset = |t: &mut Vec<Vec<u8>>| {
394 t.clear();
395 for i in 0..256u32 {
396 t.push(vec![i as u8]);
397 }
398 t.push(Vec::new()); t.push(Vec::new()); };
401 reset(&mut table);
402
403 let mut width: u32 = 9;
404 let mut bit_pos: usize = 0;
405 let total_bits = data.len() * 8;
406
407 let read_code = |bit_pos: &mut usize, width: u32| -> Option<u32> {
409 if *bit_pos + width as usize > total_bits {
410 return None;
411 }
412 let mut code: u32 = 0;
413 for _ in 0..width {
414 let byte = data[*bit_pos / 8];
415 let bit = (byte >> (7 - (*bit_pos % 8))) & 1;
416 code = (code << 1) | bit as u32;
417 *bit_pos += 1;
418 }
419 Some(code)
420 };
421
422 let mut out: Vec<u8> = Vec::new();
423 let mut prev: Option<u32> = None;
424
425 while let Some(code) = read_code(&mut bit_pos, width) {
427 if code == EOD {
428 break;
429 }
430 if code == CLEAR {
431 reset(&mut table);
432 width = 9;
433 prev = None;
434 continue;
435 }
436
437 let entry: Vec<u8> = if (code as usize) < table.len() {
439 table[code as usize].clone()
440 } else if code as usize == table.len() {
441 match prev {
443 Some(p) => {
444 let mut e = table[p as usize].clone();
445 e.push(table[p as usize][0]);
446 e
447 }
448 None => {
449 return Err(Error::StreamDecode(format!(
450 "LZWDecode: code {code} before any literal"
451 )))
452 }
453 }
454 } else {
455 return Err(Error::StreamDecode(format!(
456 "LZWDecode: invalid code {code} (table size {})",
457 table.len()
458 )));
459 };
460
461 budget.reserve(entry.len() as u64)?;
463 out.extend_from_slice(&entry);
464
465 if let Some(p) = prev {
468 if table.len() >= 4096 {
472 } else {
475 let mut new_entry = table[p as usize].clone();
476 new_entry.push(entry[0]);
477 table.push(new_entry);
478 }
479 }
480 prev = Some(code);
481
482 let next_code = table.len() as u32;
489 if width < 12 && next_code + early >= (1u32 << width) {
490 width += 1;
491 }
492 }
493
494 Ok(out)
495}
496
497enum InflateOutcome {
500 Complete(Vec<u8>),
501 Failed(Vec<u8>, String),
502}
503
504fn inflate_chunked(
508 mut reader: impl std::io::Read,
509 budget: &mut DecodeBudget,
510) -> Result<InflateOutcome> {
511 let mut out = Vec::new();
512 let mut buf = [0u8; 16 * 1024];
513 loop {
514 match reader.read(&mut buf) {
515 Ok(0) => return Ok(InflateOutcome::Complete(out)),
516 Ok(n) => {
517 budget.reserve(n as u64)?;
519 out.extend_from_slice(&buf[..n]);
520 }
521 Err(e) => return Ok(InflateOutcome::Failed(out, e.to_string())),
522 }
523 }
524}
525
526fn decode_flate(data: &[u8], budget: &mut DecodeBudget) -> Result<Vec<u8>> {
530 use flate2::read::{DeflateDecoder, ZlibDecoder};
531
532 if data.is_empty() {
534 return Ok(Vec::new());
535 }
536
537 let plausible_zlib = |i: usize| {
540 data.len() >= i + 2
541 && data[i] & 0x0f == 8
542 && ((data[i] as u32) << 8 | data[i + 1] as u32).is_multiple_of(31)
543 };
544
545 let mut zlib_err: Option<String> = None;
546 if plausible_zlib(0) {
547 match inflate_chunked(ZlibDecoder::new(data), budget)? {
548 InflateOutcome::Complete(out) => return Ok(out),
549 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
550 tracing::warn!(
551 "FlateDecode: zlib stream failed after {} bytes ({err}); keeping partial output",
552 partial.len()
553 );
554 return Ok(partial);
555 }
556 InflateOutcome::Failed(_, err) => zlib_err = Some(err),
557 }
558 }
559
560 const MAX_HEADER_SCAN: usize = 64;
563 if let Some(k) = (1..data.len().min(MAX_HEADER_SCAN)).find(|&k| plausible_zlib(k)) {
564 match inflate_chunked(ZlibDecoder::new(&data[k..]), budget)? {
565 InflateOutcome::Complete(out) => {
566 tracing::warn!("FlateDecode: skipped {k} bytes of leading garbage");
567 return Ok(out);
568 }
569 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
570 tracing::warn!(
571 "FlateDecode: zlib stream at offset {k} failed ({err}); keeping {} partial bytes",
572 partial.len()
573 );
574 return Ok(partial);
575 }
576 InflateOutcome::Failed(..) => {}
577 }
578 }
579
580 match inflate_chunked(DeflateDecoder::new(data), budget)? {
582 InflateOutcome::Complete(out) => {
583 tracing::warn!("FlateDecode: decoded as raw deflate (missing zlib header)");
584 Ok(out)
585 }
586 InflateOutcome::Failed(partial, err) if !partial.is_empty() => {
587 tracing::warn!(
588 "FlateDecode: raw deflate failed ({err}); keeping {} partial bytes",
589 partial.len()
590 );
591 Ok(partial)
592 }
593 InflateOutcome::Failed(_, err) => Err(Error::StreamDecode(format!(
594 "FlateDecode: {}",
595 zlib_err.unwrap_or(err)
596 ))),
597 }
598}
599
600fn decode_ascii_hex(data: &[u8], budget: &mut DecodeBudget) -> Result<Vec<u8>> {
604 let max_output = data.len().saturating_add(1) / 2;
606 budget.reserve(max_output as u64)?;
607
608 let mut output = Vec::with_capacity(data.len() / 2);
609 let mut high: Option<u8> = None;
610 let mut stray = 0usize;
611
612 for &b in data {
613 if b == b'>' {
614 break; }
616 if b.is_ascii_whitespace() || b == 0 {
617 continue;
618 }
619 let nibble = match b {
620 b'0'..=b'9' => b - b'0',
621 b'a'..=b'f' => b - b'a' + 10,
622 b'A'..=b'F' => b - b'A' + 10,
623 _ => {
624 stray += 1;
625 continue;
626 }
627 };
628
629 match high {
630 None => high = Some(nibble),
631 Some(h) => {
632 output.push((h << 4) | nibble);
633 high = None;
634 }
635 }
636 }
637
638 if let Some(h) = high {
639 output.push(h << 4);
640 }
641 if stray > 0 {
642 tracing::warn!("ASCIIHexDecode: ignored {stray} invalid byte(s)");
643 }
644
645 Ok(output)
646}
647
648fn decode_ascii85(data: &[u8], budget: &mut DecodeBudget) -> Result<Vec<u8>> {
652 let max_output = (data.len() as u64).saturating_mul(4);
656 budget.reserve(max_output)?;
657
658 let mut output = Vec::new();
659 let mut tuple: u64 = 0;
662 let mut count = 0usize;
663 let mut stray = 0usize;
664
665 for &b in data {
666 if b == b'~' {
667 break; }
669 if b.is_ascii_whitespace() || b == 0 {
670 continue;
671 }
672
673 if b == b'z' && count == 0 {
674 output.extend_from_slice(&[0, 0, 0, 0]);
675 continue;
676 }
677
678 if !(b'!'..=b'u').contains(&b) {
679 stray += 1;
680 continue;
681 }
682
683 tuple = tuple * 85 + (b - b'!') as u64;
684 count += 1;
685
686 if count == 5 {
687 let t = (tuple & 0xFFFF_FFFF) as u32;
688 output.extend_from_slice(&t.to_be_bytes());
689 tuple = 0;
690 count = 0;
691 }
692 }
693
694 if count > 1 {
696 for _ in count..5 {
697 tuple = tuple * 85 + 84; }
699 let t = (tuple & 0xFFFF_FFFF) as u32;
700 for i in 0..(count - 1) {
701 output.push((t >> (24 - i * 8)) as u8);
702 }
703 }
704 if stray > 0 {
705 tracing::warn!("ASCII85Decode: ignored {stray} invalid byte(s)");
706 }
707
708 Ok(output)
709}
710
711fn validate_jpeg_dimensions(
717 width: u16,
718 height: u16,
719 components: u8,
720 max_image_pixels: u64,
721 budget: &mut DecodeBudget,
722) -> Result<()> {
723 let w = width as u64;
724 let h = height as u64;
725 let c = components as u64;
726
727 let pixel_count = w.saturating_mul(h);
729 if pixel_count > max_image_pixels {
730 return Err(Error::StreamDecode(format!(
731 "JPEG dimensions {w}×{h} exceed the {max_image_pixels}-pixel limit"
732 )));
733 }
734
735 let decoded_size = pixel_count.saturating_mul(c);
737 if decoded_size > budget.remaining {
738 return Err(Error::StreamDecode(format!(
739 "JPEG output {w}×{h}×{c} = {decoded_size} bytes exceeds remaining budget {}",
740 budget.remaining
741 )));
742 }
743
744 budget.reserve(decoded_size)?;
746 Ok(())
747}
748
749fn decode_dct(data: &[u8], max_image_pixels: u64, budget: &mut DecodeBudget) -> Result<Vec<u8>> {
750 use zune_jpeg::JpegDecoder;
751
752 if jpeg_is_adobe_ycck(data) {
759 use zune_jpeg::zune_core::colorspace::ColorSpace;
760 use zune_jpeg::zune_core::options::DecoderOptions;
761 let opts = DecoderOptions::default().jpeg_set_out_colorspace(ColorSpace::YCCK);
762 let mut decoder = JpegDecoder::new_with_options(std::io::Cursor::new(data), opts);
763
764 decoder
766 .decode_headers()
767 .map_err(|e| Error::StreamDecode(format!("DCTDecode header parse failed: {e}")))?;
768 if let Some(info) = decoder.info() {
769 validate_jpeg_dimensions(
770 info.width,
771 info.height,
772 info.components,
773 max_image_pixels,
774 budget,
775 )?;
776 }
777
778 match decoder.decode() {
779 Ok(ycck) if decoder.output_colorspace() == Some(ColorSpace::YCCK) => {
780 return Ok(ycck_to_rgb(&ycck));
781 }
782 _ => {}
785 }
786 }
787
788 let mut decoder = JpegDecoder::new(std::io::Cursor::new(data));
789
790 decoder
792 .decode_headers()
793 .map_err(|e| Error::StreamDecode(format!("DCTDecode header parse failed: {e}")))?;
794 if let Some(info) = decoder.info() {
795 validate_jpeg_dimensions(
796 info.width,
797 info.height,
798 info.components,
799 max_image_pixels,
800 budget,
801 )?;
802 }
803
804 decoder
805 .decode()
806 .map_err(|e| Error::StreamDecode(format!("DCTDecode: {e}")))
807}
808
809fn ycck_to_rgb(ycck: &[u8]) -> Vec<u8> {
820 let mut out = Vec::with_capacity(ycck.len() / 4 * 3);
821 for px in ycck.chunks_exact(4) {
822 let (y, cb, cr) = (px[0] as f64, px[1] as f64, px[2] as f64);
823 let rp = (y + 1.402 * (cr - 128.0)).clamp(0.0, 255.0);
825 let gp = (y - 0.344_136 * (cb - 128.0) - 0.714_136 * (cr - 128.0)).clamp(0.0, 255.0);
826 let bp = (y + 1.772 * (cb - 128.0)).clamp(0.0, 255.0);
827 let (r, g, b) = zpdf_color::cmyk_to_rgb(
828 1.0 - rp / 255.0,
829 1.0 - gp / 255.0,
830 1.0 - bp / 255.0,
831 px[3] as f64 / 255.0,
832 );
833 out.push((r * 255.0).round() as u8);
834 out.push((g * 255.0).round() as u8);
835 out.push((b * 255.0).round() as u8);
836 }
837 out
838}
839
840fn jpeg_is_adobe_ycck(data: &[u8]) -> bool {
843 let mut adobe_ycck = false;
844 let mut four_components = false;
845 let mut i = 2; while i + 3 < data.len() {
847 if data[i] != 0xFF {
848 i += 1;
849 continue;
850 }
851 let marker = data[i + 1];
852 if marker == 0xFF || marker == 0x01 || (0xD0..=0xD9).contains(&marker) {
854 i += 2;
855 continue;
856 }
857 let seg_len = ((data[i + 2] as usize) << 8) | data[i + 3] as usize;
858 if seg_len < 2 {
859 break;
860 }
861 let payload_start = i + 4;
862 let payload_end = i + 2 + seg_len;
863 if payload_end > data.len() {
864 break;
865 }
866 let payload = &data[payload_start..payload_end];
867 match marker {
868 0xEE => {
870 if payload.len() >= 12 && &payload[0..5] == b"Adobe" {
871 adobe_ycck = payload[11] == 2;
872 }
873 }
874 0xC0..=0xCF if marker != 0xC4 && marker != 0xC8 && marker != 0xCC => {
876 if payload.len() >= 6 {
878 four_components = payload[5] == 4;
879 }
880 }
881 0xDA => break,
883 _ => {}
884 }
885 i = payload_end;
886 }
887 adobe_ycck && four_components
888}
889
890fn decode_run_length(data: &[u8], budget: &mut DecodeBudget) -> Result<Vec<u8>> {
891 let mut output = Vec::new();
892 let mut i = 0;
893
894 while i < data.len() {
895 let length_byte = data[i];
896 i += 1;
897
898 if length_byte == 128 {
899 break; } else if length_byte < 128 {
901 let count = length_byte as usize + 1;
903 if i + count > data.len() {
904 return Err(Error::StreamDecode("RunLengthDecode: truncated".into()));
905 }
906 budget.reserve(count as u64)?;
908 output.extend_from_slice(&data[i..i + count]);
909 i += count;
910 } else {
911 let count = 257 - length_byte as usize;
913 if i >= data.len() {
914 return Err(Error::StreamDecode("RunLengthDecode: truncated".into()));
915 }
916 budget.reserve(count as u64)?;
918 let byte = data[i];
919 i += 1;
920 output.resize(output.len() + count, byte);
921 }
922 }
923
924 Ok(output)
925}
926
927#[cfg(test)]
928mod tests {
929 use super::*;
930
931 #[test]
932 fn ycck_white_decodes_white_not_black() {
933 let rgb = ycck_to_rgb(&[255, 128, 128, 0]);
935 assert_eq!(rgb, vec![255, 255, 255], "Adobe YCCK white must stay white");
936 }
937
938 #[test]
939 fn ycck_full_black_ink_decodes_near_black() {
940 let rgb = ycck_to_rgb(&[255, 128, 128, 255]);
943 assert_eq!(rgb, vec![44, 46, 53]);
944 }
945
946 #[test]
947 fn ycck_neutral_gray_via_polynomial() {
948 let rgb = ycck_to_rgb(&[255, 128, 128, 128]);
951 assert_eq!(rgb, vec![154, 156, 159]);
952 }
953
954 #[test]
955 fn ycck_colored_pixel_via_polynomial() {
956 let rgb = ycck_to_rgb(&[200, 100, 150, 50]);
958 assert_eq!(rgb, vec![198, 165, 131]);
959 }
960
961 #[test]
962 fn adobe_ycck_detection() {
963 let mut j = vec![0xFF, 0xD8];
965 j.extend_from_slice(&[0xFF, 0xEE, 0x00, 0x0E]);
967 j.extend_from_slice(b"Adobe");
968 j.extend_from_slice(&[0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x02]); j.extend_from_slice(&[0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x04]);
971 j.extend_from_slice(&[1, 0x11, 0, 2, 0x11, 0, 3, 0x11, 0, 4, 0x11, 0]);
972 j.extend_from_slice(&[0xFF, 0xDA, 0x00, 0x02]); assert!(jpeg_is_adobe_ycck(&j));
974
975 let mut j0 = j.clone();
977 j0[17] = 0;
979 assert!(!jpeg_is_adobe_ycck(&j0));
980 }
981
982 #[test]
983 fn flate_roundtrip() {
984 use flate2::write::ZlibEncoder;
985 use flate2::Compression;
986 use std::io::Write;
987
988 let original = b"Hello, zpdf! This is a test of FlateDecode.";
989 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
990 encoder.write_all(original).unwrap();
991 let compressed = encoder.finish().unwrap();
992
993 let decoded = {
994 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
995 decode_flate(&compressed, &mut budget).unwrap()
996 };
997 assert_eq!(decoded, original);
998 }
999
1000 #[test]
1001 fn flate_partial_salvage_on_truncation() {
1002 use flate2::write::ZlibEncoder;
1003 use flate2::Compression;
1004 use std::io::Write;
1005
1006 let mut state = 0x2545F491u64;
1009 let original: Vec<u8> = (0..64 * 1024)
1010 .map(|_| {
1011 state = state
1012 .wrapping_mul(6364136223846793005)
1013 .wrapping_add(1442695040888963407);
1014 (state >> 33) as u8
1015 })
1016 .collect();
1017 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1018 encoder.write_all(&original).unwrap();
1019 let compressed = encoder.finish().unwrap();
1020
1021 let truncated = &compressed[..compressed.len() / 2];
1022 let decoded = {
1023 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1024 decode_flate(truncated, &mut budget).unwrap()
1025 };
1026 assert!(!decoded.is_empty(), "partial output must be salvaged");
1027 assert!(decoded.len() < original.len());
1028 assert_eq!(
1029 &original[..decoded.len()],
1030 &decoded[..],
1031 "salvaged bytes are a prefix"
1032 );
1033 }
1034
1035 #[test]
1036 fn flate_raw_deflate_fallback() {
1037 use flate2::write::DeflateEncoder;
1038 use flate2::Compression;
1039 use std::io::Write;
1040
1041 let original = b"raw deflate stream without a zlib wrapper".to_vec();
1042 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
1043 encoder.write_all(&original).unwrap();
1044 let compressed = encoder.finish().unwrap();
1045
1046 let decoded = {
1047 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1048 decode_flate(&compressed, &mut budget).unwrap()
1049 };
1050 assert_eq!(decoded, original);
1051 }
1052
1053 #[test]
1054 fn flate_skips_leading_garbage() {
1055 use flate2::write::ZlibEncoder;
1056 use flate2::Compression;
1057 use std::io::Write;
1058
1059 let original = b"zlib data behind a garbage prefix".to_vec();
1060 let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
1061 encoder.write_all(&original).unwrap();
1062 let compressed = encoder.finish().unwrap();
1063
1064 let mut data = b"\r\n\xff".to_vec();
1066 data.extend_from_slice(&compressed);
1067 let decoded = {
1068 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1069 decode_flate(&data, &mut budget).unwrap()
1070 };
1071 assert_eq!(decoded, original);
1072 }
1073
1074 #[test]
1075 fn flate_empty_input_is_empty_output() {
1076 assert_eq!(
1077 {
1078 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1079 decode_flate(&[], &mut budget).unwrap()
1080 },
1081 Vec::<u8>::new()
1082 );
1083 }
1084
1085 #[test]
1086 fn flate_garbage_still_errors() {
1087 assert!({
1089 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1090 decode_flate(b"this is not compressed data at all....", &mut budget).is_err()
1091 });
1092 }
1093
1094 #[test]
1095 fn ascii_hex() {
1096 let decoded = {
1097 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1098 decode_ascii_hex(b"48 65 6C 6C 6F>", &mut budget).unwrap()
1099 };
1100 assert_eq!(decoded, b"Hello");
1101 }
1102
1103 #[test]
1104 fn ascii_hex_tolerates_stray_bytes_and_data_after_eod() {
1105 let decoded = {
1107 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1108 decode_ascii_hex(b"48 65 x6C!6C 6F> trailing garbage \xff", &mut budget).unwrap()
1109 };
1110 assert_eq!(decoded, b"Hello");
1111 }
1112
1113 #[test]
1114 fn ascii85_basic() {
1115 let decoded = {
1117 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1118 decode_ascii85(b"9jqo^~>", &mut budget).unwrap()
1119 };
1120 assert_eq!(decoded, b"Man ");
1121 }
1122
1123 #[test]
1124 fn ascii85_ignores_bytes_after_eod() {
1125 let decoded = {
1126 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1127 decode_ascii85(b"9jqo^~> stray bytes \xff\xfe after EOD", &mut budget).unwrap()
1128 };
1129 assert_eq!(decoded, b"Man ");
1130 }
1131
1132 #[test]
1133 fn ascii85_skips_stray_bytes_and_whitespace() {
1134 let decoded = {
1137 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1138 decode_ascii85(b"9j\x00qo\xff ^~>", &mut budget).unwrap()
1139 };
1140 assert_eq!(decoded, b"Man ");
1141 }
1142
1143 #[test]
1144 fn ascii85_overflowing_group_does_not_panic() {
1145 assert!({
1148 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1149 decode_ascii85(b"uuuuu~>", &mut budget).is_ok()
1150 });
1151 }
1152
1153 #[test]
1154 fn run_length_literal_and_repeat() {
1155 let data = [1, 0x41, 0x42, 254, 0x43, 128];
1157 let decoded = {
1158 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1159 decode_run_length(&data, &mut budget).unwrap()
1160 };
1161 assert_eq!(decoded, vec![0x41, 0x42, 0x43, 0x43, 0x43]);
1162 }
1163
1164 #[test]
1165 fn png_predictor_none() {
1166 let data = [0, 0x41, 0x42];
1169 let result = decode_png_predictor(&data, 1, 8, 2).unwrap();
1170 assert_eq!(result, vec![0x41, 0x42]);
1171 }
1172
1173 #[test]
1174 fn png_predictor_sub() {
1175 let data = [1, 10, 5, 3];
1178 let result = decode_png_predictor(&data, 1, 8, 3).unwrap();
1179 assert_eq!(result, vec![10, 15, 18]);
1180 }
1181
1182 #[test]
1183 fn png_predictor_up() {
1184 let data = [0, 10, 20, 2, 5, 3];
1188 let result = decode_png_predictor(&data, 1, 8, 2).unwrap();
1189 assert_eq!(result, vec![10, 20, 15, 23]);
1190 }
1191
1192 #[test]
1193 fn png_predictor_paeth() {
1194 let data = [0, 10, 20, 30, 4, 5, 7, 3];
1201 let result = decode_png_predictor(&data, 1, 8, 3).unwrap();
1202 assert_eq!(result, vec![10, 20, 30, 15, 27, 33]);
1203 }
1204
1205 #[test]
1206 fn tiff_predictor_basic() {
1207 let data = [100, 150, 200, 10, 20, 30];
1210 let result = decode_tiff_predictor(&data, 3, 8, 2).unwrap();
1211 assert_eq!(result, vec![100, 150, 200, 110, 170, 230]);
1212 }
1213
1214 #[test]
1217 fn lzw_canonical_vector() {
1218 let data = [0x80, 0x0B, 0x60, 0x50, 0x22, 0x0C, 0x0C, 0x85, 0x01];
1222 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1223 let decoded = lzw_decode(&data, 1, &mut budget).unwrap();
1224 assert_eq!(decoded, b"-----A---B");
1225 }
1226
1227 #[test]
1228 fn lzw_via_apply_filter_default_early_change() {
1229 let data = [0x80, 0x0B, 0x60, 0x50, 0x22, 0x0C, 0x0C, 0x85, 0x01];
1230 let name = PdfName::new("LZWDecode");
1231 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1232 let out = apply_filter(&name, &data, None, &ParseLimits::default(), &mut budget).unwrap();
1233 assert_eq!(out, b"-----A---B");
1234 }
1235
1236 #[test]
1237 fn lzw_stops_at_end_without_eod() {
1238 let data = [0x80, 0x0B, 0x60, 0x50];
1240 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1241 let out = lzw_decode(&data, 1, &mut budget).unwrap();
1242 assert!(out.starts_with(b"-"));
1243 }
1244
1245 #[test]
1246 fn lzw_empty_input() {
1247 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1248 assert_eq!(lzw_decode(&[], 1, &mut budget).unwrap(), Vec::<u8>::new());
1249 }
1250
1251 fn weezl_encode(data: &[u8], early_change: i64) -> Vec<u8> {
1257 use weezl::{encode::Encoder, BitOrder};
1258 let mut enc = if early_change == 0 {
1259 Encoder::new(BitOrder::Msb, 8)
1260 } else {
1261 Encoder::with_tiff_size_switch(BitOrder::Msb, 8)
1262 };
1263 enc.encode(data).expect("weezl encode")
1264 }
1265
1266 #[test]
1267 fn lzw_roundtrip_against_weezl() {
1268 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1271 for ec in [1i64, 0] {
1272 for &len in &[0usize, 1, 300, 600, 1200, 3000, 5000, 9000] {
1273 let input: Vec<u8> = (0..len).map(|i| ((i * 7 + i / 11) % 251) as u8).collect();
1275 let encoded = weezl_encode(&input, ec);
1276 let decoded = lzw_decode(&encoded, ec, &mut budget).unwrap();
1277 assert_eq!(decoded, input, "ec={ec} len={len}");
1278 }
1279 }
1280 }
1281
1282 #[test]
1283 fn lzw_single_byte_run_roundtrip_against_weezl() {
1284 let input = vec![b'A'; 5000];
1286 let mut budget = DecodeBudget::new(ParseLimits::default().max_decoded_stream_bytes);
1287 for ec in [1i64, 0] {
1288 let encoded = weezl_encode(&input, ec);
1289 assert_eq!(
1290 lzw_decode(&encoded, ec, &mut budget).unwrap(),
1291 input,
1292 "ec={ec}"
1293 );
1294 }
1295 }
1296
1297 #[test]
1298 fn decoded_limit_applies_without_filters() {
1299 let limits = ParseLimits {
1300 max_decoded_stream_bytes: 3,
1301 ..ParseLimits::default()
1302 };
1303 let err = decode_stream_with_limits(b"four", &PdfDict::new(), &limits).unwrap_err();
1304 assert!(matches!(err, Error::StreamSizeLimit(3)));
1305 }
1306
1307 #[test]
1308 fn jpeg_dimensions_honor_custom_pixel_limit() {
1309 let mut budget = DecodeBudget::new(1024);
1310 let err = validate_jpeg_dimensions(2, 2, 3, 3, &mut budget).unwrap_err();
1311 assert!(err.to_string().contains("3-pixel limit"));
1312
1313 let mut budget = DecodeBudget::new(1024);
1314 validate_jpeg_dimensions(2, 2, 3, 4, &mut budget).unwrap();
1315 }
1316
1317 #[test]
1318 fn jpx_passthrough_consumes_decode_budget() {
1319 let mut dict = PdfDict::new();
1320 dict.insert(
1321 PdfName::new("Filter"),
1322 PdfObject::Name(PdfName::new("JPXDecode")),
1323 );
1324 let limits = ParseLimits {
1325 max_decoded_stream_bytes: 3,
1326 ..ParseLimits::default()
1327 };
1328 assert!(decode_stream_with_limits(b"four", &dict, &limits).is_err());
1329 }
1330}