diff --git a/Cargo.lock b/Cargo.lock index 4d4e483..a1a8768 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,17 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "anyhow" version = "1.0.58" @@ -69,6 +80,27 @@ dependencies = [ "safemem", ] +[[package]] +name = "bytecheck" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -90,6 +122,7 @@ dependencies = [ "blake2", "lmdb-zero", "multipart", + "rkyv", "tiny_http", "url", ] @@ -161,6 +194,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +dependencies = [ + "ahash", +] + [[package]] name = "httparse" version = "1.7.1" @@ -285,6 +327,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -297,12 +345,50 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + [[package]] name = "rand" version = "0.8.5" @@ -351,12 +437,52 @@ dependencies = [ "winapi", ] +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +dependencies = [ + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "safemem" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "subtle" version = "2.4.1" @@ -369,6 +495,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171758edb47aa306a78dfa4ab9aeb5167405bd4e3dc2b64e88f6a84bbe98bd63" +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -459,6 +596,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-normalization" version = "0.1.20" diff --git a/Cargo.toml b/Cargo.toml index c8fff7e..93fe434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ base64-url = "1.4.13" blake2 = "0.10.4" lmdb-zero = "0.4.4" multipart = { git = "https://github.com/emily-signet/multipart", default-features = false, features = ["server", "tiny_http"] } +rkyv = { version = "0.7.39", features = ["strict", "archive_le"] } tiny_http = "0.11.0" url = "2.2.2" diff --git a/src/main.rs b/src/main.rs index 493ecf2..961795d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,16 +5,20 @@ use blake2::{Blake2s256, Digest}; use lmdb::{Database, Environment, LmdbResultExt}; use lmdb_zero as lmdb; use multipart::server::Multipart; -use std::{ - io::{self, Read}, - sync::Arc, - thread, -}; -use tiny_http::{Request, Response}; +use rkyv::option::ArchivedOption; +use std::{io::Read, sync::Arc, thread}; +use tiny_http::{Header, Request, Response}; + +#[derive(rkyv::Archive, rkyv::Serialize)] +pub struct FileHeader { + file_name: Option, + content_type: Option, +} #[derive(Clone)] pub struct DatabaseContext { env: Arc, + metadata_store: Arc>, binary_store: Arc>, image_store: Arc>, } @@ -23,7 +27,7 @@ impl DatabaseContext { pub fn create(path: &str) -> anyhow::Result { let env = Arc::new(unsafe { let mut builder = lmdb::EnvBuilder::new()?; - builder.set_maxdbs(2)?; + builder.set_maxdbs(3)?; builder.open(path, lmdb::open::NOTLS, 0o600)? }); @@ -37,10 +41,16 @@ impl DatabaseContext { Some("image"), &lmdb::DatabaseOptions::new(lmdb::db::CREATE), )?); + let metadata_store = Arc::new(lmdb::Database::open( + env.clone(), + Some("metadata"), + &lmdb::DatabaseOptions::new(lmdb::db::CREATE), + )?); Ok(DatabaseContext { env, binary_store, + metadata_store, image_store, }) } @@ -123,6 +133,21 @@ fn process_upload(mut request: Request, db_context: &DatabaseContext) -> anyhow: lmdb::put::Flags::empty(), )?; + let header = FileHeader { + file_name: entry.headers.filename, + content_type: entry + .headers + .content_type + .map(|v| v.essence_str().to_owned()), + }; + + accessor.put( + &db_context.metadata_store, + data_hash.as_slice(), + rkyv::to_bytes::<_, 256>(&header)?.as_slice(), + lmdb::put::Flags::empty(), + )?; + request.respond(Response::from_string(base64_url::encode(&data_hash)))?; drop(accessor); @@ -147,13 +172,31 @@ fn process_get(request: Request, db_context: &DatabaseContext) -> anyhow::Result .to_opt() .unwrap() { - request.respond(Response::new( - 200.into(), - vec![], - data, - Some(data.len()), - None, - ))?; + let header_bytes = access + .get::<[u8], [u8]>(&db_context.metadata_store, &get_id) + .to_opt()? + .ok_or(anyhow::anyhow!("header missing for file"))?; + let header = unsafe { rkyv::archived_root::(header_bytes) }; + + let mut response = Response::new(200.into(), vec![], data, Some(data.len()), None); + + if let ArchivedOption::Some(ref file_name) = header.file_name { + response = response.with_header( + Header::from_bytes( + &b"Content-Disposition"[..], + format!("inline; filename=\"{file_name}\"").into_bytes(), + ) + .unwrap(), + ); + } + + if let ArchivedOption::Some(ref mime_type) = header.content_type { + response = response.with_header( + Header::from_bytes(&b"Content-Type"[..], mime_type.as_bytes()).unwrap(), + ); + } + + request.respond(response)?; } else { request.respond(Response::empty(404))?; }