mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 12:13:19 +00:00
EPS import: an image too large to read skips its data
An image over the pixel cap was left out without reading its data, so the program went on to read that data as PostScript and the file failed to open (Ghostscript's eps2write writes a page with a large inline image this way). The data is now skipped: the filters that find their own end (ASCIIHexDecode, ASCII85Decode, SubFileDecode) read the file up to it, without decoding what they pass.
This commit is contained in:
@@ -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)?;
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user