Fix 40+ bugs from audit passes 9-12

- PNG chunk parsing overflow protection with checked arithmetic
- Font directory traversal bounded with global result limit
- find_unique_path TOCTOU race fixed with create_new + marker byte
- Watch mode "processed" dir exclusion narrowed to prevent false skips
- Metadata copy now checks format support before little_exif calls
- Clipboard temp files cleaned up on app exit
- Atomic writes for file manager integration scripts
- BMP format support added to encoder and convert step
- Regex DoS protection with DFA size limit
- Watermark NaN/negative scale guard
- Selective EXIF stripping for privacy/custom metadata modes
- CLI watch mode: file stability checks, per-file history saves
- High contrast toggle preserves and restores original theme
- Image list deduplication uses O(1) HashSet lookups
- Saturation/trim/padding overflow guards in adjustments
This commit is contained in:
2026-03-07 22:14:48 +02:00
parent adef810691
commit d1cab8a691
18 changed files with 600 additions and 113 deletions

View File

@@ -531,8 +531,11 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
});
}
// Shared debounce counter for slider-driven previews
let slider_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
// Per-slider debounce counters (separate to avoid cross-slider cancellation)
let brightness_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let contrast_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let saturation_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let padding_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
// Brightness
{
@@ -540,7 +543,7 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
let row = brightness_row.clone();
let up = update_preview.clone();
let rst = brightness_reset.clone();
let did = slider_debounce.clone();
let did = brightness_debounce.clone();
brightness_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().brightness = val;
@@ -570,7 +573,7 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
let row = contrast_row.clone();
let up = update_preview.clone();
let rst = contrast_reset.clone();
let did = slider_debounce.clone();
let did = contrast_debounce.clone();
contrast_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().contrast = val;
@@ -600,7 +603,7 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
let row = saturation_row.clone();
let up = update_preview.clone();
let rst = saturation_reset.clone();
let did = slider_debounce.clone();
let did = saturation_debounce.clone();
saturation_scale.connect_value_changed(move |scale| {
let val = scale.value().round() as i32;
jc.borrow_mut().saturation = val;
@@ -670,7 +673,7 @@ pub fn build_adjustments_page(state: &AppState) -> adw::NavigationPage {
{
let jc = state.job_config.clone();
let up = update_preview.clone();
let did = slider_debounce.clone();
let did = padding_debounce.clone();
padding_row.connect_value_notify(move |row| {
jc.borrow_mut().canvas_padding = row.value() as u32;
let up = up.clone();