craft-xcf: allow cancellation between layer records

* xcf: stop parsing layer metadata on cancellation
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-12 06:26:24 -05:00
1 parent 9fd3db9484
commit 7e09989ce4
2 files changed
+24

No files matched your search

+9
View File
@@ -566,6 +566,12 @@ impl<'a> Xcf<'a> {
/// Reads the header, the image properties and every layer structure of an XCF file
/// (already inflated when it was gzipped).
pub fn parse(bytes: &'a [u8]) -> Result<Xcf<'a>> {
Self::parse_with(bytes, &|| true)
}
/// Parses an XCF file like [`Self::parse`], checking `keep_going` between layer records.
/// This lets callers stop large metadata parses when their operation is cancelled.
pub fn parse_with(bytes: &'a [u8], keep_going: &dyn Fn() -> bool) -> Result<Xcf<'a>> {
if !is_xcf(bytes) {
return Err(invalid("not an XCF file"));
}
@@ -639,6 +645,9 @@ impl<'a> Xcf<'a> {
let c = c.at(0);
let mut meta_budget = MAX_METADATA_BYTES;
for ptr in layer_ptrs {
if !keep_going() {
return Err(Error::Cancelled);
}
let layer = layer(c.at(ptr), &image, &mut meta_budget)?;
image.layers.push(layer);
}
+15
View File
@@ -1003,6 +1003,21 @@ fn the_pixel_budget_is_charged_before_decoding_and_decoding_can_be_cancelled() {
assert_eq!(xcf.layer_pixels(0, &mut MAX_PIXEL_BYTES.clone(), &|| false), Err(Error::Cancelled));
}
#[test]
fn parsing_layer_metadata_can_be_cancelled_between_records() {
let mut img = TestImage::rgb8(3, 3);
img.layers.push(TestLayer::rgba("first", 3, 3, rgba(3, 3, |_, _| [1, 2, 3, 255])));
img.layers.push(TestLayer::rgba("second", 3, 3, rgba(3, 3, |_, _| [4, 5, 6, 255])));
let bytes = img.write();
let checks = Cell::new(0);
let keep_going = || {
checks.set(checks.get() + 1);
checks.get() <= 1
};
assert_eq!(Xcf::parse_with(&bytes, &keep_going).err(), Some(Error::Cancelled));
assert_eq!(checks.get(), 2, "the second layer is not parsed after cancellation");
}
#[test]
fn gzipped_files_are_recognised_by_name_and_inflated() {
use std::io::Write;