Apply image preview limits to detected format instead of extension

This commit is contained in:
karl committed 2026-10-13 14:10:45 +02:00
1 parent 6ed3366d03
commit 4b0b5ebfd9
2 files changed
+40 -11

No files matched your search

+35 -11
View File
@@ -342,6 +342,14 @@ fn validated_result(index: &SearchIndex, path: &str) -> Result<PathBuf, String>
Ok(canonical)
}
fn preview_pixel_limit(format: Option<image::ImageFormat>) -> u64 {
if format == Some(image::ImageFormat::Jpeg) {
64 * 1024 * 1024
} else {
16 * 1024 * 1024
}
}
#[derive(Serialize)]
struct MediaPreview {
path: String,
@@ -373,21 +381,15 @@ async fn prepare_media_preview(
if bytes > 64 * 1024 * 1024 {
return Err("Image exceeds the 64 MiB preview limit.".into());
}
let (width, height) = image::ImageReader::open(&canonical)
let reader = image::ImageReader::open(&canonical)
.map_err(|error| error.to_string())?
.with_guessed_format()
.map_err(|error| error.to_string())?
.map_err(|error| error.to_string())?;
// Resource limits follow decoded format, not a renameable suffix.
let max_pixels = preview_pixel_limit(reader.format());
let (width, height) = reader
.into_dimensions()
.map_err(|error| format!("Image preview is unavailable: {error}"))?;
let jpeg = canonical
.extension()
.and_then(|value| value.to_str())
.is_some_and(|value| matches!(value.to_ascii_lowercase().as_str(), "jpg" | "jpeg"));
let max_pixels = if jpeg {
64 * 1024 * 1024
} else {
16 * 1024 * 1024
};
if width == 0 || height == 0 || u64::from(width) * u64::from(height) > max_pixels {
return Err("Image exceeds the supported preview dimensions.".into());
}
@@ -809,4 +811,26 @@ mod tests {
assert!(validated_result(&index, &indexed.to_string_lossy()).is_err());
assert!(validated_result(&index, &root.path().to_string_lossy()).is_err());
}
#[test]
fn renamed_png_cannot_receive_the_larger_jpeg_preview_budget() {
let root = tempfile::tempdir().unwrap();
let path = root.path().join("misnamed.jpg");
let image = image::GrayImage::new(4097, 4096);
image
.save_with_format(&path, image::ImageFormat::Png)
.unwrap();
let reader = image::ImageReader::open(&path)
.unwrap()
.with_guessed_format()
.unwrap();
assert_eq!(reader.format(), Some(image::ImageFormat::Png));
let limit = preview_pixel_limit(reader.format());
let (width, height) = reader.into_dimensions().unwrap();
assert!(u64::from(width) * u64::from(height) > limit);
assert!(
u64::from(width) * u64::from(height)
< preview_pixel_limit(Some(image::ImageFormat::Jpeg))
);
}
}
+5
View File
@@ -53,6 +53,11 @@ release. Keep their evidence and unchecked production gates intact.
see [incremental semantic design](INCREMENTAL-SEMANTIC.md).
## 3. Desktop
- [x] Image preview pixel ceilings use actual decoded format; a real PNG renamed
`.jpg` cannot receive the larger JPEG allowance (Windows CPU regression).
- [ ] Replace lifetime-growing native media asset grants with bounded preview
access, including range limits, stale-source handling and selection/reload tests.
Do not use permanent forbidden patterns as revocation.
- [x] Official single-instance transport with isolated explicit profiles;
Windows CPU duplicate launch exits without a second query worker.
See [launch evidence](SINGLE-INSTANCE.md); visual focus and other OS checks remain open.