/* use iron::headers; use iron::middleware::Handler; use iron::modifiers::{Header, Redirect}; use iron::prelude::*; use iron::response::BodyReader; use iron::status; */ #[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::{Stream, 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( file: FileHandle, old_etags: Option, ) -> http::Result>> { match old_etags { Some(old_etags) if old_etags != file.info.hash => warp::http::Response::builder() .header("content-type", file.info.file_type) .status(StatusCode::NOT_MODIFIED) .body(vec![]), _ => match file.content() { Ok(content) => warp::http::Response::builder() .header("content-type", file.info.file_type) .header("etag", file.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, 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.filename().map(|s| s.to_owned()), content)) } async fn collect_multipart( mut stream: warp::filters::multipart::FormData, ) -> Result, Vec)>, warp::Error> { let mut content: Vec<(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) } #[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 log = warp::log("file_service"); let root = warp::path!().and(warp::get()).map({ let app = app.clone(); move || { 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_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, 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, old_etags), Err(_err) => warp::http::Response::builder() .status(StatusCode::NOT_FOUND) .body(vec![]), } }); let upload = warp::path!() .and(warp::post()) .and(warp::filters::multipart::form().max_length(1024 * 1024 * 32)) .then(|form: warp::filters::multipart::FormData| async move { let files = collect_multipart(form).await; /* for (filename, content) in files { app.write() .unwrap() .add_file(Some(filename), content) .unwrap(); } */ println!("file length: {:?}", files.map(|f| f.len())); warp::reply() }); let delete = warp::path!(String).and(warp::delete()).map(|id: String| { println!("delete {}", id); warp::reply() }); /* let server = warp::serve( root.or(post_handler) .or(file) .or(thumbnail) .or(upload) .or(delete) .with(log), ); */ let server = warp::serve(root.or(upload).with(log)); server .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002)) .await; }