This commit is contained in:
jude committed 2026-10-14 20:16:39 +01:00
1 parent c3bb015d2f
commit e1e49904a8
8 files changed
+32 -68

No files matched your search

+10 -10
View File
@@ -84,6 +84,7 @@ impl ImageUploadService {
{
return Err("S3_PUBLIC_URL must be an HTTP(S) base URL of at most 400 bytes".into());
}
let endpoint = env::var("S3_ENDPOINT").ok().filter(|value| !value.trim().is_empty());
let region = match &endpoint {
Some(endpoint) => {
@@ -91,11 +92,13 @@ impl ImageUploadService {
}
None => values[1].parse()?,
};
let credentials = Credentials::new(Some(&values[2]), Some(&values[3]), None, None, None)?;
let mut bucket = Bucket::new(&values[0], region, credentials)?;
if endpoint.is_some() {
bucket = bucket.with_path_style();
}
bucket.set_request_timeout(Some(std::time::Duration::from_secs(30)));
let model = if check_nsfw {
Some(nsfw::create_model(MODEL_BYTES)?)
@@ -156,17 +159,21 @@ fn decode_image(bytes: &[u8]) -> Result<image::RgbaImage, ImageError> {
) {
return Err(ImageError::InvalidImage);
}
let mut limits = Limits::default();
limits.max_image_width = Some(8192);
limits.max_image_height = Some(8192);
limits.max_alloc = Some(128 * 1024 * 1024);
reader.limits(limits);
let decoder = reader.into_decoder().map_err(|_| ImageError::InvalidImage)?;
use image::ImageDecoder;
let (width, height) = decoder.dimensions();
if width == 0 || height == 0 || u64::from(width) * u64::from(height) > MAX_IMAGE_PIXELS {
return Err(ImageError::InvalidImage);
}
image::DynamicImage::from_decoder(decoder)
.map(|image| image.to_rgba8())
.map_err(|_| ImageError::InvalidImage)
@@ -176,6 +183,7 @@ pub fn prepare_image(model: Option<&nsfw::Model>, bytes: &[u8]) -> Result<Vec<u8
let image = decode_image(bytes)?;
// Flatten transparency so the classifier sees exactly the published image.
let mut visible = image::RgbImage::new(image.width(), image.height());
for (target, source) in visible.pixels_mut().zip(image.pixels()) {
for channel in 0..3 {
let alpha = u32::from(source[3]);
@@ -183,6 +191,7 @@ pub fn prepare_image(model: Option<&nsfw::Model>, bytes: &[u8]) -> Result<Vec<u8
((u32::from(source[channel]) * alpha + 255 * (255 - alpha)) / 255) as u8;
}
}
let visible = image::DynamicImage::ImageRgb8(visible);
if let Some(model) = model {
let predictions = nsfw::examine(model, &visible.to_rgba8()).map_err(|error| {
@@ -193,6 +202,7 @@ pub fn prepare_image(model: Option<&nsfw::Model>, bytes: &[u8]) -> Result<Vec<u8
return Err(ImageError::Nsfw);
}
}
let mut output = Cursor::new(Vec::new());
visible.write_to(&mut output, ImageFormat::Png).map_err(|_| ImageError::InvalidImage)?;
Ok(output.into_inner())
@@ -233,16 +243,6 @@ mod tests {
assert!(ImageUploads::from_settings(&settings).0.is_none());
}
#[test]
fn nsfw_check_defaults_to_enabled_and_accepts_explicit_toggles() {
for value in [None, Some(""), Some("true"), Some("1"), Some("ON"), Some("invalid")] {
assert!(nsfw_check_enabled(value));
}
for value in [Some("false"), Some("0"), Some("OFF"), Some(" false ")] {
assert!(!nsfw_check_enabled(value));
}
}
#[test]
fn disabling_nsfw_checks_skips_model_initialization() {
let settings = ["images", "eu-west-2", "test", "test", "https://images.example.com"]