diff --git a/assets/nsfw/README.md b/assets/nsfw/README.md index 34e6711..a51619f 100644 --- a/assets/nsfw/README.md +++ b/assets/nsfw/README.md @@ -5,13 +5,3 @@ [GantMan/nsfw_model](https://github.com/GantMan/nsfw_model). Source: https://github.com/fyko/nsfw/releases/download/v0.2.0/model.onnx - -Size: 17,320,596 bytes (approximately 16.5 MiB). - -SHA-256: `0dca163d933af88e03fa0f9d233655857e9cc9f38bfd2bc28ba57c486d70feaf`. - -The application embeds these bytes using `include_bytes!` and initializes the model only -when dashboard S3 uploads are configured and `NSFW_CHECK_ENABLED` is enabled (the default). -Set `NSFW_CHECK_ENABLED=false` to skip model initialization and moderation. -Keep this file in the source checkout and build -context; it does not need to be installed separately alongside the compiled binary. diff --git a/migrations/20261011120000_image_uploads.sql b/migrations/20261011120000_image_uploads.sql index b7324bb..3bebe08 100644 --- a/migrations/20261011120000_image_uploads.sql +++ b/migrations/20261011120000_image_uploads.sql @@ -4,5 +4,7 @@ CREATE TABLE image_uploads ( uploader BIGINT UNSIGNED NOT NULL, url VARCHAR(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL UNIQUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - INDEX image_uploads_uploader (uploader) + orphaned_at DATETIME DEFAULT NULL, + INDEX image_uploads_uploader (uploader), + INDEX image_uploads_orphaned_at (orphaned_at) ); diff --git a/migrations/20261012120000_image_upload_orphans.sql b/migrations/20261012120000_image_upload_orphans.sql deleted file mode 100644 index 0a3678a..0000000 --- a/migrations/20261012120000_image_upload_orphans.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE image_uploads - -- UTC time at which cleanup first observed that this upload had no references. - ADD COLUMN orphaned_at DATETIME(6) NULL DEFAULT NULL, - ADD INDEX image_uploads_orphaned_at (orphaned_at); diff --git a/src/web/image_uploads.rs b/src/web/image_uploads.rs index 327b24e..379d69b 100644 --- a/src/web/image_uploads.rs +++ b/src/web/image_uploads.rs @@ -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 { ) { 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, bytes: &[u8]) -> Result, bytes: &[u8]) -> Result