monorepo/file-service/src/main.rs

342 lines
12 KiB
Rust
Raw Normal View History

use iron::headers;
use iron::middleware::Handler;
use iron::modifiers::{Header, Redirect};
use iron::prelude::*;
use iron::response::BodyReader;
use iron::status;
use mustache::{compile_path, Template};
use orizentic::{Permissions, ResourceName, Secret};
use params::{Params, Value};
use router::Router;
use serde::Serialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
mod cookies;
mod lib;
mod middleware;
use lib::{App, FileInfo};
use middleware::{Authentication, RestForm};
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(&current_etag)),
}
}
mod files {
use super::*;
pub struct IndexHandler {
pub app: Arc<RwLock<App>>,
pub template: Template,
}
#[derive(Serialize)]
pub enum TemplateFile {
#[serde(rename = "error")]
Error { error: String },
#[serde(rename = "file")]
File {
id: String,
size: u64,
date: String,
type_: String,
},
}
#[derive(Serialize)]
pub struct IndexTemplateParams {
files: Vec<TemplateFile>,
}
impl Handler for IndexHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let app = self.app.read().unwrap();
let m_token = req.extensions.get::<Authentication>();
match m_token {
Some(token) => {
if token.check_authorizations(is_admin) {
let files: Vec<TemplateFile> = app
.list_files()
.into_iter()
.map(|entry| match entry {
Ok(file) => TemplateFile::File {
id: file.info().id,
size: file.info().size,
date: format!(
"{}",
file.info().created.format("%Y-%m-%d %H:%M:%S")
),
type_: file.info().file_type,
},
Err(err) => TemplateFile::Error {
error: format!("{}", err),
},
})
.collect();
Ok(Response::with((
status::Ok,
Header(headers::ContentType::html()),
Header(headers::SetCookie(vec![format!("auth={}", token.text)])),
self.template
.render_to_string(&IndexTemplateParams { files })
.expect("the template to render"),
)))
} else {
Ok(Response::with(status::Forbidden))
}
}
None => Ok(Response::with(status::Forbidden)),
}
}
}
pub struct GetHandler {
pub app: Arc<RwLock<App>>,
}
impl Handler for GetHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let app = self.app.read().unwrap();
let capture = req.extensions.get::<Router>().unwrap().clone();
let old_etags = req.headers.get::<headers::IfNoneMatch>();
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::<iron::mime::Mime>().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<RwLock<App>>,
}
impl Handler for GetThumbnailHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let app = self.app.read().unwrap();
let capture = req.extensions.get::<Router>().unwrap().clone();
let old_etags = req.headers.get::<headers::IfNoneMatch>();
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::<iron::mime::Mime>().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<RwLock<App>>,
}
impl Handler for PostHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let mut app = self.app.write().unwrap();
let m_token = req.extensions.get::<Authentication>();
match m_token {
Some(token) => {
if token.check_authorizations(is_admin) {
let params = req.get_ref::<Params>().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<RwLock<App>>,
}
impl Handler for DeleteHandler {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let mut app = self.app.write().unwrap();
let capture = req.extensions.get::<Router>().unwrap().clone();
let m_token = req.extensions.get::<Authentication>();
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<Response> {
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<Response> {
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 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 app = Arc::new(RwLock::new(App::new(Path::new(
&std::env::var("FILE_SHARE_DIR").unwrap(),
))));
let mut router = Router::new();
router.get(
"/",
files::IndexHandler {
app: app.clone(),
template: compile_path("templates/index.html").expect("the template to compile"),
},
"index",
);
router.get(
"/:id",
files::GetFileHandler {
app: app.clone(),
template: compile_path("templates/file.html").expect("the template to compile"),
},
"get-file-page",
);
router.get(
"/:id/raw",
files::GetHandler { app: app.clone() },
"get-file",
);
router.get(
"/:id/tn",
files::GetThumbnailHandler { app: app.clone() },
"get-thumbnail",
);
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();
}