From 0ecfdbb7f97abd0598c70121e2b4318909709a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sun, 10 May 2020 18:22:46 +0200 Subject: [PATCH] well-known mime, favicon --- .gitignore | 2 + Cargo.lock | 177 +++++++++- Cargo.toml | 7 + postit.db | Bin 0 -> 73874 bytes src/config.rs | 37 +- src/favicon.ico | Bin 0 -> 9728 bytes src/main.rs | 438 +++++++++++++++++------- src/well_known_mime.rs | 742 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 1267 insertions(+), 136 deletions(-) create mode 100644 postit.db create mode 100644 src/favicon.ico create mode 100644 src/well_known_mime.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..765220b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +.idea/ +postit.json diff --git a/Cargo.lock b/Cargo.lock index 7ea609c..78ea7d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ - "memchr", + "memchr 2.3.3", ] [[package]] @@ -87,6 +87,16 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "bincode" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -151,7 +161,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" dependencies = [ - "memchr", + "memchr 2.3.3", "safemem", ] @@ -323,6 +333,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "flate2" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -393,6 +427,15 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +dependencies = [ + "autocfg 1.0.0", +] + [[package]] name = "itoa" version = "0.4.5" @@ -461,6 +504,15 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +[[package]] +name = "memchr" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.3.3" @@ -488,6 +540,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" +dependencies = [ + "adler32", +] + [[package]] name = "multipart" version = "0.15.4" @@ -506,6 +567,61 @@ dependencies = [ "twoway", ] +[[package]] +name = "nom" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" +dependencies = [ + "memchr 1.0.2", +] + +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg 1.0.0", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.42" @@ -516,6 +632,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg 1.0.0", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.11" @@ -614,6 +753,16 @@ dependencies = [ "sha-1", ] +[[package]] +name = "petgraph" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c127eea4a29ec6c85d153c59dc1213f33ec74cead30fe4730aecc88cc1fd92" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "phf" version = "0.7.24" @@ -657,8 +806,14 @@ dependencies = [ name = "postit" version = "0.1.0" dependencies = [ + "anyhow", + "bincode", + "chrono", "clappconfig", + "flate2", "log 0.4.8", + "num", + "num-derive", "parking_lot", "rand 0.7.3", "rouille", @@ -666,6 +821,7 @@ dependencies = [ "serde_derive", "serde_json", "siphasher 0.3.3", + "tree_magic", ] [[package]] @@ -904,7 +1060,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" dependencies = [ "aho-corasick", - "memchr", + "memchr 2.3.3", "regex-syntax", "thread_local", ] @@ -1140,13 +1296,26 @@ dependencies = [ "url", ] +[[package]] +name = "tree_magic" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d99367ce3e553a84738f73bd626ccca541ef90ae757fdcdc4cbe728e6cb629" +dependencies = [ + "fnv", + "lazy_static", + "nom", + "parking_lot", + "petgraph", +] + [[package]] name = "twoway" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" dependencies = [ - "memchr", + "memchr 2.3.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f2a8969..13667dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,10 @@ serde_derive = "1.0" log = "0.4.8" siphasher = "0.3.3" rand = "0.7.3" +chrono = "0.4.11" +bincode = "1.2.1" +flate2 = "1.0.14" +anyhow = "1.0.28" +tree_magic = { version = "0.2.3", default_features = false, features = ["staticmime"] } +num = "0.2.1" +num-derive = "0.3.0" diff --git a/postit.db b/postit.db new file mode 100644 index 0000000000000000000000000000000000000000..156c8223c524247e3fbbf144a6a4aee3dc3c6336 GIT binary patch literal 73874 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

c)B6|zUSs-< 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 literal 0 HcmV?d00001 diff --git a/src/config.rs b/src/config.rs index 85d641b..f03d461 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,8 @@ -use clappconfig::{AppConfig, anyhow, clap::ArgMatches}; +use clappconfig::{anyhow, clap::ArgMatches, AppConfig}; use std::collections::HashMap; use std::time::Duration; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(default)] #[serde(deny_unknown_fields)] pub(crate) struct Config { @@ -32,6 +32,15 @@ pub(crate) struct Config { /// Max uploaded file size in bytes pub(crate) max_file_size: usize, + + /// Enable persistence + pub(crate) persistence: bool, + + /// Enable compression when persisting/loading + pub(crate) compression: bool, + + /// Persistence file + pub(crate) persist_file : String, } impl Default for Config { @@ -44,7 +53,10 @@ impl Default for Config { default_expiry: Duration::from_secs(60 * 10), max_expiry: Duration::from_secs(60 * 10), expired_gc_interval: Duration::from_secs(60), - max_file_size: 1 * (1024 * 1024) // 1MB + max_file_size: 1 * (1024 * 1024), // 1MB + persistence: false, + compression: true, + persist_file: "postit.db".to_string(), } } } @@ -66,24 +78,19 @@ impl AppConfig for Config { } mod serde_duration_secs { - use serde::{self, Deserialize, Serializer, Deserializer}; + use serde::{self, Deserialize, Deserializer, Serializer}; use std::time::Duration; - pub fn serialize( - value: &Duration, - se: S, - ) -> Result - where - S: Serializer, + pub fn serialize(value: &Duration, se: S) -> Result + where + S: Serializer, { se.serialize_u64(value.as_secs()) } - pub fn deserialize<'de, D>( - de: D, - ) -> Result - where - D: Deserializer<'de>, + pub fn deserialize<'de, D>(de: D) -> Result + where + D: Deserializer<'de>, { let s: u64 = u64::deserialize(de)?; Ok(Duration::from_secs(s)) diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9678c57bcc5ce4244ab6f7a47019f45fea44abe1 GIT binary patch literal 9728 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 -> Response { - Response { - status_code: code, - headers: vec![("Content-Type".into(), "text/plain; charset=utf8".into())], - data: rouille::ResponseBody::from_string(text), - upgrade: None, - } -} +const HDR_EXPIRES : &str = "X-Expires"; +const HDR_SECRET : &str = "X-Secret"; -fn empty_error(code : u16) -> Response { - Response { - status_code: code, - headers: vec![("Content-Type".into(), "text/plain; charset=utf8".into())], - data: rouille::ResponseBody::empty(), - upgrade: None, - } -} +const FAVICON : &[u8] = include_bytes!("favicon.ico"); +/// Post ID (represented as a 16-digit hex string) type PostId = u64; +/// Write token (represented as a 16-digit hex string) type Secret = u64; +/// Hash of a data record type DataHash = u64; -#[derive(Debug)] +/// Post stored in the repository +#[derive(Debug,Serialize,Deserialize)] struct Post { - mime : Cow<'static, str>, - hash : DataHash, - secret : Secret, - expires : Instant, + /// Content-Type + mime: Mime, + /// Data hash + hash: DataHash, + /// Secret key for editing or deleting + secret: Secret, + /// Expiration timestamp + #[serde(with = "serde_chrono_datetime_as_unix")] + expires: DateTime, } impl Post { + /// Check if the post expired pub fn is_expired(&self) -> bool { - self.expires < Instant::now() + self.expires < Utc::now() } } @@ -55,7 +65,15 @@ fn main() -> anyhow::Result<()> { let config = Config::init("postit", "postit.json", None)?; let serve_at = format!("{}:{}", config.host, config.port); - let store = Mutex::new(Repository::new(config)); + let store = Mutex::new({ + let mut store = Repository::new(config); + if store.config.persistence { + if let Err(e) = store.load() { + error!("Load failed: {}", e); + } + } + store + }); rouille::start_server(serve_at, move |req| { let mut store_w = store.lock(); @@ -63,52 +81,120 @@ fn main() -> anyhow::Result<()> { info!("{} {}", method, req.raw_url()); + if req.url() == "/favicon.ico" { + return Response::from_data("image/vnd.microsoft.icon", FAVICON); + } + store_w.gc_expired_posts_if_needed(); - match method { - "POST" | "PUT" => { - store_w.serve_post_put(req) - } - "GET" | "HEAD" => { - store_w.serve_get_head(req) - } - "DELETE" => { - store_w.serve_delete(req) - } - _ => { - rouille::Response::empty_400() + let resp = match method { + "POST" | "PUT" => store_w.serve_post_put(req), + "GET" | "HEAD" => store_w.serve_get_head(req), + "DELETE" => store_w.serve_delete(req), + _ => rouille::Response::empty_400(), + }; + + if store_w.config.persistence { + if let Err(e) = store_w.persist_if_needed() { + error!("Store failed: {}", e); } } + + if resp.is_error() { + warn!("Error resp: {}", resp.status_code); + } + + resp }); } type PostsMap = HashMap; type DataMap = HashMap)>; +#[derive(Debug,Serialize,Deserialize)] struct Repository { + #[serde(skip)] config: Config, + /// Flag that the repository needs saving + #[serde(skip)] + dirty: bool, + /// Stored posts posts: PostsMap, - /// (use_count, data) + /// Post data - (use_count, data) data: DataMap, /// Time of last expired posts GC - last_gc_time: Instant, + #[serde(with = "serde_chrono_datetime_as_unix")] + last_gc_time: DateTime, } impl Repository { + /// New instance fn new(config: Config) -> Self { Repository { config, + dirty: false, posts: Default::default(), data: Default::default(), - last_gc_time: Instant::now(), + last_gc_time: Utc::now(), + } + } + + fn persist_if_needed(&mut self) -> anyhow::Result<()> { + if self.dirty { + self.persist() + } else { + Ok(()) } } - fn serve_delete(&mut self, req : &Request) -> Response { + /// Store to a file + fn persist(&mut self) -> anyhow::Result<()> { + debug!("Persist to file: {}", self.config.persist_file); + + self.dirty = false; + let file = OpenOptions::new() + .truncate(true) + .write(true) + .create(true) + .open(&self.config.persist_file)?; + + if self.config.compression { + let flate = flate2::write::DeflateEncoder::new(file, flate2::Compression::best()); + bincode::serialize_into(flate, self)?; + } else { + bincode::serialize_into(file, self)?; + } + Ok(()) + } + + /// Load from a file + fn load(&mut self) -> anyhow::Result<()> { + debug!("Load from file: {}", self.config.persist_file); + + let file = OpenOptions::new() + .read(true) + .open(&self.config.persist_file)?; + + let result : Repository = if self.config.compression { + let flate = flate2::read::DeflateDecoder::new(file); + bincode::deserialize_from(flate)? + } else { + bincode::deserialize_from(file)? + }; + + let old_config = self.config.clone(); + std::mem::replace(self, result); + self.config = old_config; + self.dirty = false; + Ok(()) + } + + /// Serve a DELETE request + fn serve_delete(&mut self, req: &Request) -> Response { let post_id = match self.request_to_post_id(req, true) { Ok(Some(pid)) => pid, Ok(None) => return error_with_text(400, "Post ID required."), - Err(resp) => return resp + Err(resp) => return resp, }; self.delete_post(post_id); @@ -116,18 +202,25 @@ impl Repository { Response::text("Deleted.") } - fn serve_post_put(&mut self, req : &Request) -> Response { + /// Serve a POST or PUT request + /// + /// POST inserts a new record + /// PUT updates a record + fn serve_post_put(&mut self, req: &Request) -> Response { + // Post ID is empty for POST, set for PUT let post_id = match self.request_to_post_id(req, true) { Ok(pid) => { if req.method() == "PUT" && pid.is_none() { + warn!("PUT without ID!"); return error_with_text(400, "PUT requires a file ID!"); } else if req.method() == "POST" && pid.is_some() { + warn!("POST with ID!"); return error_with_text(400, "Use PUT to update a file!"); } pid - }, - Err(resp) => return resp + } + Err(resp) => return resp, }; debug!("Submit new data, post ID: {:?}", post_id); @@ -136,7 +229,9 @@ impl Repository { if let Some(body) = req.data() { // Read up to 1 byte past the limit to catch too large uploads. // We can't reply on the "Length" field, which is not present with chunked encoding. - body.take(self.config.max_file_size as u64 + 1).read_to_end(&mut data).unwrap(); + body.take(self.config.max_file_size as u64 + 1) + .read_to_end(&mut data) + .unwrap(); if data.len() > self.config.max_file_size { return empty_error(413); } @@ -144,38 +239,44 @@ impl Repository { return error_with_text(400, "Empty body!"); } + // Convert "application/x-www-form-urlencoded" to text/plain (CURL uses this) + // NOTE: rouille does NOT parse urlencoded, we will serve the encoded format back if really used. let mime = match req.header("Content-Type") { None => None, - Some("application/x-www-form-urlencoded") => Some("text/plain"), + Some("application/x-www-form-urlencoded") => None, Some(v) => Some(v), }; - let expiry = match req.header("X-Expires") { - Some(text) => { - match text.parse() { - Ok(v) => { - let dur = Duration::from_secs(v); - if dur > self.config.max_expiry { - return error_with_text(400, - format!("Expiration time {} out of allowed range 0-{} s", - v, - self.config.max_expiry.as_secs() - )); - } - Some(dur) - }, - Err(_) => { - return error_with_text(400, "Malformed \"X-Expires\", use relative time in seconds."); - }, + let expiry = match req.header(HDR_EXPIRES) { + Some(text) => match text.parse() { + Ok(v) => { + let dur = Duration::from_secs(v); + if dur > self.config.max_expiry { + return error_with_text( + 400, + format!( + "Expiration time {} out of allowed range 0-{} s", + v, + self.config.max_expiry.as_secs() + ), + ); + } + Some(dur) } - } - None => None + Err(_) => { + return error_with_text( + 400, + "Malformed \"X-Expires\", use relative time in seconds.", + ); + } + }, + None => None, }; if let Some(id) = post_id { // UPDATE self.update(id, data, mime, expiry); - Response::text("Updated.") + Response::text("Updated OK.") } else { // INSERT let (id, token) = self.insert(data, mime, expiry.unwrap_or(self.config.default_expiry)); @@ -185,11 +286,12 @@ impl Repository { } } - fn serve_get_head(&mut self, req : &Request) -> Response { + /// Serve a GET or HEAD request + fn serve_get_head(&mut self, req: &Request) -> Response { let post_id = match self.request_to_post_id(req, false) { Ok(Some(pid)) => pid, Ok(None) => return error_with_text(400, "Post ID required."), - Err(resp) => return resp + Err(resp) => return resp, }; if let Some(post) = self.posts.get(&post_id) { @@ -205,7 +307,10 @@ impl Repository { Response { status_code: 200, - headers: vec![("Content-Type".into(), format!("{}; charset=utf8", post.mime).into())], + headers: vec![( + "Content-Type".into(), + format!("{}; charset=utf8", post.mime).into(), + )], data: if req.method() == "HEAD" { ResponseBody::empty() } else { @@ -220,47 +325,64 @@ impl Repository { } } - fn request_to_post_id(&self, req : &Request, check_secret : bool) -> Result, Response> { + /// Extract post ID from a request. + /// + /// if `check_secret` is true, ensure a `X-Secret` header contains a valid write token + /// for the post ID. + fn request_to_post_id( + &self, + req: &Request, + check_secret: bool, + ) -> Result, Response> { let url = req.url(); let stripped = url.trim_matches('/'); if stripped.is_empty() { - // No ID given + debug!("No ID given"); return Ok(None); } + if stripped.len() != 16 { + warn!("Bad ID len!"); + return Err(Response::empty_404()); + } + let id = match u64::from_str_radix(stripped, 16) { Ok(bytes) => bytes, Err(_) => { - return Err(error_with_text(400, "Bad file ID format!")); - }, + warn!("ID parsing error: {}", stripped); + return Err(Response::empty_404()); + } }; if check_secret { // Check the write token match self.posts.get(&id) { - None/* | Some(_p) if _p.is_expired()*/ => { + None => { + warn!("ID {} does not exist!", id); return Err(error_with_text(404, "No file with this ID!")); }, Some(post) => { if post.is_expired() { - warn!("Access of expired post!"); + warn!("Access of expired file {}!", id); return Err(error_with_text(404, "No file with this ID!")); } - let secret: u64 = match req.header("X-Secret").map(|v| u64::from_str_radix(v, 16)) { + let secret: u64 = match req.header(HDR_SECRET).map(|v| u64::from_str_radix(v, 16)) { Some(Ok(bytes)) => bytes, None => { - return Err(error_with_text(400, "X-Secret required!")); + warn!("Missing secret token!"); + return Err(error_with_text(400, "Secret token required!")); } Some(Err(e)) => { - warn!("{:?}", e); - return Err(error_with_text(400, "Bad secret format!")); + warn!("Token parse error: {:?}", e); + return Err(error_with_text(400, "Bad secret token format!")); }, }; if post.secret != secret { - return Err(error_with_text(401, "Invalid secret!")); + warn!("Secret token mismatch"); + return Err(error_with_text(401, "Invalid secret token!")); } }, } @@ -270,15 +392,17 @@ impl Repository { Ok(Some(id)) } + /// Drop expired posts, if cleaning is due fn gc_expired_posts_if_needed(&mut self) { - if self.last_gc_time.elapsed() > self.config.expired_gc_interval { + if Utc::now().signed_duration_since(self.last_gc_time).to_std().unwrap_or_default() > self.config.expired_gc_interval { self.gc_expired_posts(); - self.last_gc_time = Instant::now(); + self.last_gc_time = Utc::now(); } } + /// Drop expired posts fn gc_expired_posts(&mut self) { - debug!("GC expired posts"); + debug!("GC expired uploads"); let mut to_rm = vec![]; for post in &self.posts { @@ -287,6 +411,10 @@ impl Repository { } } + if !to_rm.is_empty() { + self.dirty = true; + } + for id in to_rm { debug!("Drop post ID {:016x}", id); if let Some(post) = self.posts.remove(&id) { @@ -295,30 +423,37 @@ impl Repository { } } - fn hash_data(data : &Vec) -> DataHash { + /// Get hash of a byte vector (for deduplication) + fn hash_data(data: &Vec) -> DataHash { let mut hasher = siphasher::sip::SipHasher::new(); data.hash(&mut hasher); hasher.finish() } - fn store_data_or_increment_rc(data_map : &mut DataMap, hash : u64, data: Vec) { + /// Store a data buffer under a given hash. + /// If the buffer is already present in the repository, increment its use count. + fn store_data_or_increment_rc(data_map: &mut DataMap, hash: u64, data: Vec) { match data_map.get_mut(&hash) { None => { debug!("Store new data hash #{:016x}", hash); data_map.insert(hash, (1, data)); - }, + } Some(entry) => { debug!("Link new use of data hash #{:016x}", hash); entry.0 += 1; // increment use counter - }, + } } } - fn drop_data_or_decrement_rc(data_map : &mut DataMap, hash : u64) { + /// Drop a data record with the given hash, or decrement its use count if there are other uses + fn drop_data_or_decrement_rc(data_map: &mut DataMap, hash: u64) { if let Some(old_data) = data_map.get_mut(&hash) { if old_data.0 > 1 { old_data.0 -= 1; - debug!("Unlink use of data hash #{:016x} ({} remain)", hash, old_data.0); + debug!( + "Unlink use of data hash #{:016x} ({} remain)", + hash, old_data.0 + ); } else { debug!("Drop data hash #{:016x}", hash); data_map.remove(&hash); @@ -326,13 +461,26 @@ impl Repository { } } - fn insert(&mut self, data : Vec, mime : Option<&str>, expires : Duration) -> (PostId, Secret) { - info!("Insert post with data of len {} bytes, mime {}, expiry {:?}", - data.len(), mime.unwrap_or("unspecified"), - expires); + /// Insert a post + fn insert(&mut self, data: Vec, mime: Option<&str>, expires: Duration) -> (PostId, Secret) { + info!( + "Insert post with data of len {} bytes, mime {}, expiry {:?}", + data.len(), + mime.unwrap_or("unspecified"), + expires + ); let hash = Self::hash_data(&data); + let mime = match mime { + None => { + Mime::from(tree_magic::from_u8(&data)) + }, + Some(explicit) => { + Mime::from(explicit) + }, + }; + Self::store_data_or_increment_rc(&mut self.data, hash, data); let post_id = loop { @@ -344,26 +492,36 @@ impl Repository { let secret = OsRng.gen(); - debug!("Data hash = #{:016x}", hash); debug!("Post ID = #{:016x}", post_id); + debug!("Data hash = #{:016x}, mime {}", hash, mime); debug!("Secret = #{:016x}", secret); - self.posts.insert(post_id, Post { - mime: mime.map(ToString::to_string).map(Cow::Owned) - .unwrap_or(Cow::Borrowed("application/octet-stream")), - hash, - secret, - expires: Instant::now() + expires - }); + self.posts.insert( + post_id, + Post { + mime, + hash, + secret, + expires: Utc::now() + chrono::Duration::from_std(expires).unwrap(), // this is safe unless mis-configured + }, + ); + + self.dirty = true; (post_id, secret) } - fn update(&mut self, id : PostId, data : Vec, mime : Option<&str>, expires : Option) { - info!("Update post id #{:016x} with data of len {} bytes, mime {}, expiry {}", - id, data.len(), mime.unwrap_or("unchanged"), - expires.map(|v| Cow::Owned(format!("{:?}", v))) - .unwrap_or("unchanged".into())); + /// Update a post by ID + fn update(&mut self, id: PostId, data: Vec, mime: Option<&str>, expires: Option) { + info!( + "Update post id #{:016x} with data of len {} bytes, mime {}, expiry {}", + id, + data.len(), + mime.unwrap_or("unchanged"), + expires + .map(|v| Cow::Owned(format!("{:?}", v))) + .unwrap_or("unchanged".into()) + ); let hash = Self::hash_data(&data); let post = self.posts.get_mut(&id).unwrap(); // post existence was checked before @@ -374,27 +532,73 @@ impl Repository { Self::drop_data_or_decrement_rc(&mut self.data, post.hash); Self::store_data_or_increment_rc(&mut self.data, hash, data); post.hash = hash; + self.dirty = true; } else { debug!("Data hash = #{:016x} (no change)", hash); } if let Some(mime) = mime { - if &post.mime != mime { + let new_mime = Mime::from(mime); + if post.mime != new_mime { debug!("Content type changed to {}", mime); - post.mime = Cow::Owned(mime.to_string()); + post.mime = new_mime; + self.dirty = true; } } if let Some(exp) = expires { debug!("Expiration changed to {:?} from now", exp); - post.expires = Instant::now() + exp; + post.expires = Utc::now() + chrono::Duration::from_std(exp).unwrap(); // this is safe unless mis-configured; + self.dirty = true; } } - fn delete_post(&mut self, id : PostId) { + /// Delete a post by ID + fn delete_post(&mut self, id: PostId) { info!("Delete post id #{:016x}", id); let post = self.posts.remove(&id).unwrap(); // post existence was checked before Self::drop_data_or_decrement_rc(&mut self.data, post.hash); + + self.dirty = true; + } +} + +/// Serialize chrono unix timestamp as seconds +mod serde_chrono_datetime_as_unix { + use serde::{self, Deserialize, Deserializer, Serializer}; + use chrono::{DateTime, Utc, NaiveDateTime}; + + pub fn serialize(value: &DateTime, se: S) -> Result + where + S: Serializer, + { + se.serialize_i64(value.naive_utc().timestamp()) + } + + pub fn deserialize<'de, D>(de: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let ts: i64 = i64::deserialize(de)?; + Ok(DateTime::from_utc(NaiveDateTime::from_timestamp(ts, 0), Utc)) + } +} + +fn error_with_text(code: u16, text: impl Into) -> Response { + Response { + status_code: code, + headers: vec![("Content-Type".into(), "text/plain; charset=utf8".into())], + data: rouille::ResponseBody::from_string(text), + upgrade: None, + } +} + +fn empty_error(code: u16) -> Response { + Response { + status_code: code, + headers: vec![("Content-Type".into(), "text/plain; charset=utf8".into())], + data: rouille::ResponseBody::empty(), + upgrade: None, } } diff --git a/src/well_known_mime.rs b/src/well_known_mime.rs new file mode 100644 index 0000000..09cf482 --- /dev/null +++ b/src/well_known_mime.rs @@ -0,0 +1,742 @@ +use serde::{Serialize, Serializer, Deserialize, Deserializer}; +use std::fmt::{Write, Display, Formatter}; +use std::fmt; +use serde::de::Visitor; + +#[derive(Serialize,Deserialize,Debug,PartialEq,Eq,Hash)] +pub enum Mime { + WellKnown(usize), + Custom(String), +} + +impl From for Mime { + fn from(s: String) -> Self { + if let Ok(index) = WELL_KNOWN.binary_search(&s.as_str()) { + Mime::WellKnown(index) + } else { + Mime::Custom(s) + } + } +} + +impl<'a> From<&'a str> for Mime { + fn from(s: &'a str) -> Self { + if let Ok(index) = WELL_KNOWN.binary_search(&s) { + Mime::WellKnown(index) + } else { + Mime::Custom(s.to_string()) + } + } +} + +impl Display for Mime { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Mime::WellKnown(n) => { + if let Some(s) = WELL_KNOWN.get(*n) { + f.write_str(s) + } else { + f.write_str("application/octet-stream") + } + }, + Mime::Custom(s) => { + f.write_str(s) + }, + } + } +} + +// CAUTION!!!!! This list must be alphabetically sorted! +const WELL_KNOWN : &[&str] = &[ + "application/andrew-inset", + "application/applixware", + "application/atom+xml", + "application/atomcat+xml", + "application/atomsvc+xml", + "application/ccxml+xml,", + "application/cdmi-capability", + "application/cdmi-container", + "application/cdmi-domain", + "application/cdmi-object", + "application/cdmi-queue", + "application/cu-seeme", + "application/davmount+xml", + "application/dssc+der", + "application/dssc+xml", + "application/ecmascript", + "application/emma+xml", + "application/epub+zip", + "application/exi", + "application/font-tdpfr", + "application/hyperstudio", + "application/ipfix", + "application/java-archive", + "application/java-serialized-object", + "application/java-vm", + "application/javascript", + "application/json", + "application/mac-binhex40", + "application/mac-compactpro", + "application/mads+xml", + "application/marc", + "application/marcxml+xml", + "application/mathematica", + "application/mathml+xml", + "application/mbox", + "application/mediaservercontrol+xml", + "application/metalink4+xml", + "application/mets+xml", + "application/mods+xml", + "application/mp21", + "application/mp4", + "application/msword", + "application/mxf", + "application/octet-stream", + "application/oda", + "application/oebps-package+xml", + "application/ogg", + "application/onenote", + "application/patch-ops-error+xml", + "application/pdf", + "application/pgp-encrypted", + "application/pgp-signature", + "application/pics-rules", + "application/pkcs10", + "application/pkcs7-mime", + "application/pkcs7-signature", + "application/pkcs8", + "application/pkix-attr-cert", + "application/pkix-cert", + "application/pkix-crl", + "application/pkix-pkipath", + "application/pkixcmp", + "application/pls+xml", + "application/postscript", + "application/prs.cww", + "application/pskc+xml", + "application/rdf+xml", + "application/reginfo+xml", + "application/relax-ng-compact-syntax", + "application/resource-lists+xml", + "application/resource-lists-diff+xml", + "application/rls-services+xml", + "application/rsd+xml", + "application/rss+xml", + "application/rtf", + "application/sbml+xml", + "application/scvp-cv-request", + "application/scvp-cv-response", + "application/scvp-vp-request", + "application/scvp-vp-response", + "application/sdp", + "application/set-payment-initiation", + "application/set-registration-initiation", + "application/shf+xml", + "application/smil+xml", + "application/sparql-query", + "application/sparql-results+xml", + "application/srgs", + "application/srgs+xml", + "application/sru+xml", + "application/ssml+xml", + "application/tei+xml", + "application/thraud+xml", + "application/timestamped-data", + "application/vnd.3gpp.pic-bw-large", + "application/vnd.3gpp.pic-bw-small", + "application/vnd.3gpp.pic-bw-var", + "application/vnd.3gpp2.tcap", + "application/vnd.3m.post-it-notes", + "application/vnd.accpac.simply.aso", + "application/vnd.accpac.simply.imp", + "application/vnd.acucobol", + "application/vnd.acucorp", + "application/vnd.adobe.air-application-installer-package+zip", + "application/vnd.adobe.fxp", + "application/vnd.adobe.xdp+xml", + "application/vnd.adobe.xfdf", + "application/vnd.ahead.space", + "application/vnd.airzip.filesecure.azf", + "application/vnd.airzip.filesecure.azs", + "application/vnd.amazon.ebook", + "application/vnd.americandynamics.acc", + "application/vnd.amiga.ami", + "application/vnd.android.package-archive", + "application/vnd.anser-web-certificate-issue-initiation", + "application/vnd.anser-web-funds-transfer-initiation", + "application/vnd.antix.game-component", + "application/vnd.apple.installer+xml", + "application/vnd.apple.mpegurl", + "application/vnd.aristanetworks.swi", + "application/vnd.audiograph", + "application/vnd.blueice.multipass", + "application/vnd.bmi", + "application/vnd.businessobjects", + "application/vnd.chemdraw+xml", + "application/vnd.chipnuts.karaoke-mmd", + "application/vnd.cinderella", + "application/vnd.claymore", + "application/vnd.cloanto.rp9", + "application/vnd.clonk.c4group", + "application/vnd.cluetrust.cartomobile-config", + "application/vnd.cluetrust.cartomobile-config-pkg", + "application/vnd.commonspace", + "application/vnd.contact.cmsg", + "application/vnd.cosmocaller", + "application/vnd.crick.clicker", + "application/vnd.crick.clicker.keyboard", + "application/vnd.crick.clicker.palette", + "application/vnd.crick.clicker.template", + "application/vnd.crick.clicker.wordbank", + "application/vnd.criticaltools.wbs+xml", + "application/vnd.ctc-posml", + "application/vnd.cups-ppd", + "application/vnd.curl.car", + "application/vnd.curl.pcurl", + "application/vnd.data-vision.rdz", + "application/vnd.denovo.fcselayout-link", + "application/vnd.dna", + "application/vnd.dolby.mlp", + "application/vnd.dpgraph", + "application/vnd.dreamfactory", + "application/vnd.dvb.ait", + "application/vnd.dvb.service", + "application/vnd.dynageo", + "application/vnd.ecowin.chart", + "application/vnd.enliven", + "application/vnd.epson.esf", + "application/vnd.epson.msf", + "application/vnd.epson.quickanime", + "application/vnd.epson.salt", + "application/vnd.epson.ssf", + "application/vnd.eszigno3+xml", + "application/vnd.ezpix-album", + "application/vnd.ezpix-package", + "application/vnd.fdf", + "application/vnd.fdsn.seed", + "application/vnd.flographit", + "application/vnd.fluxtime.clip", + "application/vnd.framemaker", + "application/vnd.frogans.fnc", + "application/vnd.frogans.ltf", + "application/vnd.fsc.weblaunch", + "application/vnd.fujitsu.oasys", + "application/vnd.fujitsu.oasys2", + "application/vnd.fujitsu.oasys3", + "application/vnd.fujitsu.oasysgp", + "application/vnd.fujitsu.oasysprs", + "application/vnd.fujixerox.ddd", + "application/vnd.fujixerox.docuworks", + "application/vnd.fujixerox.docuworks.binder", + "application/vnd.fuzzysheet", + "application/vnd.genomatix.tuxedo", + "application/vnd.geogebra.file", + "application/vnd.geogebra.tool", + "application/vnd.geometry-explorer", + "application/vnd.geonext", + "application/vnd.geoplan", + "application/vnd.geospace", + "application/vnd.gmx", + "application/vnd.google-earth.kml+xml", + "application/vnd.google-earth.kmz", + "application/vnd.grafeq", + "application/vnd.groove-account", + "application/vnd.groove-help", + "application/vnd.groove-identity-message", + "application/vnd.groove-injector", + "application/vnd.groove-tool-message", + "application/vnd.groove-tool-template", + "application/vnd.groove-vcard", + "application/vnd.hal+xml", + "application/vnd.handheld-entertainment+xml", + "application/vnd.hbci", + "application/vnd.hhe.lesson-player", + "application/vnd.hp-hpgl", + "application/vnd.hp-hpid", + "application/vnd.hp-hps", + "application/vnd.hp-jlyt", + "application/vnd.hp-pcl", + "application/vnd.hp-pclxl", + "application/vnd.hydrostatix.sof-data", + "application/vnd.hzn-3d-crossword", + "application/vnd.ibm.minipay", + "application/vnd.ibm.modcap", + "application/vnd.ibm.rights-management", + "application/vnd.ibm.secure-container", + "application/vnd.iccprofile", + "application/vnd.igloader", + "application/vnd.immervision-ivp", + "application/vnd.immervision-ivu", + "application/vnd.insors.igm", + "application/vnd.intercon.formnet", + "application/vnd.intergeo", + "application/vnd.intu.qbo", + "application/vnd.intu.qfx", + "application/vnd.ipunplugged.rcprofile", + "application/vnd.irepository.package+xml", + "application/vnd.is-xpr", + "application/vnd.isac.fcs", + "application/vnd.jam", + "application/vnd.jcp.javame.midlet-rms", + "application/vnd.jisp", + "application/vnd.joost.joda-archive", + "application/vnd.kahootz", + "application/vnd.kde.karbon", + "application/vnd.kde.kchart", + "application/vnd.kde.kformula", + "application/vnd.kde.kivio", + "application/vnd.kde.kontour", + "application/vnd.kde.kpresenter", + "application/vnd.kde.kspread", + "application/vnd.kde.kword", + "application/vnd.kenameaapp", + "application/vnd.kidspiration", + "application/vnd.kinar", + "application/vnd.koan", + "application/vnd.kodak-descriptor", + "application/vnd.las.las+xml", + "application/vnd.llamagraphics.life-balance.desktop", + "application/vnd.llamagraphics.life-balance.exchange+xml", + "application/vnd.lotus-1-2-3", + "application/vnd.lotus-approach", + "application/vnd.lotus-freelance", + "application/vnd.lotus-notes", + "application/vnd.lotus-organizer", + "application/vnd.lotus-screencam", + "application/vnd.lotus-wordpro", + "application/vnd.macports.portpkg", + "application/vnd.mcd", + "application/vnd.medcalcdata", + "application/vnd.mediastation.cdkey", + "application/vnd.mfer", + "application/vnd.mfmp", + "application/vnd.micrografx.flo", + "application/vnd.micrografx.igx", + "application/vnd.mif", + "application/vnd.mobius.daf", + "application/vnd.mobius.dis", + "application/vnd.mobius.mbk", + "application/vnd.mobius.mqy", + "application/vnd.mobius.msl", + "application/vnd.mobius.plc", + "application/vnd.mobius.txf", + "application/vnd.mophun.application", + "application/vnd.mophun.certificate", + "application/vnd.mozilla.xul+xml", + "application/vnd.ms-artgalry", + "application/vnd.ms-cab-compressed", + "application/vnd.ms-excel", + "application/vnd.ms-excel.addin.macroenabled.12", + "application/vnd.ms-excel.sheet.binary.macroenabled.12", + "application/vnd.ms-excel.sheet.macroenabled.12", + "application/vnd.ms-excel.template.macroenabled.12", + "application/vnd.ms-fontobject", + "application/vnd.ms-htmlhelp", + "application/vnd.ms-ims", + "application/vnd.ms-lrm", + "application/vnd.ms-officetheme", + "application/vnd.ms-pki.seccat", + "application/vnd.ms-pki.stl", + "application/vnd.ms-powerpoint", + "application/vnd.ms-powerpoint.addin.macroenabled.12", + "application/vnd.ms-powerpoint.presentation.macroenabled.12", + "application/vnd.ms-powerpoint.slide.macroenabled.12", + "application/vnd.ms-powerpoint.slideshow.macroenabled.12", + "application/vnd.ms-powerpoint.template.macroenabled.12", + "application/vnd.ms-project", + "application/vnd.ms-word.document.macroenabled.12", + "application/vnd.ms-word.template.macroenabled.12", + "application/vnd.ms-works", + "application/vnd.ms-wpl", + "application/vnd.ms-xpsdocument", + "application/vnd.mseq", + "application/vnd.musician", + "application/vnd.muvee.style", + "application/vnd.neurolanguage.nlu", + "application/vnd.noblenet-directory", + "application/vnd.noblenet-sealer", + "application/vnd.noblenet-web", + "application/vnd.nokia.n-gage.data", + "application/vnd.nokia.n-gage.symbian.install", + "application/vnd.nokia.radio-preset", + "application/vnd.nokia.radio-presets", + "application/vnd.novadigm.edm", + "application/vnd.novadigm.edx", + "application/vnd.novadigm.ext", + "application/vnd.oasis.opendocument.chart", + "application/vnd.oasis.opendocument.chart-template", + "application/vnd.oasis.opendocument.database", + "application/vnd.oasis.opendocument.formula", + "application/vnd.oasis.opendocument.formula-template", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.oasis.opendocument.image", + "application/vnd.oasis.opendocument.image-template", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-master", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.text-web", + "application/vnd.olpc-sugar", + "application/vnd.oma.dd2+xml", + "application/vnd.openofficeorg.extension", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.presentationml.slide", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "application/vnd.openxmlformats-officedocument.presentationml.template", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "application/vnd.osgeo.mapguide.package", + "application/vnd.osgi.dp", + "application/vnd.palm", + "application/vnd.pawaafile", + "application/vnd.pg.format", + "application/vnd.pg.osasli", + "application/vnd.picsel", + "application/vnd.pmi.widget", + "application/vnd.pocketlearn", + "application/vnd.powerbuilder6", + "application/vnd.previewsystems.box", + "application/vnd.proteus.magazine", + "application/vnd.publishare-delta-tree", + "application/vnd.pvi.ptid1", + "application/vnd.quark.quarkxpress", + "application/vnd.realvnc.bed", + "application/vnd.recordare.musicxml", + "application/vnd.recordare.musicxml+xml", + "application/vnd.rig.cryptonote", + "application/vnd.rim.cod", + "application/vnd.rn-realmedia", + "application/vnd.route66.link66+xml", + "application/vnd.sailingtracker.track", + "application/vnd.seemail", + "application/vnd.sema", + "application/vnd.semd", + "application/vnd.semf", + "application/vnd.shana.informed.formdata", + "application/vnd.shana.informed.formtemplate", + "application/vnd.shana.informed.interchange", + "application/vnd.shana.informed.package", + "application/vnd.simtech-mindmapper", + "application/vnd.smaf", + "application/vnd.smart.teacher", + "application/vnd.solent.sdkm+xml", + "application/vnd.spotfire.dxp", + "application/vnd.spotfire.sfs", + "application/vnd.stardivision.calc", + "application/vnd.stardivision.draw", + "application/vnd.stardivision.impress", + "application/vnd.stardivision.math", + "application/vnd.stardivision.writer", + "application/vnd.stardivision.writer-global", + "application/vnd.stepmania.stepchart", + "application/vnd.sun.xml.calc", + "application/vnd.sun.xml.calc.template", + "application/vnd.sun.xml.draw", + "application/vnd.sun.xml.draw.template", + "application/vnd.sun.xml.impress", + "application/vnd.sun.xml.impress.template", + "application/vnd.sun.xml.math", + "application/vnd.sun.xml.writer", + "application/vnd.sun.xml.writer.global", + "application/vnd.sun.xml.writer.template", + "application/vnd.sus-calendar", + "application/vnd.svd", + "application/vnd.symbian.install", + "application/vnd.syncml+xml", + "application/vnd.syncml.dm+wbxml", + "application/vnd.syncml.dm+xml", + "application/vnd.tao.intent-module-archive", + "application/vnd.tmobile-livetv", + "application/vnd.trid.tpt", + "application/vnd.triscape.mxs", + "application/vnd.trueapp", + "application/vnd.ufdl", + "application/vnd.uiq.theme", + "application/vnd.umajin", + "application/vnd.unity", + "application/vnd.uoml+xml", + "application/vnd.vcx", + "application/vnd.visio", + "application/vnd.visio2013", + "application/vnd.visionary", + "application/vnd.vsf", + "application/vnd.wap.wbxml", + "application/vnd.wap.wmlc", + "application/vnd.wap.wmlscriptc", + "application/vnd.webturbo", + "application/vnd.wolfram.player", + "application/vnd.wordperfect", + "application/vnd.wqd", + "application/vnd.wt.stf", + "application/vnd.xara", + "application/vnd.xfdl", + "application/vnd.yamaha.hv-dic", + "application/vnd.yamaha.hv-script", + "application/vnd.yamaha.hv-voice", + "application/vnd.yamaha.openscoreformat", + "application/vnd.yamaha.openscoreformat.osfpvg+xml", + "application/vnd.yamaha.smaf-audio", + "application/vnd.yamaha.smaf-phrase", + "application/vnd.yellowriver-custom-menu", + "application/vnd.zul", + "application/vnd.zzazz.deck+xml", + "application/voicexml+xml", + "application/widget", + "application/winhlp", + "application/wsdl+xml", + "application/wspolicy+xml", + "application/x-7z-compressed", + "application/x-abiword", + "application/x-ace-compressed", + "application/x-apple-diskimage", + "application/x-authorware-bin", + "application/x-authorware-map", + "application/x-authorware-seg", + "application/x-bcpio", + "application/x-bittorrent", + "application/x-bzip", + "application/x-bzip2", + "application/x-cdlink", + "application/x-chat", + "application/x-chess-pgn", + "application/x-cpio", + "application/x-csh", + "application/x-debian-package", + "application/x-director", + "application/x-doom", + "application/x-dtbncx+xml", + "application/x-dtbook+xml", + "application/x-dtbresource+xml", + "application/x-dvi", + "application/x-font-bdf", + "application/x-font-ghostscript", + "application/x-font-linux-psf", + "application/x-font-otf", + "application/x-font-pcf", + "application/x-font-snf", + "application/x-font-ttf", + "application/x-font-type1", + "application/x-font-woff", + "application/x-futuresplash", + "application/x-gnumeric", + "application/x-gtar", + "application/x-hdf", + "application/x-java-jnlp-file", + "application/x-latex", + "application/x-mobipocket-ebook", + "application/x-ms-application", + "application/x-ms-wmd", + "application/x-ms-wmz", + "application/x-ms-xbap", + "application/x-msaccess", + "application/x-msbinder", + "application/x-mscardfile", + "application/x-msclip", + "application/x-msdownload", + "application/x-msmediaview", + "application/x-msmetafile", + "application/x-msmoney", + "application/x-mspublisher", + "application/x-msschedule", + "application/x-msterminal", + "application/x-mswrite", + "application/x-netcdf", + "application/x-pkcs12", + "application/x-pkcs7-certificates", + "application/x-pkcs7-certreqresp", + "application/x-rar-compressed", + "application/x-sh", + "application/x-shar", + "application/x-shockwave-flash", + "application/x-silverlight-app", + "application/x-stuffit", + "application/x-stuffitx", + "application/x-sv4cpio", + "application/x-sv4crc", + "application/x-tar", + "application/x-tcl", + "application/x-tex", + "application/x-tex-tfm", + "application/x-texinfo", + "application/x-ustar", + "application/x-wais-source", + "application/x-x509-ca-cert", + "application/x-xfig", + "application/x-xpinstall", + "application/xcap-diff+xml", + "application/xenc+xml", + "application/xhtml+xml", + "application/xml", + "application/xml-dtd", + "application/xop+xml", + "application/xslt+xml", + "application/xspf+xml", + "application/xv+xml", + "application/yang", + "application/yin+xml", + "application/zip", + "audio/adpcm", + "audio/basic", + "audio/midi", + "audio/mp4", + "audio/mpeg", + "audio/ogg", + "audio/vnd.dece.audio", + "audio/vnd.digital-winds", + "audio/vnd.dra", + "audio/vnd.dts", + "audio/vnd.dts.hd", + "audio/vnd.lucent.voice", + "audio/vnd.ms-playready.media.pya", + "audio/vnd.nuera.ecelp4800", + "audio/vnd.nuera.ecelp7470", + "audio/vnd.nuera.ecelp9600", + "audio/vnd.rip", + "audio/webm", + "audio/x-aac", + "audio/x-aiff", + "audio/x-mpegurl", + "audio/x-ms-wax", + "audio/x-ms-wma", + "audio/x-pn-realaudio", + "audio/x-pn-realaudio-plugin", + "audio/x-wav", + "chemical/x-cdx", + "chemical/x-cif", + "chemical/x-cmdf", + "chemical/x-cml", + "chemical/x-csml", + "chemical/x-xyz", + "image/bmp", + "image/cgm", + "image/g3fax", + "image/gif", + "image/ief", + "image/jpeg", + "image/ktx", + "image/pjpeg", + "image/png", + "image/prs.btif", + "image/svg+xml", + "image/tiff", + "image/vnd.adobe.photoshop", + "image/vnd.dece.graphic", + "image/vnd.djvu", + "image/vnd.dvb.subtitle", + "image/vnd.dwg", + "image/vnd.dxf", + "image/vnd.fastbidsheet", + "image/vnd.fpx", + "image/vnd.fst", + "image/vnd.fujixerox.edmics-mmr", + "image/vnd.fujixerox.edmics-rlc", + "image/vnd.ms-modi", + "image/vnd.net-fpx", + "image/vnd.wap.wbmp", + "image/vnd.xiff", + "image/webp", + "image/x-citrix-jpeg", + "image/x-citrix-png", + "image/x-cmu-raster", + "image/x-cmx", + "image/x-freehand", + "image/x-icon", + "image/x-pcx", + "image/x-pict", + "image/x-png", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", + "image/x-rgb", + "image/x-xbitmap", + "image/x-xpixmap", + "image/x-xwindowdump", + "message/rfc822", + "model/iges", + "model/mesh", + "model/vnd.collada+xml", + "model/vnd.dwf", + "model/vnd.gdl", + "model/vnd.gtw", + "model/vnd.mts", + "model/vnd.vtu", + "model/vrml", + "text/calendar", + "text/css", + "text/csv", + "text/html", + "text/n3", + "text/plain", + "text/plain-bas", + "text/prs.lines.tag", + "text/richtext", + "text/sgml", + "text/tab-separated-values", + "text/troff", + "text/turtle", + "text/uri-list", + "text/vnd.curl", + "text/vnd.curl.dcurl", + "text/vnd.curl.mcurl", + "text/vnd.curl.scurl", + "text/vnd.fly", + "text/vnd.fmi.flexstor", + "text/vnd.graphviz", + "text/vnd.in3d.3dml", + "text/vnd.in3d.spot", + "text/vnd.sun.j2me.app-descriptor", + "text/vnd.wap.wml", + "text/vnd.wap.wmlscript", + "text/x-asm", + "text/x-c", + "text/x-fortran", + "text/x-java-source,java", + "text/x-pascal", + "text/x-setext", + "text/x-uuencode", + "text/x-vcalendar", + "text/x-vcard", + "text/yaml", + "video/3gpp", + "video/3gpp2", + "video/h261", + "video/h263", + "video/h264", + "video/jpeg", + "video/jpm", + "video/mj2", + "video/mp4", + "video/mpeg", + "video/ogg", + "video/quicktime", + "video/vnd.dece.hd", + "video/vnd.dece.mobile", + "video/vnd.dece.pd", + "video/vnd.dece.sd", + "video/vnd.dece.video", + "video/vnd.fvt", + "video/vnd.mpegurl", + "video/vnd.ms-playready.media.pyv", + "video/vnd.uvvu.mp4", + "video/vnd.vivo", + "video/webm", + "video/x-f4v", + "video/x-fli", + "video/x-flv", + "video/x-m4v", + "video/x-ms-asf", + "video/x-ms-wm", + "video/x-ms-wmv", + "video/x-ms-wmx", + "video/x-ms-wvx", + "video/x-msvideo", + "video/x-sgi-movie", + "x-conference/x-cooltalk", +];