use crate::config::{AppConfig, normalize_extension}; use content_inspector::ContentType; use rocket::data::{Data, ToByteUnit}; use rocket::{State, http::Status, response::content::RawHtml}; use std::fs; use std::io::Write; use std::path::Path; use tempfile::NamedTempFile; use tokio::fs as async_fs; #[get("/")] pub fn index() -> Option> { let raw: &str = include_str!("assets/editor.html"); Some(RawHtml(raw.replace("$%{defaultText}%$", ""))) } #[get("/?")] pub async fn get_file(id: &str, raw: Option, config: &State) -> Result { let file_path = Path::new(&config.upload_dir).join(id); if file_path.exists() { if let Ok(content) = async_fs::read_to_string(file_path).await { let is_mozilla = std::env::var("HTTP_USER_AGENT") .as_deref() .unwrap_or("") .contains("Mozilla"); if raw.unwrap_or(false) || !is_mozilla { return Ok(content); } let html: &str = include_str!("assets/editor.html"); return Ok(html.replace("$%{defaultText}%$", &content)); } } Err(Status::NotFound) } #[put("/", data = "")] pub async fn upload(file_name: &str, data: Data<'_>, config: &State) -> Result { let extension = Path::new(file_name) .extension() .and_then(|ext| ext.to_str()) .map(normalize_extension) .unwrap_or_else(|| ".txt".to_string()); if !config.is_extension_allowed(&extension) { return Err(Status::UnsupportedMediaType); } let upload_dir = Path::new(&config.upload_dir); let limit = config.max_upload_size().unwrap_or(u64::MAX); let id = loop { let candidate = nanoid::format(nanoid::rngs::default, &nanoid::alphabet::SAFE[..], config.id_length); let save_name = format!("{}{}", candidate, extension); if !upload_dir.join(save_name).exists() { break candidate; } }; if !upload_dir.exists() { fs::create_dir_all(upload_dir).map_err(|_| Status::InternalServerError)?; } let mut temp = NamedTempFile::new_in(&config.upload_dir) .map_err(|_| Status::InternalServerError)?; let stream = data.open(limit.bytes()); let bytes = stream.into_bytes().await .map_err(|_| Status::InternalServerError)?; if !bytes.is_complete() { return Err(Status::PayloadTooLarge); } if config.should_block_binary() && matches!(content_inspector::inspect(bytes.as_ref()), ContentType::BINARY) { return Err(Status::UnsupportedMediaType); } temp.write_all(&bytes) .map_err(|_| Status::InternalServerError)?; temp.persist(Path::new(&config.upload_dir).join(format!("{}{}", id, extension))) .map_err(|_| Status::InternalServerError)?; Ok(format!("{}/{}{}", config.base_url.trim_end_matches('/'), id, extension)) }