diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs index 1b78901b4..4397a0e23 100644 --- a/crates/eps/src/import/data/ops.rs +++ b/crates/eps/src/import/data/ops.rs @@ -401,6 +401,30 @@ impl Interp<'_> { Ok(()) } + /// Skip the data an image left out (too large to read) would read from the program's own + /// file, so the program doesn't go on to read that data as PostScript. The filters that find + /// their own end without decompressing (`ASCIIHexDecode`, `ASCII85Decode`, `SubFileDecode`) + /// read the file up to it, the way the image reading all its data would leave it; the + /// filters over them, which would only decode what those pass, aren't run. When the first + /// filter decompresses, it is run alone, and a failure (data too large to hold) leaves the + /// data where it was, as before. + fn skip_data(&mut self, src: &Source) { + let Source::File(f) = src else { return }; + let (chain, from) = match &*f.borrow() { + Stream::Pending { chain, from } => (chain.clone(), from.clone()), + _ => return, + }; + let delimited = chain.iter().take_while(|c| matches!(&*c.name, "ASCIIHexDecode" | "ASCII85Decode" | "SubFileDecode")).count(); + let head = chain.get(..delimited.max(1)).unwrap_or_default().to_vec(); + *f.borrow_mut() = Stream::Pending { chain: head, from: from.clone() }; + // A failure leaves the file as it was; the import goes on without the image either way. + if self.materialize(f).is_err() { + *f.borrow_mut() = Stream::Pending { chain, from }; + return; + } + *f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None }; + } + /// The bytes a data procedure gives, called until it gives an empty string or `n` bytes. fn gather(&mut self, p: &Obj, n: usize) -> Res> { Ok(self.read_sources(&[Source::Proc(p.clone())], n, n)?.pop().unwrap_or_default()) @@ -520,6 +544,9 @@ impl Interp<'_> { let n = image.space.n(); if image.w.saturating_mul(image.h) > MAX_PIXELS || stencil.w.saturating_mul(stencil.h) > MAX_PIXELS { self.out.warn(TOO_LARGE); + for src in image.sources.iter().chain(&stencil.sources) { + self.skip_data(src); + } return Ok(None); } if interleave != 3.0 { @@ -626,6 +653,9 @@ impl Interp<'_> { } if s.w.saturating_mul(s.h) > MAX_PIXELS { self.out.warn(TOO_LARGE); + for src in &s.sources { + self.skip_data(src); + } return Ok(None); } self.alloc(s.w * s.h * 4)?; diff --git a/crates/eps/src/import/tests_images.rs b/crates/eps/src/import/tests_images.rs index 160074230..8aad47fc7 100644 --- a/crates/eps/src/import/tests_images.rs +++ b/crates/eps/src/import/tests_images.rs @@ -516,3 +516,35 @@ fn tiff_predicted_rows_at_every_depth() { } assert!(wrong.is_empty(), "{wrong:#?}"); } + +/// An image too large to read is left out, and its data is skipped, as reading it would leave +/// the program: the art after it is read, not the data as PostScript. Here the data ends at its +/// `~>` (an inline image of a content stream run as a file, as PDF-reading prologs write it), at +/// a `SubFileDecode` end string, and at the end of compressed data that is read whole. +#[test] +fn the_data_of_an_image_too_large_to_read_is_skipped() { + let after = "0 0 1 setrgbcolor 10 10 80 80 rectfill"; + for program in [ + format!( + "/ID {{ << /ImageType 1 /Width 10000 /Height 10000 /BitsPerComponent 8 /Decode [0 1 0 1 0 1] /ImageMatrix [1 0 0 1 0 0] \ + /DataSource currentfile 0 (~>) /SubFileDecode filter /ASCII85Decode filter /LZWDecode filter >> \ + /DeviceRGB setcolorspace image }} def\n\ + currentfile 0 (endstream) /SubFileDecode filter cvx exec\nID\nJ1_V7fG\"r'\"b]2#:hXAd)~>\n{after}\nendstream\n" + ), + format!("10000 10000 8 [1 0 0 1 0 0] currentfile /ASCIIHexDecode filter false 3 colorimage\n0a1b 2c3d 4e5f>\n{after}"), + format!( + "10000 10000 8 [1 0 0 1 0 0] currentfile 0 (%%EndData) /SubFileDecode filter /FlateDecode filter image\n\ + x\u{9c}garbage\n%%EndData\n{after}" + ), + format!( + "10000 10000 true [1 0 0 1 0 0] currentfile /ASCII85Decode filter imagemask\n{}\n{after}", + crate::ps::ascii85(&crate::ps::deflate(&[0u8; 64])) + ), + ] { + let r = read(&program); + assert!(r.warnings.iter().any(|w| w.contains("too large to read")), "{program}: {:?}", r.warnings); + let o = super::tests::objects(&r.document); + assert_eq!(o.len(), 1, "{program}: {:?}", r.warnings); + assert_eq!(super::tests::fill(&o[0]), Some(&vectorcraft_color::Paint::solid(vectorcraft_color::Color::rgb(0.0, 0.0, 1.0))), "{program}"); + } +}