#[macro_use] extern crate log; use http::status::StatusCode; // use mustache::{compile_path, Template}; // use orizentic::{Permissions, ResourceName, Secret}; use build_html::Html; use bytes::Buf; use futures_util::StreamExt; use std::{ collections::HashMap, io::Read, net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, sync::{Arc, RwLock}, }; use warp::{filters::multipart::Part, Filter}; // mod cookies; mod html; // mod middleware; mod pages; mod store; pub use store::{FileHandle, FileId, FileInfo, ReadFileError, Store}; /* fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { let Permissions(perms) = permissions; ResourceName(String::from( "https://savanni.luminescent-dreams.com/file-service/", )) == *resource && perms.contains(&String::from("admin")) } pub fn compare_etags(info: FileInfo, etag_list: &headers::IfNoneMatch) -> bool { let current_etag = headers::EntityTag::new(false, info.hash); match etag_list { headers::IfNoneMatch::Any => false, headers::IfNoneMatch::Items(lst) => lst.iter().any(|etag| etag.weak_eq(¤t_etag)), } } mod files { use super::*; pub struct GetHandler { pub app: Arc>, } impl Handler for GetHandler { fn handle(&self, req: &mut Request) -> IronResult { let app = self.app.read().unwrap(); let capture = req.extensions.get::().unwrap().clone(); let old_etags = req.headers.get::(); match capture.find("id") { Some(id) => { let info = app.get_metadata(String::from(id)); match (info, old_etags) { (Ok(info_), Some(if_none_match)) => { if compare_etags(info_, if_none_match) { return Ok(Response::with(status::NotModified)); } } _ => (), } match app.get_file(String::from(id)) { Ok((info, stream)) => Ok(Response::with(( status::Ok, Header(headers::ContentType( info.file_type.parse::().unwrap(), )), Header(headers::ETag(headers::EntityTag::new(false, info.hash))), BodyReader(stream), ))), Err(_err) => Ok(Response::with(status::NotFound)), } } _ => Ok(Response::with(status::BadRequest)), } } } pub struct GetThumbnailHandler { pub app: Arc>, } impl Handler for GetThumbnailHandler { fn handle(&self, req: &mut Request) -> IronResult { let app = self.app.read().unwrap(); let capture = req.extensions.get::().unwrap().clone(); let old_etags = req.headers.get::(); match capture.find("id") { Some(id) => { let info = app.get_metadata(String::from(id)); match (info, old_etags) { (Ok(info_), Some(if_none_match)) => { if compare_etags(info_, if_none_match) { return Ok(Response::with(status::NotModified)); } } _ => (), } match app.get_thumbnail(id) { Ok((info, stream)) => Ok(Response::with(( status::Ok, Header(headers::ContentType( info.file_type.parse::().unwrap(), )), Header(headers::ETag(headers::EntityTag::new(false, info.hash))), BodyReader(stream), ))), Err(_err) => Ok(Response::with(status::NotFound)), } } _ => Ok(Response::with(status::BadRequest)), } } } pub struct PostHandler { pub app: Arc>, } impl Handler for PostHandler { fn handle(&self, req: &mut Request) -> IronResult { let mut app = self.app.write().unwrap(); let m_token = req.extensions.get::(); match m_token { Some(token) => { if token.check_authorizations(is_admin) { let params = req.get_ref::().unwrap(); if let Value::File(f_info) = params.get("file").unwrap() { match app.add_file( &f_info.path, &f_info.filename.clone().map(|fname| PathBuf::from(fname)), ) { Ok(_) => Ok(Response::with(( status::MovedPermanently, Redirect(router::url_for(req, "index", HashMap::new())), ))), Err(_) => Ok(Response::with(status::InternalServerError)), } } else { Ok(Response::with(status::BadRequest)) } } else { Ok(Response::with(status::Forbidden)) } } None => Ok(Response::with(status::Forbidden)), } } } pub struct DeleteHandler { pub app: Arc>, } impl Handler for DeleteHandler { fn handle(&self, req: &mut Request) -> IronResult { let mut app = self.app.write().unwrap(); let capture = req.extensions.get::().unwrap().clone(); let m_token = req.extensions.get::(); match m_token { Some(token) => { if token.check_authorizations(is_admin) { match capture.find("id") { Some(id) => match app.delete_file(String::from(id)) { Ok(()) => Ok(Response::with(( status::MovedPermanently, Redirect(router::url_for(req, "index", HashMap::new())), ))), Err(_) => Ok(Response::with(status::InternalServerError)), }, None => Ok(Response::with(status::BadRequest)), } } else { Ok(Response::with(status::Forbidden)) } } None => Ok(Response::with(status::Forbidden)), } } } } fn css(_: &mut Request) -> IronResult { let mut css: String = String::from(""); File::open("templates/style.css") .unwrap() .read_to_string(&mut css) .unwrap(); Ok(Response::with(( status::Ok, Header(headers::ContentType(iron::mime::Mime( iron::mime::TopLevel::Text, iron::mime::SubLevel::Css, vec![], ))), css, ))) } fn script(_: &mut Request) -> IronResult { let mut js: String = String::from(""); File::open("templates/script.js") .unwrap() .read_to_string(&mut js) .unwrap(); Ok(Response::with(( status::Ok, Header(headers::ContentType(iron::mime::Mime( iron::mime::TopLevel::Text, iron::mime::SubLevel::Javascript, vec![], ))), js, ))) } */ fn serve_file( info: FileInfo, file: F, old_etags: Option, ) -> http::Result>> where F: FnOnce() -> Result, ReadFileError>, { match old_etags { Some(old_etags) if old_etags != info.hash => warp::http::Response::builder() .header("content-type", info.file_type) .status(StatusCode::NOT_MODIFIED) .body(vec![]), _ => match file() { Ok(content) => warp::http::Response::builder() .header("content-type", info.file_type) .header("etag", info.hash) .status(StatusCode::OK) .body(content), Err(_) => warp::http::Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) .body(vec![]), }, } } async fn collect_content( mut part: Part, ) -> Result<(Option, Option, Vec), String> { let mut content: Vec = Vec::new(); while let Some(Ok(data)) = part.data().await { let mut reader = data.reader(); reader.read_to_end(&mut content).unwrap(); } Ok(( part.content_type().map(|s| s.to_owned()), part.filename().map(|s| s.to_owned()), content, )) } async fn collect_multipart( mut stream: warp::filters::multipart::FormData, ) -> Result, Option, Vec)>, warp::Error> { let mut content: Vec<(Option, Option, Vec)> = Vec::new(); while let Some(part) = stream.next().await { match part { Ok(part) => content.push(collect_content(part).await.unwrap()), Err(err) => return Err(err), } } Ok(content) } async fn handle_upload( form: warp::filters::multipart::FormData, app: Arc>, ) -> warp::http::Result> { let files = collect_multipart(form).await; match files { Ok(files) => { for (_, filename, content) in files { match filename { Some(filename) => { app.write().unwrap().add_file(filename, content).unwrap(); } None => { return warp::http::Response::builder() .status(StatusCode::BAD_REQUEST) .body("".to_owned()) } } } } Err(_err) => { return warp::http::Response::builder() .status(StatusCode::BAD_REQUEST) .body("".to_owned()) } } // println!("file length: {:?}", files.map(|f| f.len())); warp::http::Response::builder() .header("location", "/") .status(StatusCode::SEE_OTHER) .body("".to_owned()) } #[tokio::main] pub async fn main() { /* let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap(); let secret = Secret(Vec::from( std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(), )); let auth_middleware = Authentication::new(secret, auth_db_path); let mut router = Router::new(); router.post("/", files::PostHandler { app: app.clone() }, "upload-file"); router.delete( "/:id", files::DeleteHandler { app: app.clone() }, "delete-file", ); router.get("/css", css, "styles"); router.get("/script", script, "script"); let mut chain = Chain::new(router); chain.link_before(auth_middleware); chain.link_before(RestForm {}); Iron::new(chain).http("0.0.0.0:3000").unwrap(); */ /* let root = warp::path!().and(warp::get()).map({ || { warp::http::Response::builder() .header("content-type", "text/html") .status(StatusCode::NOT_MODIFIED) .body(()) } }); let server = warp::serve(root); server .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002)) .await; */ pretty_env_logger::init(); let app = Arc::new(RwLock::new(Store::new(PathBuf::from( &std::env::var("FILE_SHARE_DIR").unwrap(), )))); let app_filter = { let app = app.clone(); warp::any().map(move || app.clone()) }; let log = warp::log("file_service"); let root = warp::path!().and(warp::get()).and(app_filter.clone()).map({ move |app: Arc>| { info!("root handler"); let app = app.read().unwrap(); match app.list_files() { Ok(ids) => { let files = ids .into_iter() .map(|id| app.get_file(&id)) .collect::>>(); warp::http::Response::builder() .header("content-type", "text/html") .status(StatusCode::OK) .body(pages::index(files).to_html_string()) } Err(_) => warp::http::Response::builder() .header("content-type", "text/html") .status(StatusCode::INTERNAL_SERVER_ERROR) .body("".to_owned()), } } }); let post_upload_handler = warp::path!("upload") .and(warp::post()) .and(warp::filters::multipart::form().max_length(1024 * 1024 * 32)) .and(app_filter.clone()) .then( |form: warp::filters::multipart::FormData, app: Arc>| { handle_upload(form, app) }, ); /* let post_delete_handler = warp::path!(String) .and(warp::post()) .and(warp::filters::body::form()) .map(|id: String, form: HashMap| { info!("post_delete {}", id); info!("form: {:?}", form); warp::http::Response::builder() .header("location", "/") .status(StatusCode::SEE_OTHER) .body(vec![]) }); */ let thumbnail = warp::path!(String / "tn") .and(warp::get()) .and(warp::header::optional::("if-none-match")) .map({ let app = app.clone(); move |id: String, old_etags: Option| match app .read() .unwrap() .get_file(&FileId::from(id)) { Ok(file) => serve_file(file.info.clone(), || file.thumbnail(), old_etags), Err(_err) => warp::http::Response::builder() .status(StatusCode::NOT_FOUND) .body(vec![]), } }); let file = warp::path!(String) .and(warp::get()) .and(warp::header::optional::("if-none-match")) .map({ let app = app.clone(); move |id: String, old_etags: Option| match app .read() .unwrap() .get_file(&FileId::from(id)) { Ok(file) => serve_file(file.info.clone(), || file.content(), old_etags), Err(_err) => warp::http::Response::builder() .status(StatusCode::NOT_FOUND) .body(vec![]), } }); let server = warp::serve( root.or(post_upload_handler) .or(thumbnail) .or(file) .with(log), ); server .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002)) .await; }