use aws_sdk_s3::Client; use rocket::form::{Form, FromForm, Lenient}; use rocket::fs::TempFile; use rocket::http::Status; use rocket::serde::json::Json; use rocket::State; use serde::Serialize; use std::path::Path; use uuid::Uuid; use crate::guards::AsymmetricAuth; use crate::AllowedExtensions; #[derive(Serialize)] pub struct UploadResponse { pub message: String, pub key: String, } #[derive(FromForm)] pub struct UploadForm<'r> { pub file: TempFile<'r>, } #[post("/upload", data = "
")] pub async fn upload_asset( form: Form>>, _auth: AsymmetricAuth, allowed_bucket: &State, allowed_exts: &State, s3_client: &State, ) -> Result, Status> { let mut upload = form.into_inner(); let file = &mut upload.file; let raw_filename = file .raw_name() .and_then(|n| n.as_str()) .map(|s| s.to_string()) .unwrap_or_else(|| Uuid::new_v4().to_string()); let mut ext = Path::new(&raw_filename) .extension() .and_then(|e| e.to_str()) .map(|e| e.to_lowercase()) .unwrap_or_default(); if ext.is_empty() { if let Some(ct) = file.content_type() { ext = match (ct.top().as_str(), ct.sub().as_str()) { ("image", "jpeg") => "jpg".to_string(), ("image", "png") => "png".to_string(), ("image", "gif") => "gif".to_string(), ("image", "webp") => "webp".to_string(), _ => ct.sub().as_str().to_lowercase(), }; } else { eprintln!("missing file extension and Content-Type header"); return Err(Status::BadRequest); } } if !allowed_exts.0.contains(&ext) { eprintln!( "Rejected upload '{}' with forbidden extension: '.{}'", raw_filename, ext ); return Err(Status::UnprocessableEntity); } let final_key = if Path::new(&raw_filename).extension().is_none() { format!("{}.{}", raw_filename, ext) } else { raw_filename }; let content_type_str = file .content_type() .map(|ct| ct.to_string()) .unwrap_or_else(|| format!("image/{}", if ext == "jpg" { "jpeg" } else { &ext })); let body_bytes = match file.path() { Some(path) => tokio::fs::read(path) .await .map_err(|_| Status::InternalServerError)?, None => { use tokio::io::AsyncReadExt; let mut buf = Vec::new(); file.open() .await .map_err(|_| Status::InternalServerError)? .read_to_end(&mut buf) .await .map_err(|_| Status::InternalServerError)?; buf } }; let body = aws_sdk_s3::primitives::ByteStream::from(body_bytes); s3_client .put_object() .bucket(allowed_bucket.inner()) .key(&final_key) .content_type(content_type_str) .body(body) .send() .await .map_err(|e| { eprintln!("S3 Upload Error: {:?}", e); Status::InternalServerError })?; Ok(Json(UploadResponse { message: "Upload successful".to_string(), key: final_key, })) }