wala-rust

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | README | LICENSE

commit 15b4bca53a0c8eefad73477257812388460f377d
parent 500eb4a8cfe8d1884cf129629a08da92f810288c
Author: lash <dev@holbrook.no>
Date:   Sat, 17 Sep 2022 11:37:25 +0000

Add set filename support

Diffstat:
MCargo.toml | 2+-
Msrc/lib.rs | 3+++
Msrc/main.rs | 90++++++++++++++++++++-----------------------------------------------------------
Msrc/meta.rs | 136++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/record.rs | 5+++++
Msrc/request.rs | 36+++++++++++++++++++++++++++++-------
Asrc/response.rs | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 293 insertions(+), 79 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" rust-version = "1.60" license = "GPL-3.0-or-later" -documentation = "https://defalsify.org/doc/crates/wala" +documentation = "https://defalsify.org/doc/crates/wala/0.1.0/wala" description = "Content-adressed HTTP file server" repository = "https://git.defalsify.org/wala" # from https://crates.io/category_slugs diff --git a/src/lib.rs b/src/lib.rs @@ -38,6 +38,9 @@ pub mod auth; /// Encapsulates an incoming remote request. pub mod request; +/// Encapsulates an outgoing response to remote. +pub mod response; + /// Interfaces a single content record lookup. pub mod record; diff --git a/src/main.rs b/src/main.rs @@ -4,13 +4,9 @@ use tiny_http::{ Server, ServerConfig, Request, - Response, - StatusCode, Header, - HeaderField, Method, }; -use ascii::AsciiString; use mime::Mime; use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; @@ -38,6 +34,7 @@ use wala::record::{ }; use wala::request::process_method; +use wala::response::exec_response; mod arg; use arg::Settings; @@ -68,68 +65,6 @@ impl fmt::Display for NoAuthError { } } - -fn exec_response(req: Request, r: RequestResult) { - let res_status: StatusCode; - match r.typ { - RequestResultType::Found => { - res_status = StatusCode(200); - }, - RequestResultType::Changed => { - res_status = StatusCode(200); - }, - RequestResultType::WriteError => { - res_status = StatusCode(500); - }, - RequestResultType::AuthError => { - res_status = StatusCode(403); - }, - RequestResultType::InputError => { - res_status = StatusCode(400); - }, - RequestResultType::RecordError => { - res_status = StatusCode(404); - }, - _ => { - res_status = StatusCode(500); - }, - } - match r.v { - Some(v) => { - let mut res = Response::from_string(v); - res = res.with_status_code(res_status); - req.respond(res); - return; - }, - None => { - match r.f { - Some(v) => { - let mut res = Response::from_file(v); - match r.m { - Some(v) => { - let h = Header{ - field: HeaderField::from_str("Content-Type").unwrap(), - value: AsciiString::from_ascii(v.as_ref()).unwrap(), - }; - res.add_header(h); - }, - _ => {}, - } - res = res.with_status_code(res_status); - req.respond(res); - return; - }, - None => { - let res = Response::empty(res_status); - req.respond(res); - return; - }, - } - } - } -} - - fn exec_auth(auth_spec: AuthSpec, data: &File, data_length: usize) -> Option<AuthResult> { #[cfg(feature = "dev")] match mock_auth_check(&auth_spec, data, data_length) { @@ -223,6 +158,7 @@ fn process_request(req: &mut Request, f: &File) -> AuthResult { fn process_meta(req: &Request, path: &Path, digest: Vec<u8>) -> Option<Mime> { let headers = req.headers(); let mut m: Option<mime::Mime> = None; + let mut n: Option<String> = None; for h in headers { let k = &h.field; @@ -237,13 +173,31 @@ fn process_meta(req: &Request, path: &Path, digest: Vec<u8>) -> Option<Mime> { Some(v) }, }; + } else if k.equiv("X-Filename") { + let v = &h.value; + let p = Path::new(v.as_str()); + let fp = p.to_str().unwrap(); + n = Some(String::from(fp)); } } #[cfg(feature = "meta")] match m { Some(v) => { - match wala::meta::register_type(path, digest, v) { + match wala::meta::register_type(path, &digest, v) { + Err(e) => { + error!("could not register content type: {}", &e); + }, + _ => {}, + }; + }, + _ => {}, + }; + + #[cfg(feature = "meta")] + match n { + Some(v) => { + match wala::meta::register_filename(path, &digest, v) { Err(e) => { error!("could not register content type: {}", &e); }, @@ -340,3 +294,5 @@ fn main() { exec_response(req, result); } } + + diff --git a/src/meta.rs b/src/meta.rs @@ -31,11 +31,12 @@ use std::path::{ use std::io::Write; use std::str::FromStr; use mime::Mime; +use hex; use log::{debug, error}; -fn meta_path(path: &Path, digest: Vec<u8>) -> Result<PathBuf, std::io::Error> { +fn meta_path(path: &Path, digest: &Vec<u8>) -> Result<PathBuf, std::io::Error> { let digest_hex = hex::encode(digest); let fp = path.join(digest_hex); @@ -44,6 +45,7 @@ fn meta_path(path: &Path, digest: Vec<u8>) -> Result<PathBuf, std::io::Error> { v }, Err(e) => { + debug!("err {:?} {:?}", e, fp); return Err(e); } }; @@ -52,6 +54,23 @@ fn meta_path(path: &Path, digest: Vec<u8>) -> Result<PathBuf, std::io::Error> { Ok(path_canon) } +fn filename_path(path: &Path, digest: &Vec<u8>) -> Result<PathBuf, std::io::Error> { + let digest_hex = hex::encode(digest); + let fp = path.join(digest_hex); + + let mut path_canon = match fp.canonicalize() { + Ok(v) => { + v + }, + Err(e) => { + return Err(e); + } + }; + + path_canon.set_extension("filename"); + Ok(path_canon) +} + /// Set a MIME type for the specified content. /// @@ -60,7 +79,7 @@ fn meta_path(path: &Path, digest: Vec<u8>) -> Result<PathBuf, std::io::Error> { /// * `path` - Absolute path to storage diectory. /// * `digest` - Immutable reference to content. /// * `typ` - MIME type to store for the content. -pub fn register_type(path: &Path, digest: Vec<u8>, typ: Mime) -> Result<(), std::io::Error> { +pub fn register_type(path: &Path, digest: &Vec<u8>, typ: Mime) -> Result<(), std::io::Error> { match meta_path(path, digest) { Ok(v) => { match File::create(v) { @@ -77,14 +96,40 @@ pub fn register_type(path: &Path, digest: Vec<u8>, typ: Mime) -> Result<(), std: Ok(()) } + +/// Set a MIME type for the specified content. +/// +/// # Arguments +/// +/// * `path` - Absolute path to storage diectory. +/// * `digest` - Immutable reference to content. +/// * `typ` - MIME type to store for the content. +pub fn register_filename(path: &Path, digest: &Vec<u8>, name: String) -> Result<(), std::io::Error> { + match filename_path(path, digest) { + Ok(v) => { + match File::create(v) { + Ok(mut f) => { + f.write(name.as_str().as_bytes()); + debug!("wrote to {:?}", f); + } + Err(e) => { + return Err(e); + } + }; + }, + _ => {}, + }; + Ok(()) +} + /// Retrieve the MIME type for the specified content. /// /// # Arguments /// /// * `path` - Absolute path to storage diectory. /// * `digest` - Immutable reference to content. -pub fn get_type(path: &Path, digest: Vec<u8>) -> Option<Mime> { - let digest_hex = hex::encode(&digest); +pub fn get_type(path: &Path, digest: &Vec<u8>) -> Option<Mime> { + let digest_hex = hex::encode(digest); match meta_path(path, digest) { Ok(v) => { match read(v) { @@ -103,3 +148,86 @@ pub fn get_type(path: &Path, digest: Vec<u8>) -> Option<Mime> { }; None } + + +/// Retrieve the alternate filename for the specified content. +/// +/// # Arguments +/// +/// * `path` - Absolute path to storage diectory. +/// * `digest` - Immutable reference to content. +pub fn get_filename(path: &Path, digest: &Vec<u8>) -> Option<String> { + let digest_hex = hex::encode(digest); + match filename_path(path, digest) { + Ok(v) => { + match read(v) { + Ok(r) => { + let filename_str = String::from_utf8(r).unwrap(); + debug!("filename {} retrieved for {}", &filename_str, &digest_hex); + return Some(filename_str); + }, + Err(e) => { + debug!("filename file not found for {}: {}", &digest_hex, e); + }, + }; + }, + _ => {}, + }; + None +} + +#[cfg(test)] +mod tests { + use hex; + use std::str::FromStr; + use tempfile::tempdir; + use std::path::Path; + use std::fs::{ + write, + File, + }; + + use mime::Mime; + + use env_logger; + use log::{debug, info, error}; + + use super::{ + register_type, + register_filename, + get_type, + get_filename, + }; + + #[test] + fn test_meta_mime() { + let d = tempdir().unwrap(); + let dp = d.path(); + let url = "deadbeef"; + let digest = hex::decode(&url).unwrap(); + let mime_type = Mime::from_str("application/zip").unwrap(); + + let fp = dp.join(&url); + write(&fp, b"foo"); + + register_type(&dp, &digest, mime_type.clone()); + let mime_type_recovered = get_type(&dp, &digest).unwrap(); + assert_eq!(mime_type_recovered, mime_type); + } + + #[test] + fn test_meta_filename() { + let d = tempdir().unwrap(); + let dp = d.path(); + let url = "deadbeef"; + let digest = hex::decode(&url).unwrap(); + let filename = "foo.zip"; + + let fp = dp.join(&url); + write(&fp, b"foo"); + + register_filename(&dp, &digest, String::from(filename)); + let filename_recovered = get_filename(&dp, &digest).unwrap(); + assert_eq!(filename_recovered, filename); + } +} diff --git a/src/record.rs b/src/record.rs @@ -53,6 +53,8 @@ pub struct RequestResult { pub f: Option<File>, /// Contains the MIME type of the content of a GET response (if build with the `meta` feature). pub m: Option<Mime>, + /// Contains the file name to use for download request requesting a filename. + pub n: Option<String>, } impl fmt::Display for RequestResult { @@ -160,6 +162,7 @@ pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Res v: None, f: None, m: None, + n: None, }; return Err(err); }, @@ -173,6 +176,7 @@ pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Res v: None, f: None, m: None, + n: None, }; return Err(err); } @@ -189,6 +193,7 @@ pub fn put_immutable(path: &Path, mut f: impl Read, expected_size: usize) -> Res v: None, f: None, m: None, + n: None, }; return Err(err); } diff --git a/src/request.rs b/src/request.rs @@ -20,7 +20,10 @@ use crate::auth::{ use std::io::Read; #[cfg(feature = "meta")] -use crate::meta::get_type as get_meta_type; +use crate::meta::{ + get_type as get_meta_type, + get_filename as get_meta_filename, +}; use log::{ debug, @@ -46,6 +49,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: None, f: None, m: None, + n: None, }; } if auth_result.active() { @@ -61,6 +65,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(digest_hex), f: None, m: None, + n: None, }; }, Err(e) => { @@ -71,6 +76,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(String::from(err_str)), f: None, m: None, + n: None, }; }, }; @@ -86,6 +92,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(digest_hex), f: None, m: None, + n: None, }; }, Err(e) => { @@ -95,6 +102,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(String::from(err_str)), f: None, m: None, + n: None, }; }, }; @@ -110,6 +118,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(String::from(err_str)), f: None, m: None, + n: None, }; }, Ok(v) => { @@ -127,14 +136,25 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: None, //Some(String::new()), f: Some(v), m: None, + n: None, }; +// match get_meta_type(path, &digest) { +// Some(v) => { +// res.m = Some(v); +// }, +// _ => {}, +// }; +// match get_meta_filename(path, &digest) { +// Some(v) => { +// res.n = Some(v); +// }, +// _ => {}, +// }; #[cfg(feature = "meta")] - match get_meta_type(path, digest) { - Some(v) => { - res.m = Some(v); - }, - _ => {}, - }; + { + res.m = get_meta_type(path, &digest); + res.n = get_meta_filename(path, &digest); + } return res; }, None => { @@ -144,6 +164,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(String::new()), f: None, m: None, + n: None, }; }, }; @@ -155,6 +176,7 @@ pub fn process_method(method: &Method, url: String, mut f: impl Read, expected_s v: Some(String::new()), f: None, m: None, + n: None, } } diff --git a/src/response.rs b/src/response.rs @@ -0,0 +1,100 @@ +use std::str::FromStr; + +use tiny_http::{ + StatusCode, + Request, + Response, + Header, + HeaderField, +}; +use ascii::AsciiString; + +use crate::record::{ + RequestResult, + RequestResultType, +}; + + +pub fn exec_response(req: Request, r: RequestResult) { + let res_status: StatusCode; + match r.typ { + RequestResultType::Found => { + res_status = StatusCode(200); + }, + RequestResultType::Changed => { + res_status = StatusCode(200); + }, + RequestResultType::WriteError => { + res_status = StatusCode(500); + }, + RequestResultType::AuthError => { + res_status = StatusCode(403); + }, + RequestResultType::InputError => { + res_status = StatusCode(400); + }, + RequestResultType::RecordError => { + res_status = StatusCode(404); + }, + _ => { + res_status = StatusCode(500); + }, + } + match r.v { + Some(v) => { + let mut res = Response::from_string(v); + res = res.with_status_code(res_status); + req.respond(res); + return; + }, + None => { + match r.f { + Some(v) => { + let mut res = Response::from_file(v); + match r.m { + Some(v) => { + let h = Header{ + field: HeaderField::from_str("Content-Type").unwrap(), + value: AsciiString::from_ascii(v.as_ref()).unwrap(), + }; + res.add_header(h); + }, + _ => {}, + } + match r.n { + Some(v) => { + let s = format!("attachment; filename=\"{}\"", &v); + let h = Header{ + field: HeaderField::from_str("Content-Disposition").unwrap(), + value: AsciiString::from_ascii(s.as_str()).unwrap(), + }; + res.add_header(h); + }, + _ => {}, + } + + res = res.with_status_code(res_status); + req.respond(res); + return; + }, + None => { + let res = Response::empty(res_status); + req.respond(res); + return; + }, + } + } + } +} + +#[cfg(test)] +mod tests { + use tiny_http::Request; + use crate::record::RequestResult; + use super::exec_response; + + #[test] + fn test_response_get_filename() { +// let r = Request{} + } +}