mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-02 22:49:48 -06:00
P1.6: reject oversize export dimensions instead of overflowing
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
82ddd95167
commit
e0b279ad8e
@@ -1256,6 +1256,7 @@ impl crate::Session {
|
||||
space: dac_pipeline::OutputSpace,
|
||||
depth: dac_pipeline::OutputDepth,
|
||||
) -> Result<RenderJob, String> {
|
||||
dac_pipeline::check_output_size(max_w, max_h)?;
|
||||
let mut job = self.render_job(id, max_w, max_h, false, true).ok_or("no such photo")?;
|
||||
// An export is made from the denoised picture its Denoise amount asks for, made when the job runs if the
|
||||
// background queue has not got to it. (A source kept in memory may lack the picture, so the job reads the file.)
|
||||
|
||||
@@ -183,3 +183,13 @@ fn pan_at_one_to_one_on_45_megapixels() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An absurd export size is refused, not a multiply overflow (it panicked before the fix).
|
||||
#[test]
|
||||
fn huge_export_size_is_an_error() {
|
||||
let mut s = Session::with_demo();
|
||||
let id = s.catalog.photos().next().unwrap().id;
|
||||
let huge = usize::MAX >> 8;
|
||||
assert!(s.render_export(id, huge, huge, OutputSpace::Srgb, OutputDepth::U8).is_err());
|
||||
assert!(s.render_export(id, 70_000, 10, OutputSpace::Srgb, OutputDepth::U8).is_err());
|
||||
}
|
||||
|
||||
@@ -564,7 +564,10 @@ pub fn render(
|
||||
};
|
||||
let plan = dac_pipeline::plan(src, info, s, req);
|
||||
let (w, h) = (plan.w, plan.h);
|
||||
let n = w * h;
|
||||
let Some(n) = w.checked_mul(h).filter(|n| n.checked_mul(12).is_some()) else {
|
||||
fail(FailKind::Limit, format!("{w}×{h} output is too large"));
|
||||
return None;
|
||||
};
|
||||
let _limit = match fault {
|
||||
Some(crate::Fault::Limit(bytes)) => Some(crate::ctx::LimitOverride::new(bytes)),
|
||||
_ => None,
|
||||
|
||||
@@ -156,6 +156,21 @@ impl PixelWindow {
|
||||
}
|
||||
}
|
||||
|
||||
/// The longest output side a render may be asked for (TIFF/PNG/JPEG all fit within it).
|
||||
pub const MAX_OUTPUT_SIDE: usize = 65_535;
|
||||
/// The most output pixels a render may be asked for (the box it fits into, ≈ 1.07 Gpx).
|
||||
pub const MAX_OUTPUT_PIXELS: usize = 1 << 30;
|
||||
|
||||
/// Rejects an output box past [`MAX_OUTPUT_SIDE`] or [`MAX_OUTPUT_PIXELS`] before anything is
|
||||
/// sized from it.
|
||||
pub fn check_output_size(max_w: usize, max_h: usize) -> Result<(), String> {
|
||||
let pixels = max_w.checked_mul(max_h);
|
||||
if max_w > MAX_OUTPUT_SIDE || max_h > MAX_OUTPUT_SIDE || pixels.is_none_or(|n| n > MAX_OUTPUT_PIXELS) {
|
||||
return Err(format!("output size {max_w}×{max_h} is too large (at most {MAX_OUTPUT_SIDE} px per side and {MAX_OUTPUT_PIXELS} pixels)"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl RenderRequest {
|
||||
pub fn fit(max_w: usize, max_h: usize) -> Self {
|
||||
Self {
|
||||
|
||||
Reference in New Issue
Block a user