Merge pull request #6003 from mockydevs/fix/eps-huge-image-skips-data

EPS import: an image too large to read skips its data instead of failing the file
This commit is contained in:
BFlat
2026-11-07 09:35:49 +08:00
committed by GitHub
2 changed files with 62 additions and 0 deletions
+30
View File
@@ -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<Vec<u8>> {
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)?;
+32
View File
@@ -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}");
}
}