commit 15b4bca53a0c8eefad73477257812388460f377d
parent 500eb4a8cfe8d1884cf129629a08da92f810288c
Author: lash <dev@holbrook.no>
Date: Sat, 17 Sep 2022 11:37:25 +0000
Add set filename support
Diffstat:
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{}
+ }
+}