mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
M7: decode a clipboard PNG before pasting it
with_pixel_size read only the PNG's header, so a PNG with a whole IHDR and a cut-off body passed as the one to paste. Since #1724 image.place refuses it as damaged, and nothing was pasted: the decoded clipboard image, which paste falls back to for an unreadable PNG, was never tried. The PNG is now decoded in full before it is accepted, so the paste falls back as it does for any other unreadable PNG. The zero-size and pixel-count checks run ahead of that decode, so an image past the limit is refused before it is decoded. Raised by CodeRabbit on #1360 after the merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8eb0b87646
commit
bea214ebb0
2 files changed
+23
-5
No files matched your search
@@ -129,9 +129,6 @@ fn clipboard_png(app: &mut FigApp) -> Option<(Vec<u8>, f64, f64)> {
|
||||
Some(readable) => readable,
|
||||
None => with_pixel_size(encoded_clipboard_image(app)?)?,
|
||||
};
|
||||
if w == 0 || h == 0 || u64::from(w) * u64::from(h) > MAX_PASTED_IMAGE_PIXELS {
|
||||
return None;
|
||||
}
|
||||
let density = figcraft_format::pixel_density(&png);
|
||||
let (w, h) = figcraft_format::placed_size(w, h, density, figcraft_engine::cmd::create::PLACED_IMAGE_MAX_SIDE);
|
||||
Some((png, w, h))
|
||||
@@ -140,8 +137,14 @@ fn clipboard_png(app: &mut FigApp) -> Option<(Vec<u8>, f64, f64)> {
|
||||
const MAX_PASTED_IMAGE_PIXELS: u64 = 100_000_000;
|
||||
|
||||
fn with_pixel_size(png: Vec<u8>) -> Option<(Vec<u8>, (u32, u32))> {
|
||||
let size = image::ImageReader::new(std::io::Cursor::new(&png)).with_guessed_format().ok()?.into_dimensions().ok()?;
|
||||
Some((png, size))
|
||||
use image::ImageDecoder as _;
|
||||
let decoder = image::ImageReader::new(std::io::Cursor::new(&png)).with_guessed_format().ok()?.into_decoder().ok()?;
|
||||
let (w, h) = decoder.dimensions();
|
||||
if w == 0 || h == 0 || u64::from(w) * u64::from(h) > MAX_PASTED_IMAGE_PIXELS {
|
||||
return None;
|
||||
}
|
||||
image::DynamicImage::from_decoder(decoder).ok()?;
|
||||
Some((png, (w, h)))
|
||||
}
|
||||
|
||||
fn encoded_clipboard_image(app: &mut FigApp) -> Option<Vec<u8>> {
|
||||
|
||||
@@ -549,3 +549,18 @@ fn a_clipboard_png_past_the_byte_limit_falls_back_to_the_decoded_image() {
|
||||
let pasted = doc.nodes().find(|n| n.name == "Pasted image").expect("pasted from the decoded image");
|
||||
assert_eq!((pasted.width, pasted.height), (2.0, 2.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_clipboard_png_with_a_cut_off_body_falls_back_to_the_decoded_image() {
|
||||
let mut w = Win::new();
|
||||
let mut png = std::io::Cursor::new(Vec::new());
|
||||
image::RgbaImage::new(4, 4).write_to(&mut png, image::ImageFormat::Png).unwrap();
|
||||
let png = png.into_inner();
|
||||
let cut_off = png[..png.len() - 20].to_vec();
|
||||
assert_eq!(image::ImageReader::new(std::io::Cursor::new(&cut_off)).with_guessed_format().unwrap().into_dimensions().unwrap(), (4, 4));
|
||||
*w.clip.borrow_mut() = Clip { png: Some(cut_off), image: Some((2, 2, vec![255; 16])), ..Clip::default() };
|
||||
w.cmd_v();
|
||||
let doc = w.app.session.doc().unwrap();
|
||||
let pasted = doc.nodes().find(|n| n.name == "Pasted image").expect("pasted from the decoded image");
|
||||
assert_eq!((pasted.width, pasted.height), (2.0, 2.0));
|
||||
}
|
||||
Reference in new issue
Block a user