From 2b824c07f8e381698971c94cbae661cb48789c4a Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 8 May 2023 21:49:05 +0200 Subject: [PATCH 01/65] Adding threading and general handle of incoming requests, as well as returning a standard site --- core/http/src/handlers.rs | 0 core/http/src/lib.rs | 78 +++++++++++++++++++++++++++++---- core/http/src/routing.rs | 2 + core/http/src/threading.rs | 90 ++++++++++++++++++++++++++++++++++++++ q | 0 site/Cargo.lock | 7 +++ site/Cargo.toml | 1 + site/hello.html | 11 +++++ site/src/main.rs | 2 +- 9 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 core/http/src/handlers.rs create mode 100644 core/http/src/routing.rs create mode 100644 core/http/src/threading.rs create mode 100644 q create mode 100644 site/hello.html diff --git a/core/http/src/handlers.rs b/core/http/src/handlers.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 7d12d9a..1d8e24d 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,14 +1,74 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +use std::{ + fs, + io::{BufRead, BufReader, Write}, + net::{TcpListener, TcpStream}, + thread::available_parallelism, +}; + +use threading::ThreadPool; + +mod handlers; +mod routing; +mod threading; + +pub struct Config { + address: TcpListener, + workers: usize, + threadpool: ThreadPool, } -#[cfg(test)] -mod tests { - use super::*; +impl Config { + pub fn mount(self, mountpoint: &str, routes: Vec<routing::Route>) {} + pub fn launch(self) { + for stream in self.address.incoming() { + let stream = stream.unwrap(); + self.threadpool.execute(|| handle_connection(stream)) + } + } +} + +fn handle_connection(mut stream: TcpStream) { + let buf_reader = BufReader::new(&mut stream); + let http_request: Vec<_> = buf_reader + .lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + let status_line = "HTTP/1.1 200 OK"; + let contents = fs::read_to_string("hello.html").unwrap(); + let length = contents.len(); + + let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); + + stream.write_all(response.as_bytes()).unwrap(); +} + +pub fn build(ip: &str) -> Config { + let listener = TcpListener::bind(ip).unwrap(); + let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); + if ip.len() != 2 { + panic!("Invalid IP Address"); + } + let port = ip[1]; + let ip = ip[0]; + let workers = available_parallelism().unwrap().get(); + let threadpool = ThreadPool::new(workers); + println!( + "\x1b[34mâš™ Configuration\x1b[0m + >> \x1b[34mIp\x1b[0m: {ip} + >> \x1b[34mPort\x1b[0m: {port} + >> \x1b[34mWorkers\x1b[0m: {workers} +\n +Server has launched from {ip}:{port}. +" + ); + Config { + address: listener, + workers, + threadpool, } } + +#[cfg(test)] +mod tests {} diff --git a/core/http/src/routing.rs b/core/http/src/routing.rs new file mode 100644 index 0000000..6ae9a73 --- /dev/null +++ b/core/http/src/routing.rs @@ -0,0 +1,2 @@ +pub struct Route; +pub struct RoutInfo; diff --git a/core/http/src/threading.rs b/core/http/src/threading.rs new file mode 100644 index 0000000..b59bec8 --- /dev/null +++ b/core/http/src/threading.rs @@ -0,0 +1,90 @@ +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; + +pub struct ThreadPool { + workers: Vec<Worker>, + sender: Option<mpsc::Sender<Job>>, +} + +type Job = Box<dyn FnOnce() + Send + 'static>; + +impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + ThreadPool { + workers, + sender: Some(sender), + } + } + + pub fn execute<F>(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + + self.sender.as_ref().unwrap().send(job).unwrap(); + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + + for worker in &mut self.workers { + println!("Shutting down worker {}", worker.id); + + if let Some(thread) = worker.thread.take() { + thread.join().unwrap(); + } + } + } +} +struct Worker { + id: usize, + thread: Option<thread::JoinHandle<()>>, +} + +impl Worker { + fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv(); + + match message { + Ok(job) => { + println!("Worker {id} got a job; executing."); + + job(); + } + Err(_) => { + println!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + + Worker { + id, + thread: Some(thread), + } + } +} diff --git a/q b/q new file mode 100644 index 0000000..e69de29 diff --git a/site/Cargo.lock b/site/Cargo.lock index d1bf507..fac1130 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -2,6 +2,13 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "http" +version = "0.1.0" + [[package]] name = "site" version = "0.1.0" +dependencies = [ + "http", +] diff --git a/site/Cargo.toml b/site/Cargo.toml index e6a3153..d4e9a4a 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +http = { path = "../core/http" } diff --git a/site/hello.html b/site/hello.html new file mode 100644 index 0000000..ff4f652 --- /dev/null +++ b/site/hello.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Hello!</title> + </head> + <body> + <h1>Hello!</h1> + <p>Hi from Rust</p> + </body> +</html> diff --git a/site/src/main.rs b/site/src/main.rs index e7a11a9..435d748 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,3 +1,3 @@ fn main() { - println!("Hello, world!"); + http::build("192.168.179.2:8000").launch(); } -- GitLab From a6251e8fc7a44b40119127767a0d04c14e6653c3 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 8 May 2023 21:52:16 +0200 Subject: [PATCH 02/65] delete random file q --- q | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 q diff --git a/q b/q deleted file mode 100644 index e69de29..0000000 -- GitLab From d319f18db94db5e534186a1d3345ca7a4547e9bb Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 9 May 2023 16:51:14 +0200 Subject: [PATCH 03/65] 1. Add SIGNIT graceful shutdown 1. Organize lib.rs in modules 1. Setting up basic text-file server functionality 1. Adding some test files --- core/http/Cargo.lock | 743 +++++++++++++++++++++++++++++++++++++ core/http/Cargo.toml | 2 + core/http/src/handlers.rs | 34 ++ core/http/src/lib.rs | 71 +--- core/http/src/routing.rs | 63 +++- core/http/src/setup.rs | 85 +++++ core/http/src/threading.rs | 5 - core/http/src/utils/mod.rs | 0 site/404.html | 12 + site/Cargo.lock | 743 +++++++++++++++++++++++++++++++++++++ site/QLC-LS.jpg | Bin 0 -> 51549 bytes site/hello.css | 4 + site/hello.html | 1 + site/src/main.rs | 2 +- 14 files changed, 1690 insertions(+), 75 deletions(-) create mode 100644 core/http/src/setup.rs create mode 100644 core/http/src/utils/mod.rs create mode 100644 site/404.html create mode 100644 site/QLC-LS.jpg create mode 100644 site/hello.css diff --git a/core/http/Cargo.lock b/core/http/Cargo.lock index 8490fd5..755ed5e 100644 --- a/core/http/Cargo.lock +++ b/core/http/Cargo.lock @@ -2,6 +2,749 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "ctrlc" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +dependencies = [ + "nix", + "windows-sys 0.45.0", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "http" version = "0.1.0" +dependencies = [ + "ctrlc", + "quinn", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "static_assertions", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-proto" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-udp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" +dependencies = [ + "libc", + "quinn-proto", + "socket2", + "tracing", + "windows-sys 0.42.0", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +dependencies = [ + "autocfg", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index 8b1c9f1..34a4506 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +quinn = "0.9.3" +ctrlc = "3.2.5" diff --git a/core/http/src/handlers.rs b/core/http/src/handlers.rs index e69de29..944f60a 100644 --- a/core/http/src/handlers.rs +++ b/core/http/src/handlers.rs @@ -0,0 +1,34 @@ +use std::{ + fs, + io::{BufRead, BufReader, Write}, + net::TcpStream, +}; + +pub fn handle_connection(mut stream: TcpStream) { + let buf_reader = BufReader::new(&mut stream); + let http_request: Vec<_> = buf_reader + .lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); + + let path_elements = http_request[0] + .split(" ") + .nth(1) + .unwrap() + .split("/") + .filter(|&val| val != ".." && val != "") + .collect::<Vec<&str>>(); + let mut path = String::from("./"); + path.push_str(&path_elements.join("/")); + println!("{:?}", path_elements); + println!("{:?}", path); + + let status_line = "HTTP/1.1 200 OK"; + let contents = fs::read_to_string(path).unwrap_or(fs::read_to_string("404.html").unwrap()); + let length = contents.len(); + + let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); + + stream.write_all(response.as_bytes()).unwrap(); +} diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 1d8e24d..0b86db5 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,74 +1,11 @@ -use std::{ - fs, - io::{BufRead, BufReader, Write}, - net::{TcpListener, TcpStream}, - thread::available_parallelism, -}; - -use threading::ThreadPool; - mod handlers; mod routing; +mod setup; mod threading; -pub struct Config { - address: TcpListener, - workers: usize, - threadpool: ThreadPool, -} - -impl Config { - pub fn mount(self, mountpoint: &str, routes: Vec<routing::Route>) {} - pub fn launch(self) { - for stream in self.address.incoming() { - let stream = stream.unwrap(); - self.threadpool.execute(|| handle_connection(stream)) - } - } -} - -fn handle_connection(mut stream: TcpStream) { - let buf_reader = BufReader::new(&mut stream); - let http_request: Vec<_> = buf_reader - .lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()) - .collect(); - - let status_line = "HTTP/1.1 200 OK"; - let contents = fs::read_to_string("hello.html").unwrap(); - let length = contents.len(); - - let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); - - stream.write_all(response.as_bytes()).unwrap(); -} - -pub fn build(ip: &str) -> Config { - let listener = TcpListener::bind(ip).unwrap(); - let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); - if ip.len() != 2 { - panic!("Invalid IP Address"); - } - let port = ip[1]; - let ip = ip[0]; - let workers = available_parallelism().unwrap().get(); - let threadpool = ThreadPool::new(workers); - println!( - "\x1b[34mâš™ Configuration\x1b[0m - >> \x1b[34mIp\x1b[0m: {ip} - >> \x1b[34mPort\x1b[0m: {port} - >> \x1b[34mWorkers\x1b[0m: {workers} -\n -Server has launched from {ip}:{port}. -" - ); - Config { - address: listener, - workers, - threadpool, - } -} +mod utils; #[cfg(test)] mod tests {} + +pub use setup::build; diff --git a/core/http/src/routing.rs b/core/http/src/routing.rs index 6ae9a73..aee6f8a 100644 --- a/core/http/src/routing.rs +++ b/core/http/src/routing.rs @@ -1,2 +1,61 @@ -pub struct Route; -pub struct RoutInfo; +pub struct RoutInfo { + name: Option<&'static str>, + method: Method, + path: &'static str, + // handler: fn(Request) -> Outcome, + format: Option<Format>, + rank: Option<isize>, +} + +pub struct Route { + name: Option<&'static str>, + method: Method, + // handler: fn(Request) -> Outcome, + uri: &'static str, + rank: isize, + format: Format, +} + +impl Route { + pub fn from(routeinfo: RoutInfo) -> Self { + let rank = routeinfo.rank.unwrap_or(0); + let format = routeinfo.format.unwrap_or(Format::Plain); + Route { + name: routeinfo.name, + method: routeinfo.method, + // handler: routeinfo.handler, + uri: routeinfo.path, + rank, + format, + } + } +} + +struct Request<'a> { + uri: &'a str, + headers: Vec<&'a str>, +} + +enum Outcome<S, E, F> { + Success(S), + Failure(E), + Forward(F), +} + +enum Method { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch, +} + +enum Format { + Json, + Plain, + Html, +} diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs new file mode 100644 index 0000000..b69bc0f --- /dev/null +++ b/core/http/src/setup.rs @@ -0,0 +1,85 @@ +use std::{ + net::TcpListener, + sync::{ + atomic::{self, AtomicBool}, + Arc, + }, + thread::available_parallelism, +}; + +use crate::{handlers::handle_connection, routing::Route, threading::ThreadPool}; + +pub struct PreMountConfig { + address: TcpListener, + workers: usize, + threadpool: ThreadPool, +} +impl PreMountConfig { + pub fn mount(self, mountpoint: &str, routes: Vec<Route>) -> Config { + Config { + mountpoint, + address: self.address, + workers: self.workers, + threadpool: self.threadpool, + routes, + } + } +} +pub struct Config<'a> { + mountpoint: &'a str, + address: TcpListener, + workers: usize, + threadpool: ThreadPool, + routes: Vec<Route>, +} + +impl Config<'_> { + pub fn launch(self) { + let running = Arc::new(AtomicBool::new(true)); + + // Clone a reference to `running` to pass to the signal handler closure + let running_for_handler = running.clone(); + + // Set up a signal handler for SIGINT + ctrlc::set_handler(move || { + // When the user sends a SIGINT signal (e.g. by pressing CTRL+C), + // set the `running` flag to false to initiate a graceful shutdown + println!("SIGNIT received, shutting down gracefully"); + running_for_handler.store(false, atomic::Ordering::SeqCst); + }) + .expect("Error setting Ctrl-C handler"); + + for stream in self.address.incoming() { + if !running.load(atomic::Ordering::SeqCst) { + break; + } + let stream = stream.unwrap(); + self.threadpool.execute(|| handle_connection(stream)) + } + } +} +pub fn build(ip: &str) -> PreMountConfig { + let listener = TcpListener::bind(ip).unwrap(); + let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); + if ip.len() != 2 { + panic!("Invalid IP Address"); + } + let port = ip[1]; + let ip = ip[0]; + let workers = available_parallelism().unwrap().get(); + let threadpool = ThreadPool::new(workers); + println!( + "\x1b[34mâš™ Configuration\x1b[0m + >> \x1b[34mIp\x1b[0m: {ip} + >> \x1b[34mPort\x1b[0m: {port} + >> \x1b[34mWorkers\x1b[0m: {workers} +\n +Server has launched from {ip}:{port}. +" + ); + PreMountConfig { + address: listener, + workers, + threadpool, + } +} diff --git a/core/http/src/threading.rs b/core/http/src/threading.rs index b59bec8..e743853 100644 --- a/core/http/src/threading.rs +++ b/core/http/src/threading.rs @@ -51,8 +51,6 @@ impl Drop for ThreadPool { drop(self.sender.take()); for worker in &mut self.workers { - println!("Shutting down worker {}", worker.id); - if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } @@ -71,12 +69,9 @@ impl Worker { match message { Ok(job) => { - println!("Worker {id} got a job; executing."); - job(); } Err(_) => { - println!("Worker {id} disconnected; shutting down."); break; } } diff --git a/core/http/src/utils/mod.rs b/core/http/src/utils/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/site/404.html b/site/404.html new file mode 100644 index 0000000..3a6ceb5 --- /dev/null +++ b/site/404.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>404</title> + <link rel="stylesheet" href="/hello.css" /> + </head> + <body> + <h1>404</h1> + <p>Hi from Rust</p> + </body> +</html> diff --git a/site/Cargo.lock b/site/Cargo.lock index fac1130..498f9ed 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -2,9 +2,357 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "ctrlc" +version = "3.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" +dependencies = [ + "nix", + "windows-sys 0.45.0", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "http" version = "0.1.0" +dependencies = [ + "ctrlc", + "quinn", +] + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "static_assertions", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "thiserror", + "tokio", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-proto" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-native-certs", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki", +] + +[[package]] +name = "quinn-udp" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" +dependencies = [ + "libc", + "quinn-proto", + "socket2", + "tracing", + "windows-sys 0.42.0", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "site" @@ -12,3 +360,398 @@ version = "0.1.0" dependencies = [ "http", ] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +dependencies = [ + "autocfg", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/site/QLC-LS.jpg b/site/QLC-LS.jpg new file mode 100644 index 0000000000000000000000000000000000000000..07eb7f85ac26c70344d552f56a81c44a0bf1b3db GIT binary patch literal 51549 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 B#&7@t literal 0 HcmV?d00001 diff --git a/site/hello.css b/site/hello.css new file mode 100644 index 0000000..aba0df0 --- /dev/null +++ b/site/hello.css @@ -0,0 +1,4 @@ +body { + background-color: #212121; + color: #ffffff; +} diff --git a/site/hello.html b/site/hello.html index ff4f652..52ef689 100644 --- a/site/hello.html +++ b/site/hello.html @@ -3,6 +3,7 @@ <head> <meta charset="utf-8" /> <title>Hello!</title> + <link rel="stylesheet" href="/hello.css" /> </head> <body> <h1>Hello!</h1> diff --git a/site/src/main.rs b/site/src/main.rs index 435d748..55ea439 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,3 +1,3 @@ fn main() { - http::build("192.168.179.2:8000").launch(); + http::build("127.0.0.1:8000").mount("/", vec![]).launch(); } -- GitLab From 9b9f4971c4cb25eed58396835db5840f4e222a68 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 9 May 2023 22:05:30 +0200 Subject: [PATCH 04/65] Add MIME support for the handler update routeing module --- core/http/Cargo.lock | 7 +++++ core/http/Cargo.toml | 1 + core/http/src/handlers.rs | 59 ++++++++++++++++++++++++++++------- core/http/src/routing.rs | 5 ++- site/Cargo.lock | 7 +++++ site/{QLC-LS.jpg => img.jpg} | Bin 6 files changed, 65 insertions(+), 14 deletions(-) rename site/{QLC-LS.jpg => img.jpg} (100%) diff --git a/core/http/Cargo.lock b/core/http/Cargo.lock index 755ed5e..6cd2bca 100644 --- a/core/http/Cargo.lock +++ b/core/http/Cargo.lock @@ -86,6 +86,7 @@ name = "http" version = "0.1.0" dependencies = [ "ctrlc", + "mime", "quinn", ] @@ -113,6 +114,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "0.8.6" diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index 34a4506..a5b887f 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] quinn = "0.9.3" ctrlc = "3.2.5" +mime = "0.3.17" diff --git a/core/http/src/handlers.rs b/core/http/src/handlers.rs index 944f60a..194eca3 100644 --- a/core/http/src/handlers.rs +++ b/core/http/src/handlers.rs @@ -2,8 +2,11 @@ use std::{ fs, io::{BufRead, BufReader, Write}, net::TcpStream, + path::PathBuf, }; +use mime::Mime; + pub fn handle_connection(mut stream: TcpStream) { let buf_reader = BufReader::new(&mut stream); let http_request: Vec<_> = buf_reader @@ -12,23 +15,57 @@ pub fn handle_connection(mut stream: TcpStream) { .take_while(|line| !line.is_empty()) .collect(); - let path_elements = http_request[0] + let path = parse_request(&http_request[0]); + + let status_line; + let mime_type; + let contents = if let Ok(file) = fs::read(&path) { + status_line = "HTTP/1.1 200 OK"; + mime_type = find_mimetype(&path.to_str().unwrap().to_string()); + file + } else { + status_line = "HTTP/1.1 404 NOT FOUND"; + mime_type = find_mimetype(&"html".to_string()); + fs::read("404.html").unwrap() + }; + + println!("{mime_type}"); + + let response = format!( + "{}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", + status_line, + contents.len(), + mime_type + ); + + stream.write_all(response.as_bytes()).unwrap(); + stream.write(&contents).unwrap(); +} + +fn parse_request(request: &str) -> PathBuf { + let path_elements = request .split(" ") .nth(1) .unwrap() .split("/") .filter(|&val| val != ".." && val != "") .collect::<Vec<&str>>(); - let mut path = String::from("./"); - path.push_str(&path_elements.join("/")); - println!("{:?}", path_elements); - println!("{:?}", path); - - let status_line = "HTTP/1.1 200 OK"; - let contents = fs::read_to_string(path).unwrap_or(fs::read_to_string("404.html").unwrap()); - let length = contents.len(); + PathBuf::from(format!("./{}", path_elements.join("/"))) +} - let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); +fn find_mimetype(filename: &String) -> Mime { + let parts: Vec<&str> = filename.split('.').collect(); - stream.write_all(response.as_bytes()).unwrap(); + let res = match parts.last() { + Some(v) => match *v { + "png" => mime::IMAGE_PNG, + "jpg" => mime::IMAGE_JPEG, + "json" => mime::APPLICATION_JSON, + "html" => mime::TEXT_HTML, + "css" => mime::TEXT_CSS, + &_ => mime::TEXT_PLAIN, + }, + None => mime::TEXT_PLAIN, + }; + return res; } diff --git a/core/http/src/routing.rs b/core/http/src/routing.rs index aee6f8a..2c5f74e 100644 --- a/core/http/src/routing.rs +++ b/core/http/src/routing.rs @@ -13,20 +13,19 @@ pub struct Route { // handler: fn(Request) -> Outcome, uri: &'static str, rank: isize, - format: Format, + format: Option<Format>, } impl Route { pub fn from(routeinfo: RoutInfo) -> Self { let rank = routeinfo.rank.unwrap_or(0); - let format = routeinfo.format.unwrap_or(Format::Plain); Route { name: routeinfo.name, method: routeinfo.method, // handler: routeinfo.handler, uri: routeinfo.path, rank, - format, + format: routeinfo.format, } } } diff --git a/site/Cargo.lock b/site/Cargo.lock index 498f9ed..7578765 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -86,6 +86,7 @@ name = "http" version = "0.1.0" dependencies = [ "ctrlc", + "mime", "quinn", ] @@ -113,6 +114,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "0.8.6" diff --git a/site/QLC-LS.jpg b/site/img.jpg similarity index 100% rename from site/QLC-LS.jpg rename to site/img.jpg -- GitLab From 299909a8fa22da06c2289d8619e234dad56d242d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 13 May 2023 20:07:44 +0200 Subject: [PATCH 05/65] Add routing with outcome functionality, improving fileserver improve division of code into different modules --- core/http/src/handlers.rs | 71 ----------------- core/http/src/handlers/file_handlers.rs | 65 +++++++++++++++ core/http/src/handlers/handlers.rs | 100 ++++++++++++++++++++++++ core/http/src/handlers/mod.rs | 2 + core/http/src/routing.rs | 60 -------------- core/http/src/routing/methods.rs | 49 ++++++++++++ core/http/src/routing/mod.rs | 2 + core/http/src/routing/routes.rs | 93 ++++++++++++++++++++++ core/http/src/setup.rs | 47 ++++++----- site/hello.html | 12 --- site/static/hello.html | 7 ++ 11 files changed, 340 insertions(+), 168 deletions(-) delete mode 100644 core/http/src/handlers.rs create mode 100644 core/http/src/handlers/file_handlers.rs create mode 100644 core/http/src/handlers/handlers.rs create mode 100644 core/http/src/handlers/mod.rs delete mode 100644 core/http/src/routing.rs create mode 100644 core/http/src/routing/methods.rs create mode 100644 core/http/src/routing/mod.rs create mode 100644 core/http/src/routing/routes.rs delete mode 100644 site/hello.html create mode 100644 site/static/hello.html diff --git a/core/http/src/handlers.rs b/core/http/src/handlers.rs deleted file mode 100644 index 194eca3..0000000 --- a/core/http/src/handlers.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::{ - fs, - io::{BufRead, BufReader, Write}, - net::TcpStream, - path::PathBuf, -}; - -use mime::Mime; - -pub fn handle_connection(mut stream: TcpStream) { - let buf_reader = BufReader::new(&mut stream); - let http_request: Vec<_> = buf_reader - .lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()) - .collect(); - - let path = parse_request(&http_request[0]); - - let status_line; - let mime_type; - let contents = if let Ok(file) = fs::read(&path) { - status_line = "HTTP/1.1 200 OK"; - mime_type = find_mimetype(&path.to_str().unwrap().to_string()); - file - } else { - status_line = "HTTP/1.1 404 NOT FOUND"; - mime_type = find_mimetype(&"html".to_string()); - fs::read("404.html").unwrap() - }; - - println!("{mime_type}"); - - let response = format!( - "{}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - status_line, - contents.len(), - mime_type - ); - - stream.write_all(response.as_bytes()).unwrap(); - stream.write(&contents).unwrap(); -} - -fn parse_request(request: &str) -> PathBuf { - let path_elements = request - .split(" ") - .nth(1) - .unwrap() - .split("/") - .filter(|&val| val != ".." && val != "") - .collect::<Vec<&str>>(); - PathBuf::from(format!("./{}", path_elements.join("/"))) -} - -fn find_mimetype(filename: &String) -> Mime { - let parts: Vec<&str> = filename.split('.').collect(); - - let res = match parts.last() { - Some(v) => match *v { - "png" => mime::IMAGE_PNG, - "jpg" => mime::IMAGE_JPEG, - "json" => mime::APPLICATION_JSON, - "html" => mime::TEXT_HTML, - "css" => mime::TEXT_CSS, - &_ => mime::TEXT_PLAIN, - }, - None => mime::TEXT_PLAIN, - }; - return res; -} diff --git a/core/http/src/handlers/file_handlers.rs b/core/http/src/handlers/file_handlers.rs new file mode 100644 index 0000000..708a046 --- /dev/null +++ b/core/http/src/handlers/file_handlers.rs @@ -0,0 +1,65 @@ +use std::{fs, path::PathBuf}; + +use mime::Mime; + +use crate::routing::routes::{ResponseBody, Status}; + +#[derive(Debug)] +pub struct NamedFile { + pub content_len: usize, + pub content_type: Mime, + pub content: Vec<u8>, +} + +impl ResponseBody for NamedFile { + fn get_data(&self) -> Vec<u8> { + self.content.clone() + } +} + +impl NamedFile { + pub fn open(path: PathBuf) -> Result<NamedFile, Status> { + let path = proove_path(path); + let data = fs::read(&path); + let data = match data { + Ok(dat) => { + println!("{:?}", dat); + dat + } + Err(_) => { + return Err(Status { code: 404 }); + } + }; + println!("{:?}", data); + Ok(NamedFile { + content_len: data.len(), + content_type: find_mimetype(path.to_str().unwrap()), + content: data, + }) + } +} + +fn proove_path(path: PathBuf) -> PathBuf { + PathBuf::from( + path.to_str() + .unwrap() + .split("/") + .filter(|&val| val != ".." && val != "") + .collect::<Vec<&str>>() + .join("/"), + ) +} + +pub fn find_mimetype(filename: &str) -> Mime { + match filename.split('.').last() { + Some(v) => match v { + "png" => mime::IMAGE_PNG, + "jpg" => mime::IMAGE_JPEG, + "json" => mime::APPLICATION_JSON, + "html" => mime::TEXT_HTML, + "css" => mime::TEXT_CSS, + &_ => mime::TEXT_PLAIN, + }, + None => mime::TEXT_PLAIN, + } +} diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs new file mode 100644 index 0000000..33a6dbd --- /dev/null +++ b/core/http/src/handlers/handlers.rs @@ -0,0 +1,100 @@ +use std::{ + fs, + io::{BufRead, BufReader, Write}, + net::TcpStream, + path::PathBuf, +}; + +use crate::routing::methods::Method; +use crate::routing::routes::{Data, Outcome, Request, Response, Status}; + +use super::file_handlers::NamedFile; + +pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { + let buf_reader = BufReader::new(&mut stream); + let http_request: Vec<String> = buf_reader + .lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); + let request_status_line = http_request.get(0); + if request_status_line == None { + return; + } + let request_status_line = request_status_line.unwrap(); + let request = Request { + uri: &request_status_line.split(" ").nth(1).unwrap(), + headers: http_request.clone(), + method: Method::Get, + }; + + let data = Data { + buffer: vec![], + is_complete: true, + }; + + let handled_response = match handler(request, data) { + Outcome::Success(success) => ( + format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) + + &success.headers.join("\r\n") + + "\r\n\r\n", + success.body.get_data(), + ), + Outcome::Failure(error) => { + let content = fs::read("./404.html").unwrap(); + ( + format!( + "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", + error.code, + content.len() + ), + content, + ) + } + Outcome::Forward(_) => ( + format!("HTTP/1.1 {} NOT FOUND", 404), + fs::read("./404.html").unwrap(), + ), + }; + println!("{:?}", handled_response); + + stream.write_all(handled_response.0.as_bytes()).unwrap(); + stream.write(&handled_response.1).unwrap(); +} + +fn parse_request<'a>(request: &'a str, mountpoint: &'a str) -> Result<PathBuf, &'a str> { + let uri = request.split(" ").nth(1).unwrap(); + if !uri.starts_with(mountpoint) { + return Err("Request isn't on mountpoint"); + } + let path_elements = uri + .split_once(mountpoint) + .map(|(_, rest)| rest) + .unwrap() + .split("/") + .filter(|&val| val != ".." && val != "") + .collect::<Vec<&str>>(); + Ok(PathBuf::from(format!("./{}", path_elements.join("/")))) +} + +fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { + let response = fileserver(request.uri); + println!("{:?}", response); + let response = match response { + Ok(dat) => Response { + headers: vec![ + format!("Content-Length: {}", dat.content_len), + format!("Content-Type: {}", dat.content_type), + ], + status: Some(Status { code: 200 }), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status { code: 404 }), + }; + Outcome::Success(response) +} + +fn fileserver(path: &str) -> Result<NamedFile, Status> { + println!("fileserver started"); + NamedFile::open(PathBuf::from("static/".to_string() + path)) +} diff --git a/core/http/src/handlers/mod.rs b/core/http/src/handlers/mod.rs new file mode 100644 index 0000000..826c4f4 --- /dev/null +++ b/core/http/src/handlers/mod.rs @@ -0,0 +1,2 @@ +pub mod file_handlers; +pub mod handlers; diff --git a/core/http/src/routing.rs b/core/http/src/routing.rs deleted file mode 100644 index 2c5f74e..0000000 --- a/core/http/src/routing.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub struct RoutInfo { - name: Option<&'static str>, - method: Method, - path: &'static str, - // handler: fn(Request) -> Outcome, - format: Option<Format>, - rank: Option<isize>, -} - -pub struct Route { - name: Option<&'static str>, - method: Method, - // handler: fn(Request) -> Outcome, - uri: &'static str, - rank: isize, - format: Option<Format>, -} - -impl Route { - pub fn from(routeinfo: RoutInfo) -> Self { - let rank = routeinfo.rank.unwrap_or(0); - Route { - name: routeinfo.name, - method: routeinfo.method, - // handler: routeinfo.handler, - uri: routeinfo.path, - rank, - format: routeinfo.format, - } - } -} - -struct Request<'a> { - uri: &'a str, - headers: Vec<&'a str>, -} - -enum Outcome<S, E, F> { - Success(S), - Failure(E), - Forward(F), -} - -enum Method { - Get, - Head, - Post, - Put, - Delete, - Connect, - Options, - Trace, - Patch, -} - -enum Format { - Json, - Plain, - Html, -} diff --git a/core/http/src/routing/methods.rs b/core/http/src/routing/methods.rs new file mode 100644 index 0000000..328e697 --- /dev/null +++ b/core/http/src/routing/methods.rs @@ -0,0 +1,49 @@ +use std::{fmt::Display, str::FromStr}; + +#[derive(PartialEq, Eq)] +pub enum Method { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch, +} + +impl Display for Method { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Get => write!(f, "GET"), + Self::Head => write!(f, "HEAD"), + Self::Post => write!(f, "POST"), + Self::Put => write!(f, "PUT"), + Self::Delete => write!(f, "DELETE"), + Self::Connect => write!(f, "CONNECT"), + Self::Options => write!(f, "OPTIONS"), + Self::Trace => write!(f, "TRACE"), + Self::Patch => write!(f, "PATCH"), + } + } +} + +impl FromStr for Method { + type Err = &'static str; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "GET" => Ok(Self::Get), + "HEAD" => Ok(Self::Head), + "POST" => Ok(Self::Post), + "PUT" => Ok(Self::Put), + "DELETE" => Ok(Self::Delete), + "CONNECT" => Ok(Self::Connect), + "OPTIONS" => Ok(Self::Options), + "TRACE" => Ok(Self::Trace), + "PATCH" => Ok(Self::Patch), + _ => Err("Not a Method"), + } + } +} diff --git a/core/http/src/routing/mod.rs b/core/http/src/routing/mod.rs new file mode 100644 index 0000000..f1e52f9 --- /dev/null +++ b/core/http/src/routing/mod.rs @@ -0,0 +1,2 @@ +pub mod methods; +pub mod routes; diff --git a/core/http/src/routing/routes.rs b/core/http/src/routing/routes.rs new file mode 100644 index 0000000..05ecf74 --- /dev/null +++ b/core/http/src/routing/routes.rs @@ -0,0 +1,93 @@ +use std::net::SocketAddr; + +use super::methods::Method; + +pub trait ResponseBody { + fn get_data(&self) -> Vec<u8>; +} + +pub struct RoutInfo { + name: Option<&'static str>, + method: Method, + path: &'static str, + handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + format: Option<MediaType>, + rank: Option<isize>, +} + +pub struct Route<'a> { + name: Option<&'static str>, + method: Method, + uri: Uri<'a>, + handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + rank: isize, + format: Option<MediaType>, +} + +impl Route<'_> { + pub fn from(routeinfo: RoutInfo) -> Self { + let rank = routeinfo.rank.unwrap_or(0); + Route { + name: routeinfo.name, + method: routeinfo.method, + uri: routeinfo.path, + handler: routeinfo.handler, + rank, + format: routeinfo.format, + } + } +} + +pub struct Request<'a> { + pub uri: Uri<'a>, + pub headers: HeaderMap, + pub method: Method, + // pub connection: ConnectionMeta, +} + +struct ConnectionMeta { + remote: Option<SocketAddr>, + // certificates +} + +type HeaderMap = Vec<String>; +type Uri<'a> = &'a str; + +pub enum Outcome<S, E, F> { + Success(S), + Failure(E), + Forward(F), +} + +enum MediaType { + Json, + Plain, + Html, +} + +#[derive(Debug)] +pub struct Status { + pub code: u16, +} + +pub struct Body { + pub size: Option<usize>, + pub body: Vec<u8>, +} + +impl ResponseBody for Body { + fn get_data(&self) -> Vec<u8> { + self.body.clone() + } +} + +pub struct Response { + pub headers: HeaderMap, + pub status: Option<Status>, + pub body: Box<dyn ResponseBody>, +} + +pub struct Data { + pub buffer: Vec<u8>, + pub is_complete: bool, +} diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index b69bc0f..d3b106c 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -7,33 +7,27 @@ use std::{ thread::available_parallelism, }; -use crate::{handlers::handle_connection, routing::Route, threading::ThreadPool}; +use crate::{handlers::handlers::handle_connection, routing::routes::Route, threading::ThreadPool}; -pub struct PreMountConfig { - address: TcpListener, - workers: usize, - threadpool: ThreadPool, -} -impl PreMountConfig { - pub fn mount(self, mountpoint: &str, routes: Vec<Route>) -> Config { - Config { - mountpoint, - address: self.address, - workers: self.workers, - threadpool: self.threadpool, - routes, - } - } -} pub struct Config<'a> { - mountpoint: &'a str, + mountpoints: Option<Vec<&'static str>>, address: TcpListener, - workers: usize, threadpool: ThreadPool, - routes: Vec<Route>, + routes: Option<Vec<Route<'a>>>, } -impl Config<'_> { +impl<'a> Config<'a> { + pub fn mount(self, mountpoint: &'static str, routes: Vec<Route<'static>>) -> Config<'a> { + if self.mountpoints == None { + return Config { + mountpoints: Some(vec![mountpoint]), + routes: Some(routes), + ..self + }; + } else { + return self; + } + } pub fn launch(self) { let running = Arc::new(AtomicBool::new(true)); @@ -54,11 +48,13 @@ impl Config<'_> { break; } let stream = stream.unwrap(); - self.threadpool.execute(|| handle_connection(stream)) + let mountpoint = self.mountpoints.clone().unwrap(); + self.threadpool + .execute(move || handle_connection(stream, &mountpoint[0].to_string())) } } } -pub fn build(ip: &str) -> PreMountConfig { +pub fn build(ip: &str) -> Config { let listener = TcpListener::bind(ip).unwrap(); let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); if ip.len() != 2 { @@ -77,9 +73,10 @@ pub fn build(ip: &str) -> PreMountConfig { Server has launched from {ip}:{port}. " ); - PreMountConfig { + Config { + mountpoints: None, + routes: None, address: listener, - workers, threadpool, } } diff --git a/site/hello.html b/site/hello.html deleted file mode 100644 index 52ef689..0000000 --- a/site/hello.html +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <title>Hello!</title> - <link rel="stylesheet" href="/hello.css" /> - </head> - <body> - <h1>Hello!</h1> - <p>Hi from Rust</p> - </body> -</html> diff --git a/site/static/hello.html b/site/static/hello.html new file mode 100644 index 0000000..1afeefd --- /dev/null +++ b/site/static/hello.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8" /> + <title>JE</title> + </head> +</html> -- GitLab From d611125a9b6f640701a17607ccc7dcd78859fe6f Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 13 May 2023 20:23:45 +0200 Subject: [PATCH 06/65] Remove unnecessary println!s of file bytes --- core/http/src/handlers/file_handlers.rs | 6 +----- core/http/src/handlers/handlers.rs | 3 --- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/core/http/src/handlers/file_handlers.rs b/core/http/src/handlers/file_handlers.rs index 708a046..879da03 100644 --- a/core/http/src/handlers/file_handlers.rs +++ b/core/http/src/handlers/file_handlers.rs @@ -22,15 +22,11 @@ impl NamedFile { let path = proove_path(path); let data = fs::read(&path); let data = match data { - Ok(dat) => { - println!("{:?}", dat); - dat - } + Ok(dat) => dat, Err(_) => { return Err(Status { code: 404 }); } }; - println!("{:?}", data); Ok(NamedFile { content_len: data.len(), content_type: find_mimetype(path.to_str().unwrap()), diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 33a6dbd..4f9073e 100644 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -56,7 +56,6 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { fs::read("./404.html").unwrap(), ), }; - println!("{:?}", handled_response); stream.write_all(handled_response.0.as_bytes()).unwrap(); stream.write(&handled_response.1).unwrap(); @@ -79,7 +78,6 @@ fn parse_request<'a>(request: &'a str, mountpoint: &'a str) -> Result<PathBuf, & fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { let response = fileserver(request.uri); - println!("{:?}", response); let response = match response { Ok(dat) => Response { headers: vec![ @@ -95,6 +93,5 @@ fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { } fn fileserver(path: &str) -> Result<NamedFile, Status> { - println!("fileserver started"); NamedFile::open(PathBuf::from("static/".to_string() + path)) } -- GitLab From a6ffcabd22044eb87ce6f2c28ce7405967ab696a Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 14 May 2023 13:25:15 +0200 Subject: [PATCH 07/65] Add addable routes --- core/http/src/handlers/handlers.rs | 112 ++++++++++++++--------------- core/http/src/lib.rs | 4 +- core/http/src/routing/methods.rs | 2 +- core/http/src/routing/routes.rs | 42 ++++++++--- core/http/src/setup.rs | 42 ++++++----- site/404.html | 6 +- site/src/main.rs | 43 ++++++++++- 7 files changed, 163 insertions(+), 88 deletions(-) mode change 100644 => 100755 core/http/src/handlers/handlers.rs diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs old mode 100644 new mode 100755 index 4f9073e..c3e06cf --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -1,16 +1,15 @@ use std::{ - fs, io::{BufRead, BufReader, Write}, net::TcpStream, path::PathBuf, }; -use crate::routing::methods::Method; use crate::routing::routes::{Data, Outcome, Request, Response, Status}; +use crate::setup::MountPoint; use super::file_handlers::NamedFile; -pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { +pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let buf_reader = BufReader::new(&mut stream); let http_request: Vec<String> = buf_reader .lines() @@ -25,7 +24,12 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { let request = Request { uri: &request_status_line.split(" ").nth(1).unwrap(), headers: http_request.clone(), - method: Method::Get, + method: request_status_line + .split(" ") + .next() + .unwrap() + .parse() + .unwrap(), }; let data = Data { @@ -33,65 +37,57 @@ pub fn handle_connection(mut stream: TcpStream, _mountpoint: &str) { is_complete: true, }; - let handled_response = match handler(request, data) { - Outcome::Success(success) => ( - format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) - + &success.headers.join("\r\n") - + "\r\n\r\n", - success.body.get_data(), - ), - Outcome::Failure(error) => { - let content = fs::read("./404.html").unwrap(); - ( - format!( - "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: text/html\r\n\r\n", - error.code, - content.len() - ), - content, - ) + let mut handled_response: Option<Outcome<Response, Status, Data>> = None; + for mountpoint in mountpoints { + if !request.uri.starts_with(mountpoint.mountpoint) { + println!("MOUNTPOINT COMPARISON {}", request.uri); + continue; + } + let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); + for route in mountpoint.routes { + if route.method != request.method { + println!("METHOD COMPARE {}", request.method); + continue; + } + if !route.compare_uri(mounted_request_uri) { + println!("URI COMPARISON {} {}", mounted_request_uri, route.uri); + continue; + } + handled_response = Some((route.handler)( + Request { + uri: mounted_request_uri, + ..request.clone() + }, + data.clone(), + )); } - Outcome::Forward(_) => ( - format!("HTTP/1.1 {} NOT FOUND", 404), - fs::read("./404.html").unwrap(), - ), - }; - - stream.write_all(handled_response.0.as_bytes()).unwrap(); - stream.write(&handled_response.1).unwrap(); -} - -fn parse_request<'a>(request: &'a str, mountpoint: &'a str) -> Result<PathBuf, &'a str> { - let uri = request.split(" ").nth(1).unwrap(); - if !uri.starts_with(mountpoint) { - return Err("Request isn't on mountpoint"); } - let path_elements = uri - .split_once(mountpoint) - .map(|(_, rest)| rest) - .unwrap() - .split("/") - .filter(|&val| val != ".." && val != "") - .collect::<Vec<&str>>(); - Ok(PathBuf::from(format!("./{}", path_elements.join("/")))) -} -fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { - let response = fileserver(request.uri); - let response = match response { - Ok(dat) => Response { - headers: vec![ - format!("Content-Length: {}", dat.content_len), - format!("Content-Type: {}", dat.content_type), - ], - status: Some(Status { code: 200 }), - body: Box::new(dat), + let response = match handled_response { + Some(val) => match val { + Outcome::Success(success) => ( + format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) + + &success.headers.join("\r\n") + + "\r\n\r\n", + success.body.get_data(), + ), + Outcome::Failure(error) => handler_404(error), + Outcome::Forward(_) => handler_404(Status { code: 404 }), }, - Err(_) => return Outcome::Failure(Status { code: 404 }), + None => handler_404(Status { code: 404 }), }; - Outcome::Success(response) + + stream.write_all(response.0.as_bytes()).unwrap(); + stream.write(&response.1).unwrap(); } -fn fileserver(path: &str) -> Result<NamedFile, Status> { - NamedFile::open(PathBuf::from("static/".to_string() + path)) +fn handler_404(status: Status) -> (String, Vec<u8>) { + let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); + ( + format!( + "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", + status.code, page_404.content_len, page_404.content_type + ), + page_404.content, + ) } diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 0b86db5..bf072c9 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,5 +1,5 @@ -mod handlers; -mod routing; +pub mod handlers; +pub mod routing; mod setup; mod threading; diff --git a/core/http/src/routing/methods.rs b/core/http/src/routing/methods.rs index 328e697..998cfa5 100644 --- a/core/http/src/routing/methods.rs +++ b/core/http/src/routing/methods.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Clone, Debug, Copy)] pub enum Method { Get, Head, diff --git a/core/http/src/routing/routes.rs b/core/http/src/routing/routes.rs index 05ecf74..6da1218 100644 --- a/core/http/src/routing/routes.rs +++ b/core/http/src/routing/routes.rs @@ -15,13 +15,14 @@ pub struct RoutInfo { rank: Option<isize>, } +#[derive(Clone, Copy)] pub struct Route<'a> { - name: Option<&'static str>, - method: Method, - uri: Uri<'a>, - handler: fn(Request, Data) -> Outcome<Response, Status, Data>, - rank: isize, - format: Option<MediaType>, + pub name: Option<&'static str>, + pub method: Method, + pub uri: Uri<'a>, + pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + pub rank: isize, + pub format: Option<MediaType>, } impl Route<'_> { @@ -36,8 +37,29 @@ impl Route<'_> { format: routeinfo.format, } } + pub fn compare_uri(self, uri: Uri) -> bool { + let mut iter_comp_str = uri.split("/"); + for true_str in self.uri.split("/") { + let comp_str = if let Some(str) = iter_comp_str.next() { + str + } else { + return false; + }; + if true_str.starts_with("<") && true_str.ends_with("..>") { + return true; + } + if true_str.starts_with("<") && true_str.ends_with(">") { + continue; + } + if true_str != comp_str { + return false; + } + } + return true; + } } +#[derive(Clone)] pub struct Request<'a> { pub uri: Uri<'a>, pub headers: HeaderMap, @@ -51,15 +73,17 @@ struct ConnectionMeta { } type HeaderMap = Vec<String>; -type Uri<'a> = &'a str; +pub type Uri<'a> = &'a str; +#[derive(Debug)] pub enum Outcome<S, E, F> { Success(S), Failure(E), Forward(F), } -enum MediaType { +#[derive(Clone, Debug, Copy)] +pub enum MediaType { Json, Plain, Html, @@ -70,6 +94,7 @@ pub struct Status { pub code: u16, } +#[derive(Debug)] pub struct Body { pub size: Option<usize>, pub body: Vec<u8>, @@ -87,6 +112,7 @@ pub struct Response { pub body: Box<dyn ResponseBody>, } +#[derive(Debug, Clone)] pub struct Data { pub buffer: Vec<u8>, pub is_complete: bool, diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index d3b106c..35c8a06 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -7,26 +7,36 @@ use std::{ thread::available_parallelism, }; -use crate::{handlers::handlers::handle_connection, routing::routes::Route, threading::ThreadPool}; +use crate::{ + handlers::handlers::handle_connection, + routing::routes::{Route, Uri}, + threading::ThreadPool, +}; + +#[derive(Clone)] +pub struct MountPoint<'a> { + pub mountpoint: Uri<'a>, + pub routes: Vec<Route<'a>>, +} -pub struct Config<'a> { - mountpoints: Option<Vec<&'static str>>, +pub struct Config { + mountpoints: Option<Vec<MountPoint<'static>>>, address: TcpListener, threadpool: ThreadPool, - routes: Option<Vec<Route<'a>>>, } -impl<'a> Config<'a> { - pub fn mount(self, mountpoint: &'static str, routes: Vec<Route<'static>>) -> Config<'a> { - if self.mountpoints == None { - return Config { - mountpoints: Some(vec![mountpoint]), - routes: Some(routes), - ..self - }; +impl<'a> Config { + pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config { + let mut temp_mountpoints = None; + std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints); + + if let Some(mut mountpoints) = temp_mountpoints { + mountpoints.push(MountPoint { mountpoint, routes }); + self.mountpoints = Some(mountpoints); } else { - return self; + self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]); } + self } pub fn launch(self) { let running = Arc::new(AtomicBool::new(true)); @@ -42,15 +52,14 @@ impl<'a> Config<'a> { running_for_handler.store(false, atomic::Ordering::SeqCst); }) .expect("Error setting Ctrl-C handler"); - for stream in self.address.incoming() { if !running.load(atomic::Ordering::SeqCst) { break; } let stream = stream.unwrap(); - let mountpoint = self.mountpoints.clone().unwrap(); + let mountpoints = self.mountpoints.clone().unwrap(); self.threadpool - .execute(move || handle_connection(stream, &mountpoint[0].to_string())) + .execute(move || handle_connection(stream, mountpoints)); } } } @@ -75,7 +84,6 @@ Server has launched from {ip}:{port}. ); Config { mountpoints: None, - routes: None, address: listener, threadpool, } diff --git a/site/404.html b/site/404.html index 3a6ceb5..550831f 100644 --- a/site/404.html +++ b/site/404.html @@ -3,10 +3,14 @@ <head> <meta charset="utf-8" /> <title>404</title> - <link rel="stylesheet" href="/hello.css" /> </head> <body> <h1>404</h1> <p>Hi from Rust</p> + <form action="/" method="POST"> + <label for="message">Enter your message:</label> + <input type="text" id="message" name="message" /> + <button type="submit">Send</button> + </form> </body> </html> diff --git a/site/src/main.rs b/site/src/main.rs index 55ea439..8974db9 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,3 +1,44 @@ +use std::path::PathBuf; + +use http::{ + handlers::file_handlers::NamedFile, + routing::routes::{Data, Outcome, Request, Response, Route, Status}, +}; + +fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { + println!("called"); + let response = fileserver(request.uri.strip_prefix("static/").unwrap()); + let response = match response { + Ok(dat) => Response { + headers: vec![ + format!("Content-Length: {}", dat.content_len), + format!("Content-Type: {}", dat.content_type), + ], + status: Some(Status { code: 200 }), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status { code: 404 }), + }; + println!("{:?}", response.headers); + Outcome::Success(response) +} + +fn fileserver(path: &str) -> Result<NamedFile, Status> { + let file = NamedFile::open(PathBuf::from("static/".to_string() + path)); + return file; +} + fn main() { - http::build("127.0.0.1:8000").mount("/", vec![]).launch(); + let fileserver = Route { + format: None, + handler, + name: Some("file_server"), + uri: "static/<path..>", + method: http::routing::methods::Method::Get, + rank: 0, + }; + + http::build("127.0.0.1:8000") + .mount("/", vec![fileserver]) + .launch(); } -- GitLab From 19c473eaac70e0ceefd0cd3eeab71db8bb2e22f1 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 15 May 2023 21:11:33 +0200 Subject: [PATCH 08/65] Refactoring a lot of stuff --- core/http/src/handlers/handlers.rs | 26 ++++--- core/http/src/handlers/mod.rs | 1 - .../{handlers => handling}/file_handlers.rs | 10 ++- .../http/src/{routing => handling}/methods.rs | 0 core/http/src/handling/mod.rs | 5 ++ core/http/src/handling/request.rs | 24 ++++++ core/http/src/handling/response.rs | 70 ++++++++++++++++++ core/http/src/{routing => handling}/routes.rs | 62 ++-------------- core/http/src/lib.rs | 2 +- core/http/src/routing/mod.rs | 2 - core/http/src/setup.rs | 2 +- site/404.html | 3 + site/src/main.rs | 20 ++--- site/static/hello.css | 4 + site/static/hello.html | 7 +- site/static/img.jpg | Bin 0 -> 51549 bytes tree.txt | 44 +++++++++++ 17 files changed, 198 insertions(+), 84 deletions(-) rename core/http/src/{handlers => handling}/file_handlers.rs (87%) rename core/http/src/{routing => handling}/methods.rs (100%) create mode 100644 core/http/src/handling/mod.rs create mode 100644 core/http/src/handling/request.rs create mode 100644 core/http/src/handling/response.rs rename core/http/src/{routing => handling}/routes.rs (63%) delete mode 100644 core/http/src/routing/mod.rs create mode 100644 site/static/hello.css create mode 100644 site/static/img.jpg create mode 100644 tree.txt diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index c3e06cf..2045ee5 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -4,11 +4,14 @@ use std::{ path::PathBuf, }; -use crate::routing::routes::{Data, Outcome, Request, Response, Status}; +use crate::handling::{ + file_handlers::NamedFile, + request::Request, + response::{Outcome, Response, ResponseBody, Status}, + routes::Data, +}; use crate::setup::MountPoint; -use super::file_handlers::NamedFile; - pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let buf_reader = BufReader::new(&mut stream); let http_request: Vec<String> = buf_reader @@ -40,17 +43,14 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { if !request.uri.starts_with(mountpoint.mountpoint) { - println!("MOUNTPOINT COMPARISON {}", request.uri); continue; } let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); for route in mountpoint.routes { if route.method != request.method { - println!("METHOD COMPARE {}", request.method); continue; } if !route.compare_uri(mounted_request_uri) { - println!("URI COMPARISON {} {}", mounted_request_uri, route.uri); continue; } handled_response = Some((route.handler)( @@ -71,23 +71,25 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { + "\r\n\r\n", success.body.get_data(), ), - Outcome::Failure(error) => handler_404(error), - Outcome::Forward(_) => handler_404(Status { code: 404 }), + Outcome::Failure(error) => failure_handler(error), + Outcome::Forward(_) => failure_handler(Status { code: 404 }), }, - None => handler_404(Status { code: 404 }), + None => failure_handler(Status { code: 404 }), }; stream.write_all(response.0.as_bytes()).unwrap(); stream.write(&response.1).unwrap(); } -fn handler_404(status: Status) -> (String, Vec<u8>) { +fn failure_handler(status: Status) -> (String, Vec<u8>) { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); ( format!( "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - status.code, page_404.content_len, page_404.content_type + status.code, + page_404.get_len(), + page_404.get_mime() ), - page_404.content, + page_404.get_data(), ) } diff --git a/core/http/src/handlers/mod.rs b/core/http/src/handlers/mod.rs index 826c4f4..c3d4495 100644 --- a/core/http/src/handlers/mod.rs +++ b/core/http/src/handlers/mod.rs @@ -1,2 +1 @@ -pub mod file_handlers; pub mod handlers; diff --git a/core/http/src/handlers/file_handlers.rs b/core/http/src/handling/file_handlers.rs similarity index 87% rename from core/http/src/handlers/file_handlers.rs rename to core/http/src/handling/file_handlers.rs index 879da03..fdfd3d0 100644 --- a/core/http/src/handlers/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -2,7 +2,7 @@ use std::{fs, path::PathBuf}; use mime::Mime; -use crate::routing::routes::{ResponseBody, Status}; +use crate::handling::response::{ResponseBody, Status}; #[derive(Debug)] pub struct NamedFile { @@ -15,6 +15,14 @@ impl ResponseBody for NamedFile { fn get_data(&self) -> Vec<u8> { self.content.clone() } + + fn get_mime(&self) -> Mime { + self.content_type.clone() + } + + fn get_len(&self) -> usize { + self.content_len + } } impl NamedFile { diff --git a/core/http/src/routing/methods.rs b/core/http/src/handling/methods.rs similarity index 100% rename from core/http/src/routing/methods.rs rename to core/http/src/handling/methods.rs diff --git a/core/http/src/handling/mod.rs b/core/http/src/handling/mod.rs new file mode 100644 index 0000000..20cf31e --- /dev/null +++ b/core/http/src/handling/mod.rs @@ -0,0 +1,5 @@ +pub mod file_handlers; +pub mod methods; +pub mod request; +pub mod response; +pub mod routes; diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs new file mode 100644 index 0000000..d37ebb5 --- /dev/null +++ b/core/http/src/handling/request.rs @@ -0,0 +1,24 @@ +use std::net::SocketAddr; + +use super::{methods::Method, routes::Uri}; + +type HeaderMap = Vec<String>; +#[derive(Clone)] +pub struct Request<'a> { + pub uri: Uri<'a>, + pub headers: HeaderMap, + pub method: Method, + // pub connection: ConnectionMeta, +} + +struct ConnectionMeta { + remote: Option<SocketAddr>, + // certificates +} + +#[derive(Clone, Debug, Copy)] +pub enum MediaType { + Json, + Plain, + Html, +} diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs new file mode 100644 index 0000000..9ca6f4b --- /dev/null +++ b/core/http/src/handling/response.rs @@ -0,0 +1,70 @@ +use mime::Mime; + +use super::routes::Body; + +type HeaderMap = Vec<String>; + +#[derive(Debug)] +pub enum Outcome<S, E, F> { + Success(S), + Failure(E), + Forward(F), +} + +pub trait ResponseBody { + fn get_data(&self) -> Vec<u8>; + fn get_mime(&self) -> Mime; + fn get_len(&self) -> usize; +} + +impl ResponseBody for Body { + fn get_data(&self) -> Vec<u8> { + self.body.clone() + } + fn get_mime(&self) -> Mime { + mime::TEXT_PLAIN + } + + fn get_len(&self) -> usize { + self.get_data().len() + } +} + +impl ResponseBody for &str { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + mime::TEXT_PLAIN + } + + fn get_len(&self) -> usize { + self.len() + } +} + +impl ResponseBody for String { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + mime::TEXT_PLAIN + } + + fn get_len(&self) -> usize { + self.len() + } +} + +pub struct Response { + pub headers: HeaderMap, + pub status: Option<Status>, + pub body: Box<dyn ResponseBody>, +} + +#[derive(Debug)] +pub struct Status { + pub code: u16, +} diff --git a/core/http/src/routing/routes.rs b/core/http/src/handling/routes.rs similarity index 63% rename from core/http/src/routing/routes.rs rename to core/http/src/handling/routes.rs index 6da1218..fd3512d 100644 --- a/core/http/src/routing/routes.rs +++ b/core/http/src/handling/routes.rs @@ -1,10 +1,8 @@ -use std::net::SocketAddr; - -use super::methods::Method; - -pub trait ResponseBody { - fn get_data(&self) -> Vec<u8>; -} +use super::{ + methods::Method, + request::{MediaType, Request}, + response::{Outcome, Response, Status}, +}; pub struct RoutInfo { name: Option<&'static str>, @@ -55,63 +53,17 @@ impl Route<'_> { return false; } } - return true; + true } } -#[derive(Clone)] -pub struct Request<'a> { - pub uri: Uri<'a>, - pub headers: HeaderMap, - pub method: Method, - // pub connection: ConnectionMeta, -} - -struct ConnectionMeta { - remote: Option<SocketAddr>, - // certificates -} - -type HeaderMap = Vec<String>; pub type Uri<'a> = &'a str; -#[derive(Debug)] -pub enum Outcome<S, E, F> { - Success(S), - Failure(E), - Forward(F), -} - -#[derive(Clone, Debug, Copy)] -pub enum MediaType { - Json, - Plain, - Html, -} - -#[derive(Debug)] -pub struct Status { - pub code: u16, -} - -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Body { - pub size: Option<usize>, pub body: Vec<u8>, } -impl ResponseBody for Body { - fn get_data(&self) -> Vec<u8> { - self.body.clone() - } -} - -pub struct Response { - pub headers: HeaderMap, - pub status: Option<Status>, - pub body: Box<dyn ResponseBody>, -} - #[derive(Debug, Clone)] pub struct Data { pub buffer: Vec<u8>, diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index bf072c9..7a1379c 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,5 +1,5 @@ pub mod handlers; -pub mod routing; +pub mod handling; mod setup; mod threading; diff --git a/core/http/src/routing/mod.rs b/core/http/src/routing/mod.rs deleted file mode 100644 index f1e52f9..0000000 --- a/core/http/src/routing/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod methods; -pub mod routes; diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 35c8a06..e2ba232 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -9,7 +9,7 @@ use std::{ use crate::{ handlers::handlers::handle_connection, - routing::routes::{Route, Uri}, + handling::routes::{Route, Uri}, threading::ThreadPool, }; diff --git a/site/404.html b/site/404.html index 550831f..d4c09f4 100644 --- a/site/404.html +++ b/site/404.html @@ -11,6 +11,9 @@ <label for="message">Enter your message:</label> <input type="text" id="message" name="message" /> <button type="submit">Send</button> + <label for="message">Enter your message:</label> + <input type="text" id="asdf" name="message" /> + <button type="submit">Send</button> </form> </body> </html> diff --git a/site/src/main.rs b/site/src/main.rs index 8974db9..da51a33 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,31 +1,31 @@ use std::path::PathBuf; -use http::{ - handlers::file_handlers::NamedFile, - routing::routes::{Data, Outcome, Request, Response, Route, Status}, +use http::handling::{ + file_handlers::NamedFile, + methods::Method, + request::Request, + response::{Outcome, Response, ResponseBody, Status}, + routes::{Data, Route}, }; fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { - println!("called"); let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { Ok(dat) => Response { headers: vec![ - format!("Content-Length: {}", dat.content_len), - format!("Content-Type: {}", dat.content_type), + format!("Content-Length: {}", dat.get_len()), + format!("Content-Type: {}", dat.get_mime()), ], status: Some(Status { code: 200 }), body: Box::new(dat), }, Err(_) => return Outcome::Failure(Status { code: 404 }), }; - println!("{:?}", response.headers); Outcome::Success(response) } fn fileserver(path: &str) -> Result<NamedFile, Status> { - let file = NamedFile::open(PathBuf::from("static/".to_string() + path)); - return file; + NamedFile::open(PathBuf::from("static/".to_string() + path)) } fn main() { @@ -34,7 +34,7 @@ fn main() { handler, name: Some("file_server"), uri: "static/<path..>", - method: http::routing::methods::Method::Get, + method: Method::Get, rank: 0, }; diff --git a/site/static/hello.css b/site/static/hello.css new file mode 100644 index 0000000..aba0df0 --- /dev/null +++ b/site/static/hello.css @@ -0,0 +1,4 @@ +body { + background-color: #212121; + color: #ffffff; +} diff --git a/site/static/hello.html b/site/static/hello.html index 1afeefd..ff042af 100644 --- a/site/static/hello.html +++ b/site/static/hello.html @@ -2,6 +2,11 @@ <html> <head> <meta charset="UTF-8" /> - <title>JE</title> + <title>Hello</title> + <link rel="stylesheet" href="/static/hello.css" /> </head> + <body> + <h1>Managed</h1> + <img src="/static/img.jpg" alt="" /> + </body> </html> diff --git a/site/static/img.jpg b/site/static/img.jpg new file mode 100644 index 0000000000000000000000000000000000000000..07eb7f85ac26c70344d552f56a81c44a0bf1b3db GIT binary patch literal 51549 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 B#&7@t literal 0 HcmV?d00001 diff --git a/tree.txt b/tree.txt new file mode 100644 index 0000000..05d432b --- /dev/null +++ b/tree.txt @@ -0,0 +1,44 @@ +. +├── core +│  ├── codegen +│  │  ├── Cargo.lock +│  │  ├── Cargo.toml +│  │  ├── src +│  │  │  └── lib.rs +│  ├── html +│  │  ├── Cargo.lock +│  │  ├── Cargo.toml +│  │  └── src +│  │    └── lib.rs +│  └── http +│  ├── Cargo.lock +│  ├── Cargo.toml +│  └── src +│    ├── handlers +│    │  ├── file_handlers.rs +│    │  ├── handlers.rs +│    │  └── mod.rs +│    ├── lib.rs +│    ├── routing +│    │  ├── methods.rs +│    │  ├── mod.rs +│    │  └── routes.rs +│    ├── setup.rs +│    ├── threading.rs +│    └── utils +│    └── mod.rs +├── README.md +├── site +│  ├── 404.html +│  ├── Cargo.lock +│  ├── Cargo.toml +│  ├── hello.css +│  ├── img.jpg +│  ├── src +│  │  └── main.rs +│  ├── static +│  │  └── hello.html +│  └── target +│  └── CACHEDIR.TAG +└── tree.txt + -- GitLab From 3eca9706cc21fb46fccfbaeaacef9bf83cf4da5f Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 18 May 2023 18:53:05 +0200 Subject: [PATCH 09/65] Adding route and mountpoint listing --- core/http/src/setup.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index e2ba232..4bacada 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -27,6 +27,24 @@ pub struct Config { impl<'a> Config { pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config { + let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint); + for (index, route) in routes.iter().enumerate() { + let indent_sign; + match index { + i if i == routes.len() - 1 => indent_sign = "└─", + _ => indent_sign = "├─", + }; + + mount_message += &format!( + " \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n", + route.name.unwrap_or(""), + route.method, + mountpoint, + route.uri + ) + } + + println!("{mount_message}"); let mut temp_mountpoints = None; std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints); @@ -78,9 +96,7 @@ pub fn build(ip: &str) -> Config { >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} -\n -Server has launched from {ip}:{port}. -" +\x1b[35m🛪 Mountpoints\x1b[0m" ); Config { mountpoints: None, -- GitLab From e40abea488d4a597f9567206b1aea542acd54b40 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 19 May 2023 13:59:18 +0200 Subject: [PATCH 10/65] Reading in body of requests with body --- core/http/src/handlers/handlers.rs | 55 +++++++++++++++++++++++++----- core/http/src/handling/request.rs | 9 +++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 2045ee5..c0e77e3 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -1,5 +1,5 @@ use std::{ - io::{BufRead, BufReader, Write}, + io::{BufRead, BufReader, Read, Write}, net::TcpStream, path::PathBuf, }; @@ -13,12 +13,20 @@ use crate::handling::{ use crate::setup::MountPoint; pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { - let buf_reader = BufReader::new(&mut stream); - let http_request: Vec<String> = buf_reader - .lines() - .map(|result| result.unwrap()) - .take_while(|line| !line.is_empty()) - .collect(); + let mut buf_reader = BufReader::new(&mut stream); + let mut http_request: Vec<String> = vec![]; + + loop { + let mut buffer = String::new(); + let _ = buf_reader.read_line(&mut buffer).unwrap(); + if buffer == "\r\n" { + break; + } + http_request.push(buffer); + } + + println!("{:#?}", http_request); + let request_status_line = http_request.get(0); if request_status_line == None { return; @@ -35,10 +43,39 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { .unwrap(), }; - let data = Data { + let mut data = Data { + is_complete: false, buffer: vec![], - is_complete: true, }; + if request.can_have_body() { + let length = if let Some(len) = request + .headers + .iter() + .filter(|header| header.contains("Content-Length: ")) + .clone() + .map(|header| { + let header = header.strip_prefix("Content-Length: ").unwrap(); + header.trim().parse::<usize>() + }) + .next() + { + if let Ok(size) = len { + size + } else { + 0 + } + } else { + 0 + }; + let mut buffer: Vec<u8> = vec![0; length]; + let read_len = buf_reader.read(&mut buffer).unwrap(); + if read_len != length { + eprintln!("User inputted invalid BUFLEN"); + return; + } + data.is_complete = true; + data.buffer = buffer; + } let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index d37ebb5..ba1cf37 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -22,3 +22,12 @@ pub enum MediaType { Plain, Html, } + +impl Request<'_> { + pub fn can_have_body(&self) -> bool { + match self.method { + Method::Post | Method::Put | Method::Patch | Method::Delete => true, + _ => false, + } + } +} -- GitLab From 8d669447dffb1e635e6e256036a0c3f19103bdc6 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 19 May 2023 14:55:14 +0200 Subject: [PATCH 11/65] Implement Display for HTTP Status Codes --- core/http/src/handlers/handlers.rs | 16 ++++- core/http/src/handling/response.rs | 97 ++++++++++++++++++++++++++++++ site/411.html | 9 +++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 site/411.html diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index c0e77e3..b34416b 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -70,7 +70,9 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let mut buffer: Vec<u8> = vec![0; length]; let read_len = buf_reader.read(&mut buffer).unwrap(); if read_len != length { - eprintln!("User inputted invalid BUFLEN"); + let respone = len_not_defined(Status { code: 411 }); + stream.write_all(respone.0.as_bytes()).unwrap(); + stream.write(&respone.1).unwrap(); return; } data.is_complete = true; @@ -130,3 +132,15 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) { page_404.get_data(), ) } + +fn len_not_defined(status: Status) -> (String, Vec<u8>) { + let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); + ( + format!( + "HTTP/1.1 {status}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", + page_411.get_len(), + page_411.get_mime() + ), + page_411.get_data(), + ) +} diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs index 9ca6f4b..603ca55 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use mime::Mime; use super::routes::Body; @@ -68,3 +70,98 @@ pub struct Response { pub struct Status { pub code: u16, } + +impl Display for Status { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.code { + // INFORMATIONAL RESPONSE + 100 => write!(f, "{} Continue", self.code), + 101 => write!(f, "{} Switching Protocols", self.code), + + // WebDAV + 102 => write!(f, "{} Processing", self.code), + + // Experimental + 103 => write!(f, "{} Early Hints", self.code), + + // SUCCESSFUL RESPONSE + 200 => write!(f, "{} OK", self.code), + 201 => write!(f, "{} Created", self.code), + 203 => write!(f, "{} Non-Authorative Information", self.code), + 204 => write!(f, "{} No Content", self.code), + 205 => write!(f, "{} Reset Content", self.code), + 206 => write!(f, "{} Partial Content", self.code), + + // WebDAV + 207 => write!(f, "{} Mutli-Status", self.code), + 208 => write!(f, "{} Already Reported", self.code), + + // HTTP Delta Encoding + 226 => write!(f, "{} IM Used", self.code), + + // REDIRECTION MESSAGES + 300 => write!(f, "{} Multiple Choices", self.code), + 301 => write!(f, "{} Moved Permanently", self.code), + 302 => write!(f, "{} Found", self.code), + 303 => write!(f, "{} See Other", self.code), + 304 => write!(f, "{} Not Modified", self.code), + 307 => write!(f, "{} Temporary Redirect", self.code), + 308 => write!(f, "{} Permanent Redirect", self.code), + + // Unused / Deprecated + 305 => write!(f, "{} Use Proxy", self.code), + 306 => write!(f, "{} unused", self.code), + + // CLIENT ERROR + 400 => write!(f, "{} Bad Request", self.code), + 401 => write!(f, "{} Unauthorized", self.code), + 403 => write!(f, "{} Forbidden", self.code), + 404 => write!(f, "{} Not Found", self.code), + 405 => write!(f, "{} Method Not Allowed", self.code), + 406 => write!(f, "{} Not Acceptable", self.code), + 407 => write!(f, "{} Proxy Athentication Required", self.code), + 408 => write!(f, "{} Request Timout", self.code), + 409 => write!(f, "{} Conflict", self.code), + 410 => write!(f, "{} Gone", self.code), + 411 => write!(f, "{} Length Required", self.code), + 412 => write!(f, "{} Precondition Failed", self.code), + 413 => write!(f, "{} Payload Too Large", self.code), + 414 => write!(f, "{} URI Too Long", self.code), + 415 => write!(f, "{} Unsupported Media Type", self.code), + 416 => write!(f, "{} Range Not Satisfiable", self.code), + 417 => write!(f, "{} Expectation Failed", self.code), + 418 => write!(f, "{} I'm a Teapot", self.code), + 421 => write!(f, "{} Misdirected Request", self.code), + 422 => write!(f, "{} Unprocessable Content", self.code), + 426 => write!(f, "{} Upgrade Required", self.code), + 428 => write!(f, "{} Precondition Required", self.code), + 429 => write!(f, "{} Too Many Requests", self.code), + 431 => write!(f, "{} Request Header Fields Too Large", self.code), + + 451 => write!(f, "{} Unavailable For Legal Reasons", self.code), + + // WebDAV + 423 => write!(f, "{} Locked", self.code), + 424 => write!(f, "{} Failed Dependency", self.code), + + // Experimental / Not in use + 402 => write!(f, "{} Payment Required", self.code), + 425 => write!(f, "{} Too Early", self.code), + + // SERVER ERROR RESPONSES + 500 => write!(f, "{} Internal Server Error", self.code), + 501 => write!(f, "{} Not Implmenented", self.code), + 502 => write!(f, "{} Bad Getaway", self.code), + 503 => write!(f, "{} Service Unavailable", self.code), + 504 => write!(f, "{} Getaway Timeout", self.code), + 505 => write!(f, "{} HTTP Version Not Supported", self.code), + 506 => write!(f, "{} Variant Also Negotiates", self.code), + 507 => write!(f, "{} Insufficient Storage", self.code), + 508 => write!(f, "{} Loop Detected", self.code), + 510 => write!(f, "{} Not Extendend", self.code), + 511 => write!(f, "{} Network Authentication Required", self.code), + + _ => write!(f, "500 "), + } + } +} diff --git a/site/411.html b/site/411.html new file mode 100644 index 0000000..802df86 --- /dev/null +++ b/site/411.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8" /> + <title>411</title> + </head> + <h1>411</h1> +</html> -- GitLab From 874798002e4bfa00ab56a361b458ee2cd46522b1 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 19 May 2023 22:40:58 +0200 Subject: [PATCH 12/65] Change Status to be an enum --- core/http/src/handlers/handlers.rs | 12 +- core/http/src/handling/file_handlers.rs | 2 +- core/http/src/handling/response.rs | 229 ++++++++++++++---------- site/src/main.rs | 4 +- 4 files changed, 146 insertions(+), 101 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index b34416b..9477569 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -70,7 +70,7 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let mut buffer: Vec<u8> = vec![0; length]; let read_len = buf_reader.read(&mut buffer).unwrap(); if read_len != length { - let respone = len_not_defined(Status { code: 411 }); + let respone = len_not_defined(Status::LengthRequired); stream.write_all(respone.0.as_bytes()).unwrap(); stream.write(&respone.1).unwrap(); return; @@ -105,15 +105,15 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { let response = match handled_response { Some(val) => match val { Outcome::Success(success) => ( - format!("HTTP/1.1 {} OK\r\n", success.status.unwrap().code) + format!("HTTP/1.1 {}\r\n", success.status.unwrap()) + &success.headers.join("\r\n") + "\r\n\r\n", success.body.get_data(), ), Outcome::Failure(error) => failure_handler(error), - Outcome::Forward(_) => failure_handler(Status { code: 404 }), + Outcome::Forward(_) => failure_handler(Status::NotFound), }, - None => failure_handler(Status { code: 404 }), + None => failure_handler(Status::NotFound), }; stream.write_all(response.0.as_bytes()).unwrap(); @@ -124,8 +124,8 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); ( format!( - "HTTP/1.1 {} NOT FOUND\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - status.code, + "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", + status, page_404.get_len(), page_404.get_mime() ), diff --git a/core/http/src/handling/file_handlers.rs b/core/http/src/handling/file_handlers.rs index fdfd3d0..c2945ae 100644 --- a/core/http/src/handling/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -32,7 +32,7 @@ impl NamedFile { let data = match data { Ok(dat) => dat, Err(_) => { - return Err(Status { code: 404 }); + return Err(Status::NotFound); } }; Ok(NamedFile { diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs index 603ca55..8968852 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response.rs @@ -1,4 +1,4 @@ -use std::fmt::Display; +use std::{fmt::Display, fs::write}; use mime::Mime; @@ -67,101 +67,146 @@ pub struct Response { } #[derive(Debug)] -pub struct Status { - pub code: u16, +pub enum Status { + Continue, + SwitchingProtocols, + WebDavProcessing, + ExperimentalEarlyHints, + Ok, + Created, + Accepted, + NonAuthorativeIfnormation, + NoContent, + ResetContent, + PartialContent, + WebDavMultiStatus, + WebDavAlreadyReported, + HttpDataEncodingImUsed, + MultipleChoices, + MovedPermanently, + Found, + SeeOther, + NotModfiied, + DeprecatedUseProxy, + UnusedUnused, + TemporaryRedirect, + PermanentRedirect, + BadRequest, + Unauthorized, + ExperimentalPaymentRequired, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + ProxyAuthenticationRequired, + RequestTimeout, + Conflict, + Gone, + LengthRequired, + PreconditionFailed, + PayloadTooLarge, + UriTooLong, + UnsupportedMediaType, + RangeNotSatisfiable, + ExpectationFailed, + ImATeapot, + MisdirectedRequest, + WebDavUnprocessableContent, + WebDavLocked, + WebDavFailedDependency, + ExperimenalTooEarly, + UpgradeRequred, + PreconditionRequired, + TooManyRequests, + RequestHeaderFieldsTooLarge, + UnavailableForLegalReasons, + InternalServerError, + NotImplemented, + BadGetaway, + ServiceUnavailable, + GetawayTimeout, + HttpVersionNotSupported, + VariantAlsoNegotiates, + WebDavInsufficientStorage, + WebDavLoopDetected, + NotExtended, + NetworkAuthenticationRequired, } impl Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.code { - // INFORMATIONAL RESPONSE - 100 => write!(f, "{} Continue", self.code), - 101 => write!(f, "{} Switching Protocols", self.code), - - // WebDAV - 102 => write!(f, "{} Processing", self.code), - - // Experimental - 103 => write!(f, "{} Early Hints", self.code), - - // SUCCESSFUL RESPONSE - 200 => write!(f, "{} OK", self.code), - 201 => write!(f, "{} Created", self.code), - 203 => write!(f, "{} Non-Authorative Information", self.code), - 204 => write!(f, "{} No Content", self.code), - 205 => write!(f, "{} Reset Content", self.code), - 206 => write!(f, "{} Partial Content", self.code), - - // WebDAV - 207 => write!(f, "{} Mutli-Status", self.code), - 208 => write!(f, "{} Already Reported", self.code), - - // HTTP Delta Encoding - 226 => write!(f, "{} IM Used", self.code), - - // REDIRECTION MESSAGES - 300 => write!(f, "{} Multiple Choices", self.code), - 301 => write!(f, "{} Moved Permanently", self.code), - 302 => write!(f, "{} Found", self.code), - 303 => write!(f, "{} See Other", self.code), - 304 => write!(f, "{} Not Modified", self.code), - 307 => write!(f, "{} Temporary Redirect", self.code), - 308 => write!(f, "{} Permanent Redirect", self.code), - - // Unused / Deprecated - 305 => write!(f, "{} Use Proxy", self.code), - 306 => write!(f, "{} unused", self.code), - - // CLIENT ERROR - 400 => write!(f, "{} Bad Request", self.code), - 401 => write!(f, "{} Unauthorized", self.code), - 403 => write!(f, "{} Forbidden", self.code), - 404 => write!(f, "{} Not Found", self.code), - 405 => write!(f, "{} Method Not Allowed", self.code), - 406 => write!(f, "{} Not Acceptable", self.code), - 407 => write!(f, "{} Proxy Athentication Required", self.code), - 408 => write!(f, "{} Request Timout", self.code), - 409 => write!(f, "{} Conflict", self.code), - 410 => write!(f, "{} Gone", self.code), - 411 => write!(f, "{} Length Required", self.code), - 412 => write!(f, "{} Precondition Failed", self.code), - 413 => write!(f, "{} Payload Too Large", self.code), - 414 => write!(f, "{} URI Too Long", self.code), - 415 => write!(f, "{} Unsupported Media Type", self.code), - 416 => write!(f, "{} Range Not Satisfiable", self.code), - 417 => write!(f, "{} Expectation Failed", self.code), - 418 => write!(f, "{} I'm a Teapot", self.code), - 421 => write!(f, "{} Misdirected Request", self.code), - 422 => write!(f, "{} Unprocessable Content", self.code), - 426 => write!(f, "{} Upgrade Required", self.code), - 428 => write!(f, "{} Precondition Required", self.code), - 429 => write!(f, "{} Too Many Requests", self.code), - 431 => write!(f, "{} Request Header Fields Too Large", self.code), - - 451 => write!(f, "{} Unavailable For Legal Reasons", self.code), - - // WebDAV - 423 => write!(f, "{} Locked", self.code), - 424 => write!(f, "{} Failed Dependency", self.code), - - // Experimental / Not in use - 402 => write!(f, "{} Payment Required", self.code), - 425 => write!(f, "{} Too Early", self.code), - - // SERVER ERROR RESPONSES - 500 => write!(f, "{} Internal Server Error", self.code), - 501 => write!(f, "{} Not Implmenented", self.code), - 502 => write!(f, "{} Bad Getaway", self.code), - 503 => write!(f, "{} Service Unavailable", self.code), - 504 => write!(f, "{} Getaway Timeout", self.code), - 505 => write!(f, "{} HTTP Version Not Supported", self.code), - 506 => write!(f, "{} Variant Also Negotiates", self.code), - 507 => write!(f, "{} Insufficient Storage", self.code), - 508 => write!(f, "{} Loop Detected", self.code), - 510 => write!(f, "{} Not Extendend", self.code), - 511 => write!(f, "{} Network Authentication Required", self.code), - - _ => write!(f, "500 "), + match self { + Status::Continue => write!(f, "100 Continue"), + Status::SwitchingProtocols => write!(f, "101 Switching Protocols"), + Status::WebDavProcessing => write!(f, "102 Processing"), + Status::ExperimentalEarlyHints => write!(f, "103 Early Hints"), + Status::Ok => write!(f, "200 OK"), + Status::Created => write!(f, "201 Created"), + Status::Accepted => write!(f, "202 Accepted"), + Status::NonAuthorativeIfnormation => write!(f, "203 Non-Authorative Information"), + Status::NoContent => write!(f, "204 No Content"), + Status::ResetContent => write!(f, "205 Reset Content"), + Status::PartialContent => write!(f, "206 Partial Content"), + Status::WebDavMultiStatus => write!(f, "207 Mutli-Status"), + Status::WebDavAlreadyReported => write!(f, "208 Already Reported"), + Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"), + Status::MultipleChoices => write!(f, "300 Multiple Choices"), + Status::MovedPermanently => write!(f, "301 Moved Permanently"), + Status::Found => write!(f, "302 Found"), + Status::SeeOther => write!(f, "303 See Other"), + Status::NotModfiied => write!(f, "304 Not Modified"), + Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"), + Status::PermanentRedirect => write!(f, "308 Permanent Redirect"), + Status::DeprecatedUseProxy => write!(f, "305 Use Proxy"), + Status::UnusedUnused => write!(f, "306 unused"), + Status::BadRequest => write!(f, "400 Bad Request"), + Status::Unauthorized => write!(f, "401 Unauthorized"), + Status::ExperimentalPaymentRequired => write!(f, "402 Payment Required"), + Status::Forbidden => write!(f, "403 Forbidden"), + Status::NotFound => write!(f, "404 Not Found"), + Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"), + Status::NotAcceptable => write!(f, "406 Not Acceptable"), + Status::ProxyAuthenticationRequired => { + write!(f, "407 Proxy Athentication Required") + } + Status::RequestTimeout => write!(f, "408 Request Timout"), + Status::Conflict => write!(f, "409 Conflict"), + Status::Gone => write!(f, "410 Gone"), + Status::LengthRequired => write!(f, "411 Length Required"), + Status::PreconditionFailed => write!(f, "412 Precondition Failed"), + Status::PayloadTooLarge => write!(f, "413 Payload Too Large"), + Status::UriTooLong => write!(f, "414 URI Too Long"), + Status::UnsupportedMediaType => write!(f, "415 Unsupported Media Type"), + Status::RangeNotSatisfiable => write!(f, "416 Range Not Satisfiable"), + Status::ExpectationFailed => write!(f, "417 Expectation Failed"), + Status::ImATeapot => write!(f, "418 I'm a Teapot"), + Status::MisdirectedRequest => write!(f, "421 Misdirected Request"), + Status::WebDavUnprocessableContent => write!(f, "422 Unprocessable Content"), + Status::WebDavLocked => write!(f, "423 Locked"), + Status::WebDavFailedDependency => write!(f, "424 Failed Dependency"), + Status::ExperimenalTooEarly => write!(f, "425 Too Early"), + Status::UpgradeRequred => write!(f, "426 Upgrade Required"), + Status::PreconditionRequired => write!(f, "428 Precondition Required"), + Status::TooManyRequests => write!(f, "429 Too Many Requests"), + Status::RequestHeaderFieldsTooLarge => { + write!(f, "431 Request Header Fields Too Large") + } + Status::UnavailableForLegalReasons => { + write!(f, "451 Unavailable For Legal Reasons") + } + Status::InternalServerError => write!(f, "500 Internal Server Error"), + Status::NotImplemented => write!(f, "501 Not Implmenented"), + Status::BadGetaway => write!(f, "502 Bad Getaway"), + Status::ServiceUnavailable => write!(f, "503 Service Unavailable"), + Status::GetawayTimeout => write!(f, "504 Getaway Timeout"), + Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"), + Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"), + Status::WebDavInsufficientStorage => write!(f, "507 Insufficient Storage"), + Status::WebDavLoopDetected => write!(f, "508 Loop Detected"), + Status::NotExtended => write!(f, "510 Not Extendend"), + Status::NetworkAuthenticationRequired => { + write!(f, "511 Network Authentication Required") + } } } } diff --git a/site/src/main.rs b/site/src/main.rs index da51a33..0d90d26 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -16,10 +16,10 @@ fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { format!("Content-Length: {}", dat.get_len()), format!("Content-Type: {}", dat.get_mime()), ], - status: Some(Status { code: 200 }), + status: Some(Status::Ok), body: Box::new(dat), }, - Err(_) => return Outcome::Failure(Status { code: 404 }), + Err(_) => return Outcome::Failure(Status::NotFound), }; Outcome::Success(response) } -- GitLab From 0c607be9fbb3538df5f52109cedb9ba30ef380fd Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 22 May 2023 22:47:26 +0200 Subject: [PATCH 13/65] add a post handler --- core/http/src/handling/routes.rs | 6 ++++++ site/src/main.rs | 35 +++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index fd3512d..3d8b405 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -69,3 +69,9 @@ pub struct Data { pub buffer: Vec<u8>, pub is_complete: bool, } + +impl Data { + pub fn is_empty(&self) -> bool { + self.buffer.len() == 0 + } +} diff --git a/site/src/main.rs b/site/src/main.rs index 0d90d26..dd6cf06 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -28,6 +28,30 @@ fn fileserver(path: &str) -> Result<NamedFile, Status> { NamedFile::open(PathBuf::from("static/".to_string() + path)) } +fn post_hi_handler(_request: Request, data: Data) -> Outcome<Response, Status, Data> { + if data.is_empty() { + return Outcome::Forward(data); + } + let data = if let Ok(str) = String::from_utf8(data.buffer) { + str + } else { + return Outcome::Failure(Status::BadRequest); + }; + let dat = post_hi(data); + Outcome::Success(Response { + headers: vec![ + format!("Content-Length: {}", dat.len()), + format!("Content-Type: text/plain"), + ], + status: Some(Status::Ok), + body: Box::new(dat), + }) +} + +fn post_hi(msg: String) -> String { + msg +} + fn main() { let fileserver = Route { format: None, @@ -38,7 +62,16 @@ fn main() { rank: 0, }; + let post_test = Route { + format: None, + handler: post_hi_handler, + name: Some("post_test"), + uri: "post", + method: Method::Post, + rank: 0, + }; + http::build("127.0.0.1:8000") - .mount("/", vec![fileserver]) + .mount("/", vec![fileserver, post_test]) .launch(); } -- GitLab From db039567ecbc1a5582eb5537c8fd0791731b8c49 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 27 May 2023 16:34:18 +0200 Subject: [PATCH 14/65] add runtime crate for async/await --- core/runtime/.gitignore | 1 + core/runtime/Cargo.lock | 7 +++++++ core/runtime/Cargo.toml | 8 ++++++++ core/runtime/src/lib.rs | 14 ++++++++++++++ site/static/table.html | 0 5 files changed, 30 insertions(+) create mode 100644 core/runtime/.gitignore create mode 100644 core/runtime/Cargo.lock create mode 100644 core/runtime/Cargo.toml create mode 100644 core/runtime/src/lib.rs create mode 100644 site/static/table.html diff --git a/core/runtime/.gitignore b/core/runtime/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/core/runtime/.gitignore @@ -0,0 +1 @@ +/target diff --git a/core/runtime/Cargo.lock b/core/runtime/Cargo.lock new file mode 100644 index 0000000..0728315 --- /dev/null +++ b/core/runtime/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "runtime" +version = "0.1.0" diff --git a/core/runtime/Cargo.toml b/core/runtime/Cargo.toml new file mode 100644 index 0000000..65e9c1d --- /dev/null +++ b/core/runtime/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "runtime" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs new file mode 100644 index 0000000..7d12d9a --- /dev/null +++ b/core/runtime/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/site/static/table.html b/site/static/table.html new file mode 100644 index 0000000..e69de29 -- GitLab From ab9873adfa7a120e0892a1c9d1a25279713c9191 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 28 May 2023 17:17:52 +0200 Subject: [PATCH 15/65] Add Tokio async runtime --- core/http/Cargo.lock | 482 ++++----------------------- core/http/Cargo.toml | 3 +- core/http/src/handlers/handlers.rs | 46 +-- core/http/src/handling/response.rs | 4 +- core/http/src/lib.rs | 1 - core/http/src/setup.rs | 48 +-- core/http/src/threading.rs | 85 ----- core/runtime/.gitignore | 1 - core/runtime/Cargo.lock | 7 - core/runtime/Cargo.toml | 8 - core/runtime/src/lib.rs | 14 - site/Cargo.lock | 505 ++++------------------------- site/Cargo.toml | 1 + site/src/main.rs | 9 +- 14 files changed, 172 insertions(+), 1042 deletions(-) delete mode 100644 core/http/src/threading.rs delete mode 100644 core/runtime/.gitignore delete mode 100644 core/runtime/Cargo.lock delete mode 100644 core/runtime/Cargo.toml delete mode 100644 core/runtime/src/lib.rs diff --git a/core/http/Cargo.lock b/core/http/Cargo.lock index 6cd2bca..fd71f4c 100644 --- a/core/http/Cargo.lock +++ b/core/http/Cargo.lock @@ -8,36 +8,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bumpalo" -version = "3.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" - [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -45,58 +27,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "core-foundation" -version = "0.9.3" +name = "hermit-abi" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ - "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "ctrlc" -version = "3.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" -dependencies = [ - "nix", - "windows-sys 0.45.0", -] - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "http" version = "0.1.0" dependencies = [ - "ctrlc", "mime", - "quinn", -] - -[[package]] -name = "js-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" -dependencies = [ - "wasm-bindgen", + "tokio", ] [[package]] @@ -105,6 +49,16 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.17" @@ -133,28 +87,37 @@ dependencies = [ ] [[package]] -name = "nix" -version = "0.26.2" +name = "num_cpus" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "bitflags", - "cfg-if", + "hermit-abi", "libc", - "static_assertions", ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "parking_lot_core" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] [[package]] name = "pin-project-lite" @@ -162,12 +125,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - [[package]] name = "proc-macro2" version = "1.0.56" @@ -177,56 +134,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quinn" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "thiserror", - "tokio", - "tracing", - "webpki", -] - -[[package]] -name = "quinn-proto" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" -dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-native-certs", - "slab", - "thiserror", - "tinyvec", - "tracing", - "webpki", -] - -[[package]] -name = "quinn-udp" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" -dependencies = [ - "libc", - "quinn-proto", - "socket2", - "tracing", - "windows-sys 0.42.0", -] - [[package]] name = "quote" version = "1.0.27" @@ -237,138 +144,34 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "ring" -version = "0.16.20" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", + "bitflags", ] [[package]] -name = "rustc-hash" +name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-native-certs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64", -] - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "security-framework-sys" -version = "2.8.0" +name = "signal-hook-registry" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ - "core-foundation-sys", "libc", ] [[package]] -name = "slab" -version = "0.4.8" +name = "smallvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -380,29 +183,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.15" @@ -414,85 +194,34 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", + "bytes", "libc", "mio", + "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.48.0", ] [[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.24" +name = "tokio-macros" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", + "syn", ] [[package]] @@ -501,92 +230,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" - -[[package]] -name = "web-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "winapi" version = "0.3.9" @@ -609,21 +258,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index a5b887f..4bc7eaf 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -6,6 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -quinn = "0.9.3" -ctrlc = "3.2.5" mime = "0.3.17" +tokio = { version = "1.28.2", features = ["full"] } diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 9477569..0cf7d48 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -1,8 +1,6 @@ -use std::{ - io::{BufRead, BufReader, Read, Write}, - net::TcpStream, - path::PathBuf, -}; +use std::path::PathBuf; + +use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream}; use crate::handling::{ file_handlers::NamedFile, @@ -12,13 +10,12 @@ use crate::handling::{ }; use crate::setup::MountPoint; -pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { +pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); - let mut http_request: Vec<String> = vec![]; - + let mut http_request: Vec<String> = Vec::with_capacity(100); loop { let mut buffer = String::new(); - let _ = buf_reader.read_line(&mut buffer).unwrap(); + let _ = buf_reader.read_line(&mut buffer).await.unwrap(); if buffer == "\r\n" { break; } @@ -67,12 +64,12 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { } else { 0 }; - let mut buffer: Vec<u8> = vec![0; length]; - let read_len = buf_reader.read(&mut buffer).unwrap(); - if read_len != length { - let respone = len_not_defined(Status::LengthRequired); - stream.write_all(respone.0.as_bytes()).unwrap(); - stream.write(&respone.1).unwrap(); + let mut buffer: Vec<u8> = vec![]; + buf_reader.read_buf(&mut buffer).await.unwrap(); + if buffer.len() != length { + let respone = len_not_defined(Status::LengthRequired).await; + stream.write_all(respone.0.as_bytes()).await.unwrap(); + stream.write(&respone.1).await.unwrap(); return; } data.is_complete = true; @@ -99,6 +96,11 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { }, data.clone(), )); + + if let Some(Outcome::Forward(_)) = handled_response { + continue; + } + break; } } @@ -110,17 +112,17 @@ pub fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint>) { + "\r\n\r\n", success.body.get_data(), ), - Outcome::Failure(error) => failure_handler(error), - Outcome::Forward(_) => failure_handler(Status::NotFound), + Outcome::Failure(error) => failure_handler(error).await, + Outcome::Forward(_) => failure_handler(Status::NotFound).await, }, - None => failure_handler(Status::NotFound), + None => failure_handler(Status::NotFound).await, }; - stream.write_all(response.0.as_bytes()).unwrap(); - stream.write(&response.1).unwrap(); + stream.write_all(response.0.as_bytes()).await.unwrap(); + stream.write(&response.1).await.unwrap(); } -fn failure_handler(status: Status) -> (String, Vec<u8>) { +async fn failure_handler(status: Status) -> (String, Vec<u8>) { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); ( format!( @@ -133,7 +135,7 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) { ) } -fn len_not_defined(status: Status) -> (String, Vec<u8>) { +async fn len_not_defined(status: Status) -> (String, Vec<u8>) { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); ( format!( diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs index 8968852..446c8af 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, fs::write}; +use std::fmt::Display; use mime::Mime; @@ -13,7 +13,7 @@ pub enum Outcome<S, E, F> { Forward(F), } -pub trait ResponseBody { +pub trait ResponseBody: Send { fn get_data(&self) -> Vec<u8>; fn get_mime(&self) -> Mime; fn get_len(&self) -> usize; diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 7a1379c..a51c899 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,7 +1,6 @@ pub mod handlers; pub mod handling; mod setup; -mod threading; mod utils; diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 4bacada..a38b154 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -1,16 +1,12 @@ use std::{ - net::TcpListener, - sync::{ - atomic::{self, AtomicBool}, - Arc, - }, - thread::available_parallelism, + thread::available_parallelism, process::exit, }; +use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}}; + use crate::{ handlers::handlers::handle_connection, handling::routes::{Route, Uri}, - threading::ThreadPool, }; #[derive(Clone)] @@ -22,7 +18,6 @@ pub struct MountPoint<'a> { pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, address: TcpListener, - threadpool: ThreadPool, } impl<'a> Config { @@ -56,33 +51,24 @@ impl<'a> Config { } self } - pub fn launch(self) { - let running = Arc::new(AtomicBool::new(true)); - - // Clone a reference to `running` to pass to the signal handler closure - let running_for_handler = running.clone(); - + pub async fn launch(self) { // Set up a signal handler for SIGINT - ctrlc::set_handler(move || { - // When the user sends a SIGINT signal (e.g. by pressing CTRL+C), - // set the `running` flag to false to initiate a graceful shutdown - println!("SIGNIT received, shutting down gracefully"); - running_for_handler.store(false, atomic::Ordering::SeqCst); - }) - .expect("Error setting Ctrl-C handler"); - for stream in self.address.incoming() { - if !running.load(atomic::Ordering::SeqCst) { - break; - } - let stream = stream.unwrap(); + tokio::spawn(async { + let mut sigint = signal(SignalKind::interrupt()).unwrap(); + sigint.recv().await; + println!("Shutting down..."); + exit(0) + }); + + loop { + let (socket, _) = self.address.accept().await.unwrap(); let mountpoints = self.mountpoints.clone().unwrap(); - self.threadpool - .execute(move || handle_connection(stream, mountpoints)); + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); } } } -pub fn build(ip: &str) -> Config { - let listener = TcpListener::bind(ip).unwrap(); +pub async fn build(ip: &str) -> Config { + let listener = TcpListener::bind(ip).await.unwrap(); let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); if ip.len() != 2 { panic!("Invalid IP Address"); @@ -90,7 +76,6 @@ pub fn build(ip: &str) -> Config { let port = ip[1]; let ip = ip[0]; let workers = available_parallelism().unwrap().get(); - let threadpool = ThreadPool::new(workers); println!( "\x1b[34mâš™ Configuration\x1b[0m >> \x1b[34mIp\x1b[0m: {ip} @@ -101,6 +86,5 @@ pub fn build(ip: &str) -> Config { Config { mountpoints: None, address: listener, - threadpool, } } diff --git a/core/http/src/threading.rs b/core/http/src/threading.rs deleted file mode 100644 index e743853..0000000 --- a/core/http/src/threading.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::{ - sync::{mpsc, Arc, Mutex}, - thread, -}; - -pub struct ThreadPool { - workers: Vec<Worker>, - sender: Option<mpsc::Sender<Job>>, -} - -type Job = Box<dyn FnOnce() + Send + 'static>; - -impl ThreadPool { - /// Create a new ThreadPool. - /// - /// The size is the number of threads in the pool. - /// - /// # Panics - /// - /// The `new` function will panic if the size is zero. - pub fn new(size: usize) -> ThreadPool { - assert!(size > 0); - - let (sender, receiver) = mpsc::channel(); - - let receiver = Arc::new(Mutex::new(receiver)); - - let mut workers = Vec::with_capacity(size); - - for id in 0..size { - workers.push(Worker::new(id, Arc::clone(&receiver))); - } - ThreadPool { - workers, - sender: Some(sender), - } - } - - pub fn execute<F>(&self, f: F) - where - F: FnOnce() + Send + 'static, - { - let job = Box::new(f); - - self.sender.as_ref().unwrap().send(job).unwrap(); - } -} - -impl Drop for ThreadPool { - fn drop(&mut self) { - drop(self.sender.take()); - - for worker in &mut self.workers { - if let Some(thread) = worker.thread.take() { - thread.join().unwrap(); - } - } - } -} -struct Worker { - id: usize, - thread: Option<thread::JoinHandle<()>>, -} - -impl Worker { - fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { - let thread = thread::spawn(move || loop { - let message = receiver.lock().unwrap().recv(); - - match message { - Ok(job) => { - job(); - } - Err(_) => { - break; - } - } - }); - - Worker { - id, - thread: Some(thread), - } - } -} diff --git a/core/runtime/.gitignore b/core/runtime/.gitignore deleted file mode 100644 index ea8c4bf..0000000 --- a/core/runtime/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/core/runtime/Cargo.lock b/core/runtime/Cargo.lock deleted file mode 100644 index 0728315..0000000 --- a/core/runtime/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "runtime" -version = "0.1.0" diff --git a/core/runtime/Cargo.toml b/core/runtime/Cargo.toml deleted file mode 100644 index 65e9c1d..0000000 --- a/core/runtime/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "runtime" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs deleted file mode 100644 index 7d12d9a..0000000 --- a/core/runtime/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} diff --git a/site/Cargo.lock b/site/Cargo.lock index 7578765..c714959 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -8,36 +8,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bumpalo" -version = "3.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" - [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -45,58 +27,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "core-foundation" -version = "0.9.3" +name = "hermit-abi" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ - "core-foundation-sys", "libc", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "ctrlc" -version = "3.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbcf33c2a618cbe41ee43ae6e9f2e48368cd9f9db2896f10167d8d762679f639" -dependencies = [ - "nix", - "windows-sys 0.45.0", -] - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "http" version = "0.1.0" dependencies = [ - "ctrlc", "mime", - "quinn", -] - -[[package]] -name = "js-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" -dependencies = [ - "wasm-bindgen", + "tokio", ] [[package]] @@ -106,12 +50,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] -name = "log" -version = "0.4.17" +name = "lock_api" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ - "cfg-if", + "autocfg", + "scopeguard", ] [[package]] @@ -122,39 +67,47 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "eebffdb73fe72e917997fad08bdbf31ac50b0fa91cec93e69a0662e4264d454c" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] -name = "nix" -version = "0.26.2" +name = "num_cpus" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "bitflags", - "cfg-if", + "hermit-abi", "libc", - "static_assertions", ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "parking_lot_core" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] [[package]] name = "pin-project-lite" @@ -162,202 +115,45 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] -[[package]] -name = "quinn" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445cbfe2382fa023c4f2f3c7e1c95c03dcc1df2bf23cebcb2b13e1402c4394d1" -dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "thiserror", - "tokio", - "tracing", - "webpki", -] - -[[package]] -name = "quinn-proto" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" -dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-native-certs", - "slab", - "thiserror", - "tinyvec", - "tracing", - "webpki", -] - -[[package]] -name = "quinn-udp" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641538578b21f5e5c8ea733b736895576d0fe329bb883b937db6f4d163dbaaf4" -dependencies = [ - "libc", - "quinn-proto", - "socket2", - "tracing", - "windows-sys 0.42.0", -] - [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "ring" -version = "0.16.20" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", + "bitflags", ] [[package]] -name = "rustc-hash" +name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-native-certs" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64", -] - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] -name = "security-framework-sys" -version = "2.8.0" +name = "signal-hook-registry" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ - "core-foundation-sys", "libc", ] @@ -366,16 +162,14 @@ name = "site" version = "0.1.0" dependencies = [ "http", + "tokio", ] [[package]] -name = "slab" -version = "0.4.8" +name = "smallvec" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" @@ -387,132 +181,52 @@ dependencies = [ "winapi", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", + "bytes", "libc", "mio", + "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.48.0", ] [[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.24" +name = "tokio-macros" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", + "syn", ] [[package]] name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "untrusted" -version = "0.7.1" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "wasi" @@ -520,80 +234,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" - -[[package]] -name = "web-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "winapi" version = "0.3.9" @@ -616,21 +256,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/site/Cargo.toml b/site/Cargo.toml index d4e9a4a..55ff9b1 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] http = { path = "../core/http" } +tokio = { version = "1.28.2", features = ["full"] } diff --git a/site/src/main.rs b/site/src/main.rs index dd6cf06..fc87c26 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -8,7 +8,7 @@ use http::handling::{ routes::{Data, Route}, }; -fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { +fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { Ok(dat) => Response { @@ -52,7 +52,8 @@ fn post_hi(msg: String) -> String { msg } -fn main() { +#[tokio::main] +async fn main() { let fileserver = Route { format: None, handler, @@ -71,7 +72,7 @@ fn main() { rank: 0, }; - http::build("127.0.0.1:8000") + http::build("127.0.0.1:8000").await .mount("/", vec![fileserver, post_test]) - .launch(); + .launch().await; } -- GitLab From a9ee87ffa056fb2e4cbcfe59add05d6dedc091cc Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 28 May 2023 17:30:38 +0200 Subject: [PATCH 16/65] Add instant graceful shutdown with SIGNIT --- core/http/src/setup.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index a38b154..dfdb496 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -2,7 +2,7 @@ use std::{ thread::available_parallelism, process::exit, }; -use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}}; +use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, runtime, select}; use crate::{ handlers::handlers::handle_connection, @@ -52,18 +52,21 @@ impl<'a> Config { self } pub async fn launch(self) { - // Set up a signal handler for SIGINT - tokio::spawn(async { - let mut sigint = signal(SignalKind::interrupt()).unwrap(); - sigint.recv().await; - println!("Shutting down..."); - exit(0) - }); - + let mut sigint = signal(SignalKind::interrupt()).unwrap(); loop { - let (socket, _) = self.address.accept().await.unwrap(); - let mountpoints = self.mountpoints.clone().unwrap(); - tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + select! { + _ = sigint.recv() => { + println!("Shutting down..."); + break; + } + + income = self.address.accept() => { + let income = income.unwrap(); + let (socket, _) = income; + let mountpoints = self.mountpoints.clone().unwrap(); + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + } + } } } } -- GitLab From 677eb8a01a8f0cdc047872dadb9c1c7233328b88 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 28 May 2023 18:04:45 +0200 Subject: [PATCH 17/65] Add Ranking --- core/http/src/handling/methods.rs | 2 +- core/http/src/handling/request.rs | 12 ++++++------ core/http/src/setup.rs | 9 ++++----- site/src/main.rs | 22 ++++++++++++++++++++-- site/static/hi | 1 + 5 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 site/static/hi diff --git a/core/http/src/handling/methods.rs b/core/http/src/handling/methods.rs index 998cfa5..5ca6211 100644 --- a/core/http/src/handling/methods.rs +++ b/core/http/src/handling/methods.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, str::FromStr}; -#[derive(PartialEq, Eq, Clone, Debug, Copy)] +#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)] pub enum Method { Get, Head, diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index ba1cf37..adfb6b8 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +// use std::net::SocketAddr; use super::{methods::Method, routes::Uri}; @@ -11,12 +11,12 @@ pub struct Request<'a> { // pub connection: ConnectionMeta, } -struct ConnectionMeta { - remote: Option<SocketAddr>, - // certificates -} +// struct ConnectionMeta { +// remote: Option<SocketAddr>, +// // certificates +// } -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum MediaType { Json, Plain, diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index dfdb496..a50e7d1 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -1,8 +1,6 @@ -use std::{ - thread::available_parallelism, process::exit, -}; +use std::thread::available_parallelism; -use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, runtime, select}; +use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, select}; use crate::{ handlers::handlers::handle_connection, @@ -21,7 +19,8 @@ pub struct Config { } impl<'a> Config { - pub fn mount(mut self, mountpoint: Uri<'static>, routes: Vec<Route<'static>>) -> Config { + pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Config { + routes.sort_by(|a, b| a.rank.cmp(&b.rank)); let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint); for (index, route) in routes.iter().enumerate() { let indent_sign; diff --git a/site/src/main.rs b/site/src/main.rs index fc87c26..e58195a 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -8,6 +8,15 @@ use http::handling::{ routes::{Data, Route}, }; +fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { + // Outcome::Success(Response { headers: vec![ + // format!("Content-Length: 4"), + // format!("Content-Type: text/plain") + // ], status: Some(Status::Ok), body: Box::new("jkl;") }) + Outcome::Forward(data) +} + + fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { @@ -60,7 +69,7 @@ async fn main() { name: Some("file_server"), uri: "static/<path..>", method: Method::Get, - rank: 0, + rank: 1, }; let post_test = Route { @@ -72,7 +81,16 @@ async fn main() { rank: 0, }; + let static_hi = Route { + format: None, + handler: handle_static_hi, + name: Some("Handle_Static_hi"), + uri: "static/hi", + method: Method::Get, + rank: 0 + }; + http::build("127.0.0.1:8000").await - .mount("/", vec![fileserver, post_test]) + .mount("/", vec![fileserver, post_test, static_hi]) .launch().await; } diff --git a/site/static/hi b/site/static/hi new file mode 100644 index 0000000..8bd6648 --- /dev/null +++ b/site/static/hi @@ -0,0 +1 @@ +asdf -- GitLab From 844c43899839161d91c3ff71a505b6f2a30b22c9 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 28 May 2023 20:03:15 +0200 Subject: [PATCH 18/65] Add Get request form data function --- core/http/src/handling/request.rs | 76 ++++++++++++++++++++++++++++++- core/http/src/handling/routes.rs | 4 +- site/src/main.rs | 28 +++++++++--- 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index adfb6b8..b3615f1 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -1,6 +1,11 @@ // use std::net::SocketAddr; -use super::{methods::Method, routes::Uri}; +use std::{collections::HashMap, error::Error, fmt::Display}; + +use super::{ + methods::Method, + routes::{Data, Uri}, +}; type HeaderMap = Vec<String>; #[derive(Clone)] @@ -23,6 +28,34 @@ pub enum MediaType { Html, } +#[derive(Debug)] +pub enum ParseErrors { + NoData, + BadData, +} + +#[derive(Debug)] +pub struct ParseFormError { + pub error: ParseErrors, +} + +impl Display for ParseFormError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.error) + } +} + +impl Display for ParseErrors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseErrors::NoData => write!(f, "No Data at key"), + ParseErrors::BadData => write!(f, "Bad Data at key"), + } + } +} + +impl Error for ParseFormError {} + impl Request<'_> { pub fn can_have_body(&self) -> bool { match self.method { @@ -30,4 +63,45 @@ impl Request<'_> { _ => false, } } + pub fn get_form_keys(&self, keys: Vec<&str>) -> Result<HashMap<&str, &str>, ParseFormError> { + let data = self.uri.split_once("?"); + if data == None { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); + } + let data = data + .unwrap() + .1 + .split("&") + .map(|kvp| kvp.split_once("=")) + .collect::<Vec<Option<(&str, &str)>>>(); + + let mut values: HashMap<&str, &str> = HashMap::new(); + for kvp in data { + if kvp == None { + continue; + } + let kvp = kvp.unwrap(); + values.insert(kvp.0, kvp.1); + } + let mut response = HashMap::new(); + for key in keys { + let entry = values.get_key_value(key); + if entry == None { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); + } + response.insert(*entry.unwrap().0, *entry.unwrap().1); + } + Ok(response) + } + pub fn get_form_keys_with_data( + &self, + _keys: Vec<&str>, + _data_buffer: Data, + ) -> Result<HashMap<&str, &str>, ParseFormError> { + todo!() + } } diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index 3d8b405..795e760 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -43,7 +43,9 @@ impl Route<'_> { } else { return false; }; - if true_str.starts_with("<") && true_str.ends_with("..>") { + if (true_str.starts_with("<") && true_str.ends_with("..>")) + || (comp_str.starts_with(true_str) && comp_str.contains("?")) + { return true; } if true_str.starts_with("<") && true_str.ends_with(">") { diff --git a/site/src/main.rs b/site/src/main.rs index e58195a..22897e2 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, collections::HashMap}; use http::handling::{ file_handlers::NamedFile, @@ -8,12 +8,26 @@ use http::handling::{ routes::{Data, Route}, }; -fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { - // Outcome::Success(Response { headers: vec![ - // format!("Content-Length: 4"), - // format!("Content-Type: text/plain") - // ], status: Some(Status::Ok), body: Box::new("jkl;") }) - Outcome::Forward(data) +fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { + let mut result = String::new(); + for (key, value) in map { + result.push_str(key); + result.push('='); + result.push_str(value); + result.push(';'); + } + result.pop(); // Remove the trailing semicolon if desired + result +} + + +fn handle_static_hi(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { + let response = hashmap_to_string(&request.get_form_keys(vec!["asdf", "jkj"]).unwrap()); + Outcome::Success(Response { headers: vec![ + format!("Content-Length: {}", response.len()), + format!("Content-Type: text/plain") + ], status: Some(Status::Ok), body: Box::new(response) }) + // Outcome::Forward(data) } -- GitLab From 2b30adc7e03eb236007e8a481bc1a6248d005478 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 29 May 2023 14:19:46 +0200 Subject: [PATCH 19/65] Cleaning up the codebase removing `unwraps()` and replacing them with propper error handling remove unnecessary `clone()` calls --- core/http/src/handlers/handlers.rs | 77 ++++++++++++++++++++---------- core/http/src/handling/request.rs | 24 +++++----- core/http/src/handling/routes.rs | 2 +- core/http/src/setup.rs | 28 ++++++++--- site/src/main.rs | 27 ++++++----- 5 files changed, 103 insertions(+), 55 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 0cf7d48..d9ba8df 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -15,29 +15,45 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut http_request: Vec<String> = Vec::with_capacity(100); loop { let mut buffer = String::new(); - let _ = buf_reader.read_line(&mut buffer).await.unwrap(); + if let Err(_) = buf_reader.read_line(&mut buffer).await { + eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m"); + return; + } if buffer == "\r\n" { break; } http_request.push(buffer); } - println!("{:#?}", http_request); + // println!("{:#?}", http_request); - let request_status_line = http_request.get(0); - if request_status_line == None { + let request_status_line = if let Some(status_line) = http_request.get(0).cloned() { + status_line + } else { + eprintln!("\x1b[31mAborting due to missing headers\x1b[0m"); return; - } - let request_status_line = request_status_line.unwrap(); + }; let request = Request { - uri: &request_status_line.split(" ").nth(1).unwrap(), - headers: http_request.clone(), - method: request_status_line + uri: if let Some(uri) = &request_status_line.split(" ").nth(1) { + *uri + } else { + eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); + return; + }, + headers: http_request, + method: if let Some(method) = request_status_line .split(" ") - .next() - .unwrap() - .parse() - .unwrap(), + .next() { + if let Ok(ok) = method.parse() { + ok + } else { + eprintln!("\x1b[31mAborting due to invalid request method\x1b[0m"); + return; + } + } else { + eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); + return; + } }; let mut data = Data { @@ -48,8 +64,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let length = if let Some(len) = request .headers .iter() - .filter(|header| header.contains("Content-Length: ")) - .clone() + .filter(|header| header.starts_with("Content-Length: ")) .map(|header| { let header = header.strip_prefix("Content-Length: ").unwrap(); header.trim().parse::<usize>() @@ -64,12 +79,17 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } else { 0 }; + let mut buffer: Vec<u8> = vec![]; buf_reader.read_buf(&mut buffer).await.unwrap(); if buffer.len() != length { - let respone = len_not_defined(Status::LengthRequired).await; - stream.write_all(respone.0.as_bytes()).await.unwrap(); - stream.write(&respone.1).await.unwrap(); + let respone = len_not_defined(Status::LengthRequired); + let mut response = respone.0.as_bytes().to_vec(); + response.extend_from_slice(&respone.1); + if let Err(e) = stream.write_all(&response).await { + eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); + return; + } return; } data.is_complete = true; @@ -107,22 +127,27 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let response = match handled_response { Some(val) => match val { Outcome::Success(success) => ( - format!("HTTP/1.1 {}\r\n", success.status.unwrap()) + format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok)) + &success.headers.join("\r\n") + "\r\n\r\n", success.body.get_data(), ), - Outcome::Failure(error) => failure_handler(error).await, - Outcome::Forward(_) => failure_handler(Status::NotFound).await, + Outcome::Failure(error) => failure_handler(error), + Outcome::Forward(_) => failure_handler(Status::NotFound), }, - None => failure_handler(Status::NotFound).await, + None => failure_handler(Status::NotFound), }; - stream.write_all(response.0.as_bytes()).await.unwrap(); - stream.write(&response.1).await.unwrap(); + let mut resp = response.0.as_bytes().to_vec(); + resp.extend_from_slice(&response.1); + if let Err(e) = stream.write_all(&resp).await { + eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); + return; + } + return; } -async fn failure_handler(status: Status) -> (String, Vec<u8>) { +fn failure_handler(status: Status) -> (String, Vec<u8>) { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); ( format!( @@ -135,7 +160,7 @@ async fn failure_handler(status: Status) -> (String, Vec<u8>) { ) } -async fn len_not_defined(status: Status) -> (String, Vec<u8>) { +fn len_not_defined(status: Status) -> (String, Vec<u8>) { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); ( format!( diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index b3615f1..7fa10b4 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -64,14 +64,14 @@ impl Request<'_> { } } pub fn get_form_keys(&self, keys: Vec<&str>) -> Result<HashMap<&str, &str>, ParseFormError> { - let data = self.uri.split_once("?"); - if data == None { + let data = if let Some(val) = self.uri.split_once("?") { + val + } else { return Err(ParseFormError { error: ParseErrors::NoData, }); - } + }; let data = data - .unwrap() .1 .split("&") .map(|kvp| kvp.split_once("=")) @@ -79,21 +79,23 @@ impl Request<'_> { let mut values: HashMap<&str, &str> = HashMap::new(); for kvp in data { - if kvp == None { + let kvp = if let Some(kvp) = kvp { + kvp + } else { continue; - } - let kvp = kvp.unwrap(); + }; values.insert(kvp.0, kvp.1); } let mut response = HashMap::new(); for key in keys { - let entry = values.get_key_value(key); - if entry == None { + let entry = if let Some(val) = values.get_key_value(key) { + val + } else { return Err(ParseFormError { error: ParseErrors::NoData, }); - } - response.insert(*entry.unwrap().0, *entry.unwrap().1); + }; + response.insert(*entry.0, *entry.1); } Ok(response) } diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index 795e760..9ded1ce 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -35,7 +35,7 @@ impl Route<'_> { format: routeinfo.format, } } - pub fn compare_uri(self, uri: Uri) -> bool { + pub fn compare_uri(&self, uri: Uri) -> bool { let mut iter_comp_str = uri.split("/"); for true_str in self.uri.split("/") { let comp_str = if let Some(str) = iter_comp_str.next() { diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index a50e7d1..9f57e76 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -19,7 +19,22 @@ pub struct Config { } impl<'a> Config { - pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Config { + fn check_mountpoint_taken(&self, to_insert: Uri) -> bool { + if let Some(to_check) = &self.mountpoints { + let len = to_check.len(); + for i in 0..len { + if to_check[i].mountpoint == to_insert { + return true; // Found a duplicate &str + } + } + }; + false + } + pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self { + if self.check_mountpoint_taken(mountpoint) { + eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m"); + return self; + } routes.sort_by(|a, b| a.rank.cmp(&b.rank)); let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint); for (index, route) in routes.iter().enumerate() { @@ -58,10 +73,7 @@ impl<'a> Config { println!("Shutting down..."); break; } - - income = self.address.accept() => { - let income = income.unwrap(); - let (socket, _) = income; + Ok((socket, _)) = self.address.accept() => { let mountpoints = self.mountpoints.clone().unwrap(); tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); } @@ -70,7 +82,11 @@ impl<'a> Config { } } pub async fn build(ip: &str) -> Config { - let listener = TcpListener::bind(ip).await.unwrap(); + let listener = if let Ok(listener) = TcpListener::bind(ip).await { + listener + } else { + panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); + }; let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); if ip.len() != 2 { panic!("Invalid IP Address"); diff --git a/site/src/main.rs b/site/src/main.rs index 22897e2..48d109d 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, collections::HashMap}; +use std::{collections::HashMap, path::PathBuf}; use http::handling::{ file_handlers::NamedFile, @@ -20,17 +20,19 @@ fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { result } - fn handle_static_hi(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { let response = hashmap_to_string(&request.get_form_keys(vec!["asdf", "jkj"]).unwrap()); - Outcome::Success(Response { headers: vec![ - format!("Content-Length: {}", response.len()), - format!("Content-Type: text/plain") - ], status: Some(Status::Ok), body: Box::new(response) }) + Outcome::Success(Response { + headers: vec![ + format!("Content-Length: {}", response.len()), + format!("Content-Type: text/plain"), + ], + status: Some(Status::Ok), + body: Box::new(response), + }) // Outcome::Forward(data) } - fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { @@ -96,15 +98,18 @@ async fn main() { }; let static_hi = Route { - format: None, + format: None, handler: handle_static_hi, name: Some("Handle_Static_hi"), uri: "static/hi", method: Method::Get, - rank: 0 + rank: 0, }; - http::build("127.0.0.1:8000").await + http::build("127.0.0.1:8000") + .await .mount("/", vec![fileserver, post_test, static_hi]) - .launch().await; + .mount("/post/", vec![post_test]) + .launch() + .await; } -- GitLab From 21a170df00884fcb1d93f7352757149829bc89c2 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 29 May 2023 17:11:18 +0200 Subject: [PATCH 20/65] Add HEAD Method support --- core/http/src/handlers/handlers.rs | 11 ++++++++--- site/src/main.rs | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index d9ba8df..f81441c 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -6,7 +6,7 @@ use crate::handling::{ file_handlers::NamedFile, request::Request, response::{Outcome, Response, ResponseBody, Status}, - routes::Data, + routes::Data, methods::Method, }; use crate::setup::MountPoint; @@ -103,7 +103,8 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); for route in mountpoint.routes { - if route.method != request.method { + if (route.method != request.method) && + ((route.method != Method::Get) && (request.method == Method::Head)) { continue; } if !route.compare_uri(mounted_request_uri) { @@ -130,7 +131,11 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok)) + &success.headers.join("\r\n") + "\r\n\r\n", - success.body.get_data(), + if request.method == Method::Head { + vec![] + } else { + success.body.get_data() + } ), Outcome::Failure(error) => failure_handler(error), Outcome::Forward(_) => failure_handler(Status::NotFound), diff --git a/site/src/main.rs b/site/src/main.rs index 48d109d..6e560d4 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -20,8 +20,13 @@ fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { result } -fn handle_static_hi(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { - let response = hashmap_to_string(&request.get_form_keys(vec!["asdf", "jkj"]).unwrap()); +fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { + let keys = if let Ok(keys) = request.get_form_keys(vec!["asdf", "jkl;"]) { + keys + } else { + return Outcome::Forward(data); + }; + let response = hashmap_to_string(&keys); Outcome::Success(Response { headers: vec![ format!("Content-Length: {}", response.len()), -- GitLab From 8c05e9c37c9eca247c21e680f3afb36cd100674b Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 29 May 2023 17:52:57 +0200 Subject: [PATCH 21/65] rename `get_form_keys` to `get_get_form_keys` --- core/http/src/handling/request.rs | 8 +++++++- site/src/main.rs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 7fa10b4..f2d4e10 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,7 +2,10 @@ use std::{collections::HashMap, error::Error, fmt::Display}; +use mime::Mime; + use super::{ + file_handlers::find_mimetype, methods::Method, routes::{Data, Uri}, }; @@ -63,7 +66,10 @@ impl Request<'_> { _ => false, } } - pub fn get_form_keys(&self, keys: Vec<&str>) -> Result<HashMap<&str, &str>, ParseFormError> { + pub fn get_get_form_keys( + &self, + keys: Vec<&str>, + ) -> Result<HashMap<&str, &str>, ParseFormError> { let data = if let Some(val) = self.uri.split_once("?") { val } else { diff --git a/site/src/main.rs b/site/src/main.rs index 6e560d4..b8e4a50 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -21,7 +21,7 @@ fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { } fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { - let keys = if let Ok(keys) = request.get_form_keys(vec!["asdf", "jkl;"]) { + let keys = if let Ok(keys) = request.get_get_form_keys(vec!["asdf", "jkl;"]) { keys } else { return Outcome::Forward(data); -- GitLab From d377be4bf6fbfc9d7495d2f6168b1bcebbdb3ce6 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 1 Jun 2023 21:38:32 +0200 Subject: [PATCH 22/65] Approach from request forms/data add a get request with form --- core/http/src/handling/request.rs | 4 +--- site/404.html | 7 +++++++ site/src/main.rs | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index f2d4e10..efed546 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,10 +2,7 @@ use std::{collections::HashMap, error::Error, fmt::Display}; -use mime::Mime; - use super::{ - file_handlers::find_mimetype, methods::Method, routes::{Data, Uri}, }; @@ -66,6 +63,7 @@ impl Request<'_> { _ => false, } } + // pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {} pub fn get_get_form_keys( &self, keys: Vec<&str>, diff --git a/site/404.html b/site/404.html index d4c09f4..0a1a4ed 100644 --- a/site/404.html +++ b/site/404.html @@ -15,5 +15,12 @@ <input type="text" id="asdf" name="message" /> <button type="submit">Send</button> </form> + <form action="/static/hi" method="get"> + <label for="jump">Enter asdf</label> + <input type="text" id="jump" name="asdf" /> + <label for="jkl">Enter jkl;</label> + <input type="text" id="jkl" name="jkl" /> + <button type="submit">Send</button> + </form> </body> </html> diff --git a/site/src/main.rs b/site/src/main.rs index b8e4a50..deb31d6 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -21,7 +21,7 @@ fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { } fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { - let keys = if let Ok(keys) = request.get_get_form_keys(vec!["asdf", "jkl;"]) { + let keys = if let Ok(keys) = request.get_get_form_keys(vec!["asdf", "jkl"]) { keys } else { return Outcome::Forward(data); @@ -111,7 +111,7 @@ async fn main() { rank: 0, }; - http::build("127.0.0.1:8000") + http::build("192.168.178.32:8000") .await .mount("/", vec![fileserver, post_test, static_hi]) .mount("/post/", vec![post_test]) -- GitLab From 99f0bbdf127f2d77d1c08cabd2d1dd5d247d6908 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 2 Jun 2023 21:54:05 +0200 Subject: [PATCH 23/65] Introduced the MIME enum, not populated --- core/http/src/handling/request.rs | 11 ++++++----- core/http/src/utils/mime.rs | 1 + core/http/src/utils/mod.rs | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 core/http/src/utils/mime.rs diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index efed546..f90bacb 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,6 +2,11 @@ use std::{collections::HashMap, error::Error, fmt::Display}; +trait FromPost {} + +impl FromPost for &str {} +impl FromPost for Vec<u8> {} + use super::{ methods::Method, routes::{Data, Uri}, @@ -103,11 +108,7 @@ impl Request<'_> { } Ok(response) } - pub fn get_form_keys_with_data( - &self, - _keys: Vec<&str>, - _data_buffer: Data, - ) -> Result<HashMap<&str, &str>, ParseFormError> { + pub fn get_post_form_key(&self, key: &str, data: Data) { todo!() } } diff --git a/core/http/src/utils/mime.rs b/core/http/src/utils/mime.rs new file mode 100644 index 0000000..9d17bfb --- /dev/null +++ b/core/http/src/utils/mime.rs @@ -0,0 +1 @@ +enum Mime {} diff --git a/core/http/src/utils/mod.rs b/core/http/src/utils/mod.rs index e69de29..909a531 100644 --- a/core/http/src/utils/mod.rs +++ b/core/http/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod mime; -- GitLab From 2306b7b2eb7381021a2b277099879c0c11d86e25 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 3 Jun 2023 20:18:05 +0200 Subject: [PATCH 24/65] add mime enum, remove mime::Mime dependency --- core/http/Cargo.lock | 84 +- core/http/Cargo.toml | 2 +- core/http/src/handling/file_handlers.rs | 23 +- core/http/src/handling/response.rs | 8 +- core/http/src/utils/mime.rs | 1 - core/http/src/utils/mime/map.rs | 2022 +++++++++++ core/http/src/utils/mime/mime_enum.rs | 4095 +++++++++++++++++++++++ core/http/src/utils/mime/mod.rs | 2 + generatersenum.py | 73 + site/Cargo.lock | 84 +- small.csv | 20 + 11 files changed, 6374 insertions(+), 40 deletions(-) delete mode 100644 core/http/src/utils/mime.rs create mode 100644 core/http/src/utils/mime/map.rs create mode 100644 core/http/src/utils/mime/mime_enum.rs create mode 100644 core/http/src/utils/mime/mod.rs create mode 100644 generatersenum.py create mode 100644 small.csv diff --git a/core/http/Cargo.lock b/core/http/Cargo.lock index fd71f4c..8496ac6 100644 --- a/core/http/Cargo.lock +++ b/core/http/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ name = "http" version = "0.1.0" dependencies = [ - "mime", + "phf", "tokio", ] @@ -68,12 +68,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mio" version = "0.8.6" @@ -119,6 +113,48 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -143,6 +179,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -167,6 +218,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "smallvec" version = "1.10.0" @@ -183,6 +240,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.15" @@ -221,7 +289,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.15", ] [[package]] diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index 4bc7eaf..45da492 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -mime = "0.3.17" tokio = { version = "1.28.2", features = ["full"] } +phf = { version = "0.11", features = ["macros"] } diff --git a/core/http/src/handling/file_handlers.rs b/core/http/src/handling/file_handlers.rs index c2945ae..9e34054 100644 --- a/core/http/src/handling/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -1,8 +1,9 @@ use std::{fs, path::PathBuf}; -use mime::Mime; - -use crate::handling::response::{ResponseBody, Status}; +use crate::{ + handling::response::{ResponseBody, Status}, + utils::mime::mime_enum::Mime, +}; #[derive(Debug)] pub struct NamedFile { @@ -37,7 +38,7 @@ impl NamedFile { }; Ok(NamedFile { content_len: data.len(), - content_type: find_mimetype(path.to_str().unwrap()), + content_type: Mime::from_filename(path.to_str().unwrap()), content: data, }) } @@ -53,17 +54,3 @@ fn proove_path(path: PathBuf) -> PathBuf { .join("/"), ) } - -pub fn find_mimetype(filename: &str) -> Mime { - match filename.split('.').last() { - Some(v) => match v { - "png" => mime::IMAGE_PNG, - "jpg" => mime::IMAGE_JPEG, - "json" => mime::APPLICATION_JSON, - "html" => mime::TEXT_HTML, - "css" => mime::TEXT_CSS, - &_ => mime::TEXT_PLAIN, - }, - None => mime::TEXT_PLAIN, - } -} diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs index 446c8af..2f7fdcf 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use mime::Mime; +use crate::utils::mime::mime_enum::Mime; use super::routes::Body; @@ -24,7 +24,7 @@ impl ResponseBody for Body { self.body.clone() } fn get_mime(&self) -> Mime { - mime::TEXT_PLAIN + Mime::TextPlain } fn get_len(&self) -> usize { @@ -38,7 +38,7 @@ impl ResponseBody for &str { } fn get_mime(&self) -> Mime { - mime::TEXT_PLAIN + Mime::TextPlain } fn get_len(&self) -> usize { @@ -52,7 +52,7 @@ impl ResponseBody for String { } fn get_mime(&self) -> Mime { - mime::TEXT_PLAIN + Mime::TextPlain } fn get_len(&self) -> usize { diff --git a/core/http/src/utils/mime.rs b/core/http/src/utils/mime.rs deleted file mode 100644 index 9d17bfb..0000000 --- a/core/http/src/utils/mime.rs +++ /dev/null @@ -1 +0,0 @@ -enum Mime {} diff --git a/core/http/src/utils/mime/map.rs b/core/http/src/utils/mime/map.rs new file mode 100644 index 0000000..7117314 --- /dev/null +++ b/core/http/src/utils/mime/map.rs @@ -0,0 +1,2022 @@ +use super::mime_enum::Mime; + +pub static MIME_MAP: phf::Map<&'static str, Mime> = phf::phf_map! { + "application/1d-interleaved-parityfec" => Mime::Application1dInterleavedParityfec, + "application/3gpdash-qoe-report+xml" => Mime::Application3gpdashQoeReportXml, + "application/3gppHal+json" => Mime::Application3gpphalJson, + "application/3gppHalForms+json" => Mime::Application3gpphalformsJson, + "application/3gpp-ims+xml" => Mime::Application3gppImsXml, + "application/A2L" => Mime::ApplicationA2l, + "application/ace+cbor" => Mime::ApplicationAceCbor, + "application/ace+json" => Mime::ApplicationAceJson, + "application/activemessage" => Mime::ApplicationActivemessage, + "application/activity+json" => Mime::ApplicationActivityJson, + "application/aif+cbor" => Mime::ApplicationAifCbor, + "application/aif+json" => Mime::ApplicationAifJson, + "application/alto-cdni+json" => Mime::ApplicationAltoCdniJson, + "application/alto-cdnifilter+json" => Mime::ApplicationAltoCdnifilterJson, + "application/alto-costmap+json" => Mime::ApplicationAltoCostmapJson, + "application/alto-costmapfilter+json" => Mime::ApplicationAltoCostmapfilterJson, + "application/alto-directory+json" => Mime::ApplicationAltoDirectoryJson, + "application/alto-endpointprop+json" => Mime::ApplicationAltoEndpointpropJson, + "application/alto-endpointpropparams+json" => Mime::ApplicationAltoEndpointpropparamsJson, + "application/alto-endpointcost+json" => Mime::ApplicationAltoEndpointcostJson, + "application/alto-endpointcostparams+json" => Mime::ApplicationAltoEndpointcostparamsJson, + "application/alto-error+json" => Mime::ApplicationAltoErrorJson, + "application/alto-networkmapfilter+json" => Mime::ApplicationAltoNetworkmapfilterJson, + "application/alto-networkmap+json" => Mime::ApplicationAltoNetworkmapJson, + "application/alto-propmap+json" => Mime::ApplicationAltoPropmapJson, + "application/alto-propmapparams+json" => Mime::ApplicationAltoPropmapparamsJson, + "application/alto-updatestreamcontrol+json" => Mime::ApplicationAltoUpdatestreamcontrolJson, + "application/alto-updatestreamparams+json" => Mime::ApplicationAltoUpdatestreamparamsJson, + "application/AML" => Mime::ApplicationAml, + "application/andrew-inset" => Mime::ApplicationAndrewInset, + "application/applefile" => Mime::ApplicationApplefile, + "application/at+jwt" => Mime::ApplicationAtJwt, + "application/ATF" => Mime::ApplicationAtf, + "application/ATFX" => Mime::ApplicationAtfx, + "application/atom+xml" => Mime::ApplicationAtomXml, + "application/atomcat+xml" => Mime::ApplicationAtomcatXml, + "application/atomdeleted+xml" => Mime::ApplicationAtomdeletedXml, + "application/atomicmail" => Mime::ApplicationAtomicmail, + "application/atomsvc+xml" => Mime::ApplicationAtomsvcXml, + "application/atsc-dwd+xml" => Mime::ApplicationAtscDwdXml, + "application/atsc-dynamic-event-message" => Mime::ApplicationAtscDynamicEventMessage, + "application/atsc-held+xml" => Mime::ApplicationAtscHeldXml, + "application/atsc-rdt+json" => Mime::ApplicationAtscRdtJson, + "application/atsc-rsat+xml" => Mime::ApplicationAtscRsatXml, + "application/ATXML" => Mime::ApplicationAtxml, + "application/auth-policy+xml" => Mime::ApplicationAuthPolicyXml, + "application/automationml-aml+xml" => Mime::ApplicationAutomationmlAmlXml, + "application/automationml-amlx+zip" => Mime::ApplicationAutomationmlAmlxZip, + "application/bacnet-xdd+zip" => Mime::ApplicationBacnetXddZip, + "application/batch-SMTP" => Mime::ApplicationBatchSmtp, + "application/beep+xml" => Mime::ApplicationBeepXml, + "application/calendar+json" => Mime::ApplicationCalendarJson, + "application/calendar+xml" => Mime::ApplicationCalendarXml, + "application/call-completion" => Mime::ApplicationCallCompletion, + "application/CALS-1840" => Mime::ApplicationCals1840, + "application/captive+json" => Mime::ApplicationCaptiveJson, + "application/cbor" => Mime::ApplicationCbor, + "application/cbor-seq" => Mime::ApplicationCborSeq, + "application/cccex" => Mime::ApplicationCccex, + "application/ccmp+xml" => Mime::ApplicationCcmpXml, + "application/ccxml+xml" => Mime::ApplicationCcxmlXml, + "application/cda+xml" => Mime::ApplicationCdaXml, + "application/CDFX+XML" => Mime::ApplicationCdfxXml, + "application/cdmi-capability" => Mime::ApplicationCdmiCapability, + "application/cdmi-container" => Mime::ApplicationCdmiContainer, + "application/cdmi-domain" => Mime::ApplicationCdmiDomain, + "application/cdmi-object" => Mime::ApplicationCdmiObject, + "application/cdmi-queue" => Mime::ApplicationCdmiQueue, + "application/cdni" => Mime::ApplicationCdni, + "application/CEA" => Mime::ApplicationCea, + "application/cea-2018+xml" => Mime::ApplicationCea2018Xml, + "application/cellml+xml" => Mime::ApplicationCellmlXml, + "application/cfw" => Mime::ApplicationCfw, + "application/city+json" => Mime::ApplicationCityJson, + "application/clr" => Mime::ApplicationClr, + "application/clue_info+xml" => Mime::ApplicationClueInfoXml, + "application/clue+xml" => Mime::ApplicationClueXml, + "application/cms" => Mime::ApplicationCms, + "application/cnrp+xml" => Mime::ApplicationCnrpXml, + "application/coap-group+json" => Mime::ApplicationCoapGroupJson, + "application/coap-payload" => Mime::ApplicationCoapPayload, + "application/commonground" => Mime::ApplicationCommonground, + "application/concise-problem-details+cbor" => Mime::ApplicationConciseProblemDetailsCbor, + "application/conference-info+xml" => Mime::ApplicationConferenceInfoXml, + "application/cpl+xml" => Mime::ApplicationCplXml, + "application/cose" => Mime::ApplicationCose, + "application/cose-key" => Mime::ApplicationCoseKey, + "application/cose-key-set" => Mime::ApplicationCoseKeySet, + "application/cose-x509" => Mime::ApplicationCoseX509, + "application/csrattrs" => Mime::ApplicationCsrattrs, + "application/csta+xml" => Mime::ApplicationCstaXml, + "application/CSTAdata+xml" => Mime::ApplicationCstadataXml, + "application/csvm+json" => Mime::ApplicationCsvmJson, + "application/cwl" => Mime::ApplicationCwl, + "application/cwl+json" => Mime::ApplicationCwlJson, + "application/cwt" => Mime::ApplicationCwt, + "application/cybercash" => Mime::ApplicationCybercash, + "application/dash+xml" => Mime::ApplicationDashXml, + "application/dash-patch+xml" => Mime::ApplicationDashPatchXml, + "application/dashdelta" => Mime::ApplicationDashdelta, + "application/davmount+xml" => Mime::ApplicationDavmountXml, + "application/dca-rft" => Mime::ApplicationDcaRft, + "application/DCD" => Mime::ApplicationDcd, + "application/dec-dx" => Mime::ApplicationDecDx, + "application/dialog-info+xml" => Mime::ApplicationDialogInfoXml, + "application/dicom" => Mime::ApplicationDicom, + "application/dicom+json" => Mime::ApplicationDicomJson, + "application/dicom+xml" => Mime::ApplicationDicomXml, + "application/DII" => Mime::ApplicationDii, + "application/DIT" => Mime::ApplicationDit, + "application/dns" => Mime::ApplicationDns, + "application/dns+json" => Mime::ApplicationDnsJson, + "application/dns-message" => Mime::ApplicationDnsMessage, + "application/dots+cbor" => Mime::ApplicationDotsCbor, + "application/dpop+jwt" => Mime::ApplicationDpopJwt, + "application/dskpp+xml" => Mime::ApplicationDskppXml, + "application/dssc+der" => Mime::ApplicationDsscDer, + "application/dssc+xml" => Mime::ApplicationDsscXml, + "application/dvcs" => Mime::ApplicationDvcs, + "application/EDI-consent" => Mime::ApplicationEdiConsent, + "application/EDIFACT" => Mime::ApplicationEdifact, + "application/EDI-X12" => Mime::ApplicationEdiX12, + "application/efi" => Mime::ApplicationEfi, + "application/elm+json" => Mime::ApplicationElmJson, + "application/elm+xml" => Mime::ApplicationElmXml, + "application/EmergencyCallData.cap+xml" => Mime::ApplicationEmergencycalldataCapXml, + "application/EmergencyCallData.Comment+xml" => Mime::ApplicationEmergencycalldataCommentXml, + "application/EmergencyCallData.Control+xml" => Mime::ApplicationEmergencycalldataControlXml, + "application/EmergencyCallData.DeviceInfo+xml" => Mime::ApplicationEmergencycalldataDeviceinfoXml, + "application/EmergencyCallData.eCall.MSD" => Mime::ApplicationEmergencycalldataEcallMsd, + "application/EmergencyCallData.LegacyESN+json" => Mime::ApplicationEmergencycalldataLegacyesnJson, + "application/EmergencyCallData.ProviderInfo+xml" => Mime::ApplicationEmergencycalldataProviderinfoXml, + "application/EmergencyCallData.ServiceInfo+xml" => Mime::ApplicationEmergencycalldataServiceinfoXml, + "application/EmergencyCallData.SubscriberInfo+xml" => Mime::ApplicationEmergencycalldataSubscriberinfoXml, + "application/EmergencyCallData.VEDS+xml" => Mime::ApplicationEmergencycalldataVedsXml, + "application/emma+xml" => Mime::ApplicationEmmaXml, + "application/emotionml+xml" => Mime::ApplicationEmotionmlXml, + "application/encaprtp" => Mime::ApplicationEncaprtp, + "application/epp+xml" => Mime::ApplicationEppXml, + "application/epub+zip" => Mime::ApplicationEpubZip, + "application/eshop" => Mime::ApplicationEshop, + "application/example" => Mime::ApplicationExample, + "application/exi" => Mime::ApplicationExi, + "application/expect-ct-report+json" => Mime::ApplicationExpectCtReportJson, + "application/express" => Mime::ApplicationExpress, + "application/fastinfoset" => Mime::ApplicationFastinfoset, + "application/fastsoap" => Mime::ApplicationFastsoap, + "application/fdf" => Mime::ApplicationFdf, + "application/fdt+xml" => Mime::ApplicationFdtXml, + "application/fhir+json" => Mime::ApplicationFhirJson, + "application/fhir+xml" => Mime::ApplicationFhirXml, + "application/fits" => Mime::ApplicationFits, + "application/flexfec" => Mime::ApplicationFlexfec, + "application/font-tdpfr" => Mime::ApplicationFontTdpfr, + "application/framework-attributes+xml" => Mime::ApplicationFrameworkAttributesXml, + "application/geo+json" => Mime::ApplicationGeoJson, + "application/geo+json-seq" => Mime::ApplicationGeoJsonSeq, + "application/geopackage+sqlite3" => Mime::ApplicationGeopackageSqlite3, + "application/geoxacml+xml" => Mime::ApplicationGeoxacmlXml, + "application/gltf-buffer" => Mime::ApplicationGltfBuffer, + "application/gml+xml" => Mime::ApplicationGmlXml, + "application/gzip" => Mime::ApplicationGzip, + "application/H224" => Mime::ApplicationH224, + "application/held+xml" => Mime::ApplicationHeldXml, + "application/hl7v2+xml" => Mime::ApplicationHl7v2Xml, + "application/http" => Mime::ApplicationHttp, + "application/hyperstudio" => Mime::ApplicationHyperstudio, + "application/ibe-key-request+xml" => Mime::ApplicationIbeKeyRequestXml, + "application/ibe-pkg-reply+xml" => Mime::ApplicationIbePkgReplyXml, + "application/ibe-pp-data" => Mime::ApplicationIbePpData, + "application/iges" => Mime::ApplicationIges, + "application/im-iscomposing+xml" => Mime::ApplicationImIscomposingXml, + "application/index" => Mime::ApplicationIndex, + "application/index.cmd" => Mime::ApplicationIndexCmd, + "application/index.obj" => Mime::ApplicationIndexObj, + "application/index.response" => Mime::ApplicationIndexResponse, + "application/index.vnd" => Mime::ApplicationIndexVnd, + "application/inkml+xml" => Mime::ApplicationInkmlXml, + "application/IOTP" => Mime::ApplicationIotp, + "application/ipfix" => Mime::ApplicationIpfix, + "application/ipp" => Mime::ApplicationIpp, + "application/ISUP" => Mime::ApplicationIsup, + "application/its+xml" => Mime::ApplicationItsXml, + "application/java-archive" => Mime::ApplicationJavaArchive, + "application/jf2feed+json" => Mime::ApplicationJf2feedJson, + "application/jose" => Mime::ApplicationJose, + "application/jose+json" => Mime::ApplicationJoseJson, + "application/jrd+json" => Mime::ApplicationJrdJson, + "application/jscalendar+json" => Mime::ApplicationJscalendarJson, + "application/json" => Mime::ApplicationJson, + "application/json-patch+json" => Mime::ApplicationJsonPatchJson, + "application/json-seq" => Mime::ApplicationJsonSeq, + "application/jwk+json" => Mime::ApplicationJwkJson, + "application/jwk-set+json" => Mime::ApplicationJwkSetJson, + "application/jwt" => Mime::ApplicationJwt, + "application/kpml-request+xml" => Mime::ApplicationKpmlRequestXml, + "application/kpml-response+xml" => Mime::ApplicationKpmlResponseXml, + "application/ld+json" => Mime::ApplicationLdJson, + "application/lgr+xml" => Mime::ApplicationLgrXml, + "application/link-format" => Mime::ApplicationLinkFormat, + "application/linkset" => Mime::ApplicationLinkset, + "application/linkset+json" => Mime::ApplicationLinksetJson, + "application/load-control+xml" => Mime::ApplicationLoadControlXml, + "application/logout+jwt" => Mime::ApplicationLogoutJwt, + "application/lost+xml" => Mime::ApplicationLostXml, + "application/lostsync+xml" => Mime::ApplicationLostsyncXml, + "application/lpf+zip" => Mime::ApplicationLpfZip, + "application/LXF" => Mime::ApplicationLxf, + "application/mac-binhex40" => Mime::ApplicationMacBinhex40, + "application/macwriteii" => Mime::ApplicationMacwriteii, + "application/mads+xml" => Mime::ApplicationMadsXml, + "application/manifest+json" => Mime::ApplicationManifestJson, + "application/marc" => Mime::ApplicationMarc, + "application/marcxml+xml" => Mime::ApplicationMarcxmlXml, + "application/mathematica" => Mime::ApplicationMathematica, + "application/mathml+xml" => Mime::ApplicationMathmlXml, + "application/mathml-content+xml" => Mime::ApplicationMathmlContentXml, + "application/mathml-presentation+xml" => Mime::ApplicationMathmlPresentationXml, + "application/mbms-associated-procedure-description+xml" => Mime::ApplicationMbmsAssociatedProcedureDescriptionXml, + "application/mbms-deregister+xml" => Mime::ApplicationMbmsDeregisterXml, + "application/mbms-envelope+xml" => Mime::ApplicationMbmsEnvelopeXml, + "application/mbms-msk-response+xml" => Mime::ApplicationMbmsMskResponseXml, + "application/mbms-msk+xml" => Mime::ApplicationMbmsMskXml, + "application/mbms-protection-description+xml" => Mime::ApplicationMbmsProtectionDescriptionXml, + "application/mbms-reception-report+xml" => Mime::ApplicationMbmsReceptionReportXml, + "application/mbms-register-response+xml" => Mime::ApplicationMbmsRegisterResponseXml, + "application/mbms-register+xml" => Mime::ApplicationMbmsRegisterXml, + "application/mbms-schedule+xml" => Mime::ApplicationMbmsScheduleXml, + "application/mbms-user-service-description+xml" => Mime::ApplicationMbmsUserServiceDescriptionXml, + "application/mbox" => Mime::ApplicationMbox, + "application/media_control+xml" => Mime::ApplicationMediaControlXml, + "application/media-policy-dataset+xml" => Mime::ApplicationMediaPolicyDatasetXml, + "application/mediaservercontrol+xml" => Mime::ApplicationMediaservercontrolXml, + "application/merge-patch+json" => Mime::ApplicationMergePatchJson, + "application/metalink4+xml" => Mime::ApplicationMetalink4Xml, + "application/mets+xml" => Mime::ApplicationMetsXml, + "application/MF4" => Mime::ApplicationMf4, + "application/mikey" => Mime::ApplicationMikey, + "application/mipc" => Mime::ApplicationMipc, + "application/missing-blocks+cbor-seq" => Mime::ApplicationMissingBlocksCborSeq, + "application/mmt-aei+xml" => Mime::ApplicationMmtAeiXml, + "application/mmt-usd+xml" => Mime::ApplicationMmtUsdXml, + "application/mods+xml" => Mime::ApplicationModsXml, + "application/moss-keys" => Mime::ApplicationMossKeys, + "application/moss-signature" => Mime::ApplicationMossSignature, + "application/mosskey-data" => Mime::ApplicationMosskeyData, + "application/mosskey-request" => Mime::ApplicationMosskeyRequest, + "application/mp21" => Mime::ApplicationMp21, + "application/mp4" => Mime::ApplicationMp4, + "application/mpeg4-generic" => Mime::ApplicationMpeg4Generic, + "application/mpeg4-iod" => Mime::ApplicationMpeg4Iod, + "application/mpeg4-iod-xmt" => Mime::ApplicationMpeg4IodXmt, + "application/mrb-consumer+xml" => Mime::ApplicationMrbConsumerXml, + "application/mrb-publish+xml" => Mime::ApplicationMrbPublishXml, + "application/msc-ivr+xml" => Mime::ApplicationMscIvrXml, + "application/msc-mixer+xml" => Mime::ApplicationMscMixerXml, + "application/msword" => Mime::ApplicationMsword, + "application/mud+json" => Mime::ApplicationMudJson, + "application/multipart-core" => Mime::ApplicationMultipartCore, + "application/mxf" => Mime::ApplicationMxf, + "application/n-quads" => Mime::ApplicationNQuads, + "application/n-triples" => Mime::ApplicationNTriples, + "application/nasdata" => Mime::ApplicationNasdata, + "application/news-checkgroups" => Mime::ApplicationNewsCheckgroups, + "application/news-groupinfo" => Mime::ApplicationNewsGroupinfo, + "application/news-transmission" => Mime::ApplicationNewsTransmission, + "application/nlsml+xml" => Mime::ApplicationNlsmlXml, + "application/node" => Mime::ApplicationNode, + "application/nss" => Mime::ApplicationNss, + "application/oauth-authz-req+jwt" => Mime::ApplicationOauthAuthzReqJwt, + "application/oblivious-dns-message" => Mime::ApplicationObliviousDnsMessage, + "application/ocsp-request" => Mime::ApplicationOcspRequest, + "application/ocsp-response" => Mime::ApplicationOcspResponse, + "application/octet-stream" => Mime::ApplicationOctetStream, + "application/ODA" => Mime::ApplicationOda, + "application/odm+xml" => Mime::ApplicationOdmXml, + "application/ODX" => Mime::ApplicationOdx, + "application/oebps-package+xml" => Mime::ApplicationOebpsPackageXml, + "application/ogg" => Mime::ApplicationOgg, + "application/ohttp-keys" => Mime::ApplicationOhttpKeys, + "application/opc-nodeset+xml" => Mime::ApplicationOpcNodesetXml, + "application/oscore" => Mime::ApplicationOscore, + "application/oxps" => Mime::ApplicationOxps, + "application/p21" => Mime::ApplicationP21, + "application/p21+zip" => Mime::ApplicationP21Zip, + "application/p2p-overlay+xml" => Mime::ApplicationP2pOverlayXml, + "application/parityfec" => Mime::ApplicationParityfec, + "application/passport" => Mime::ApplicationPassport, + "application/patch-ops-error+xml" => Mime::ApplicationPatchOpsErrorXml, + "application/pdf" => Mime::ApplicationPdf, + "application/PDX" => Mime::ApplicationPdx, + "application/pem-certificate-chain" => Mime::ApplicationPemCertificateChain, + "application/pgp-encrypted" => Mime::ApplicationPgpEncrypted, + "application/pgp-keys" => Mime::ApplicationPgpKeys, + "application/pgp-signature" => Mime::ApplicationPgpSignature, + "application/pidf-diff+xml" => Mime::ApplicationPidfDiffXml, + "application/pidf+xml" => Mime::ApplicationPidfXml, + "application/pkcs10" => Mime::ApplicationPkcs10, + "application/pkcs7-mime" => Mime::ApplicationPkcs7Mime, + "application/pkcs7-signature" => Mime::ApplicationPkcs7Signature, + "application/pkcs8" => Mime::ApplicationPkcs8, + "application/pkcs8-encrypted" => Mime::ApplicationPkcs8Encrypted, + "application/pkcs12" => Mime::ApplicationPkcs12, + "application/pkix-attr-cert" => Mime::ApplicationPkixAttrCert, + "application/pkix-cert" => Mime::ApplicationPkixCert, + "application/pkix-crl" => Mime::ApplicationPkixCrl, + "application/pkix-pkipath" => Mime::ApplicationPkixPkipath, + "application/pkixcmp" => Mime::ApplicationPkixcmp, + "application/pls+xml" => Mime::ApplicationPlsXml, + "application/poc-settings+xml" => Mime::ApplicationPocSettingsXml, + "application/postscript" => Mime::ApplicationPostscript, + "application/ppsp-tracker+json" => Mime::ApplicationPpspTrackerJson, + "application/problem+json" => Mime::ApplicationProblemJson, + "application/problem+xml" => Mime::ApplicationProblemXml, + "application/provenance+xml" => Mime::ApplicationProvenanceXml, + "application/prs.alvestrand.titrax-sheet" => Mime::ApplicationPrsAlvestrandTitraxSheet, + "application/prs.cww" => Mime::ApplicationPrsCww, + "application/prs.cyn" => Mime::ApplicationPrsCyn, + "application/prs.hpub+zip" => Mime::ApplicationPrsHpubZip, + "application/prs.implied-document+xml" => Mime::ApplicationPrsImpliedDocumentXml, + "application/prs.implied-executable" => Mime::ApplicationPrsImpliedExecutable, + "application/prs.implied-structure" => Mime::ApplicationPrsImpliedStructure, + "application/prs.nprend" => Mime::ApplicationPrsNprend, + "application/prs.plucker" => Mime::ApplicationPrsPlucker, + "application/prs.rdf-xml-crypt" => Mime::ApplicationPrsRdfXmlCrypt, + "application/prs.xsf+xml" => Mime::ApplicationPrsXsfXml, + "application/pskc+xml" => Mime::ApplicationPskcXml, + "application/pvd+json" => Mime::ApplicationPvdJson, + "application/rdf+xml" => Mime::ApplicationRdfXml, + "application/route-apd+xml" => Mime::ApplicationRouteApdXml, + "application/route-s-tsid+xml" => Mime::ApplicationRouteSTsidXml, + "application/route-usd+xml" => Mime::ApplicationRouteUsdXml, + "application/QSIG" => Mime::ApplicationQsig, + "application/raptorfec" => Mime::ApplicationRaptorfec, + "application/rdap+json" => Mime::ApplicationRdapJson, + "application/reginfo+xml" => Mime::ApplicationReginfoXml, + "application/relax-ng-compact-syntax" => Mime::ApplicationRelaxNgCompactSyntax, + "application/reputon+json" => Mime::ApplicationReputonJson, + "application/resource-lists-diff+xml" => Mime::ApplicationResourceListsDiffXml, + "application/resource-lists+xml" => Mime::ApplicationResourceListsXml, + "application/rfc+xml" => Mime::ApplicationRfcXml, + "application/riscos" => Mime::ApplicationRiscos, + "application/rlmi+xml" => Mime::ApplicationRlmiXml, + "application/rls-services+xml" => Mime::ApplicationRlsServicesXml, + "application/rpki-checklist" => Mime::ApplicationRpkiChecklist, + "application/rpki-ghostbusters" => Mime::ApplicationRpkiGhostbusters, + "application/rpki-manifest" => Mime::ApplicationRpkiManifest, + "application/rpki-publication" => Mime::ApplicationRpkiPublication, + "application/rpki-roa" => Mime::ApplicationRpkiRoa, + "application/rpki-updown" => Mime::ApplicationRpkiUpdown, + "application/rtf" => Mime::ApplicationRtf, + "application/rtploopback" => Mime::ApplicationRtploopback, + "application/rtx" => Mime::ApplicationRtx, + "application/samlassertion+xml" => Mime::ApplicationSamlassertionXml, + "application/samlmetadata+xml" => Mime::ApplicationSamlmetadataXml, + "application/sarif-external-properties+json" => Mime::ApplicationSarifExternalPropertiesJson, + "application/sarif+json" => Mime::ApplicationSarifJson, + "application/sbe" => Mime::ApplicationSbe, + "application/sbml+xml" => Mime::ApplicationSbmlXml, + "application/scaip+xml" => Mime::ApplicationScaipXml, + "application/scim+json" => Mime::ApplicationScimJson, + "application/scvp-cv-request" => Mime::ApplicationScvpCvRequest, + "application/scvp-cv-response" => Mime::ApplicationScvpCvResponse, + "application/scvp-vp-request" => Mime::ApplicationScvpVpRequest, + "application/scvp-vp-response" => Mime::ApplicationScvpVpResponse, + "application/sdp" => Mime::ApplicationSdp, + "application/secevent+jwt" => Mime::ApplicationSeceventJwt, + "application/senml-etch+cbor" => Mime::ApplicationSenmlEtchCbor, + "application/senml-etch+json" => Mime::ApplicationSenmlEtchJson, + "application/senml-exi" => Mime::ApplicationSenmlExi, + "application/senml+cbor" => Mime::ApplicationSenmlCbor, + "application/senml+json" => Mime::ApplicationSenmlJson, + "application/senml+xml" => Mime::ApplicationSenmlXml, + "application/sensml-exi" => Mime::ApplicationSensmlExi, + "application/sensml+cbor" => Mime::ApplicationSensmlCbor, + "application/sensml+json" => Mime::ApplicationSensmlJson, + "application/sensml+xml" => Mime::ApplicationSensmlXml, + "application/sep-exi" => Mime::ApplicationSepExi, + "application/sep+xml" => Mime::ApplicationSepXml, + "application/session-info" => Mime::ApplicationSessionInfo, + "application/set-payment" => Mime::ApplicationSetPayment, + "application/set-payment-initiation" => Mime::ApplicationSetPaymentInitiation, + "application/set-registration" => Mime::ApplicationSetRegistration, + "application/set-registration-initiation" => Mime::ApplicationSetRegistrationInitiation, + "application/SGML" => Mime::ApplicationSgml, + "application/sgml-open-catalog" => Mime::ApplicationSgmlOpenCatalog, + "application/shf+xml" => Mime::ApplicationShfXml, + "application/sieve" => Mime::ApplicationSieve, + "application/simple-filter+xml" => Mime::ApplicationSimpleFilterXml, + "application/simple-message-summary" => Mime::ApplicationSimpleMessageSummary, + "application/simpleSymbolContainer" => Mime::ApplicationSimplesymbolcontainer, + "application/sipc" => Mime::ApplicationSipc, + "application/slate" => Mime::ApplicationSlate, + "application/smil+xml" => Mime::ApplicationSmilXml, + "application/smpte336m" => Mime::ApplicationSmpte336m, + "application/soap+fastinfoset" => Mime::ApplicationSoapFastinfoset, + "application/soap+xml" => Mime::ApplicationSoapXml, + "application/sparql-query" => Mime::ApplicationSparqlQuery, + "application/spdx+json" => Mime::ApplicationSpdxJson, + "application/sparql-results+xml" => Mime::ApplicationSparqlResultsXml, + "application/spirits-event+xml" => Mime::ApplicationSpiritsEventXml, + "application/sql" => Mime::ApplicationSql, + "application/srgs" => Mime::ApplicationSrgs, + "application/srgs+xml" => Mime::ApplicationSrgsXml, + "application/sru+xml" => Mime::ApplicationSruXml, + "application/ssml+xml" => Mime::ApplicationSsmlXml, + "application/stix+json" => Mime::ApplicationStixJson, + "application/swid+cbor" => Mime::ApplicationSwidCbor, + "application/swid+xml" => Mime::ApplicationSwidXml, + "application/tamp-apex-update" => Mime::ApplicationTampApexUpdate, + "application/tamp-apex-update-confirm" => Mime::ApplicationTampApexUpdateConfirm, + "application/tamp-community-update" => Mime::ApplicationTampCommunityUpdate, + "application/tamp-community-update-confirm" => Mime::ApplicationTampCommunityUpdateConfirm, + "application/tamp-error" => Mime::ApplicationTampError, + "application/tamp-sequence-adjust" => Mime::ApplicationTampSequenceAdjust, + "application/tamp-sequence-adjust-confirm" => Mime::ApplicationTampSequenceAdjustConfirm, + "application/tamp-status-query" => Mime::ApplicationTampStatusQuery, + "application/tamp-status-response" => Mime::ApplicationTampStatusResponse, + "application/tamp-update" => Mime::ApplicationTampUpdate, + "application/tamp-update-confirm" => Mime::ApplicationTampUpdateConfirm, + "application/taxii+json" => Mime::ApplicationTaxiiJson, + "application/td+json" => Mime::ApplicationTdJson, + "application/tei+xml" => Mime::ApplicationTeiXml, + "application/TETRA_ISI" => Mime::ApplicationTetraIsi, + "application/thraud+xml" => Mime::ApplicationThraudXml, + "application/timestamp-query" => Mime::ApplicationTimestampQuery, + "application/timestamp-reply" => Mime::ApplicationTimestampReply, + "application/timestamped-data" => Mime::ApplicationTimestampedData, + "application/tlsrpt+gzip" => Mime::ApplicationTlsrptGzip, + "application/tlsrpt+json" => Mime::ApplicationTlsrptJson, + "application/tm+json" => Mime::ApplicationTmJson, + "application/tnauthlist" => Mime::ApplicationTnauthlist, + "application/token-introspection+jwt" => Mime::ApplicationTokenIntrospectionJwt, + "application/trickle-ice-sdpfrag" => Mime::ApplicationTrickleIceSdpfrag, + "application/trig" => Mime::ApplicationTrig, + "application/ttml+xml" => Mime::ApplicationTtmlXml, + "application/tve-trigger" => Mime::ApplicationTveTrigger, + "application/tzif" => Mime::ApplicationTzif, + "application/tzif-leap" => Mime::ApplicationTzifLeap, + "application/ulpfec" => Mime::ApplicationUlpfec, + "application/urc-grpsheet+xml" => Mime::ApplicationUrcGrpsheetXml, + "application/urc-ressheet+xml" => Mime::ApplicationUrcRessheetXml, + "application/urc-targetdesc+xml" => Mime::ApplicationUrcTargetdescXml, + "application/urc-uisocketdesc+xml" => Mime::ApplicationUrcUisocketdescXml, + "application/vcard+json" => Mime::ApplicationVcardJson, + "application/vcard+xml" => Mime::ApplicationVcardXml, + "application/vemmi" => Mime::ApplicationVemmi, + "application/vnd.1000minds.decision-model+xml" => Mime::ApplicationVnd1000mindsDecisionModelXml, + "application/vnd.1ob" => Mime::ApplicationVnd1ob, + "application/vnd.3gpp.5gnas" => Mime::ApplicationVnd3gpp5gnas, + "application/vnd.3gpp.access-transfer-events+xml" => Mime::ApplicationVnd3gppAccessTransferEventsXml, + "application/vnd.3gpp.bsf+xml" => Mime::ApplicationVnd3gppBsfXml, + "application/vnd.3gpp.crs+xml" => Mime::ApplicationVnd3gppCrsXml, + "application/vnd.3gpp.current-location-discovery+xml" => Mime::ApplicationVnd3gppCurrentLocationDiscoveryXml, + "application/vnd.3gpp.GMOP+xml" => Mime::ApplicationVnd3gppGmopXml, + "application/vnd.3gpp.gtpc" => Mime::ApplicationVnd3gppGtpc, + "application/vnd.3gpp.interworking-data" => Mime::ApplicationVnd3gppInterworkingData, + "application/vnd.3gpp.lpp" => Mime::ApplicationVnd3gppLpp, + "application/vnd.3gpp.mc-signalling-ear" => Mime::ApplicationVnd3gppMcSignallingEar, + "application/vnd.3gpp.mcdata-affiliation-command+xml" => Mime::ApplicationVnd3gppMcdataAffiliationCommandXml, + "application/vnd.3gpp.mcdata-info+xml" => Mime::ApplicationVnd3gppMcdataInfoXml, + "application/vnd.3gpp.mcdata-msgstore-ctrl-request+xml" => Mime::ApplicationVnd3gppMcdataMsgstoreCtrlRequestXml, + "application/vnd.3gpp.mcdata-payload" => Mime::ApplicationVnd3gppMcdataPayload, + "application/vnd.3gpp.mcdata-regroup+xml" => Mime::ApplicationVnd3gppMcdataRegroupXml, + "application/vnd.3gpp.mcdata-service-config+xml" => Mime::ApplicationVnd3gppMcdataServiceConfigXml, + "application/vnd.3gpp.mcdata-signalling" => Mime::ApplicationVnd3gppMcdataSignalling, + "application/vnd.3gpp.mcdata-ue-config+xml" => Mime::ApplicationVnd3gppMcdataUeConfigXml, + "application/vnd.3gpp.mcdata-user-profile+xml" => Mime::ApplicationVnd3gppMcdataUserProfileXml, + "application/vnd.3gpp.mcptt-affiliation-command+xml" => Mime::ApplicationVnd3gppMcpttAffiliationCommandXml, + "application/vnd.3gpp.mcptt-floor-request+xml" => Mime::ApplicationVnd3gppMcpttFloorRequestXml, + "application/vnd.3gpp.mcptt-info+xml" => Mime::ApplicationVnd3gppMcpttInfoXml, + "application/vnd.3gpp.mcptt-location-info+xml" => Mime::ApplicationVnd3gppMcpttLocationInfoXml, + "application/vnd.3gpp.mcptt-mbms-usage-info+xml" => Mime::ApplicationVnd3gppMcpttMbmsUsageInfoXml, + "application/vnd.3gpp.mcptt-regroup+xml" => Mime::ApplicationVnd3gppMcpttRegroupXml, + "application/vnd.3gpp.mcptt-service-config+xml" => Mime::ApplicationVnd3gppMcpttServiceConfigXml, + "application/vnd.3gpp.mcptt-signed+xml" => Mime::ApplicationVnd3gppMcpttSignedXml, + "application/vnd.3gpp.mcptt-ue-config+xml" => Mime::ApplicationVnd3gppMcpttUeConfigXml, + "application/vnd.3gpp.mcptt-ue-init-config+xml" => Mime::ApplicationVnd3gppMcpttUeInitConfigXml, + "application/vnd.3gpp.mcptt-user-profile+xml" => Mime::ApplicationVnd3gppMcpttUserProfileXml, + "application/vnd.3gpp.mcvideo-affiliation-command+xml" => Mime::ApplicationVnd3gppMcvideoAffiliationCommandXml, + "application/vnd.3gpp.mcvideo-info+xml" => Mime::ApplicationVnd3gppMcvideoInfoXml, + "application/vnd.3gpp.mcvideo-location-info+xml" => Mime::ApplicationVnd3gppMcvideoLocationInfoXml, + "application/vnd.3gpp.mcvideo-mbms-usage-info+xml" => Mime::ApplicationVnd3gppMcvideoMbmsUsageInfoXml, + "application/vnd.3gpp.mcvideo-regroup+xml" => Mime::ApplicationVnd3gppMcvideoRegroupXml, + "application/vnd.3gpp.mcvideo-service-config+xml" => Mime::ApplicationVnd3gppMcvideoServiceConfigXml, + "application/vnd.3gpp.mcvideo-transmission-request+xml" => Mime::ApplicationVnd3gppMcvideoTransmissionRequestXml, + "application/vnd.3gpp.mcvideo-ue-config+xml" => Mime::ApplicationVnd3gppMcvideoUeConfigXml, + "application/vnd.3gpp.mcvideo-user-profile+xml" => Mime::ApplicationVnd3gppMcvideoUserProfileXml, + "application/vnd.3gpp.mid-call+xml" => Mime::ApplicationVnd3gppMidCallXml, + "application/vnd.3gpp.ngap" => Mime::ApplicationVnd3gppNgap, + "application/vnd.3gpp.pfcp" => Mime::ApplicationVnd3gppPfcp, + "application/vnd.3gpp.pic-bw-large" => Mime::ApplicationVnd3gppPicBwLarge, + "application/vnd.3gpp.pic-bw-small" => Mime::ApplicationVnd3gppPicBwSmall, + "application/vnd.3gpp.pic-bw-var" => Mime::ApplicationVnd3gppPicBwVar, + "application/vnd.3gpp-prose-pc3a+xml" => Mime::ApplicationVnd3gppProsePc3aXml, + "application/vnd.3gpp-prose-pc3ach+xml" => Mime::ApplicationVnd3gppProsePc3achXml, + "application/vnd.3gpp-prose-pc3ch+xml" => Mime::ApplicationVnd3gppProsePc3chXml, + "application/vnd.3gpp-prose-pc8+xml" => Mime::ApplicationVnd3gppProsePc8Xml, + "application/vnd.3gpp-prose+xml" => Mime::ApplicationVnd3gppProseXml, + "application/vnd.3gpp.s1ap" => Mime::ApplicationVnd3gppS1ap, + "application/vnd.3gpp.seal-group-doc+xml" => Mime::ApplicationVnd3gppSealGroupDocXml, + "application/vnd.3gpp.seal-info+xml" => Mime::ApplicationVnd3gppSealInfoXml, + "application/vnd.3gpp.seal-location-info+xml" => Mime::ApplicationVnd3gppSealLocationInfoXml, + "application/vnd.3gpp.seal-mbms-usage-info+xml" => Mime::ApplicationVnd3gppSealMbmsUsageInfoXml, + "application/vnd.3gpp.seal-network-QoS-management-info+xml" => Mime::ApplicationVnd3gppSealNetworkQosManagementInfoXml, + "application/vnd.3gpp.seal-ue-config-info+xml" => Mime::ApplicationVnd3gppSealUeConfigInfoXml, + "application/vnd.3gpp.seal-unicast-info+xml" => Mime::ApplicationVnd3gppSealUnicastInfoXml, + "application/vnd.3gpp.seal-user-profile-info+xml" => Mime::ApplicationVnd3gppSealUserProfileInfoXml, + "application/vnd.3gpp.sms" => Mime::ApplicationVnd3gppSms, + "application/vnd.3gpp.sms+xml" => Mime::ApplicationVnd3gppSmsXml, + "application/vnd.3gpp.srvcc-ext+xml" => Mime::ApplicationVnd3gppSrvccExtXml, + "application/vnd.3gpp.SRVCC-info+xml" => Mime::ApplicationVnd3gppSrvccInfoXml, + "application/vnd.3gpp.state-and-event-info+xml" => Mime::ApplicationVnd3gppStateAndEventInfoXml, + "application/vnd.3gpp.ussd+xml" => Mime::ApplicationVnd3gppUssdXml, + "application/vnd.3gpp.vae-info+xml" => Mime::ApplicationVnd3gppVaeInfoXml, + "application/vnd.3gpp-v2x-local-service-information" => Mime::ApplicationVnd3gppV2xLocalServiceInformation, + "application/vnd.3gpp2.bcmcsinfo+xml" => Mime::ApplicationVnd3gpp2BcmcsinfoXml, + "application/vnd.3gpp2.sms" => Mime::ApplicationVnd3gpp2Sms, + "application/vnd.3gpp2.tcap" => Mime::ApplicationVnd3gpp2Tcap, + "application/vnd.3gpp.v2x" => Mime::ApplicationVnd3gppV2x, + "application/vnd.3lightssoftware.imagescal" => Mime::ApplicationVnd3lightssoftwareImagescal, + "application/vnd.3M.Post-it-Notes" => Mime::ApplicationVnd3mPostItNotes, + "application/vnd.accpac.simply.aso" => Mime::ApplicationVndAccpacSimplyAso, + "application/vnd.accpac.simply.imp" => Mime::ApplicationVndAccpacSimplyImp, + "application/vnd.acm.addressxfer+json" => Mime::ApplicationVndAcmAddressxferJson, + "application/vnd.acucobol" => Mime::ApplicationVndAcucobol, + "application/vnd.acucorp" => Mime::ApplicationVndAcucorp, + "application/vnd.adobe.flash.movie" => Mime::ApplicationVndAdobeFlashMovie, + "application/vnd.adobe.formscentral.fcdt" => Mime::ApplicationVndAdobeFormscentralFcdt, + "application/vnd.adobe.fxp" => Mime::ApplicationVndAdobeFxp, + "application/vnd.adobe.partial-upload" => Mime::ApplicationVndAdobePartialUpload, + "application/vnd.adobe.xdp+xml" => Mime::ApplicationVndAdobeXdpXml, + "application/vnd.aether.imp" => Mime::ApplicationVndAetherImp, + "application/vnd.afpc.afplinedata" => Mime::ApplicationVndAfpcAfplinedata, + "application/vnd.afpc.afplinedata-pagedef" => Mime::ApplicationVndAfpcAfplinedataPagedef, + "application/vnd.afpc.cmoca-cmresource" => Mime::ApplicationVndAfpcCmocaCmresource, + "application/vnd.afpc.foca-charset" => Mime::ApplicationVndAfpcFocaCharset, + "application/vnd.afpc.foca-codedfont" => Mime::ApplicationVndAfpcFocaCodedfont, + "application/vnd.afpc.foca-codepage" => Mime::ApplicationVndAfpcFocaCodepage, + "application/vnd.afpc.modca" => Mime::ApplicationVndAfpcModca, + "application/vnd.afpc.modca-cmtable" => Mime::ApplicationVndAfpcModcaCmtable, + "application/vnd.afpc.modca-formdef" => Mime::ApplicationVndAfpcModcaFormdef, + "application/vnd.afpc.modca-mediummap" => Mime::ApplicationVndAfpcModcaMediummap, + "application/vnd.afpc.modca-objectcontainer" => Mime::ApplicationVndAfpcModcaObjectcontainer, + "application/vnd.afpc.modca-overlay" => Mime::ApplicationVndAfpcModcaOverlay, + "application/vnd.afpc.modca-pagesegment" => Mime::ApplicationVndAfpcModcaPagesegment, + "application/vnd.age" => Mime::ApplicationVndAge, + "application/vnd.ah-barcode" => Mime::ApplicationVndAhBarcode, + "application/vnd.ahead.space" => Mime::ApplicationVndAheadSpace, + "application/vnd.airzip.filesecure.azf" => Mime::ApplicationVndAirzipFilesecureAzf, + "application/vnd.airzip.filesecure.azs" => Mime::ApplicationVndAirzipFilesecureAzs, + "application/vnd.amadeus+json" => Mime::ApplicationVndAmadeusJson, + "application/vnd.amazon.mobi8-ebook" => Mime::ApplicationVndAmazonMobi8Ebook, + "application/vnd.americandynamics.acc" => Mime::ApplicationVndAmericandynamicsAcc, + "application/vnd.amiga.ami" => Mime::ApplicationVndAmigaAmi, + "application/vnd.amundsen.maze+xml" => Mime::ApplicationVndAmundsenMazeXml, + "application/vnd.android.ota" => Mime::ApplicationVndAndroidOta, + "application/vnd.anki" => Mime::ApplicationVndAnki, + "application/vnd.anser-web-certificate-issue-initiation" => Mime::ApplicationVndAnserWebCertificateIssueInitiation, + "application/vnd.antix.game-component" => Mime::ApplicationVndAntixGameComponent, + "application/vnd.apache.arrow.file" => Mime::ApplicationVndApacheArrowFile, + "application/vnd.apache.arrow.stream" => Mime::ApplicationVndApacheArrowStream, + "application/vnd.apache.thrift.binary" => Mime::ApplicationVndApacheThriftBinary, + "application/vnd.apache.thrift.compact" => Mime::ApplicationVndApacheThriftCompact, + "application/vnd.apache.thrift.json" => Mime::ApplicationVndApacheThriftJson, + "application/vnd.apexlang" => Mime::ApplicationVndApexlang, + "application/vnd.api+json" => Mime::ApplicationVndApiJson, + "application/vnd.aplextor.warrp+json" => Mime::ApplicationVndAplextorWarrpJson, + "application/vnd.apothekende.reservation+json" => Mime::ApplicationVndApothekendeReservationJson, + "application/vnd.apple.installer+xml" => Mime::ApplicationVndAppleInstallerXml, + "application/vnd.apple.keynote" => Mime::ApplicationVndAppleKeynote, + "application/vnd.apple.mpegurl" => Mime::ApplicationVndAppleMpegurl, + "application/vnd.apple.numbers" => Mime::ApplicationVndAppleNumbers, + "application/vnd.apple.pages" => Mime::ApplicationVndApplePages, + "application/vnd.aristanetworks.swi" => Mime::ApplicationVndAristanetworksSwi, + "application/vnd.artisan+json" => Mime::ApplicationVndArtisanJson, + "application/vnd.artsquare" => Mime::ApplicationVndArtsquare, + "application/vnd.astraea-software.iota" => Mime::ApplicationVndAstraeaSoftwareIota, + "application/vnd.audiograph" => Mime::ApplicationVndAudiograph, + "application/vnd.autopackage" => Mime::ApplicationVndAutopackage, + "application/vnd.avalon+json" => Mime::ApplicationVndAvalonJson, + "application/vnd.avistar+xml" => Mime::ApplicationVndAvistarXml, + "application/vnd.balsamiq.bmml+xml" => Mime::ApplicationVndBalsamiqBmmlXml, + "application/vnd.banana-accounting" => Mime::ApplicationVndBananaAccounting, + "application/vnd.bbf.usp.error" => Mime::ApplicationVndBbfUspError, + "application/vnd.bbf.usp.msg" => Mime::ApplicationVndBbfUspMsg, + "application/vnd.bbf.usp.msg+json" => Mime::ApplicationVndBbfUspMsgJson, + "application/vnd.balsamiq.bmpr" => Mime::ApplicationVndBalsamiqBmpr, + "application/vnd.bekitzur-stech+json" => Mime::ApplicationVndBekitzurStechJson, + "application/vnd.belightsoft.lhzd+zip" => Mime::ApplicationVndBelightsoftLhzdZip, + "application/vnd.belightsoft.lhzl+zip" => Mime::ApplicationVndBelightsoftLhzlZip, + "application/vnd.bint.med-content" => Mime::ApplicationVndBintMedContent, + "application/vnd.biopax.rdf+xml" => Mime::ApplicationVndBiopaxRdfXml, + "application/vnd.blink-idb-value-wrapper" => Mime::ApplicationVndBlinkIdbValueWrapper, + "application/vnd.blueice.multipass" => Mime::ApplicationVndBlueiceMultipass, + "application/vnd.bluetooth.ep.oob" => Mime::ApplicationVndBluetoothEpOob, + "application/vnd.bluetooth.le.oob" => Mime::ApplicationVndBluetoothLeOob, + "application/vnd.bmi" => Mime::ApplicationVndBmi, + "application/vnd.bpf" => Mime::ApplicationVndBpf, + "application/vnd.bpf3" => Mime::ApplicationVndBpf3, + "application/vnd.businessobjects" => Mime::ApplicationVndBusinessobjects, + "application/vnd.byu.uapi+json" => Mime::ApplicationVndByuUapiJson, + "application/vnd.cab-jscript" => Mime::ApplicationVndCabJscript, + "application/vnd.canon-cpdl" => Mime::ApplicationVndCanonCpdl, + "application/vnd.canon-lips" => Mime::ApplicationVndCanonLips, + "application/vnd.capasystems-pg+json" => Mime::ApplicationVndCapasystemsPgJson, + "application/vnd.cendio.thinlinc.clientconf" => Mime::ApplicationVndCendioThinlincClientconf, + "application/vnd.century-systems.tcp_stream" => Mime::ApplicationVndCenturySystemsTcpStream, + "application/vnd.chemdraw+xml" => Mime::ApplicationVndChemdrawXml, + "application/vnd.chess-pgn" => Mime::ApplicationVndChessPgn, + "application/vnd.chipnuts.karaoke-mmd" => Mime::ApplicationVndChipnutsKaraokeMmd, + "application/vnd.ciedi" => Mime::ApplicationVndCiedi, + "application/vnd.cinderella" => Mime::ApplicationVndCinderella, + "application/vnd.cirpack.isdn-ext" => Mime::ApplicationVndCirpackIsdnExt, + "application/vnd.citationstyles.style+xml" => Mime::ApplicationVndCitationstylesStyleXml, + "application/vnd.claymore" => Mime::ApplicationVndClaymore, + "application/vnd.cloanto.rp9" => Mime::ApplicationVndCloantoRp9, + "application/vnd.clonk.c4group" => Mime::ApplicationVndClonkC4group, + "application/vnd.cluetrust.cartomobile-config" => Mime::ApplicationVndCluetrustCartomobileConfig, + "application/vnd.cluetrust.cartomobile-config-pkg" => Mime::ApplicationVndCluetrustCartomobileConfigPkg, + "application/vnd.cncf.helm.chart.content.v1.tar+gzip" => Mime::ApplicationVndCncfHelmChartContentV1TarGzip, + "application/vnd.cncf.helm.chart.provenance.v1.prov" => Mime::ApplicationVndCncfHelmChartProvenanceV1Prov, + "application/vnd.cncf.helm.config.v1+json" => Mime::ApplicationVndCncfHelmConfigV1Json, + "application/vnd.coffeescript" => Mime::ApplicationVndCoffeescript, + "application/vnd.collabio.xodocuments.document" => Mime::ApplicationVndCollabioXodocumentsDocument, + "application/vnd.collabio.xodocuments.document-template" => Mime::ApplicationVndCollabioXodocumentsDocumentTemplate, + "application/vnd.collabio.xodocuments.presentation" => Mime::ApplicationVndCollabioXodocumentsPresentation, + "application/vnd.collabio.xodocuments.presentation-template" => Mime::ApplicationVndCollabioXodocumentsPresentationTemplate, + "application/vnd.collabio.xodocuments.spreadsheet" => Mime::ApplicationVndCollabioXodocumentsSpreadsheet, + "application/vnd.collabio.xodocuments.spreadsheet-template" => Mime::ApplicationVndCollabioXodocumentsSpreadsheetTemplate, + "application/vnd.collection.doc+json" => Mime::ApplicationVndCollectionDocJson, + "application/vnd.collection+json" => Mime::ApplicationVndCollectionJson, + "application/vnd.collection.next+json" => Mime::ApplicationVndCollectionNextJson, + "application/vnd.comicbook-rar" => Mime::ApplicationVndComicbookRar, + "application/vnd.comicbook+zip" => Mime::ApplicationVndComicbookZip, + "application/vnd.commerce-battelle" => Mime::ApplicationVndCommerceBattelle, + "application/vnd.commonspace" => Mime::ApplicationVndCommonspace, + "application/vnd.coreos.ignition+json" => Mime::ApplicationVndCoreosIgnitionJson, + "application/vnd.cosmocaller" => Mime::ApplicationVndCosmocaller, + "application/vnd.contact.cmsg" => Mime::ApplicationVndContactCmsg, + "application/vnd.crick.clicker" => Mime::ApplicationVndCrickClicker, + "application/vnd.crick.clicker.keyboard" => Mime::ApplicationVndCrickClickerKeyboard, + "application/vnd.crick.clicker.palette" => Mime::ApplicationVndCrickClickerPalette, + "application/vnd.crick.clicker.template" => Mime::ApplicationVndCrickClickerTemplate, + "application/vnd.crick.clicker.wordbank" => Mime::ApplicationVndCrickClickerWordbank, + "application/vnd.criticaltools.wbs+xml" => Mime::ApplicationVndCriticaltoolsWbsXml, + "application/vnd.cryptii.pipe+json" => Mime::ApplicationVndCryptiiPipeJson, + "application/vnd.crypto-shade-file" => Mime::ApplicationVndCryptoShadeFile, + "application/vnd.cryptomator.encrypted" => Mime::ApplicationVndCryptomatorEncrypted, + "application/vnd.cryptomator.vault" => Mime::ApplicationVndCryptomatorVault, + "application/vnd.ctc-posml" => Mime::ApplicationVndCtcPosml, + "application/vnd.ctct.ws+xml" => Mime::ApplicationVndCtctWsXml, + "application/vnd.cups-pdf" => Mime::ApplicationVndCupsPdf, + "application/vnd.cups-postscript" => Mime::ApplicationVndCupsPostscript, + "application/vnd.cups-ppd" => Mime::ApplicationVndCupsPpd, + "application/vnd.cups-raster" => Mime::ApplicationVndCupsRaster, + "application/vnd.cups-raw" => Mime::ApplicationVndCupsRaw, + "application/vnd.curl" => Mime::ApplicationVndCurl, + "application/vnd.cyan.dean.root+xml" => Mime::ApplicationVndCyanDeanRootXml, + "application/vnd.cybank" => Mime::ApplicationVndCybank, + "application/vnd.cyclonedx+json" => Mime::ApplicationVndCyclonedxJson, + "application/vnd.cyclonedx+xml" => Mime::ApplicationVndCyclonedxXml, + "application/vnd.d2l.coursepackage1p0+zip" => Mime::ApplicationVndD2lCoursepackage1p0Zip, + "application/vnd.d3m-dataset" => Mime::ApplicationVndD3mDataset, + "application/vnd.d3m-problem" => Mime::ApplicationVndD3mProblem, + "application/vnd.dart" => Mime::ApplicationVndDart, + "application/vnd.data-vision.rdz" => Mime::ApplicationVndDataVisionRdz, + "application/vnd.datalog" => Mime::ApplicationVndDatalog, + "application/vnd.datapackage+json" => Mime::ApplicationVndDatapackageJson, + "application/vnd.dataresource+json" => Mime::ApplicationVndDataresourceJson, + "application/vnd.dbf" => Mime::ApplicationVndDbf, + "application/vnd.debian.binary-package" => Mime::ApplicationVndDebianBinaryPackage, + "application/vnd.dece.data" => Mime::ApplicationVndDeceData, + "application/vnd.dece.ttml+xml" => Mime::ApplicationVndDeceTtmlXml, + "application/vnd.dece.unspecified" => Mime::ApplicationVndDeceUnspecified, + "application/vnd.dece.zip" => Mime::ApplicationVndDeceZip, + "application/vnd.denovo.fcselayout-link" => Mime::ApplicationVndDenovoFcselayoutLink, + "application/vnd.desmume.movie" => Mime::ApplicationVndDesmumeMovie, + "application/vnd.dir-bi.plate-dl-nosuffix" => Mime::ApplicationVndDirBiPlateDlNosuffix, + "application/vnd.dm.delegation+xml" => Mime::ApplicationVndDmDelegationXml, + "application/vnd.dna" => Mime::ApplicationVndDna, + "application/vnd.document+json" => Mime::ApplicationVndDocumentJson, + "application/vnd.dolby.mobile.1" => Mime::ApplicationVndDolbyMobile1, + "application/vnd.dolby.mobile.2" => Mime::ApplicationVndDolbyMobile2, + "application/vnd.doremir.scorecloud-binary-document" => Mime::ApplicationVndDoremirScorecloudBinaryDocument, + "application/vnd.dpgraph" => Mime::ApplicationVndDpgraph, + "application/vnd.dreamfactory" => Mime::ApplicationVndDreamfactory, + "application/vnd.drive+json" => Mime::ApplicationVndDriveJson, + "application/vnd.dtg.local" => Mime::ApplicationVndDtgLocal, + "application/vnd.dtg.local.flash" => Mime::ApplicationVndDtgLocalFlash, + "application/vnd.dtg.local.html" => Mime::ApplicationVndDtgLocalHtml, + "application/vnd.dvb.ait" => Mime::ApplicationVndDvbAit, + "application/vnd.dvb.dvbisl+xml" => Mime::ApplicationVndDvbDvbislXml, + "application/vnd.dvb.dvbj" => Mime::ApplicationVndDvbDvbj, + "application/vnd.dvb.esgcontainer" => Mime::ApplicationVndDvbEsgcontainer, + "application/vnd.dvb.ipdcdftnotifaccess" => Mime::ApplicationVndDvbIpdcdftnotifaccess, + "application/vnd.dvb.ipdcesgaccess" => Mime::ApplicationVndDvbIpdcesgaccess, + "application/vnd.dvb.ipdcesgaccess2" => Mime::ApplicationVndDvbIpdcesgaccess2, + "application/vnd.dvb.ipdcesgpdd" => Mime::ApplicationVndDvbIpdcesgpdd, + "application/vnd.dvb.ipdcroaming" => Mime::ApplicationVndDvbIpdcroaming, + "application/vnd.dvb.iptv.alfec-base" => Mime::ApplicationVndDvbIptvAlfecBase, + "application/vnd.dvb.iptv.alfec-enhancement" => Mime::ApplicationVndDvbIptvAlfecEnhancement, + "application/vnd.dvb.notif-aggregate-root+xml" => Mime::ApplicationVndDvbNotifAggregateRootXml, + "application/vnd.dvb.notif-container+xml" => Mime::ApplicationVndDvbNotifContainerXml, + "application/vnd.dvb.notif-generic+xml" => Mime::ApplicationVndDvbNotifGenericXml, + "application/vnd.dvb.notif-ia-msglist+xml" => Mime::ApplicationVndDvbNotifIaMsglistXml, + "application/vnd.dvb.notif-ia-registration-request+xml" => Mime::ApplicationVndDvbNotifIaRegistrationRequestXml, + "application/vnd.dvb.notif-ia-registration-response+xml" => Mime::ApplicationVndDvbNotifIaRegistrationResponseXml, + "application/vnd.dvb.notif-init+xml" => Mime::ApplicationVndDvbNotifInitXml, + "application/vnd.dvb.pfr" => Mime::ApplicationVndDvbPfr, + "application/vnd.dvb.service" => Mime::ApplicationVndDvbService, + "application/vnd.dxr" => Mime::ApplicationVndDxr, + "application/vnd.dynageo" => Mime::ApplicationVndDynageo, + "application/vnd.dzr" => Mime::ApplicationVndDzr, + "application/vnd.easykaraoke.cdgdownload" => Mime::ApplicationVndEasykaraokeCdgdownload, + "application/vnd.ecip.rlp" => Mime::ApplicationVndEcipRlp, + "application/vnd.ecdis-update" => Mime::ApplicationVndEcdisUpdate, + "application/vnd.eclipse.ditto+json" => Mime::ApplicationVndEclipseDittoJson, + "application/vnd.ecowin.chart" => Mime::ApplicationVndEcowinChart, + "application/vnd.ecowin.filerequest" => Mime::ApplicationVndEcowinFilerequest, + "application/vnd.ecowin.fileupdate" => Mime::ApplicationVndEcowinFileupdate, + "application/vnd.ecowin.series" => Mime::ApplicationVndEcowinSeries, + "application/vnd.ecowin.seriesrequest" => Mime::ApplicationVndEcowinSeriesrequest, + "application/vnd.ecowin.seriesupdate" => Mime::ApplicationVndEcowinSeriesupdate, + "application/vnd.efi.img" => Mime::ApplicationVndEfiImg, + "application/vnd.efi.iso" => Mime::ApplicationVndEfiIso, + "application/vnd.eln+zip" => Mime::ApplicationVndElnZip, + "application/vnd.emclient.accessrequest+xml" => Mime::ApplicationVndEmclientAccessrequestXml, + "application/vnd.enliven" => Mime::ApplicationVndEnliven, + "application/vnd.enphase.envoy" => Mime::ApplicationVndEnphaseEnvoy, + "application/vnd.eprints.data+xml" => Mime::ApplicationVndEprintsDataXml, + "application/vnd.epson.esf" => Mime::ApplicationVndEpsonEsf, + "application/vnd.epson.msf" => Mime::ApplicationVndEpsonMsf, + "application/vnd.epson.quickanime" => Mime::ApplicationVndEpsonQuickanime, + "application/vnd.epson.salt" => Mime::ApplicationVndEpsonSalt, + "application/vnd.epson.ssf" => Mime::ApplicationVndEpsonSsf, + "application/vnd.ericsson.quickcall" => Mime::ApplicationVndEricssonQuickcall, + "application/vnd.espass-espass+zip" => Mime::ApplicationVndEspassEspassZip, + "application/vnd.eszigno3+xml" => Mime::ApplicationVndEszigno3Xml, + "application/vnd.etsi.aoc+xml" => Mime::ApplicationVndEtsiAocXml, + "application/vnd.etsi.asic-s+zip" => Mime::ApplicationVndEtsiAsicSZip, + "application/vnd.etsi.asic-e+zip" => Mime::ApplicationVndEtsiAsicEZip, + "application/vnd.etsi.cug+xml" => Mime::ApplicationVndEtsiCugXml, + "application/vnd.etsi.iptvcommand+xml" => Mime::ApplicationVndEtsiIptvcommandXml, + "application/vnd.etsi.iptvdiscovery+xml" => Mime::ApplicationVndEtsiIptvdiscoveryXml, + "application/vnd.etsi.iptvprofile+xml" => Mime::ApplicationVndEtsiIptvprofileXml, + "application/vnd.etsi.iptvsad-bc+xml" => Mime::ApplicationVndEtsiIptvsadBcXml, + "application/vnd.etsi.iptvsad-cod+xml" => Mime::ApplicationVndEtsiIptvsadCodXml, + "application/vnd.etsi.iptvsad-npvr+xml" => Mime::ApplicationVndEtsiIptvsadNpvrXml, + "application/vnd.etsi.iptvservice+xml" => Mime::ApplicationVndEtsiIptvserviceXml, + "application/vnd.etsi.iptvsync+xml" => Mime::ApplicationVndEtsiIptvsyncXml, + "application/vnd.etsi.iptvueprofile+xml" => Mime::ApplicationVndEtsiIptvueprofileXml, + "application/vnd.etsi.mcid+xml" => Mime::ApplicationVndEtsiMcidXml, + "application/vnd.etsi.mheg5" => Mime::ApplicationVndEtsiMheg5, + "application/vnd.etsi.overload-control-policy-dataset+xml" => Mime::ApplicationVndEtsiOverloadControlPolicyDatasetXml, + "application/vnd.etsi.pstn+xml" => Mime::ApplicationVndEtsiPstnXml, + "application/vnd.etsi.sci+xml" => Mime::ApplicationVndEtsiSciXml, + "application/vnd.etsi.simservs+xml" => Mime::ApplicationVndEtsiSimservsXml, + "application/vnd.etsi.timestamp-token" => Mime::ApplicationVndEtsiTimestampToken, + "application/vnd.etsi.tsl+xml" => Mime::ApplicationVndEtsiTslXml, + "application/vnd.etsi.tsl.der" => Mime::ApplicationVndEtsiTslDer, + "application/vnd.eu.kasparian.car+json" => Mime::ApplicationVndEuKasparianCarJson, + "application/vnd.eudora.data" => Mime::ApplicationVndEudoraData, + "application/vnd.evolv.ecig.profile" => Mime::ApplicationVndEvolvEcigProfile, + "application/vnd.evolv.ecig.settings" => Mime::ApplicationVndEvolvEcigSettings, + "application/vnd.evolv.ecig.theme" => Mime::ApplicationVndEvolvEcigTheme, + "application/vnd.exstream-empower+zip" => Mime::ApplicationVndExstreamEmpowerZip, + "application/vnd.exstream-package" => Mime::ApplicationVndExstreamPackage, + "application/vnd.ezpix-album" => Mime::ApplicationVndEzpixAlbum, + "application/vnd.ezpix-package" => Mime::ApplicationVndEzpixPackage, + "application/vnd.f-secure.mobile" => Mime::ApplicationVndFSecureMobile, + "application/vnd.fastcopy-disk-image" => Mime::ApplicationVndFastcopyDiskImage, + "application/vnd.familysearch.gedcom+zip" => Mime::ApplicationVndFamilysearchGedcomZip, + "application/vnd.fdsn.mseed" => Mime::ApplicationVndFdsnMseed, + "application/vnd.fdsn.seed" => Mime::ApplicationVndFdsnSeed, + "application/vnd.ffsns" => Mime::ApplicationVndFfsns, + "application/vnd.ficlab.flb+zip" => Mime::ApplicationVndFiclabFlbZip, + "application/vnd.filmit.zfc" => Mime::ApplicationVndFilmitZfc, + "application/vnd.fints" => Mime::ApplicationVndFints, + "application/vnd.firemonkeys.cloudcell" => Mime::ApplicationVndFiremonkeysCloudcell, + "application/vnd.FloGraphIt" => Mime::ApplicationVndFlographit, + "application/vnd.fluxtime.clip" => Mime::ApplicationVndFluxtimeClip, + "application/vnd.font-fontforge-sfd" => Mime::ApplicationVndFontFontforgeSfd, + "application/vnd.framemaker" => Mime::ApplicationVndFramemaker, + "application/vnd.fsc.weblaunch" => Mime::ApplicationVndFscWeblaunch, + "application/vnd.fujifilm.fb.docuworks" => Mime::ApplicationVndFujifilmFbDocuworks, + "application/vnd.fujifilm.fb.docuworks.binder" => Mime::ApplicationVndFujifilmFbDocuworksBinder, + "application/vnd.fujifilm.fb.docuworks.container" => Mime::ApplicationVndFujifilmFbDocuworksContainer, + "application/vnd.fujifilm.fb.jfi+xml" => Mime::ApplicationVndFujifilmFbJfiXml, + "application/vnd.fujitsu.oasys" => Mime::ApplicationVndFujitsuOasys, + "application/vnd.fujitsu.oasys2" => Mime::ApplicationVndFujitsuOasys2, + "application/vnd.fujitsu.oasys3" => Mime::ApplicationVndFujitsuOasys3, + "application/vnd.fujitsu.oasysgp" => Mime::ApplicationVndFujitsuOasysgp, + "application/vnd.fujitsu.oasysprs" => Mime::ApplicationVndFujitsuOasysprs, + "application/vnd.fujixerox.ART4" => Mime::ApplicationVndFujixeroxArt4, + "application/vnd.fujixerox.ART-EX" => Mime::ApplicationVndFujixeroxArtEx, + "application/vnd.fujixerox.ddd" => Mime::ApplicationVndFujixeroxDdd, + "application/vnd.fujixerox.docuworks" => Mime::ApplicationVndFujixeroxDocuworks, + "application/vnd.fujixerox.docuworks.binder" => Mime::ApplicationVndFujixeroxDocuworksBinder, + "application/vnd.fujixerox.docuworks.container" => Mime::ApplicationVndFujixeroxDocuworksContainer, + "application/vnd.fujixerox.HBPL" => Mime::ApplicationVndFujixeroxHbpl, + "application/vnd.fut-misnet" => Mime::ApplicationVndFutMisnet, + "application/vnd.futoin+cbor" => Mime::ApplicationVndFutoinCbor, + "application/vnd.futoin+json" => Mime::ApplicationVndFutoinJson, + "application/vnd.fuzzysheet" => Mime::ApplicationVndFuzzysheet, + "application/vnd.genomatix.tuxedo" => Mime::ApplicationVndGenomatixTuxedo, + "application/vnd.genozip" => Mime::ApplicationVndGenozip, + "application/vnd.gentics.grd+json" => Mime::ApplicationVndGenticsGrdJson, + "application/vnd.gentoo.catmetadata+xml" => Mime::ApplicationVndGentooCatmetadataXml, + "application/vnd.gentoo.ebuild" => Mime::ApplicationVndGentooEbuild, + "application/vnd.gentoo.eclass" => Mime::ApplicationVndGentooEclass, + "application/vnd.gentoo.gpkg" => Mime::ApplicationVndGentooGpkg, + "application/vnd.gentoo.manifest" => Mime::ApplicationVndGentooManifest, + "application/vnd.gentoo.xpak" => Mime::ApplicationVndGentooXpak, + "application/vnd.gentoo.pkgmetadata+xml" => Mime::ApplicationVndGentooPkgmetadataXml, + "application/vnd.geogebra.file" => Mime::ApplicationVndGeogebraFile, + "application/vnd.geogebra.slides" => Mime::ApplicationVndGeogebraSlides, + "application/vnd.geogebra.tool" => Mime::ApplicationVndGeogebraTool, + "application/vnd.geometry-explorer" => Mime::ApplicationVndGeometryExplorer, + "application/vnd.geonext" => Mime::ApplicationVndGeonext, + "application/vnd.geoplan" => Mime::ApplicationVndGeoplan, + "application/vnd.geospace" => Mime::ApplicationVndGeospace, + "application/vnd.gerber" => Mime::ApplicationVndGerber, + "application/vnd.globalplatform.card-content-mgt" => Mime::ApplicationVndGlobalplatformCardContentMgt, + "application/vnd.globalplatform.card-content-mgt-response" => Mime::ApplicationVndGlobalplatformCardContentMgtResponse, + "application/vnd.gnu.taler.exchange+json" => Mime::ApplicationVndGnuTalerExchangeJson, + "application/vnd.gnu.taler.merchant+json" => Mime::ApplicationVndGnuTalerMerchantJson, + "application/vnd.google-earth.kml+xml" => Mime::ApplicationVndGoogleEarthKmlXml, + "application/vnd.google-earth.kmz" => Mime::ApplicationVndGoogleEarthKmz, + "application/vnd.gov.sk.e-form+xml" => Mime::ApplicationVndGovSkEFormXml, + "application/vnd.gov.sk.e-form+zip" => Mime::ApplicationVndGovSkEFormZip, + "application/vnd.gov.sk.xmldatacontainer+xml" => Mime::ApplicationVndGovSkXmldatacontainerXml, + "application/vnd.gpxsee.map+xml" => Mime::ApplicationVndGpxseeMapXml, + "application/vnd.grafeq" => Mime::ApplicationVndGrafeq, + "application/vnd.gridmp" => Mime::ApplicationVndGridmp, + "application/vnd.groove-account" => Mime::ApplicationVndGrooveAccount, + "application/vnd.groove-help" => Mime::ApplicationVndGrooveHelp, + "application/vnd.groove-identity-message" => Mime::ApplicationVndGrooveIdentityMessage, + "application/vnd.groove-injector" => Mime::ApplicationVndGrooveInjector, + "application/vnd.groove-tool-message" => Mime::ApplicationVndGrooveToolMessage, + "application/vnd.groove-tool-template" => Mime::ApplicationVndGrooveToolTemplate, + "application/vnd.groove-vcard" => Mime::ApplicationVndGrooveVcard, + "application/vnd.hal+json" => Mime::ApplicationVndHalJson, + "application/vnd.hal+xml" => Mime::ApplicationVndHalXml, + "application/vnd.HandHeld-Entertainment+xml" => Mime::ApplicationVndHandheldEntertainmentXml, + "application/vnd.hbci" => Mime::ApplicationVndHbci, + "application/vnd.hc+json" => Mime::ApplicationVndHcJson, + "application/vnd.hcl-bireports" => Mime::ApplicationVndHclBireports, + "application/vnd.hdt" => Mime::ApplicationVndHdt, + "application/vnd.heroku+json" => Mime::ApplicationVndHerokuJson, + "application/vnd.hhe.lesson-player" => Mime::ApplicationVndHheLessonPlayer, + "application/vnd.hp-HPGL" => Mime::ApplicationVndHpHpgl, + "application/vnd.hp-hpid" => Mime::ApplicationVndHpHpid, + "application/vnd.hp-hps" => Mime::ApplicationVndHpHps, + "application/vnd.hp-jlyt" => Mime::ApplicationVndHpJlyt, + "application/vnd.hp-PCL" => Mime::ApplicationVndHpPcl, + "application/vnd.hp-PCLXL" => Mime::ApplicationVndHpPclxl, + "application/vnd.hsl" => Mime::ApplicationVndHsl, + "application/vnd.httphone" => Mime::ApplicationVndHttphone, + "application/vnd.hydrostatix.sof-data" => Mime::ApplicationVndHydrostatixSofData, + "application/vnd.hyper-item+json" => Mime::ApplicationVndHyperItemJson, + "application/vnd.hyper+json" => Mime::ApplicationVndHyperJson, + "application/vnd.hyperdrive+json" => Mime::ApplicationVndHyperdriveJson, + "application/vnd.hzn-3d-crossword" => Mime::ApplicationVndHzn3dCrossword, + "application/vnd.ibm.electronic-media" => Mime::ApplicationVndIbmElectronicMedia, + "application/vnd.ibm.MiniPay" => Mime::ApplicationVndIbmMinipay, + "application/vnd.ibm.rights-management" => Mime::ApplicationVndIbmRightsManagement, + "application/vnd.ibm.secure-container" => Mime::ApplicationVndIbmSecureContainer, + "application/vnd.iccprofile" => Mime::ApplicationVndIccprofile, + "application/vnd.ieee.1905" => Mime::ApplicationVndIeee1905, + "application/vnd.igloader" => Mime::ApplicationVndIgloader, + "application/vnd.imagemeter.folder+zip" => Mime::ApplicationVndImagemeterFolderZip, + "application/vnd.imagemeter.image+zip" => Mime::ApplicationVndImagemeterImageZip, + "application/vnd.immervision-ivp" => Mime::ApplicationVndImmervisionIvp, + "application/vnd.immervision-ivu" => Mime::ApplicationVndImmervisionIvu, + "application/vnd.ims.imsccv1p1" => Mime::ApplicationVndImsImsccv1p1, + "application/vnd.ims.imsccv1p2" => Mime::ApplicationVndImsImsccv1p2, + "application/vnd.ims.imsccv1p3" => Mime::ApplicationVndImsImsccv1p3, + "application/vnd.ims.lis.v2.result+json" => Mime::ApplicationVndImsLisV2ResultJson, + "application/vnd.ims.lti.v2.toolconsumerprofile+json" => Mime::ApplicationVndImsLtiV2ToolconsumerprofileJson, + "application/vnd.ims.lti.v2.toolproxy.id+json" => Mime::ApplicationVndImsLtiV2ToolproxyIdJson, + "application/vnd.ims.lti.v2.toolproxy+json" => Mime::ApplicationVndImsLtiV2ToolproxyJson, + "application/vnd.ims.lti.v2.toolsettings+json" => Mime::ApplicationVndImsLtiV2ToolsettingsJson, + "application/vnd.ims.lti.v2.toolsettings.simple+json" => Mime::ApplicationVndImsLtiV2ToolsettingsSimpleJson, + "application/vnd.informedcontrol.rms+xml" => Mime::ApplicationVndInformedcontrolRmsXml, + "application/vnd.infotech.project" => Mime::ApplicationVndInfotechProject, + "application/vnd.infotech.project+xml" => Mime::ApplicationVndInfotechProjectXml, + "application/vnd.innopath.wamp.notification" => Mime::ApplicationVndInnopathWampNotification, + "application/vnd.insors.igm" => Mime::ApplicationVndInsorsIgm, + "application/vnd.intercon.formnet" => Mime::ApplicationVndInterconFormnet, + "application/vnd.intergeo" => Mime::ApplicationVndIntergeo, + "application/vnd.intertrust.digibox" => Mime::ApplicationVndIntertrustDigibox, + "application/vnd.intertrust.nncp" => Mime::ApplicationVndIntertrustNncp, + "application/vnd.intu.qbo" => Mime::ApplicationVndIntuQbo, + "application/vnd.intu.qfx" => Mime::ApplicationVndIntuQfx, + "application/vnd.ipfs.ipns-record" => Mime::ApplicationVndIpfsIpnsRecord, + "application/vnd.ipld.car" => Mime::ApplicationVndIpldCar, + "application/vnd.ipld.dag-cbor" => Mime::ApplicationVndIpldDagCbor, + "application/vnd.ipld.dag-json" => Mime::ApplicationVndIpldDagJson, + "application/vnd.ipld.raw" => Mime::ApplicationVndIpldRaw, + "application/vnd.iptc.g2.catalogitem+xml" => Mime::ApplicationVndIptcG2CatalogitemXml, + "application/vnd.iptc.g2.conceptitem+xml" => Mime::ApplicationVndIptcG2ConceptitemXml, + "application/vnd.iptc.g2.knowledgeitem+xml" => Mime::ApplicationVndIptcG2KnowledgeitemXml, + "application/vnd.iptc.g2.newsitem+xml" => Mime::ApplicationVndIptcG2NewsitemXml, + "application/vnd.iptc.g2.newsmessage+xml" => Mime::ApplicationVndIptcG2NewsmessageXml, + "application/vnd.iptc.g2.packageitem+xml" => Mime::ApplicationVndIptcG2PackageitemXml, + "application/vnd.iptc.g2.planningitem+xml" => Mime::ApplicationVndIptcG2PlanningitemXml, + "application/vnd.ipunplugged.rcprofile" => Mime::ApplicationVndIpunpluggedRcprofile, + "application/vnd.irepository.package+xml" => Mime::ApplicationVndIrepositoryPackageXml, + "application/vnd.is-xpr" => Mime::ApplicationVndIsXpr, + "application/vnd.isac.fcs" => Mime::ApplicationVndIsacFcs, + "application/vnd.jam" => Mime::ApplicationVndJam, + "application/vnd.iso11783-10+zip" => Mime::ApplicationVndIso1178310Zip, + "application/vnd.japannet-directory-service" => Mime::ApplicationVndJapannetDirectoryService, + "application/vnd.japannet-jpnstore-wakeup" => Mime::ApplicationVndJapannetJpnstoreWakeup, + "application/vnd.japannet-payment-wakeup" => Mime::ApplicationVndJapannetPaymentWakeup, + "application/vnd.japannet-registration" => Mime::ApplicationVndJapannetRegistration, + "application/vnd.japannet-registration-wakeup" => Mime::ApplicationVndJapannetRegistrationWakeup, + "application/vnd.japannet-setstore-wakeup" => Mime::ApplicationVndJapannetSetstoreWakeup, + "application/vnd.japannet-verification" => Mime::ApplicationVndJapannetVerification, + "application/vnd.japannet-verification-wakeup" => Mime::ApplicationVndJapannetVerificationWakeup, + "application/vnd.jcp.javame.midlet-rms" => Mime::ApplicationVndJcpJavameMidletRms, + "application/vnd.jisp" => Mime::ApplicationVndJisp, + "application/vnd.joost.joda-archive" => Mime::ApplicationVndJoostJodaArchive, + "application/vnd.jsk.isdn-ngn" => Mime::ApplicationVndJskIsdnNgn, + "application/vnd.kahootz" => Mime::ApplicationVndKahootz, + "application/vnd.kde.karbon" => Mime::ApplicationVndKdeKarbon, + "application/vnd.kde.kchart" => Mime::ApplicationVndKdeKchart, + "application/vnd.kde.kformula" => Mime::ApplicationVndKdeKformula, + "application/vnd.kde.kivio" => Mime::ApplicationVndKdeKivio, + "application/vnd.kde.kontour" => Mime::ApplicationVndKdeKontour, + "application/vnd.kde.kpresenter" => Mime::ApplicationVndKdeKpresenter, + "application/vnd.kde.kspread" => Mime::ApplicationVndKdeKspread, + "application/vnd.kde.kword" => Mime::ApplicationVndKdeKword, + "application/vnd.kenameaapp" => Mime::ApplicationVndKenameaapp, + "application/vnd.kidspiration" => Mime::ApplicationVndKidspiration, + "application/vnd.Kinar" => Mime::ApplicationVndKinar, + "application/vnd.koan" => Mime::ApplicationVndKoan, + "application/vnd.kodak-descriptor" => Mime::ApplicationVndKodakDescriptor, + "application/vnd.las" => Mime::ApplicationVndLas, + "application/vnd.las.las+json" => Mime::ApplicationVndLasLasJson, + "application/vnd.las.las+xml" => Mime::ApplicationVndLasLasXml, + "application/vnd.laszip" => Mime::ApplicationVndLaszip, + "application/vnd.leap+json" => Mime::ApplicationVndLeapJson, + "application/vnd.liberty-request+xml" => Mime::ApplicationVndLibertyRequestXml, + "application/vnd.llamagraphics.life-balance.desktop" => Mime::ApplicationVndLlamagraphicsLifeBalanceDesktop, + "application/vnd.llamagraphics.life-balance.exchange+xml" => Mime::ApplicationVndLlamagraphicsLifeBalanceExchangeXml, + "application/vnd.logipipe.circuit+zip" => Mime::ApplicationVndLogipipeCircuitZip, + "application/vnd.loom" => Mime::ApplicationVndLoom, + "application/vnd.lotus-1-2-3" => Mime::ApplicationVndLotus123, + "application/vnd.lotus-approach" => Mime::ApplicationVndLotusApproach, + "application/vnd.lotus-freelance" => Mime::ApplicationVndLotusFreelance, + "application/vnd.lotus-notes" => Mime::ApplicationVndLotusNotes, + "application/vnd.lotus-organizer" => Mime::ApplicationVndLotusOrganizer, + "application/vnd.lotus-screencam" => Mime::ApplicationVndLotusScreencam, + "application/vnd.lotus-wordpro" => Mime::ApplicationVndLotusWordpro, + "application/vnd.macports.portpkg" => Mime::ApplicationVndMacportsPortpkg, + "application/vnd.mapbox-vector-tile" => Mime::ApplicationVndMapboxVectorTile, + "application/vnd.marlin.drm.actiontoken+xml" => Mime::ApplicationVndMarlinDrmActiontokenXml, + "application/vnd.marlin.drm.conftoken+xml" => Mime::ApplicationVndMarlinDrmConftokenXml, + "application/vnd.marlin.drm.license+xml" => Mime::ApplicationVndMarlinDrmLicenseXml, + "application/vnd.marlin.drm.mdcf" => Mime::ApplicationVndMarlinDrmMdcf, + "application/vnd.mason+json" => Mime::ApplicationVndMasonJson, + "application/vnd.maxar.archive.3tz+zip" => Mime::ApplicationVndMaxarArchive3tzZip, + "application/vnd.maxmind.maxmind-db" => Mime::ApplicationVndMaxmindMaxmindDb, + "application/vnd.mcd" => Mime::ApplicationVndMcd, + "application/vnd.mdl" => Mime::ApplicationVndMdl, + "application/vnd.mdl-mbsdf" => Mime::ApplicationVndMdlMbsdf, + "application/vnd.medcalcdata" => Mime::ApplicationVndMedcalcdata, + "application/vnd.mediastation.cdkey" => Mime::ApplicationVndMediastationCdkey, + "application/vnd.medicalholodeck.recordxr" => Mime::ApplicationVndMedicalholodeckRecordxr, + "application/vnd.meridian-slingshot" => Mime::ApplicationVndMeridianSlingshot, + "application/vnd.MFER" => Mime::ApplicationVndMfer, + "application/vnd.mfmp" => Mime::ApplicationVndMfmp, + "application/vnd.micro+json" => Mime::ApplicationVndMicroJson, + "application/vnd.micrografx.flo" => Mime::ApplicationVndMicrografxFlo, + "application/vnd.micrografx.igx" => Mime::ApplicationVndMicrografxIgx, + "application/vnd.microsoft.portable-executable" => Mime::ApplicationVndMicrosoftPortableExecutable, + "application/vnd.microsoft.windows.thumbnail-cache" => Mime::ApplicationVndMicrosoftWindowsThumbnailCache, + "application/vnd.miele+json" => Mime::ApplicationVndMieleJson, + "application/vnd.mif" => Mime::ApplicationVndMif, + "application/vnd.minisoft-hp3000-save" => Mime::ApplicationVndMinisoftHp3000Save, + "application/vnd.mitsubishi.misty-guard.trustweb" => Mime::ApplicationVndMitsubishiMistyGuardTrustweb, + "application/vnd.Mobius.DAF" => Mime::ApplicationVndMobiusDaf, + "application/vnd.Mobius.DIS" => Mime::ApplicationVndMobiusDis, + "application/vnd.Mobius.MBK" => Mime::ApplicationVndMobiusMbk, + "application/vnd.Mobius.MQY" => Mime::ApplicationVndMobiusMqy, + "application/vnd.Mobius.MSL" => Mime::ApplicationVndMobiusMsl, + "application/vnd.Mobius.PLC" => Mime::ApplicationVndMobiusPlc, + "application/vnd.Mobius.TXF" => Mime::ApplicationVndMobiusTxf, + "application/vnd.modl" => Mime::ApplicationVndModl, + "application/vnd.mophun.application" => Mime::ApplicationVndMophunApplication, + "application/vnd.mophun.certificate" => Mime::ApplicationVndMophunCertificate, + "application/vnd.motorola.flexsuite" => Mime::ApplicationVndMotorolaFlexsuite, + "application/vnd.motorola.flexsuite.adsi" => Mime::ApplicationVndMotorolaFlexsuiteAdsi, + "application/vnd.motorola.flexsuite.fis" => Mime::ApplicationVndMotorolaFlexsuiteFis, + "application/vnd.motorola.flexsuite.gotap" => Mime::ApplicationVndMotorolaFlexsuiteGotap, + "application/vnd.motorola.flexsuite.kmr" => Mime::ApplicationVndMotorolaFlexsuiteKmr, + "application/vnd.motorola.flexsuite.ttc" => Mime::ApplicationVndMotorolaFlexsuiteTtc, + "application/vnd.motorola.flexsuite.wem" => Mime::ApplicationVndMotorolaFlexsuiteWem, + "application/vnd.motorola.iprm" => Mime::ApplicationVndMotorolaIprm, + "application/vnd.mozilla.xul+xml" => Mime::ApplicationVndMozillaXulXml, + "application/vnd.ms-artgalry" => Mime::ApplicationVndMsArtgalry, + "application/vnd.ms-asf" => Mime::ApplicationVndMsAsf, + "application/vnd.ms-cab-compressed" => Mime::ApplicationVndMsCabCompressed, + "application/vnd.ms-3mfdocument" => Mime::ApplicationVndMs3mfdocument, + "application/vnd.ms-excel" => Mime::ApplicationVndMsExcel, + "application/vnd.ms-excel.addin.macroEnabled.12" => Mime::ApplicationVndMsExcelAddinMacroenabled12, + "application/vnd.ms-excel.sheet.binary.macroEnabled.12" => Mime::ApplicationVndMsExcelSheetBinaryMacroenabled12, + "application/vnd.ms-excel.sheet.macroEnabled.12" => Mime::ApplicationVndMsExcelSheetMacroenabled12, + "application/vnd.ms-excel.template.macroEnabled.12" => Mime::ApplicationVndMsExcelTemplateMacroenabled12, + "application/vnd.ms-fontobject" => Mime::ApplicationVndMsFontobject, + "application/vnd.ms-htmlhelp" => Mime::ApplicationVndMsHtmlhelp, + "application/vnd.ms-ims" => Mime::ApplicationVndMsIms, + "application/vnd.ms-lrm" => Mime::ApplicationVndMsLrm, + "application/vnd.ms-office.activeX+xml" => Mime::ApplicationVndMsOfficeActivexXml, + "application/vnd.ms-officetheme" => Mime::ApplicationVndMsOfficetheme, + "application/vnd.ms-playready.initiator+xml" => Mime::ApplicationVndMsPlayreadyInitiatorXml, + "application/vnd.ms-powerpoint" => Mime::ApplicationVndMsPowerpoint, + "application/vnd.ms-powerpoint.addin.macroEnabled.12" => Mime::ApplicationVndMsPowerpointAddinMacroenabled12, + "application/vnd.ms-powerpoint.presentation.macroEnabled.12" => Mime::ApplicationVndMsPowerpointPresentationMacroenabled12, + "application/vnd.ms-powerpoint.slide.macroEnabled.12" => Mime::ApplicationVndMsPowerpointSlideMacroenabled12, + "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" => Mime::ApplicationVndMsPowerpointSlideshowMacroenabled12, + "application/vnd.ms-powerpoint.template.macroEnabled.12" => Mime::ApplicationVndMsPowerpointTemplateMacroenabled12, + "application/vnd.ms-PrintDeviceCapabilities+xml" => Mime::ApplicationVndMsPrintdevicecapabilitiesXml, + "application/vnd.ms-PrintSchemaTicket+xml" => Mime::ApplicationVndMsPrintschematicketXml, + "application/vnd.ms-project" => Mime::ApplicationVndMsProject, + "application/vnd.ms-tnef" => Mime::ApplicationVndMsTnef, + "application/vnd.ms-windows.devicepairing" => Mime::ApplicationVndMsWindowsDevicepairing, + "application/vnd.ms-windows.nwprinting.oob" => Mime::ApplicationVndMsWindowsNwprintingOob, + "application/vnd.ms-windows.printerpairing" => Mime::ApplicationVndMsWindowsPrinterpairing, + "application/vnd.ms-windows.wsd.oob" => Mime::ApplicationVndMsWindowsWsdOob, + "application/vnd.ms-wmdrm.lic-chlg-req" => Mime::ApplicationVndMsWmdrmLicChlgReq, + "application/vnd.ms-wmdrm.lic-resp" => Mime::ApplicationVndMsWmdrmLicResp, + "application/vnd.ms-wmdrm.meter-chlg-req" => Mime::ApplicationVndMsWmdrmMeterChlgReq, + "application/vnd.ms-wmdrm.meter-resp" => Mime::ApplicationVndMsWmdrmMeterResp, + "application/vnd.ms-word.document.macroEnabled.12" => Mime::ApplicationVndMsWordDocumentMacroenabled12, + "application/vnd.ms-word.template.macroEnabled.12" => Mime::ApplicationVndMsWordTemplateMacroenabled12, + "application/vnd.ms-works" => Mime::ApplicationVndMsWorks, + "application/vnd.ms-wpl" => Mime::ApplicationVndMsWpl, + "application/vnd.ms-xpsdocument" => Mime::ApplicationVndMsXpsdocument, + "application/vnd.msa-disk-image" => Mime::ApplicationVndMsaDiskImage, + "application/vnd.mseq" => Mime::ApplicationVndMseq, + "application/vnd.msign" => Mime::ApplicationVndMsign, + "application/vnd.multiad.creator" => Mime::ApplicationVndMultiadCreator, + "application/vnd.multiad.creator.cif" => Mime::ApplicationVndMultiadCreatorCif, + "application/vnd.musician" => Mime::ApplicationVndMusician, + "application/vnd.music-niff" => Mime::ApplicationVndMusicNiff, + "application/vnd.muvee.style" => Mime::ApplicationVndMuveeStyle, + "application/vnd.mynfc" => Mime::ApplicationVndMynfc, + "application/vnd.nacamar.ybrid+json" => Mime::ApplicationVndNacamarYbridJson, + "application/vnd.ncd.control" => Mime::ApplicationVndNcdControl, + "application/vnd.ncd.reference" => Mime::ApplicationVndNcdReference, + "application/vnd.nearst.inv+json" => Mime::ApplicationVndNearstInvJson, + "application/vnd.nebumind.line" => Mime::ApplicationVndNebumindLine, + "application/vnd.nervana" => Mime::ApplicationVndNervana, + "application/vnd.netfpx" => Mime::ApplicationVndNetfpx, + "application/vnd.neurolanguage.nlu" => Mime::ApplicationVndNeurolanguageNlu, + "application/vnd.nimn" => Mime::ApplicationVndNimn, + "application/vnd.nintendo.snes.rom" => Mime::ApplicationVndNintendoSnesRom, + "application/vnd.nintendo.nitro.rom" => Mime::ApplicationVndNintendoNitroRom, + "application/vnd.nitf" => Mime::ApplicationVndNitf, + "application/vnd.noblenet-directory" => Mime::ApplicationVndNoblenetDirectory, + "application/vnd.noblenet-sealer" => Mime::ApplicationVndNoblenetSealer, + "application/vnd.noblenet-web" => Mime::ApplicationVndNoblenetWeb, + "application/vnd.nokia.catalogs" => Mime::ApplicationVndNokiaCatalogs, + "application/vnd.nokia.conml+wbxml" => Mime::ApplicationVndNokiaConmlWbxml, + "application/vnd.nokia.conml+xml" => Mime::ApplicationVndNokiaConmlXml, + "application/vnd.nokia.iptv.config+xml" => Mime::ApplicationVndNokiaIptvConfigXml, + "application/vnd.nokia.iSDS-radio-presets" => Mime::ApplicationVndNokiaIsdsRadioPresets, + "application/vnd.nokia.landmark+wbxml" => Mime::ApplicationVndNokiaLandmarkWbxml, + "application/vnd.nokia.landmark+xml" => Mime::ApplicationVndNokiaLandmarkXml, + "application/vnd.nokia.landmarkcollection+xml" => Mime::ApplicationVndNokiaLandmarkcollectionXml, + "application/vnd.nokia.ncd" => Mime::ApplicationVndNokiaNcd, + "application/vnd.nokia.n-gage.ac+xml" => Mime::ApplicationVndNokiaNGageAcXml, + "application/vnd.nokia.n-gage.data" => Mime::ApplicationVndNokiaNGageData, + "application/vnd.nokia.pcd+wbxml" => Mime::ApplicationVndNokiaPcdWbxml, + "application/vnd.nokia.pcd+xml" => Mime::ApplicationVndNokiaPcdXml, + "application/vnd.nokia.radio-preset" => Mime::ApplicationVndNokiaRadioPreset, + "application/vnd.nokia.radio-presets" => Mime::ApplicationVndNokiaRadioPresets, + "application/vnd.novadigm.EDM" => Mime::ApplicationVndNovadigmEdm, + "application/vnd.novadigm.EDX" => Mime::ApplicationVndNovadigmEdx, + "application/vnd.novadigm.EXT" => Mime::ApplicationVndNovadigmExt, + "application/vnd.ntt-local.content-share" => Mime::ApplicationVndNttLocalContentShare, + "application/vnd.ntt-local.file-transfer" => Mime::ApplicationVndNttLocalFileTransfer, + "application/vnd.ntt-local.ogw_remote-access" => Mime::ApplicationVndNttLocalOgwRemoteAccess, + "application/vnd.ntt-local.sip-ta_remote" => Mime::ApplicationVndNttLocalSipTaRemote, + "application/vnd.ntt-local.sip-ta_tcp_stream" => Mime::ApplicationVndNttLocalSipTaTcpStream, + "application/vnd.oasis.opendocument.base" => Mime::ApplicationVndOasisOpendocumentBase, + "application/vnd.oasis.opendocument.chart" => Mime::ApplicationVndOasisOpendocumentChart, + "application/vnd.oasis.opendocument.chart-template" => Mime::ApplicationVndOasisOpendocumentChartTemplate, + "application/vnd.oasis.opendocument.formula" => Mime::ApplicationVndOasisOpendocumentFormula, + "application/vnd.oasis.opendocument.formula-template" => Mime::ApplicationVndOasisOpendocumentFormulaTemplate, + "application/vnd.oasis.opendocument.graphics" => Mime::ApplicationVndOasisOpendocumentGraphics, + "application/vnd.oasis.opendocument.graphics-template" => Mime::ApplicationVndOasisOpendocumentGraphicsTemplate, + "application/vnd.oasis.opendocument.image" => Mime::ApplicationVndOasisOpendocumentImage, + "application/vnd.oasis.opendocument.image-template" => Mime::ApplicationVndOasisOpendocumentImageTemplate, + "application/vnd.oasis.opendocument.presentation" => Mime::ApplicationVndOasisOpendocumentPresentation, + "application/vnd.oasis.opendocument.presentation-template" => Mime::ApplicationVndOasisOpendocumentPresentationTemplate, + "application/vnd.oasis.opendocument.spreadsheet" => Mime::ApplicationVndOasisOpendocumentSpreadsheet, + "application/vnd.oasis.opendocument.spreadsheet-template" => Mime::ApplicationVndOasisOpendocumentSpreadsheetTemplate, + "application/vnd.oasis.opendocument.text" => Mime::ApplicationVndOasisOpendocumentText, + "application/vnd.oasis.opendocument.text-master" => Mime::ApplicationVndOasisOpendocumentTextMaster, + "application/vnd.oasis.opendocument.text-master-template" => Mime::ApplicationVndOasisOpendocumentTextMasterTemplate, + "application/vnd.oasis.opendocument.text-template" => Mime::ApplicationVndOasisOpendocumentTextTemplate, + "application/vnd.oasis.opendocument.text-web" => Mime::ApplicationVndOasisOpendocumentTextWeb, + "application/vnd.obn" => Mime::ApplicationVndObn, + "application/vnd.ocf+cbor" => Mime::ApplicationVndOcfCbor, + "application/vnd.oci.image.manifest.v1+json" => Mime::ApplicationVndOciImageManifestV1Json, + "application/vnd.oftn.l10n+json" => Mime::ApplicationVndOftnL10nJson, + "application/vnd.oipf.contentaccessdownload+xml" => Mime::ApplicationVndOipfContentaccessdownloadXml, + "application/vnd.oipf.contentaccessstreaming+xml" => Mime::ApplicationVndOipfContentaccessstreamingXml, + "application/vnd.oipf.cspg-hexbinary" => Mime::ApplicationVndOipfCspgHexbinary, + "application/vnd.oipf.dae.svg+xml" => Mime::ApplicationVndOipfDaeSvgXml, + "application/vnd.oipf.dae.xhtml+xml" => Mime::ApplicationVndOipfDaeXhtmlXml, + "application/vnd.oipf.mippvcontrolmessage+xml" => Mime::ApplicationVndOipfMippvcontrolmessageXml, + "application/vnd.oipf.pae.gem" => Mime::ApplicationVndOipfPaeGem, + "application/vnd.oipf.spdiscovery+xml" => Mime::ApplicationVndOipfSpdiscoveryXml, + "application/vnd.oipf.spdlist+xml" => Mime::ApplicationVndOipfSpdlistXml, + "application/vnd.oipf.ueprofile+xml" => Mime::ApplicationVndOipfUeprofileXml, + "application/vnd.oipf.userprofile+xml" => Mime::ApplicationVndOipfUserprofileXml, + "application/vnd.olpc-sugar" => Mime::ApplicationVndOlpcSugar, + "application/vnd.oma.bcast.associated-procedure-parameter+xml" => Mime::ApplicationVndOmaBcastAssociatedProcedureParameterXml, + "application/vnd.oma.bcast.drm-trigger+xml" => Mime::ApplicationVndOmaBcastDrmTriggerXml, + "application/vnd.oma.bcast.imd+xml" => Mime::ApplicationVndOmaBcastImdXml, + "application/vnd.oma.bcast.ltkm" => Mime::ApplicationVndOmaBcastLtkm, + "application/vnd.oma.bcast.notification+xml" => Mime::ApplicationVndOmaBcastNotificationXml, + "application/vnd.oma.bcast.provisioningtrigger" => Mime::ApplicationVndOmaBcastProvisioningtrigger, + "application/vnd.oma.bcast.sgboot" => Mime::ApplicationVndOmaBcastSgboot, + "application/vnd.oma.bcast.sgdd+xml" => Mime::ApplicationVndOmaBcastSgddXml, + "application/vnd.oma.bcast.sgdu" => Mime::ApplicationVndOmaBcastSgdu, + "application/vnd.oma.bcast.simple-symbol-container" => Mime::ApplicationVndOmaBcastSimpleSymbolContainer, + "application/vnd.oma.bcast.smartcard-trigger+xml" => Mime::ApplicationVndOmaBcastSmartcardTriggerXml, + "application/vnd.oma.bcast.sprov+xml" => Mime::ApplicationVndOmaBcastSprovXml, + "application/vnd.oma.bcast.stkm" => Mime::ApplicationVndOmaBcastStkm, + "application/vnd.oma.cab-address-book+xml" => Mime::ApplicationVndOmaCabAddressBookXml, + "application/vnd.oma.cab-feature-handler+xml" => Mime::ApplicationVndOmaCabFeatureHandlerXml, + "application/vnd.oma.cab-pcc+xml" => Mime::ApplicationVndOmaCabPccXml, + "application/vnd.oma.cab-subs-invite+xml" => Mime::ApplicationVndOmaCabSubsInviteXml, + "application/vnd.oma.cab-user-prefs+xml" => Mime::ApplicationVndOmaCabUserPrefsXml, + "application/vnd.oma.dcd" => Mime::ApplicationVndOmaDcd, + "application/vnd.oma.dcdc" => Mime::ApplicationVndOmaDcdc, + "application/vnd.oma.dd2+xml" => Mime::ApplicationVndOmaDd2Xml, + "application/vnd.oma.drm.risd+xml" => Mime::ApplicationVndOmaDrmRisdXml, + "application/vnd.oma.group-usage-list+xml" => Mime::ApplicationVndOmaGroupUsageListXml, + "application/vnd.oma.lwm2m+cbor" => Mime::ApplicationVndOmaLwm2mCbor, + "application/vnd.oma.lwm2m+json" => Mime::ApplicationVndOmaLwm2mJson, + "application/vnd.oma.lwm2m+tlv" => Mime::ApplicationVndOmaLwm2mTlv, + "application/vnd.oma.pal+xml" => Mime::ApplicationVndOmaPalXml, + "application/vnd.oma.poc.detailed-progress-report+xml" => Mime::ApplicationVndOmaPocDetailedProgressReportXml, + "application/vnd.oma.poc.final-report+xml" => Mime::ApplicationVndOmaPocFinalReportXml, + "application/vnd.oma.poc.groups+xml" => Mime::ApplicationVndOmaPocGroupsXml, + "application/vnd.oma.poc.invocation-descriptor+xml" => Mime::ApplicationVndOmaPocInvocationDescriptorXml, + "application/vnd.oma.poc.optimized-progress-report+xml" => Mime::ApplicationVndOmaPocOptimizedProgressReportXml, + "application/vnd.oma.push" => Mime::ApplicationVndOmaPush, + "application/vnd.oma.scidm.messages+xml" => Mime::ApplicationVndOmaScidmMessagesXml, + "application/vnd.oma.xcap-directory+xml" => Mime::ApplicationVndOmaXcapDirectoryXml, + "application/vnd.omads-email+xml" => Mime::ApplicationVndOmadsEmailXml, + "application/vnd.omads-file+xml" => Mime::ApplicationVndOmadsFileXml, + "application/vnd.omads-folder+xml" => Mime::ApplicationVndOmadsFolderXml, + "application/vnd.omaloc-supl-init" => Mime::ApplicationVndOmalocSuplInit, + "application/vnd.oma-scws-config" => Mime::ApplicationVndOmaScwsConfig, + "application/vnd.oma-scws-http-request" => Mime::ApplicationVndOmaScwsHttpRequest, + "application/vnd.oma-scws-http-response" => Mime::ApplicationVndOmaScwsHttpResponse, + "application/vnd.onepager" => Mime::ApplicationVndOnepager, + "application/vnd.onepagertamp" => Mime::ApplicationVndOnepagertamp, + "application/vnd.onepagertamx" => Mime::ApplicationVndOnepagertamx, + "application/vnd.onepagertat" => Mime::ApplicationVndOnepagertat, + "application/vnd.onepagertatp" => Mime::ApplicationVndOnepagertatp, + "application/vnd.onepagertatx" => Mime::ApplicationVndOnepagertatx, + "application/vnd.onvif.metadata" => Mime::ApplicationVndOnvifMetadata, + "application/vnd.openblox.game-binary" => Mime::ApplicationVndOpenbloxGameBinary, + "application/vnd.openblox.game+xml" => Mime::ApplicationVndOpenbloxGameXml, + "application/vnd.openeye.oeb" => Mime::ApplicationVndOpeneyeOeb, + "application/vnd.openstreetmap.data+xml" => Mime::ApplicationVndOpenstreetmapDataXml, + "application/vnd.opentimestamps.ots" => Mime::ApplicationVndOpentimestampsOts, + "application/vnd.openxmlformats-officedocument.custom-properties+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentCustomPropertiesXml, + "application/vnd.openxmlformats-officedocument.customXmlProperties+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentCustomxmlpropertiesXml, + "application/vnd.openxmlformats-officedocument.drawing+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingXml, + "application/vnd.openxmlformats-officedocument.drawingml.chart+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartXml, + "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartshapesXml, + "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramcolorsXml, + "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramdataXml, + "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramlayoutXml, + "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramstyleXml, + "application/vnd.openxmlformats-officedocument.extended-properties+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentExtendedPropertiesXml, + "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentauthorsXml, + "application/vnd.openxmlformats-officedocument.presentationml.comments+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentsXml, + "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlHandoutmasterXml, + "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesmasterXml, + "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesslideXml, + "application/vnd.openxmlformats-officedocument.presentationml.presentation" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentation, + "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentationMainXml, + "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPrespropsXml, + "application/vnd.openxmlformats-officedocument.presentationml.slide" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlide, + "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideXml, + "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidelayoutXml, + "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidemasterXml, + "application/vnd.openxmlformats-officedocument.presentationml.slideshow" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshow, + "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshowMainXml, + "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideupdateinfoXml, + "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTablestylesXml, + "application/vnd.openxmlformats-officedocument.presentationml.tags+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTagsXml, + "application/vnd.openxmlformats-officedocument.presentationml.template" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplate, + "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplateMainXml, + "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlViewpropsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCalcchainXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlChartsheetXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCommentsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlConnectionsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlDialogsheetXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlExternallinkXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcachedefinitionXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcacherecordsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivottableXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlQuerytableXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionheadersXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionlogXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSharedstringsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheet, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetMainXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetmetadataXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlStylesXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTableXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTablesinglecellsXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.template" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate, + "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplateMainXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlUsernamesXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlVolatiledependenciesXml, + "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlWorksheetXml, + "application/vnd.openxmlformats-officedocument.theme+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentThemeXml, + "application/vnd.openxmlformats-officedocument.themeOverride+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentThemeoverrideXml, + "application/vnd.openxmlformats-officedocument.vmlDrawing" => Mime::ApplicationVndOpenxmlformatsOfficedocumentVmldrawing, + "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlCommentsXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocument, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentGlossaryXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentMainXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlEndnotesXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFonttableXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFooterXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFootnotesXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlNumberingXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlSettingsXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlStylesXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.template" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplate, + "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplateMainXml, + "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml" => Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlWebsettingsXml, + "application/vnd.openxmlformats-package.core-properties+xml" => Mime::ApplicationVndOpenxmlformatsPackageCorePropertiesXml, + "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml" => Mime::ApplicationVndOpenxmlformatsPackageDigitalSignatureXmlsignatureXml, + "application/vnd.openxmlformats-package.relationships+xml" => Mime::ApplicationVndOpenxmlformatsPackageRelationshipsXml, + "application/vnd.oracle.resource+json" => Mime::ApplicationVndOracleResourceJson, + "application/vnd.orange.indata" => Mime::ApplicationVndOrangeIndata, + "application/vnd.osa.netdeploy" => Mime::ApplicationVndOsaNetdeploy, + "application/vnd.osgeo.mapguide.package" => Mime::ApplicationVndOsgeoMapguidePackage, + "application/vnd.osgi.bundle" => Mime::ApplicationVndOsgiBundle, + "application/vnd.osgi.dp" => Mime::ApplicationVndOsgiDp, + "application/vnd.osgi.subsystem" => Mime::ApplicationVndOsgiSubsystem, + "application/vnd.otps.ct-kip+xml" => Mime::ApplicationVndOtpsCtKipXml, + "application/vnd.oxli.countgraph" => Mime::ApplicationVndOxliCountgraph, + "application/vnd.pagerduty+json" => Mime::ApplicationVndPagerdutyJson, + "application/vnd.palm" => Mime::ApplicationVndPalm, + "application/vnd.panoply" => Mime::ApplicationVndPanoply, + "application/vnd.paos.xml" => Mime::ApplicationVndPaosXml, + "application/vnd.patentdive" => Mime::ApplicationVndPatentdive, + "application/vnd.patientecommsdoc" => Mime::ApplicationVndPatientecommsdoc, + "application/vnd.pawaafile" => Mime::ApplicationVndPawaafile, + "application/vnd.pcos" => Mime::ApplicationVndPcos, + "application/vnd.pg.format" => Mime::ApplicationVndPgFormat, + "application/vnd.pg.osasli" => Mime::ApplicationVndPgOsasli, + "application/vnd.piaccess.application-licence" => Mime::ApplicationVndPiaccessApplicationLicence, + "application/vnd.picsel" => Mime::ApplicationVndPicsel, + "application/vnd.pmi.widget" => Mime::ApplicationVndPmiWidget, + "application/vnd.poc.group-advertisement+xml" => Mime::ApplicationVndPocGroupAdvertisementXml, + "application/vnd.pocketlearn" => Mime::ApplicationVndPocketlearn, + "application/vnd.powerbuilder6" => Mime::ApplicationVndPowerbuilder6, + "application/vnd.powerbuilder6-s" => Mime::ApplicationVndPowerbuilder6S, + "application/vnd.powerbuilder7" => Mime::ApplicationVndPowerbuilder7, + "application/vnd.powerbuilder75" => Mime::ApplicationVndPowerbuilder75, + "application/vnd.powerbuilder75-s" => Mime::ApplicationVndPowerbuilder75S, + "application/vnd.powerbuilder7-s" => Mime::ApplicationVndPowerbuilder7S, + "application/vnd.preminet" => Mime::ApplicationVndPreminet, + "application/vnd.previewsystems.box" => Mime::ApplicationVndPreviewsystemsBox, + "application/vnd.proteus.magazine" => Mime::ApplicationVndProteusMagazine, + "application/vnd.psfs" => Mime::ApplicationVndPsfs, + "application/vnd.pt.mundusmundi" => Mime::ApplicationVndPtMundusmundi, + "application/vnd.publishare-delta-tree" => Mime::ApplicationVndPublishareDeltaTree, + "application/vnd.pvi.ptid1" => Mime::ApplicationVndPviPtid1, + "application/vnd.pwg-multiplexed" => Mime::ApplicationVndPwgMultiplexed, + "application/vnd.pwg-xhtml-print+xml" => Mime::ApplicationVndPwgXhtmlPrintXml, + "application/vnd.qualcomm.brew-app-res" => Mime::ApplicationVndQualcommBrewAppRes, + "application/vnd.quarantainenet" => Mime::ApplicationVndQuarantainenet, + "application/vnd.Quark.QuarkXPress" => Mime::ApplicationVndQuarkQuarkxpress, + "application/vnd.quobject-quoxdocument" => Mime::ApplicationVndQuobjectQuoxdocument, + "application/vnd.radisys.moml+xml" => Mime::ApplicationVndRadisysMomlXml, + "application/vnd.radisys.msml-audit-conf+xml" => Mime::ApplicationVndRadisysMsmlAuditConfXml, + "application/vnd.radisys.msml-audit-conn+xml" => Mime::ApplicationVndRadisysMsmlAuditConnXml, + "application/vnd.radisys.msml-audit-dialog+xml" => Mime::ApplicationVndRadisysMsmlAuditDialogXml, + "application/vnd.radisys.msml-audit-stream+xml" => Mime::ApplicationVndRadisysMsmlAuditStreamXml, + "application/vnd.radisys.msml-audit+xml" => Mime::ApplicationVndRadisysMsmlAuditXml, + "application/vnd.radisys.msml-conf+xml" => Mime::ApplicationVndRadisysMsmlConfXml, + "application/vnd.radisys.msml-dialog-base+xml" => Mime::ApplicationVndRadisysMsmlDialogBaseXml, + "application/vnd.radisys.msml-dialog-fax-detect+xml" => Mime::ApplicationVndRadisysMsmlDialogFaxDetectXml, + "application/vnd.radisys.msml-dialog-fax-sendrecv+xml" => Mime::ApplicationVndRadisysMsmlDialogFaxSendrecvXml, + "application/vnd.radisys.msml-dialog-group+xml" => Mime::ApplicationVndRadisysMsmlDialogGroupXml, + "application/vnd.radisys.msml-dialog-speech+xml" => Mime::ApplicationVndRadisysMsmlDialogSpeechXml, + "application/vnd.radisys.msml-dialog-transform+xml" => Mime::ApplicationVndRadisysMsmlDialogTransformXml, + "application/vnd.radisys.msml-dialog+xml" => Mime::ApplicationVndRadisysMsmlDialogXml, + "application/vnd.radisys.msml+xml" => Mime::ApplicationVndRadisysMsmlXml, + "application/vnd.rainstor.data" => Mime::ApplicationVndRainstorData, + "application/vnd.rapid" => Mime::ApplicationVndRapid, + "application/vnd.rar" => Mime::ApplicationVndRar, + "application/vnd.realvnc.bed" => Mime::ApplicationVndRealvncBed, + "application/vnd.recordare.musicxml" => Mime::ApplicationVndRecordareMusicxml, + "application/vnd.recordare.musicxml+xml" => Mime::ApplicationVndRecordareMusicxmlXml, + "application/vnd.RenLearn.rlprint" => Mime::ApplicationVndRenlearnRlprint, + "application/vnd.resilient.logic" => Mime::ApplicationVndResilientLogic, + "application/vnd.restful+json" => Mime::ApplicationVndRestfulJson, + "application/vnd.rig.cryptonote" => Mime::ApplicationVndRigCryptonote, + "application/vnd.route66.link66+xml" => Mime::ApplicationVndRoute66Link66Xml, + "application/vnd.rs-274x" => Mime::ApplicationVndRs274x, + "application/vnd.ruckus.download" => Mime::ApplicationVndRuckusDownload, + "application/vnd.s3sms" => Mime::ApplicationVndS3sms, + "application/vnd.sailingtracker.track" => Mime::ApplicationVndSailingtrackerTrack, + "application/vnd.sar" => Mime::ApplicationVndSar, + "application/vnd.sbm.cid" => Mime::ApplicationVndSbmCid, + "application/vnd.sbm.mid2" => Mime::ApplicationVndSbmMid2, + "application/vnd.scribus" => Mime::ApplicationVndScribus, + "application/vnd.sealed.3df" => Mime::ApplicationVndSealed3df, + "application/vnd.sealed.csf" => Mime::ApplicationVndSealedCsf, + "application/vnd.sealed.doc" => Mime::ApplicationVndSealedDoc, + "application/vnd.sealed.eml" => Mime::ApplicationVndSealedEml, + "application/vnd.sealed.mht" => Mime::ApplicationVndSealedMht, + "application/vnd.sealed.net" => Mime::ApplicationVndSealedNet, + "application/vnd.sealed.ppt" => Mime::ApplicationVndSealedPpt, + "application/vnd.sealed.tiff" => Mime::ApplicationVndSealedTiff, + "application/vnd.sealed.xls" => Mime::ApplicationVndSealedXls, + "application/vnd.sealedmedia.softseal.html" => Mime::ApplicationVndSealedmediaSoftsealHtml, + "application/vnd.sealedmedia.softseal.pdf" => Mime::ApplicationVndSealedmediaSoftsealPdf, + "application/vnd.seemail" => Mime::ApplicationVndSeemail, + "application/vnd.seis+json" => Mime::ApplicationVndSeisJson, + "application/vnd.sema" => Mime::ApplicationVndSema, + "application/vnd.semd" => Mime::ApplicationVndSemd, + "application/vnd.semf" => Mime::ApplicationVndSemf, + "application/vnd.shade-save-file" => Mime::ApplicationVndShadeSaveFile, + "application/vnd.shana.informed.formdata" => Mime::ApplicationVndShanaInformedFormdata, + "application/vnd.shana.informed.formtemplate" => Mime::ApplicationVndShanaInformedFormtemplate, + "application/vnd.shana.informed.interchange" => Mime::ApplicationVndShanaInformedInterchange, + "application/vnd.shana.informed.package" => Mime::ApplicationVndShanaInformedPackage, + "application/vnd.shootproof+json" => Mime::ApplicationVndShootproofJson, + "application/vnd.shopkick+json" => Mime::ApplicationVndShopkickJson, + "application/vnd.shp" => Mime::ApplicationVndShp, + "application/vnd.shx" => Mime::ApplicationVndShx, + "application/vnd.sigrok.session" => Mime::ApplicationVndSigrokSession, + "application/vnd.SimTech-MindMapper" => Mime::ApplicationVndSimtechMindmapper, + "application/vnd.siren+json" => Mime::ApplicationVndSirenJson, + "application/vnd.smaf" => Mime::ApplicationVndSmaf, + "application/vnd.smart.notebook" => Mime::ApplicationVndSmartNotebook, + "application/vnd.smart.teacher" => Mime::ApplicationVndSmartTeacher, + "application/vnd.smintio.portals.archive" => Mime::ApplicationVndSmintioPortalsArchive, + "application/vnd.snesdev-page-table" => Mime::ApplicationVndSnesdevPageTable, + "application/vnd.software602.filler.form+xml" => Mime::ApplicationVndSoftware602FillerFormXml, + "application/vnd.software602.filler.form-xml-zip" => Mime::ApplicationVndSoftware602FillerFormXmlZip, + "application/vnd.solent.sdkm+xml" => Mime::ApplicationVndSolentSdkmXml, + "application/vnd.spotfire.dxp" => Mime::ApplicationVndSpotfireDxp, + "application/vnd.spotfire.sfs" => Mime::ApplicationVndSpotfireSfs, + "application/vnd.sqlite3" => Mime::ApplicationVndSqlite3, + "application/vnd.sss-cod" => Mime::ApplicationVndSssCod, + "application/vnd.sss-dtf" => Mime::ApplicationVndSssDtf, + "application/vnd.sss-ntf" => Mime::ApplicationVndSssNtf, + "application/vnd.stepmania.package" => Mime::ApplicationVndStepmaniaPackage, + "application/vnd.stepmania.stepchart" => Mime::ApplicationVndStepmaniaStepchart, + "application/vnd.street-stream" => Mime::ApplicationVndStreetStream, + "application/vnd.sun.wadl+xml" => Mime::ApplicationVndSunWadlXml, + "application/vnd.sus-calendar" => Mime::ApplicationVndSusCalendar, + "application/vnd.svd" => Mime::ApplicationVndSvd, + "application/vnd.swiftview-ics" => Mime::ApplicationVndSwiftviewIcs, + "application/vnd.sybyl.mol2" => Mime::ApplicationVndSybylMol2, + "application/vnd.sycle+xml" => Mime::ApplicationVndSycleXml, + "application/vnd.syft+json" => Mime::ApplicationVndSyftJson, + "application/vnd.syncml.dm.notification" => Mime::ApplicationVndSyncmlDmNotification, + "application/vnd.syncml.dmddf+xml" => Mime::ApplicationVndSyncmlDmddfXml, + "application/vnd.syncml.dmtnds+wbxml" => Mime::ApplicationVndSyncmlDmtndsWbxml, + "application/vnd.syncml.dmtnds+xml" => Mime::ApplicationVndSyncmlDmtndsXml, + "application/vnd.syncml.dmddf+wbxml" => Mime::ApplicationVndSyncmlDmddfWbxml, + "application/vnd.syncml.dm+wbxml" => Mime::ApplicationVndSyncmlDmWbxml, + "application/vnd.syncml.dm+xml" => Mime::ApplicationVndSyncmlDmXml, + "application/vnd.syncml.ds.notification" => Mime::ApplicationVndSyncmlDsNotification, + "application/vnd.syncml+xml" => Mime::ApplicationVndSyncmlXml, + "application/vnd.tableschema+json" => Mime::ApplicationVndTableschemaJson, + "application/vnd.tao.intent-module-archive" => Mime::ApplicationVndTaoIntentModuleArchive, + "application/vnd.tcpdump.pcap" => Mime::ApplicationVndTcpdumpPcap, + "application/vnd.think-cell.ppttc+json" => Mime::ApplicationVndThinkCellPpttcJson, + "application/vnd.tml" => Mime::ApplicationVndTml, + "application/vnd.tmd.mediaflex.api+xml" => Mime::ApplicationVndTmdMediaflexApiXml, + "application/vnd.tmobile-livetv" => Mime::ApplicationVndTmobileLivetv, + "application/vnd.tri.onesource" => Mime::ApplicationVndTriOnesource, + "application/vnd.trid.tpt" => Mime::ApplicationVndTridTpt, + "application/vnd.triscape.mxs" => Mime::ApplicationVndTriscapeMxs, + "application/vnd.trueapp" => Mime::ApplicationVndTrueapp, + "application/vnd.truedoc" => Mime::ApplicationVndTruedoc, + "application/vnd.ubisoft.webplayer" => Mime::ApplicationVndUbisoftWebplayer, + "application/vnd.ufdl" => Mime::ApplicationVndUfdl, + "application/vnd.uiq.theme" => Mime::ApplicationVndUiqTheme, + "application/vnd.umajin" => Mime::ApplicationVndUmajin, + "application/vnd.unity" => Mime::ApplicationVndUnity, + "application/vnd.uoml+xml" => Mime::ApplicationVndUomlXml, + "application/vnd.uplanet.alert" => Mime::ApplicationVndUplanetAlert, + "application/vnd.uplanet.alert-wbxml" => Mime::ApplicationVndUplanetAlertWbxml, + "application/vnd.uplanet.bearer-choice" => Mime::ApplicationVndUplanetBearerChoice, + "application/vnd.uplanet.bearer-choice-wbxml" => Mime::ApplicationVndUplanetBearerChoiceWbxml, + "application/vnd.uplanet.cacheop" => Mime::ApplicationVndUplanetCacheop, + "application/vnd.uplanet.cacheop-wbxml" => Mime::ApplicationVndUplanetCacheopWbxml, + "application/vnd.uplanet.channel" => Mime::ApplicationVndUplanetChannel, + "application/vnd.uplanet.channel-wbxml" => Mime::ApplicationVndUplanetChannelWbxml, + "application/vnd.uplanet.list" => Mime::ApplicationVndUplanetList, + "application/vnd.uplanet.listcmd" => Mime::ApplicationVndUplanetListcmd, + "application/vnd.uplanet.listcmd-wbxml" => Mime::ApplicationVndUplanetListcmdWbxml, + "application/vnd.uplanet.list-wbxml" => Mime::ApplicationVndUplanetListWbxml, + "application/vnd.uri-map" => Mime::ApplicationVndUriMap, + "application/vnd.uplanet.signal" => Mime::ApplicationVndUplanetSignal, + "application/vnd.valve.source.material" => Mime::ApplicationVndValveSourceMaterial, + "application/vnd.vcx" => Mime::ApplicationVndVcx, + "application/vnd.vd-study" => Mime::ApplicationVndVdStudy, + "application/vnd.vectorworks" => Mime::ApplicationVndVectorworks, + "application/vnd.vel+json" => Mime::ApplicationVndVelJson, + "application/vnd.verimatrix.vcas" => Mime::ApplicationVndVerimatrixVcas, + "application/vnd.veritone.aion+json" => Mime::ApplicationVndVeritoneAionJson, + "application/vnd.veryant.thin" => Mime::ApplicationVndVeryantThin, + "application/vnd.ves.encrypted" => Mime::ApplicationVndVesEncrypted, + "application/vnd.vidsoft.vidconference" => Mime::ApplicationVndVidsoftVidconference, + "application/vnd.visio" => Mime::ApplicationVndVisio, + "application/vnd.visionary" => Mime::ApplicationVndVisionary, + "application/vnd.vividence.scriptfile" => Mime::ApplicationVndVividenceScriptfile, + "application/vnd.vsf" => Mime::ApplicationVndVsf, + "application/vnd.wap.sic" => Mime::ApplicationVndWapSic, + "application/vnd.wap.slc" => Mime::ApplicationVndWapSlc, + "application/vnd.wap.wbxml" => Mime::ApplicationVndWapWbxml, + "application/vnd.wap.wmlc" => Mime::ApplicationVndWapWmlc, + "application/vnd.wap.wmlscriptc" => Mime::ApplicationVndWapWmlscriptc, + "application/vnd.wasmflow.wafl" => Mime::ApplicationVndWasmflowWafl, + "application/vnd.webturbo" => Mime::ApplicationVndWebturbo, + "application/vnd.wfa.dpp" => Mime::ApplicationVndWfaDpp, + "application/vnd.wfa.p2p" => Mime::ApplicationVndWfaP2p, + "application/vnd.wfa.wsc" => Mime::ApplicationVndWfaWsc, + "application/vnd.windows.devicepairing" => Mime::ApplicationVndWindowsDevicepairing, + "application/vnd.wmc" => Mime::ApplicationVndWmc, + "application/vnd.wmf.bootstrap" => Mime::ApplicationVndWmfBootstrap, + "application/vnd.wolfram.mathematica" => Mime::ApplicationVndWolframMathematica, + "application/vnd.wolfram.mathematica.package" => Mime::ApplicationVndWolframMathematicaPackage, + "application/vnd.wolfram.player" => Mime::ApplicationVndWolframPlayer, + "application/vnd.wordlift" => Mime::ApplicationVndWordlift, + "application/vnd.wordperfect" => Mime::ApplicationVndWordperfect, + "application/vnd.wqd" => Mime::ApplicationVndWqd, + "application/vnd.wrq-hp3000-labelled" => Mime::ApplicationVndWrqHp3000Labelled, + "application/vnd.wt.stf" => Mime::ApplicationVndWtStf, + "application/vnd.wv.csp+xml" => Mime::ApplicationVndWvCspXml, + "application/vnd.wv.csp+wbxml" => Mime::ApplicationVndWvCspWbxml, + "application/vnd.wv.ssp+xml" => Mime::ApplicationVndWvSspXml, + "application/vnd.xacml+json" => Mime::ApplicationVndXacmlJson, + "application/vnd.xara" => Mime::ApplicationVndXara, + "application/vnd.xfdl" => Mime::ApplicationVndXfdl, + "application/vnd.xfdl.webform" => Mime::ApplicationVndXfdlWebform, + "application/vnd.xmi+xml" => Mime::ApplicationVndXmiXml, + "application/vnd.xmpie.cpkg" => Mime::ApplicationVndXmpieCpkg, + "application/vnd.xmpie.dpkg" => Mime::ApplicationVndXmpieDpkg, + "application/vnd.xmpie.plan" => Mime::ApplicationVndXmpiePlan, + "application/vnd.xmpie.ppkg" => Mime::ApplicationVndXmpiePpkg, + "application/vnd.xmpie.xlim" => Mime::ApplicationVndXmpieXlim, + "application/vnd.yamaha.hv-dic" => Mime::ApplicationVndYamahaHvDic, + "application/vnd.yamaha.hv-script" => Mime::ApplicationVndYamahaHvScript, + "application/vnd.yamaha.hv-voice" => Mime::ApplicationVndYamahaHvVoice, + "application/vnd.yamaha.openscoreformat.osfpvg+xml" => Mime::ApplicationVndYamahaOpenscoreformatOsfpvgXml, + "application/vnd.yamaha.openscoreformat" => Mime::ApplicationVndYamahaOpenscoreformat, + "application/vnd.yamaha.remote-setup" => Mime::ApplicationVndYamahaRemoteSetup, + "application/vnd.yamaha.smaf-audio" => Mime::ApplicationVndYamahaSmafAudio, + "application/vnd.yamaha.smaf-phrase" => Mime::ApplicationVndYamahaSmafPhrase, + "application/vnd.yamaha.through-ngn" => Mime::ApplicationVndYamahaThroughNgn, + "application/vnd.yamaha.tunnel-udpencap" => Mime::ApplicationVndYamahaTunnelUdpencap, + "application/vnd.yaoweme" => Mime::ApplicationVndYaoweme, + "application/vnd.yellowriver-custom-menu" => Mime::ApplicationVndYellowriverCustomMenu, + "application/vnd.zul" => Mime::ApplicationVndZul, + "application/vnd.zzazz.deck+xml" => Mime::ApplicationVndZzazzDeckXml, + "application/voicexml+xml" => Mime::ApplicationVoicexmlXml, + "application/voucher-cms+json" => Mime::ApplicationVoucherCmsJson, + "application/vq-rtcpxr" => Mime::ApplicationVqRtcpxr, + "application/wasm" => Mime::ApplicationWasm, + "application/watcherinfo+xml" => Mime::ApplicationWatcherinfoXml, + "application/webpush-options+json" => Mime::ApplicationWebpushOptionsJson, + "application/whoispp-query" => Mime::ApplicationWhoisppQuery, + "application/whoispp-response" => Mime::ApplicationWhoisppResponse, + "application/widget" => Mime::ApplicationWidget, + "application/wita" => Mime::ApplicationWita, + "application/wordperfect5.1" => Mime::ApplicationWordperfect51, + "application/wsdl+xml" => Mime::ApplicationWsdlXml, + "application/wspolicy+xml" => Mime::ApplicationWspolicyXml, + "application/x-pki-message" => Mime::ApplicationXPkiMessage, + "application/x-www-form-urlencoded" => Mime::ApplicationXWwwFormUrlencoded, + "application/x-x509-ca-cert" => Mime::ApplicationXX509CaCert, + "application/x-x509-ca-ra-cert" => Mime::ApplicationXX509CaRaCert, + "application/x-x509-next-ca-cert" => Mime::ApplicationXX509NextCaCert, + "application/x400-bp" => Mime::ApplicationX400Bp, + "application/xacml+xml" => Mime::ApplicationXacmlXml, + "application/xcap-att+xml" => Mime::ApplicationXcapAttXml, + "application/xcap-caps+xml" => Mime::ApplicationXcapCapsXml, + "application/xcap-diff+xml" => Mime::ApplicationXcapDiffXml, + "application/xcap-el+xml" => Mime::ApplicationXcapElXml, + "application/xcap-error+xml" => Mime::ApplicationXcapErrorXml, + "application/xcap-ns+xml" => Mime::ApplicationXcapNsXml, + "application/xcon-conference-info-diff+xml" => Mime::ApplicationXconConferenceInfoDiffXml, + "application/xcon-conference-info+xml" => Mime::ApplicationXconConferenceInfoXml, + "application/xenc+xml" => Mime::ApplicationXencXml, + "application/xfdf" => Mime::ApplicationXfdf, + "application/xhtml+xml" => Mime::ApplicationXhtmlXml, + "application/xliff+xml" => Mime::ApplicationXliffXml, + "application/xml" => Mime::ApplicationXml, + "application/xml-dtd" => Mime::ApplicationXmlDtd, + "application/xml-external-parsed-entity" => Mime::ApplicationXmlExternalParsedEntity, + "application/xml-patch+xml" => Mime::ApplicationXmlPatchXml, + "application/xmpp+xml" => Mime::ApplicationXmppXml, + "application/xop+xml" => Mime::ApplicationXopXml, + "application/xslt+xml" => Mime::ApplicationXsltXml, + "application/xv+xml" => Mime::ApplicationXvXml, + "application/yang" => Mime::ApplicationYang, + "application/yang-data+cbor" => Mime::ApplicationYangDataCbor, + "application/yang-data+json" => Mime::ApplicationYangDataJson, + "application/yang-data+xml" => Mime::ApplicationYangDataXml, + "application/yang-patch+json" => Mime::ApplicationYangPatchJson, + "application/yang-patch+xml" => Mime::ApplicationYangPatchXml, + "application/yin+xml" => Mime::ApplicationYinXml, + "application/zip" => Mime::ApplicationZip, + "application/zlib" => Mime::ApplicationZlib, + "application/zstd" => Mime::ApplicationZstd, + "audio/1d-interleaved-parityfec" => Mime::Audio1dInterleavedParityfec, + "audio/32kadpcm" => Mime::Audio32kadpcm, + "audio/3gpp" => Mime::Audio3gpp, + "audio/3gpp2" => Mime::Audio3gpp2, + "audio/aac" => Mime::AudioAac, + "audio/ac3" => Mime::AudioAc3, + "audio/AMR" => Mime::AudioAmr, + "audio/AMR-WB" => Mime::AudioAmrWb, + "audio/amr-wb+" => Mime::AudioAmrWbPlus, + "audio/aptx" => Mime::AudioAptx, + "audio/asc" => Mime::AudioAsc, + "audio/ATRAC-ADVANCED-LOSSLESS" => Mime::AudioAtracAdvancedLossless, + "audio/ATRAC-X" => Mime::AudioAtracX, + "audio/ATRAC3" => Mime::AudioAtrac3, + "audio/basic" => Mime::AudioBasic, + "audio/BV16" => Mime::AudioBv16, + "audio/BV32" => Mime::AudioBv32, + "audio/clearmode" => Mime::AudioClearmode, + "audio/CN" => Mime::AudioCn, + "audio/DAT12" => Mime::AudioDat12, + "audio/dls" => Mime::AudioDls, + "audio/dsr-es201108" => Mime::AudioDsrEs201108, + "audio/dsr-es202050" => Mime::AudioDsrEs202050, + "audio/dsr-es202211" => Mime::AudioDsrEs202211, + "audio/dsr-es202212" => Mime::AudioDsrEs202212, + "audio/DV" => Mime::AudioDv, + "audio/DVI4" => Mime::AudioDvi4, + "audio/eac3" => Mime::AudioEac3, + "audio/encaprtp" => Mime::AudioEncaprtp, + "audio/EVRC" => Mime::AudioEvrc, + "audio/EVRC-QCP" => Mime::AudioEvrcQcp, + "audio/EVRC0" => Mime::AudioEvrc0, + "audio/EVRC1" => Mime::AudioEvrc1, + "audio/EVRCB" => Mime::AudioEvrcb, + "audio/EVRCB0" => Mime::AudioEvrcb0, + "audio/EVRCB1" => Mime::AudioEvrcb1, + "audio/EVRCNW" => Mime::AudioEvrcnw, + "audio/EVRCNW0" => Mime::AudioEvrcnw0, + "audio/EVRCNW1" => Mime::AudioEvrcnw1, + "audio/EVRCWB" => Mime::AudioEvrcwb, + "audio/EVRCWB0" => Mime::AudioEvrcwb0, + "audio/EVRCWB1" => Mime::AudioEvrcwb1, + "audio/EVS" => Mime::AudioEvs, + "audio/example" => Mime::AudioExample, + "audio/flexfec" => Mime::AudioFlexfec, + "audio/fwdred" => Mime::AudioFwdred, + "audio/G711-0" => Mime::AudioG7110, + "audio/G719" => Mime::AudioG719, + "audio/G7221" => Mime::AudioG7221, + "audio/G722" => Mime::AudioG722, + "audio/G723" => Mime::AudioG723, + "audio/G726-16" => Mime::AudioG72616, + "audio/G726-24" => Mime::AudioG72624, + "audio/G726-32" => Mime::AudioG72632, + "audio/G726-40" => Mime::AudioG72640, + "audio/G728" => Mime::AudioG728, + "audio/G729" => Mime::AudioG729, + "audio/G7291" => Mime::AudioG7291, + "audio/G729D" => Mime::AudioG729d, + "audio/G729E" => Mime::AudioG729e, + "audio/GSM" => Mime::AudioGsm, + "audio/GSM-EFR" => Mime::AudioGsmEfr, + "audio/GSM-HR-08" => Mime::AudioGsmHr08, + "audio/iLBC" => Mime::AudioIlbc, + "audio/ip-mr_v2.5" => Mime::AudioIpMrV25 , + "audio/L8" => Mime::AudioL8, + "audio/L16" => Mime::AudioL16, + "audio/L20" => Mime::AudioL20, + "audio/L24" => Mime::AudioL24, + "audio/LPC" => Mime::AudioLpc, + "audio/MELP" => Mime::AudioMelp, + "audio/MELP600" => Mime::AudioMelp600, + "audio/MELP1200" => Mime::AudioMelp1200, + "audio/MELP2400" => Mime::AudioMelp2400, + "audio/mhas" => Mime::AudioMhas, + "audio/mobile-xmf" => Mime::AudioMobileXmf, + "audio/MPA" => Mime::AudioMpa, + "audio/mp4" => Mime::AudioMp4, + "audio/MP4A-LATM" => Mime::AudioMp4aLatm, + "audio/mpa-robust" => Mime::AudioMpaRobust, + "audio/mpeg" => Mime::AudioMpeg, + "audio/mpeg4-generic" => Mime::AudioMpeg4Generic, + "audio/ogg" => Mime::AudioOgg, + "audio/opus" => Mime::AudioOpus, + "audio/parityfec" => Mime::AudioParityfec, + "audio/PCMA" => Mime::AudioPcma, + "audio/PCMA-WB" => Mime::AudioPcmaWb, + "audio/PCMU" => Mime::AudioPcmu, + "audio/PCMU-WB" => Mime::AudioPcmuWb, + "audio/prs.sid" => Mime::AudioPrsSid, + "audio/QCELP" => Mime::AudioQcelp, + "audio/raptorfec" => Mime::AudioRaptorfec, + "audio/RED" => Mime::AudioRed, + "audio/rtp-enc-aescm128" => Mime::AudioRtpEncAescm128, + "audio/rtploopback" => Mime::AudioRtploopback, + "audio/rtp-midi" => Mime::AudioRtpMidi, + "audio/rtx" => Mime::AudioRtx, + "audio/scip" => Mime::AudioScip, + "audio/SMV" => Mime::AudioSmv, + "audio/SMV0" => Mime::AudioSmv0, + "audio/SMV-QCP" => Mime::AudioSmvQcp, + "audio/sofa" => Mime::AudioSofa, + "audio/sp-midi" => Mime::AudioSpMidi, + "audio/speex" => Mime::AudioSpeex, + "audio/t140c" => Mime::AudioT140c, + "audio/t38" => Mime::AudioT38, + "audio/telephone-event" => Mime::AudioTelephoneEvent, + "audio/tone" => Mime::AudioTone, + "audio/TETRA_ACELP" => Mime::AudioTetraAcelp, + "audio/TETRA_ACELP_BB" => Mime::AudioTetraAcelpBb , + "audio/TSVCIS" => Mime::AudioTsvcis, + "audio/UEMCLIP" => Mime::AudioUemclip, + "audio/ulpfec" => Mime::AudioUlpfec, + "audio/usac" => Mime::AudioUsac, + "audio/VDVI" => Mime::AudioVdvi, + "audio/VMR-WB" => Mime::AudioVmrWb, + "audio/vnd.3gpp.iufp" => Mime::AudioVnd3gppIufp, + "audio/vnd.4SB" => Mime::AudioVnd4sb, + "audio/vnd.audiokoz" => Mime::AudioVndAudiokoz, + "audio/vnd.CELP" => Mime::AudioVndCelp, + "audio/vnd.cisco.nse" => Mime::AudioVndCiscoNse, + "audio/vnd.cmles.radio-events" => Mime::AudioVndCmlesRadioEvents, + "audio/vnd.cns.anp1" => Mime::AudioVndCnsAnp1, + "audio/vnd.cns.inf1" => Mime::AudioVndCnsInf1, + "audio/vnd.dece.audio" => Mime::AudioVndDeceAudio, + "audio/vnd.digital-winds" => Mime::AudioVndDigitalWinds, + "audio/vnd.dlna.adts" => Mime::AudioVndDlnaAdts, + "audio/vnd.dolby.heaac.1" => Mime::AudioVndDolbyHeaac1, + "audio/vnd.dolby.heaac.2" => Mime::AudioVndDolbyHeaac2, + "audio/vnd.dolby.mlp" => Mime::AudioVndDolbyMlp, + "audio/vnd.dolby.mps" => Mime::AudioVndDolbyMps, + "audio/vnd.dolby.pl2" => Mime::AudioVndDolbyPl2, + "audio/vnd.dolby.pl2x" => Mime::AudioVndDolbyPl2x, + "audio/vnd.dolby.pl2z" => Mime::AudioVndDolbyPl2z, + "audio/vnd.dolby.pulse.1" => Mime::AudioVndDolbyPulse1, + "audio/vnd.dra" => Mime::AudioVndDra, + "audio/vnd.dts" => Mime::AudioVndDts, + "audio/vnd.dts.hd" => Mime::AudioVndDtsHd, + "audio/vnd.dts.uhd" => Mime::AudioVndDtsUhd, + "audio/vnd.dvb.file" => Mime::AudioVndDvbFile, + "audio/vnd.everad.plj" => Mime::AudioVndEveradPlj, + "audio/vnd.hns.audio" => Mime::AudioVndHnsAudio, + "audio/vnd.lucent.voice" => Mime::AudioVndLucentVoice, + "audio/vnd.ms-playready.media.pya" => Mime::AudioVndMsPlayreadyMediaPya, + "audio/vnd.nokia.mobile-xmf" => Mime::AudioVndNokiaMobileXmf, + "audio/vnd.nortel.vbk" => Mime::AudioVndNortelVbk, + "audio/vnd.nuera.ecelp4800" => Mime::AudioVndNueraEcelp4800, + "audio/vnd.nuera.ecelp7470" => Mime::AudioVndNueraEcelp7470, + "audio/vnd.nuera.ecelp9600" => Mime::AudioVndNueraEcelp9600, + "audio/vnd.octel.sbc" => Mime::AudioVndOctelSbc, + "audio/vnd.presonus.multitrack" => Mime::AudioVndPresonusMultitrack, + "audio/vnd.rhetorex.32kadpcm" => Mime::AudioVndRhetorex32kadpcm, + "audio/vnd.rip" => Mime::AudioVndRip, + "audio/vnd.sealedmedia.softseal.mpeg" => Mime::AudioVndSealedmediaSoftsealMpeg, + "audio/vnd.vmx.cvsd" => Mime::AudioVndVmxCvsd, + "audio/vorbis" => Mime::AudioVorbis, + "audio/vorbis-config" => Mime::AudioVorbisConfig, + "font/collection" => Mime::FontCollection, + "font/otf" => Mime::FontOtf, + "font/sfnt" => Mime::FontSfnt, + "font/ttf" => Mime::FontTtf, + "font/woff" => Mime::FontWoff, + "font/woff2" => Mime::FontWoff2, + "image/aces" => Mime::ImageAces, + "image/apng" => Mime::ImageApng, + "image/avci" => Mime::ImageAvci, + "image/avcs" => Mime::ImageAvcs, + "image/avif" => Mime::ImageAvif, + "image/bmp" => Mime::ImageBmp, + "image/cgm" => Mime::ImageCgm, + "image/dicom-rle" => Mime::ImageDicomRle, + "image/dpx" => Mime::ImageDpx, + "image/emf" => Mime::ImageEmf, + "image/example" => Mime::ImageExample, + "image/fits" => Mime::ImageFits, + "image/g3fax" => Mime::ImageG3fax, + "image/heic" => Mime::ImageHeic, + "image/heic-sequence" => Mime::ImageHeicSequence, + "image/heif" => Mime::ImageHeif, + "image/heif-sequence" => Mime::ImageHeifSequence, + "image/hej2k" => Mime::ImageHej2k, + "image/hsj2" => Mime::ImageHsj2, + "image/jls" => Mime::ImageJls, + "image/jp2" => Mime::ImageJp2, + "image/jpeg" => Mime::ImageJpeg, + "image/jph" => Mime::ImageJph, + "image/jphc" => Mime::ImageJphc, + "image/jpm" => Mime::ImageJpm, + "image/jpx" => Mime::ImageJpx, + "image/jxr" => Mime::ImageJxr, + "image/jxrA" => Mime::ImageJxra, + "image/jxrS" => Mime::ImageJxrs, + "image/jxs" => Mime::ImageJxs, + "image/jxsc" => Mime::ImageJxsc, + "image/jxsi" => Mime::ImageJxsi, + "image/jxss" => Mime::ImageJxss, + "image/ktx" => Mime::ImageKtx, + "image/ktx2" => Mime::ImageKtx2, + "image/naplps" => Mime::ImageNaplps, + "image/png" => Mime::ImagePng, + "image/prs.btif" => Mime::ImagePrsBtif, + "image/prs.pti" => Mime::ImagePrsPti, + "image/pwg-raster" => Mime::ImagePwgRaster, + "image/svg+xml" => Mime::ImageSvgXml, + "image/t38" => Mime::ImageT38, + "image/tiff" => Mime::ImageTiff, + "image/tiff-fx" => Mime::ImageTiffFx, + "image/vnd.adobe.photoshop" => Mime::ImageVndAdobePhotoshop, + "image/vnd.airzip.accelerator.azv" => Mime::ImageVndAirzipAcceleratorAzv, + "image/vnd.cns.inf2" => Mime::ImageVndCnsInf2, + "image/vnd.dece.graphic" => Mime::ImageVndDeceGraphic, + "image/vnd.djvu" => Mime::ImageVndDjvu, + "image/vnd.dwg" => Mime::ImageVndDwg, + "image/vnd.dxf" => Mime::ImageVndDxf, + "image/vnd.dvb.subtitle" => Mime::ImageVndDvbSubtitle, + "image/vnd.fastbidsheet" => Mime::ImageVndFastbidsheet, + "image/vnd.fpx" => Mime::ImageVndFpx, + "image/vnd.fst" => Mime::ImageVndFst, + "image/vnd.fujixerox.edmics-mmr" => Mime::ImageVndFujixeroxEdmicsMmr, + "image/vnd.fujixerox.edmics-rlc" => Mime::ImageVndFujixeroxEdmicsRlc, + "image/vnd.globalgraphics.pgb" => Mime::ImageVndGlobalgraphicsPgb, + "image/vnd.microsoft.icon" => Mime::ImageVndMicrosoftIcon, + "image/vnd.mix" => Mime::ImageVndMix, + "image/vnd.ms-modi" => Mime::ImageVndMsModi, + "image/vnd.mozilla.apng" => Mime::ImageVndMozillaApng, + "image/vnd.net-fpx" => Mime::ImageVndNetFpx, + "image/vnd.pco.b16" => Mime::ImageVndPcoB16, + "image/vnd.radiance" => Mime::ImageVndRadiance, + "image/vnd.sealed.png" => Mime::ImageVndSealedPng, + "image/vnd.sealedmedia.softseal.gif" => Mime::ImageVndSealedmediaSoftsealGif, + "image/vnd.sealedmedia.softseal.jpg" => Mime::ImageVndSealedmediaSoftsealJpg, + "image/vnd.svf" => Mime::ImageVndSvf, + "image/vnd.tencent.tap" => Mime::ImageVndTencentTap, + "image/vnd.valve.source.texture" => Mime::ImageVndValveSourceTexture, + "image/vnd.wap.wbmp" => Mime::ImageVndWapWbmp, + "image/vnd.xiff" => Mime::ImageVndXiff, + "image/vnd.zbrush.pcx" => Mime::ImageVndZbrushPcx, + "image/webp" => Mime::ImageWebp, + "image/wmf" => Mime::ImageWmf, + "message/bhttp" => Mime::MessageBhttp, + "message/CPIM" => Mime::MessageCpim, + "message/delivery-status" => Mime::MessageDeliveryStatus, + "message/disposition-notification" => Mime::MessageDispositionNotification, + "message/example" => Mime::MessageExample, + "message/feedback-report" => Mime::MessageFeedbackReport, + "message/global" => Mime::MessageGlobal, + "message/global-delivery-status" => Mime::MessageGlobalDeliveryStatus, + "message/global-disposition-notification" => Mime::MessageGlobalDispositionNotification, + "message/global-headers" => Mime::MessageGlobalHeaders, + "message/http" => Mime::MessageHttp, + "message/imdn+xml" => Mime::MessageImdnXml, + "message/mls" => Mime::MessageMls, + "message/ohttp-req" => Mime::MessageOhttpReq, + "message/ohttp-res" => Mime::MessageOhttpRes, + "message/sip" => Mime::MessageSip, + "message/sipfrag" => Mime::MessageSipfrag, + "message/tracking-status" => Mime::MessageTrackingStatus, + "message/vnd.wfa.wsc" => Mime::MessageVndWfaWsc, + "model/3mf" => Mime::Model3mf, + "model/e57" => Mime::ModelE57, + "model/example" => Mime::ModelExample, + "model/gltf-binary" => Mime::ModelGltfBinary, + "model/gltf+json" => Mime::ModelGltfJson, + "model/JT" => Mime::ModelJt, + "model/iges" => Mime::ModelIges, + "model/mtl" => Mime::ModelMtl, + "model/obj" => Mime::ModelObj, + "model/prc" => Mime::ModelPrc, + "model/step" => Mime::ModelStep, + "model/step+xml" => Mime::ModelStepXml, + "model/step+zip" => Mime::ModelStepZip, + "model/step-xml+zip" => Mime::ModelStepXmlZip, + "model/stl" => Mime::ModelStl, + "model/u3d" => Mime::ModelU3d, + "model/vnd.bary" => Mime::ModelVndBary, + "model/vnd.cld" => Mime::ModelVndCld, + "model/vnd.collada+xml" => Mime::ModelVndColladaXml, + "model/vnd.dwf" => Mime::ModelVndDwf, + "model/vnd.flatland.3dml" => Mime::ModelVndFlatland3dml, + "model/vnd.gdl" => Mime::ModelVndGdl, + "model/vnd.gs-gdl" => Mime::ModelVndGsGdl, + "model/vnd.gtw" => Mime::ModelVndGtw, + "model/vnd.moml+xml" => Mime::ModelVndMomlXml, + "model/vnd.mts" => Mime::ModelVndMts, + "model/vnd.opengex" => Mime::ModelVndOpengex, + "model/vnd.parasolid.transmit.binary" => Mime::ModelVndParasolidTransmitBinary, + "model/vnd.parasolid.transmit.text" => Mime::ModelVndParasolidTransmitText, + "model/vnd.pytha.pyox" => Mime::ModelVndPythaPyox, + "model/vnd.rosette.annotated-data-model" => Mime::ModelVndRosetteAnnotatedDataModel, + "model/vnd.sap.vds" => Mime::ModelVndSapVds, + "model/vnd.usda" => Mime::ModelVndUsda, + "model/vnd.usdz+zip" => Mime::ModelVndUsdzZip, + "model/vnd.valve.source.compiled-map" => Mime::ModelVndValveSourceCompiledMap, + "model/vnd.vtu" => Mime::ModelVndVtu, + "model/x3d-vrml" => Mime::ModelX3dVrml, + "model/x3d+fastinfoset" => Mime::ModelX3dFastinfoset, + "model/x3d+xml" => Mime::ModelX3dXml, + "multipart/appledouble" => Mime::MultipartAppledouble, + "multipart/byteranges" => Mime::MultipartByteranges, + "multipart/encrypted" => Mime::MultipartEncrypted, + "multipart/example" => Mime::MultipartExample, + "multipart/form-data" => Mime::MultipartFormData, + "multipart/header-set" => Mime::MultipartHeaderSet, + "multipart/multilingual" => Mime::MultipartMultilingual, + "multipart/related" => Mime::MultipartRelated, + "multipart/report" => Mime::MultipartReport, + "multipart/signed" => Mime::MultipartSigned, + "multipart/vnd.bint.med-plus" => Mime::MultipartVndBintMedPlus, + "multipart/voice-message" => Mime::MultipartVoiceMessage, + "multipart/x-mixed-replace" => Mime::MultipartXMixedReplace, + "text/1d-interleaved-parityfec" => Mime::Text1dInterleavedParityfec, + "text/cache-manifest" => Mime::TextCacheManifest, + "text/calendar" => Mime::TextCalendar, + "text/cql" => Mime::TextCql, + "text/cql-expression" => Mime::TextCqlExpression, + "text/cql-identifier" => Mime::TextCqlIdentifier, + "text/css" => Mime::TextCss, + "text/csv" => Mime::TextCsv, + "text/csv-schema" => Mime::TextCsvSchema, + "text/dns" => Mime::TextDns, + "text/encaprtp" => Mime::TextEncaprtp, + "text/example" => Mime::TextExample, + "text/fhirpath" => Mime::TextFhirpath, + "text/flexfec" => Mime::TextFlexfec, + "text/fwdred" => Mime::TextFwdred, + "text/gff3" => Mime::TextGff3, + "text/grammar-ref-list" => Mime::TextGrammarRefList, + "text/hl7v2" => Mime::TextHl7v2, + "text/html" => Mime::TextHtml, + "text/javascript" => Mime::TextJavascript, + "text/jcr-cnd" => Mime::TextJcrCnd, + "text/markdown" => Mime::TextMarkdown, + "text/mizar" => Mime::TextMizar, + "text/n3" => Mime::TextN3, + "text/parameters" => Mime::TextParameters, + "text/parityfec" => Mime::TextParityfec, + "text/plain" => Mime::TextPlain, + "text/provenance-notation" => Mime::TextProvenanceNotation, + "text/prs.fallenstein.rst" => Mime::TextPrsFallensteinRst, + "text/prs.lines.tag" => Mime::TextPrsLinesTag, + "text/prs.prop.logic" => Mime::TextPrsPropLogic, + "text/raptorfec" => Mime::TextRaptorfec, + "text/RED" => Mime::TextRed, + "text/rfc822-headers" => Mime::TextRfc822Headers, + "text/rtf" => Mime::TextRtf, + "text/rtp-enc-aescm128" => Mime::TextRtpEncAescm128, + "text/rtploopback" => Mime::TextRtploopback, + "text/rtx" => Mime::TextRtx, + "text/SGML" => Mime::TextSgml, + "text/shaclc" => Mime::TextShaclc, + "text/shex" => Mime::TextShex, + "text/spdx" => Mime::TextSpdx, + "text/strings" => Mime::TextStrings, + "text/t140" => Mime::TextT140, + "text/tab-separated-values" => Mime::TextTabSeparatedValues, + "text/troff" => Mime::TextTroff, + "text/turtle" => Mime::TextTurtle, + "text/ulpfec" => Mime::TextUlpfec, + "text/uri-list" => Mime::TextUriList, + "text/vcard" => Mime::TextVcard, + "text/vnd.a" => Mime::TextVndA, + "text/vnd.abc" => Mime::TextVndAbc, + "text/vnd.ascii-art" => Mime::TextVndAsciiArt, + "text/vnd.curl" => Mime::TextVndCurl, + "text/vnd.debian.copyright" => Mime::TextVndDebianCopyright, + "text/vnd.DMClientScript" => Mime::TextVndDmclientscript, + "text/vnd.dvb.subtitle" => Mime::TextVndDvbSubtitle, + "text/vnd.esmertec.theme-descriptor" => Mime::TextVndEsmertecThemeDescriptor, + "text/vnd.exchangeable" => Mime::TextVndExchangeable, + "text/vnd.familysearch.gedcom" => Mime::TextVndFamilysearchGedcom, + "text/vnd.ficlab.flt" => Mime::TextVndFiclabFlt, + "text/vnd.fly" => Mime::TextVndFly, + "text/vnd.fmi.flexstor" => Mime::TextVndFmiFlexstor, + "text/vnd.gml" => Mime::TextVndGml, + "text/vnd.graphviz" => Mime::TextVndGraphviz, + "text/vnd.hans" => Mime::TextVndHans, + "text/vnd.hgl" => Mime::TextVndHgl, + "text/vnd.in3d.3dml" => Mime::TextVndIn3d3dml, + "text/vnd.in3d.spot" => Mime::TextVndIn3dSpot, + "text/vnd.IPTC.NewsML" => Mime::TextVndIptcNewsml, + "text/vnd.IPTC.NITF" => Mime::TextVndIptcNitf, + "text/vnd.latex-z" => Mime::TextVndLatexZ, + "text/vnd.motorola.reflex" => Mime::TextVndMotorolaReflex, + "text/vnd.ms-mediapackage" => Mime::TextVndMsMediapackage, + "text/vnd.net2phone.commcenter.command" => Mime::TextVndNet2phoneCommcenterCommand, + "text/vnd.radisys.msml-basic-layout" => Mime::TextVndRadisysMsmlBasicLayout, + "text/vnd.senx.warpscript" => Mime::TextVndSenxWarpscript, + "text/vnd.sun.j2me.app-descriptor" => Mime::TextVndSunJ2meAppDescriptor, + "text/vnd.sosi" => Mime::TextVndSosi, + "text/vnd.trolltech.linguist" => Mime::TextVndTrolltechLinguist, + "text/vnd.wap.si" => Mime::TextVndWapSi, + "text/vnd.wap.sl" => Mime::TextVndWapSl, + "text/vnd.wap.wml" => Mime::TextVndWapWml, + "text/vnd.wap.wmlscript" => Mime::TextVndWapWmlscript, + "text/vtt" => Mime::TextVtt, + "text/wgsl" => Mime::TextWgsl, + "text/xml" => Mime::TextXml, + "text/xml-external-parsed-entity" => Mime::TextXmlExternalParsedEntity, + "video/1d-interleaved-parityfec" => Mime::Video1dInterleavedParityfec, + "video/3gpp" => Mime::Video3gpp, + "video/3gpp2" => Mime::Video3gpp2, + "video/3gpp-tt" => Mime::Video3gppTt, + "video/AV1" => Mime::VideoAv1, + "video/BMPEG" => Mime::VideoBmpeg, + "video/BT656" => Mime::VideoBt656, + "video/CelB" => Mime::VideoCelb, + "video/DV" => Mime::VideoDv, + "video/encaprtp" => Mime::VideoEncaprtp, + "video/example" => Mime::VideoExample, + "video/FFV1" => Mime::VideoFfv1, + "video/flexfec" => Mime::VideoFlexfec, + "video/H261" => Mime::VideoH261, + "video/H263" => Mime::VideoH263, + "video/H263-1998" => Mime::VideoH2631998, + "video/H263-2000" => Mime::VideoH2632000, + "video/H264" => Mime::VideoH264, + "video/H264-RCDO" => Mime::VideoH264Rcdo, + "video/H264-SVC" => Mime::VideoH264Svc, + "video/H265" => Mime::VideoH265, + "video/H266" => Mime::VideoH266, + "video/iso.segment" => Mime::VideoIsoSegment, + "video/JPEG" => Mime::VideoJpeg, + "video/jpeg2000" => Mime::VideoJpeg2000, + "video/jxsv" => Mime::VideoJxsv, + "video/mj2" => Mime::VideoMj2, + "video/MP1S" => Mime::VideoMp1s, + "video/MP2P" => Mime::VideoMp2p, + "video/MP2T" => Mime::VideoMp2t, + "video/mp4" => Mime::VideoMp4, + "video/MP4V-ES" => Mime::VideoMp4vEs, + "video/MPV" => Mime::VideoMpv, + "video/mpeg4-generic" => Mime::VideoMpeg4Generic, + "video/nv" => Mime::VideoNv, + "video/ogg" => Mime::VideoOgg, + "video/parityfec" => Mime::VideoParityfec, + "video/pointer" => Mime::VideoPointer, + "video/quicktime" => Mime::VideoQuicktime, + "video/raptorfec" => Mime::VideoRaptorfec, + "video/raw" => Mime::VideoRaw, + "video/rtp-enc-aescm128" => Mime::VideoRtpEncAescm128, + "video/rtploopback" => Mime::VideoRtploopback, + "video/rtx" => Mime::VideoRtx, + "video/scip" => Mime::VideoScip, + "video/smpte291" => Mime::VideoSmpte291, + "video/SMPTE292M" => Mime::VideoSmpte292m, + "video/ulpfec" => Mime::VideoUlpfec, + "video/vc1" => Mime::VideoVc1, + "video/vc2" => Mime::VideoVc2, + "video/vnd.CCTV" => Mime::VideoVndCctv, + "video/vnd.dece.hd" => Mime::VideoVndDeceHd, + "video/vnd.dece.mobile" => Mime::VideoVndDeceMobile, + "video/vnd.dece.mp4" => Mime::VideoVndDeceMp4, + "video/vnd.dece.pd" => Mime::VideoVndDecePd, + "video/vnd.dece.sd" => Mime::VideoVndDeceSd, + "video/vnd.dece.video" => Mime::VideoVndDeceVideo, + "video/vnd.directv.mpeg" => Mime::VideoVndDirectvMpeg, + "video/vnd.directv.mpeg-tts" => Mime::VideoVndDirectvMpegTts, + "video/vnd.dlna.mpeg-tts" => Mime::VideoVndDlnaMpegTts, + "video/vnd.dvb.file" => Mime::VideoVndDvbFile, + "video/vnd.fvt" => Mime::VideoVndFvt, + "video/vnd.hns.video" => Mime::VideoVndHnsVideo, + "video/vnd.iptvforum.1dparityfec-1010" => Mime::VideoVndIptvforum1dparityfec1010, + "video/vnd.iptvforum.1dparityfec-2005" => Mime::VideoVndIptvforum1dparityfec2005, + "video/vnd.iptvforum.2dparityfec-1010" => Mime::VideoVndIptvforum2dparityfec1010, + "video/vnd.iptvforum.2dparityfec-2005" => Mime::VideoVndIptvforum2dparityfec2005, + "video/vnd.iptvforum.ttsavc" => Mime::VideoVndIptvforumTtsavc, + "video/vnd.iptvforum.ttsmpeg2" => Mime::VideoVndIptvforumTtsmpeg2, + "video/vnd.motorola.video" => Mime::VideoVndMotorolaVideo, + "video/vnd.motorola.videop" => Mime::VideoVndMotorolaVideop, + "video/vnd.mpegurl" => Mime::VideoVndMpegurl, + "video/vnd.ms-playready.media.pyv" => Mime::VideoVndMsPlayreadyMediaPyv, + "video/vnd.nokia.interleaved-multimedia" => Mime::VideoVndNokiaInterleavedMultimedia, + "video/vnd.nokia.mp4vr" => Mime::VideoVndNokiaMp4vr, + "video/vnd.nokia.videovoip" => Mime::VideoVndNokiaVideovoip, + "video/vnd.objectvideo" => Mime::VideoVndObjectvideo, + "video/vnd.radgamettools.bink" => Mime::VideoVndRadgamettoolsBink, + "video/vnd.radgamettools.smacker" => Mime::VideoVndRadgamettoolsSmacker, + "video/vnd.sealed.mpeg1" => Mime::VideoVndSealedMpeg1, + "video/vnd.sealed.mpeg4" => Mime::VideoVndSealedMpeg4, + "video/vnd.sealed.swf" => Mime::VideoVndSealedSwf, + "video/vnd.sealedmedia.softseal.mov" => Mime::VideoVndSealedmediaSoftsealMov, + "video/vnd.uvvu.mp4" => Mime::VideoVndUvvuMp4, + "video/vnd.youtube.yt" => Mime::VideoVndYoutubeYt, + "video/vnd.vivo" => Mime::VideoVndVivo, + "video/VP8" => Mime::VideoVp8, + "video/VP9" => Mime::VideoVp9, +}; diff --git a/core/http/src/utils/mime/mime_enum.rs b/core/http/src/utils/mime/mime_enum.rs new file mode 100644 index 0000000..fbf4415 --- /dev/null +++ b/core/http/src/utils/mime/mime_enum.rs @@ -0,0 +1,4095 @@ +use super::map::MIME_MAP; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Mime { + Application1dInterleavedParityfec, + Application3gpdashQoeReportXml, + Application3gpphalJson, + Application3gpphalformsJson, + Application3gppImsXml, + ApplicationA2l, + ApplicationAceCbor, + ApplicationAceJson, + ApplicationActivemessage, + ApplicationActivityJson, + ApplicationAifCbor, + ApplicationAifJson, + ApplicationAltoCdniJson, + ApplicationAltoCdnifilterJson, + ApplicationAltoCostmapJson, + ApplicationAltoCostmapfilterJson, + ApplicationAltoDirectoryJson, + ApplicationAltoEndpointpropJson, + ApplicationAltoEndpointpropparamsJson, + ApplicationAltoEndpointcostJson, + ApplicationAltoEndpointcostparamsJson, + ApplicationAltoErrorJson, + ApplicationAltoNetworkmapfilterJson, + ApplicationAltoNetworkmapJson, + ApplicationAltoPropmapJson, + ApplicationAltoPropmapparamsJson, + ApplicationAltoUpdatestreamcontrolJson, + ApplicationAltoUpdatestreamparamsJson, + ApplicationAml, + ApplicationAndrewInset, + ApplicationApplefile, + ApplicationAtJwt, + ApplicationAtf, + ApplicationAtfx, + ApplicationAtomXml, + ApplicationAtomcatXml, + ApplicationAtomdeletedXml, + ApplicationAtomicmail, + ApplicationAtomsvcXml, + ApplicationAtscDwdXml, + ApplicationAtscDynamicEventMessage, + ApplicationAtscHeldXml, + ApplicationAtscRdtJson, + ApplicationAtscRsatXml, + ApplicationAtxml, + ApplicationAuthPolicyXml, + ApplicationAutomationmlAmlXml, + ApplicationAutomationmlAmlxZip, + ApplicationBacnetXddZip, + ApplicationBatchSmtp, + ApplicationBeepXml, + ApplicationCalendarJson, + ApplicationCalendarXml, + ApplicationCallCompletion, + ApplicationCals1840, + ApplicationCaptiveJson, + ApplicationCbor, + ApplicationCborSeq, + ApplicationCccex, + ApplicationCcmpXml, + ApplicationCcxmlXml, + ApplicationCdaXml, + ApplicationCdfxXml, + ApplicationCdmiCapability, + ApplicationCdmiContainer, + ApplicationCdmiDomain, + ApplicationCdmiObject, + ApplicationCdmiQueue, + ApplicationCdni, + ApplicationCea, + ApplicationCea2018Xml, + ApplicationCellmlXml, + ApplicationCfw, + ApplicationCityJson, + ApplicationClr, + ApplicationClueInfoXml, + ApplicationClueXml, + ApplicationCms, + ApplicationCnrpXml, + ApplicationCoapGroupJson, + ApplicationCoapPayload, + ApplicationCommonground, + ApplicationConciseProblemDetailsCbor, + ApplicationConferenceInfoXml, + ApplicationCplXml, + ApplicationCose, + ApplicationCoseKey, + ApplicationCoseKeySet, + ApplicationCoseX509, + ApplicationCsrattrs, + ApplicationCstaXml, + ApplicationCstadataXml, + ApplicationCsvmJson, + ApplicationCwl, + ApplicationCwlJson, + ApplicationCwt, + ApplicationCybercash, + ApplicationDashXml, + ApplicationDashPatchXml, + ApplicationDashdelta, + ApplicationDavmountXml, + ApplicationDcaRft, + ApplicationDcd, + ApplicationDecDx, + ApplicationDialogInfoXml, + ApplicationDicom, + ApplicationDicomJson, + ApplicationDicomXml, + ApplicationDii, + ApplicationDit, + ApplicationDns, + ApplicationDnsJson, + ApplicationDnsMessage, + ApplicationDotsCbor, + ApplicationDpopJwt, + ApplicationDskppXml, + ApplicationDsscDer, + ApplicationDsscXml, + ApplicationDvcs, + ApplicationEdiConsent, + ApplicationEdifact, + ApplicationEdiX12, + ApplicationEfi, + ApplicationElmJson, + ApplicationElmXml, + ApplicationEmergencycalldataCapXml, + ApplicationEmergencycalldataCommentXml, + ApplicationEmergencycalldataControlXml, + ApplicationEmergencycalldataDeviceinfoXml, + ApplicationEmergencycalldataEcallMsd, + ApplicationEmergencycalldataLegacyesnJson, + ApplicationEmergencycalldataProviderinfoXml, + ApplicationEmergencycalldataServiceinfoXml, + ApplicationEmergencycalldataSubscriberinfoXml, + ApplicationEmergencycalldataVedsXml, + ApplicationEmmaXml, + ApplicationEmotionmlXml, + ApplicationEncaprtp, + ApplicationEppXml, + ApplicationEpubZip, + ApplicationEshop, + ApplicationExample, + ApplicationExi, + ApplicationExpectCtReportJson, + ApplicationExpress, + ApplicationFastinfoset, + ApplicationFastsoap, + ApplicationFdf, + ApplicationFdtXml, + ApplicationFhirJson, + ApplicationFhirXml, + ApplicationFits, + ApplicationFlexfec, + ApplicationFontTdpfr, + ApplicationFrameworkAttributesXml, + ApplicationGeoJson, + ApplicationGeoJsonSeq, + ApplicationGeopackageSqlite3, + ApplicationGeoxacmlXml, + ApplicationGltfBuffer, + ApplicationGmlXml, + ApplicationGzip, + ApplicationH224, + ApplicationHeldXml, + ApplicationHl7v2Xml, + ApplicationHttp, + ApplicationHyperstudio, + ApplicationIbeKeyRequestXml, + ApplicationIbePkgReplyXml, + ApplicationIbePpData, + ApplicationIges, + ApplicationImIscomposingXml, + ApplicationIndex, + ApplicationIndexCmd, + ApplicationIndexObj, + ApplicationIndexResponse, + ApplicationIndexVnd, + ApplicationInkmlXml, + ApplicationIotp, + ApplicationIpfix, + ApplicationIpp, + ApplicationIsup, + ApplicationItsXml, + ApplicationJavaArchive, + ApplicationJf2feedJson, + ApplicationJose, + ApplicationJoseJson, + ApplicationJrdJson, + ApplicationJscalendarJson, + ApplicationJson, + ApplicationJsonPatchJson, + ApplicationJsonSeq, + ApplicationJwkJson, + ApplicationJwkSetJson, + ApplicationJwt, + ApplicationKpmlRequestXml, + ApplicationKpmlResponseXml, + ApplicationLdJson, + ApplicationLgrXml, + ApplicationLinkFormat, + ApplicationLinkset, + ApplicationLinksetJson, + ApplicationLoadControlXml, + ApplicationLogoutJwt, + ApplicationLostXml, + ApplicationLostsyncXml, + ApplicationLpfZip, + ApplicationLxf, + ApplicationMacBinhex40, + ApplicationMacwriteii, + ApplicationMadsXml, + ApplicationManifestJson, + ApplicationMarc, + ApplicationMarcxmlXml, + ApplicationMathematica, + ApplicationMathmlXml, + ApplicationMathmlContentXml, + ApplicationMathmlPresentationXml, + ApplicationMbmsAssociatedProcedureDescriptionXml, + ApplicationMbmsDeregisterXml, + ApplicationMbmsEnvelopeXml, + ApplicationMbmsMskResponseXml, + ApplicationMbmsMskXml, + ApplicationMbmsProtectionDescriptionXml, + ApplicationMbmsReceptionReportXml, + ApplicationMbmsRegisterResponseXml, + ApplicationMbmsRegisterXml, + ApplicationMbmsScheduleXml, + ApplicationMbmsUserServiceDescriptionXml, + ApplicationMbox, + ApplicationMediaControlXml, + ApplicationMediaPolicyDatasetXml, + ApplicationMediaservercontrolXml, + ApplicationMergePatchJson, + ApplicationMetalink4Xml, + ApplicationMetsXml, + ApplicationMf4, + ApplicationMikey, + ApplicationMipc, + ApplicationMissingBlocksCborSeq, + ApplicationMmtAeiXml, + ApplicationMmtUsdXml, + ApplicationModsXml, + ApplicationMossKeys, + ApplicationMossSignature, + ApplicationMosskeyData, + ApplicationMosskeyRequest, + ApplicationMp21, + ApplicationMp4, + ApplicationMpeg4Generic, + ApplicationMpeg4Iod, + ApplicationMpeg4IodXmt, + ApplicationMrbConsumerXml, + ApplicationMrbPublishXml, + ApplicationMscIvrXml, + ApplicationMscMixerXml, + ApplicationMsword, + ApplicationMudJson, + ApplicationMultipartCore, + ApplicationMxf, + ApplicationNQuads, + ApplicationNTriples, + ApplicationNasdata, + ApplicationNewsCheckgroups, + ApplicationNewsGroupinfo, + ApplicationNewsTransmission, + ApplicationNlsmlXml, + ApplicationNode, + ApplicationNss, + ApplicationOauthAuthzReqJwt, + ApplicationObliviousDnsMessage, + ApplicationOcspRequest, + ApplicationOcspResponse, + ApplicationOctetStream, + ApplicationOda, + ApplicationOdmXml, + ApplicationOdx, + ApplicationOebpsPackageXml, + ApplicationOgg, + ApplicationOhttpKeys, + ApplicationOpcNodesetXml, + ApplicationOscore, + ApplicationOxps, + ApplicationP21, + ApplicationP21Zip, + ApplicationP2pOverlayXml, + ApplicationParityfec, + ApplicationPassport, + ApplicationPatchOpsErrorXml, + ApplicationPdf, + ApplicationPdx, + ApplicationPemCertificateChain, + ApplicationPgpEncrypted, + ApplicationPgpKeys, + ApplicationPgpSignature, + ApplicationPidfDiffXml, + ApplicationPidfXml, + ApplicationPkcs10, + ApplicationPkcs7Mime, + ApplicationPkcs7Signature, + ApplicationPkcs8, + ApplicationPkcs8Encrypted, + ApplicationPkcs12, + ApplicationPkixAttrCert, + ApplicationPkixCert, + ApplicationPkixCrl, + ApplicationPkixPkipath, + ApplicationPkixcmp, + ApplicationPlsXml, + ApplicationPocSettingsXml, + ApplicationPostscript, + ApplicationPpspTrackerJson, + ApplicationProblemJson, + ApplicationProblemXml, + ApplicationProvenanceXml, + ApplicationPrsAlvestrandTitraxSheet, + ApplicationPrsCww, + ApplicationPrsCyn, + ApplicationPrsHpubZip, + ApplicationPrsImpliedDocumentXml, + ApplicationPrsImpliedExecutable, + ApplicationPrsImpliedStructure, + ApplicationPrsNprend, + ApplicationPrsPlucker, + ApplicationPrsRdfXmlCrypt, + ApplicationPrsXsfXml, + ApplicationPskcXml, + ApplicationPvdJson, + ApplicationRdfXml, + ApplicationRouteApdXml, + ApplicationRouteSTsidXml, + ApplicationRouteUsdXml, + ApplicationQsig, + ApplicationRaptorfec, + ApplicationRdapJson, + ApplicationReginfoXml, + ApplicationRelaxNgCompactSyntax, + ApplicationReputonJson, + ApplicationResourceListsDiffXml, + ApplicationResourceListsXml, + ApplicationRfcXml, + ApplicationRiscos, + ApplicationRlmiXml, + ApplicationRlsServicesXml, + ApplicationRpkiChecklist, + ApplicationRpkiGhostbusters, + ApplicationRpkiManifest, + ApplicationRpkiPublication, + ApplicationRpkiRoa, + ApplicationRpkiUpdown, + ApplicationRtf, + ApplicationRtploopback, + ApplicationRtx, + ApplicationSamlassertionXml, + ApplicationSamlmetadataXml, + ApplicationSarifExternalPropertiesJson, + ApplicationSarifJson, + ApplicationSbe, + ApplicationSbmlXml, + ApplicationScaipXml, + ApplicationScimJson, + ApplicationScvpCvRequest, + ApplicationScvpCvResponse, + ApplicationScvpVpRequest, + ApplicationScvpVpResponse, + ApplicationSdp, + ApplicationSeceventJwt, + ApplicationSenmlEtchCbor, + ApplicationSenmlEtchJson, + ApplicationSenmlExi, + ApplicationSenmlCbor, + ApplicationSenmlJson, + ApplicationSenmlXml, + ApplicationSensmlExi, + ApplicationSensmlCbor, + ApplicationSensmlJson, + ApplicationSensmlXml, + ApplicationSepExi, + ApplicationSepXml, + ApplicationSessionInfo, + ApplicationSetPayment, + ApplicationSetPaymentInitiation, + ApplicationSetRegistration, + ApplicationSetRegistrationInitiation, + ApplicationSgml, + ApplicationSgmlOpenCatalog, + ApplicationShfXml, + ApplicationSieve, + ApplicationSimpleFilterXml, + ApplicationSimpleMessageSummary, + ApplicationSimplesymbolcontainer, + ApplicationSipc, + ApplicationSlate, + ApplicationSmilXml, + ApplicationSmpte336m, + ApplicationSoapFastinfoset, + ApplicationSoapXml, + ApplicationSparqlQuery, + ApplicationSpdxJson, + ApplicationSparqlResultsXml, + ApplicationSpiritsEventXml, + ApplicationSql, + ApplicationSrgs, + ApplicationSrgsXml, + ApplicationSruXml, + ApplicationSsmlXml, + ApplicationStixJson, + ApplicationSwidCbor, + ApplicationSwidXml, + ApplicationTampApexUpdate, + ApplicationTampApexUpdateConfirm, + ApplicationTampCommunityUpdate, + ApplicationTampCommunityUpdateConfirm, + ApplicationTampError, + ApplicationTampSequenceAdjust, + ApplicationTampSequenceAdjustConfirm, + ApplicationTampStatusQuery, + ApplicationTampStatusResponse, + ApplicationTampUpdate, + ApplicationTampUpdateConfirm, + ApplicationTaxiiJson, + ApplicationTdJson, + ApplicationTeiXml, + ApplicationTetraIsi, + ApplicationThraudXml, + ApplicationTimestampQuery, + ApplicationTimestampReply, + ApplicationTimestampedData, + ApplicationTlsrptGzip, + ApplicationTlsrptJson, + ApplicationTmJson, + ApplicationTnauthlist, + ApplicationTokenIntrospectionJwt, + ApplicationTrickleIceSdpfrag, + ApplicationTrig, + ApplicationTtmlXml, + ApplicationTveTrigger, + ApplicationTzif, + ApplicationTzifLeap, + ApplicationUlpfec, + ApplicationUrcGrpsheetXml, + ApplicationUrcRessheetXml, + ApplicationUrcTargetdescXml, + ApplicationUrcUisocketdescXml, + ApplicationVcardJson, + ApplicationVcardXml, + ApplicationVemmi, + ApplicationVnd1000mindsDecisionModelXml, + ApplicationVnd1ob, + ApplicationVnd3gpp5gnas, + ApplicationVnd3gppAccessTransferEventsXml, + ApplicationVnd3gppBsfXml, + ApplicationVnd3gppCrsXml, + ApplicationVnd3gppCurrentLocationDiscoveryXml, + ApplicationVnd3gppGmopXml, + ApplicationVnd3gppGtpc, + ApplicationVnd3gppInterworkingData, + ApplicationVnd3gppLpp, + ApplicationVnd3gppMcSignallingEar, + ApplicationVnd3gppMcdataAffiliationCommandXml, + ApplicationVnd3gppMcdataInfoXml, + ApplicationVnd3gppMcdataMsgstoreCtrlRequestXml, + ApplicationVnd3gppMcdataPayload, + ApplicationVnd3gppMcdataRegroupXml, + ApplicationVnd3gppMcdataServiceConfigXml, + ApplicationVnd3gppMcdataSignalling, + ApplicationVnd3gppMcdataUeConfigXml, + ApplicationVnd3gppMcdataUserProfileXml, + ApplicationVnd3gppMcpttAffiliationCommandXml, + ApplicationVnd3gppMcpttFloorRequestXml, + ApplicationVnd3gppMcpttInfoXml, + ApplicationVnd3gppMcpttLocationInfoXml, + ApplicationVnd3gppMcpttMbmsUsageInfoXml, + ApplicationVnd3gppMcpttRegroupXml, + ApplicationVnd3gppMcpttServiceConfigXml, + ApplicationVnd3gppMcpttSignedXml, + ApplicationVnd3gppMcpttUeConfigXml, + ApplicationVnd3gppMcpttUeInitConfigXml, + ApplicationVnd3gppMcpttUserProfileXml, + ApplicationVnd3gppMcvideoAffiliationCommandXml, + ApplicationVnd3gppMcvideoInfoXml, + ApplicationVnd3gppMcvideoLocationInfoXml, + ApplicationVnd3gppMcvideoMbmsUsageInfoXml, + ApplicationVnd3gppMcvideoRegroupXml, + ApplicationVnd3gppMcvideoServiceConfigXml, + ApplicationVnd3gppMcvideoTransmissionRequestXml, + ApplicationVnd3gppMcvideoUeConfigXml, + ApplicationVnd3gppMcvideoUserProfileXml, + ApplicationVnd3gppMidCallXml, + ApplicationVnd3gppNgap, + ApplicationVnd3gppPfcp, + ApplicationVnd3gppPicBwLarge, + ApplicationVnd3gppPicBwSmall, + ApplicationVnd3gppPicBwVar, + ApplicationVnd3gppProsePc3aXml, + ApplicationVnd3gppProsePc3achXml, + ApplicationVnd3gppProsePc3chXml, + ApplicationVnd3gppProsePc8Xml, + ApplicationVnd3gppProseXml, + ApplicationVnd3gppS1ap, + ApplicationVnd3gppSealGroupDocXml, + ApplicationVnd3gppSealInfoXml, + ApplicationVnd3gppSealLocationInfoXml, + ApplicationVnd3gppSealMbmsUsageInfoXml, + ApplicationVnd3gppSealNetworkQosManagementInfoXml, + ApplicationVnd3gppSealUeConfigInfoXml, + ApplicationVnd3gppSealUnicastInfoXml, + ApplicationVnd3gppSealUserProfileInfoXml, + ApplicationVnd3gppSms, + ApplicationVnd3gppSmsXml, + ApplicationVnd3gppSrvccExtXml, + ApplicationVnd3gppSrvccInfoXml, + ApplicationVnd3gppStateAndEventInfoXml, + ApplicationVnd3gppUssdXml, + ApplicationVnd3gppVaeInfoXml, + ApplicationVnd3gppV2xLocalServiceInformation, + ApplicationVnd3gpp2BcmcsinfoXml, + ApplicationVnd3gpp2Sms, + ApplicationVnd3gpp2Tcap, + ApplicationVnd3gppV2x, + ApplicationVnd3lightssoftwareImagescal, + ApplicationVnd3mPostItNotes, + ApplicationVndAccpacSimplyAso, + ApplicationVndAccpacSimplyImp, + ApplicationVndAcmAddressxferJson, + ApplicationVndAcucobol, + ApplicationVndAcucorp, + ApplicationVndAdobeFlashMovie, + ApplicationVndAdobeFormscentralFcdt, + ApplicationVndAdobeFxp, + ApplicationVndAdobePartialUpload, + ApplicationVndAdobeXdpXml, + ApplicationVndAetherImp, + ApplicationVndAfpcAfplinedata, + ApplicationVndAfpcAfplinedataPagedef, + ApplicationVndAfpcCmocaCmresource, + ApplicationVndAfpcFocaCharset, + ApplicationVndAfpcFocaCodedfont, + ApplicationVndAfpcFocaCodepage, + ApplicationVndAfpcModca, + ApplicationVndAfpcModcaCmtable, + ApplicationVndAfpcModcaFormdef, + ApplicationVndAfpcModcaMediummap, + ApplicationVndAfpcModcaObjectcontainer, + ApplicationVndAfpcModcaOverlay, + ApplicationVndAfpcModcaPagesegment, + ApplicationVndAge, + ApplicationVndAhBarcode, + ApplicationVndAheadSpace, + ApplicationVndAirzipFilesecureAzf, + ApplicationVndAirzipFilesecureAzs, + ApplicationVndAmadeusJson, + ApplicationVndAmazonMobi8Ebook, + ApplicationVndAmericandynamicsAcc, + ApplicationVndAmigaAmi, + ApplicationVndAmundsenMazeXml, + ApplicationVndAndroidOta, + ApplicationVndAnki, + ApplicationVndAnserWebCertificateIssueInitiation, + ApplicationVndAntixGameComponent, + ApplicationVndApacheArrowFile, + ApplicationVndApacheArrowStream, + ApplicationVndApacheThriftBinary, + ApplicationVndApacheThriftCompact, + ApplicationVndApacheThriftJson, + ApplicationVndApexlang, + ApplicationVndApiJson, + ApplicationVndAplextorWarrpJson, + ApplicationVndApothekendeReservationJson, + ApplicationVndAppleInstallerXml, + ApplicationVndAppleKeynote, + ApplicationVndAppleMpegurl, + ApplicationVndAppleNumbers, + ApplicationVndApplePages, + ApplicationVndAristanetworksSwi, + ApplicationVndArtisanJson, + ApplicationVndArtsquare, + ApplicationVndAstraeaSoftwareIota, + ApplicationVndAudiograph, + ApplicationVndAutopackage, + ApplicationVndAvalonJson, + ApplicationVndAvistarXml, + ApplicationVndBalsamiqBmmlXml, + ApplicationVndBananaAccounting, + ApplicationVndBbfUspError, + ApplicationVndBbfUspMsg, + ApplicationVndBbfUspMsgJson, + ApplicationVndBalsamiqBmpr, + ApplicationVndBekitzurStechJson, + ApplicationVndBelightsoftLhzdZip, + ApplicationVndBelightsoftLhzlZip, + ApplicationVndBintMedContent, + ApplicationVndBiopaxRdfXml, + ApplicationVndBlinkIdbValueWrapper, + ApplicationVndBlueiceMultipass, + ApplicationVndBluetoothEpOob, + ApplicationVndBluetoothLeOob, + ApplicationVndBmi, + ApplicationVndBpf, + ApplicationVndBpf3, + ApplicationVndBusinessobjects, + ApplicationVndByuUapiJson, + ApplicationVndCabJscript, + ApplicationVndCanonCpdl, + ApplicationVndCanonLips, + ApplicationVndCapasystemsPgJson, + ApplicationVndCendioThinlincClientconf, + ApplicationVndCenturySystemsTcpStream, + ApplicationVndChemdrawXml, + ApplicationVndChessPgn, + ApplicationVndChipnutsKaraokeMmd, + ApplicationVndCiedi, + ApplicationVndCinderella, + ApplicationVndCirpackIsdnExt, + ApplicationVndCitationstylesStyleXml, + ApplicationVndClaymore, + ApplicationVndCloantoRp9, + ApplicationVndClonkC4group, + ApplicationVndCluetrustCartomobileConfig, + ApplicationVndCluetrustCartomobileConfigPkg, + ApplicationVndCncfHelmChartContentV1TarGzip, + ApplicationVndCncfHelmChartProvenanceV1Prov, + ApplicationVndCncfHelmConfigV1Json, + ApplicationVndCoffeescript, + ApplicationVndCollabioXodocumentsDocument, + ApplicationVndCollabioXodocumentsDocumentTemplate, + ApplicationVndCollabioXodocumentsPresentation, + ApplicationVndCollabioXodocumentsPresentationTemplate, + ApplicationVndCollabioXodocumentsSpreadsheet, + ApplicationVndCollabioXodocumentsSpreadsheetTemplate, + ApplicationVndCollectionDocJson, + ApplicationVndCollectionJson, + ApplicationVndCollectionNextJson, + ApplicationVndComicbookRar, + ApplicationVndComicbookZip, + ApplicationVndCommerceBattelle, + ApplicationVndCommonspace, + ApplicationVndCoreosIgnitionJson, + ApplicationVndCosmocaller, + ApplicationVndContactCmsg, + ApplicationVndCrickClicker, + ApplicationVndCrickClickerKeyboard, + ApplicationVndCrickClickerPalette, + ApplicationVndCrickClickerTemplate, + ApplicationVndCrickClickerWordbank, + ApplicationVndCriticaltoolsWbsXml, + ApplicationVndCryptiiPipeJson, + ApplicationVndCryptoShadeFile, + ApplicationVndCryptomatorEncrypted, + ApplicationVndCryptomatorVault, + ApplicationVndCtcPosml, + ApplicationVndCtctWsXml, + ApplicationVndCupsPdf, + ApplicationVndCupsPostscript, + ApplicationVndCupsPpd, + ApplicationVndCupsRaster, + ApplicationVndCupsRaw, + ApplicationVndCurl, + ApplicationVndCyanDeanRootXml, + ApplicationVndCybank, + ApplicationVndCyclonedxJson, + ApplicationVndCyclonedxXml, + ApplicationVndD2lCoursepackage1p0Zip, + ApplicationVndD3mDataset, + ApplicationVndD3mProblem, + ApplicationVndDart, + ApplicationVndDataVisionRdz, + ApplicationVndDatalog, + ApplicationVndDatapackageJson, + ApplicationVndDataresourceJson, + ApplicationVndDbf, + ApplicationVndDebianBinaryPackage, + ApplicationVndDeceData, + ApplicationVndDeceTtmlXml, + ApplicationVndDeceUnspecified, + ApplicationVndDeceZip, + ApplicationVndDenovoFcselayoutLink, + ApplicationVndDesmumeMovie, + ApplicationVndDirBiPlateDlNosuffix, + ApplicationVndDmDelegationXml, + ApplicationVndDna, + ApplicationVndDocumentJson, + ApplicationVndDolbyMobile1, + ApplicationVndDolbyMobile2, + ApplicationVndDoremirScorecloudBinaryDocument, + ApplicationVndDpgraph, + ApplicationVndDreamfactory, + ApplicationVndDriveJson, + ApplicationVndDtgLocal, + ApplicationVndDtgLocalFlash, + ApplicationVndDtgLocalHtml, + ApplicationVndDvbAit, + ApplicationVndDvbDvbislXml, + ApplicationVndDvbDvbj, + ApplicationVndDvbEsgcontainer, + ApplicationVndDvbIpdcdftnotifaccess, + ApplicationVndDvbIpdcesgaccess, + ApplicationVndDvbIpdcesgaccess2, + ApplicationVndDvbIpdcesgpdd, + ApplicationVndDvbIpdcroaming, + ApplicationVndDvbIptvAlfecBase, + ApplicationVndDvbIptvAlfecEnhancement, + ApplicationVndDvbNotifAggregateRootXml, + ApplicationVndDvbNotifContainerXml, + ApplicationVndDvbNotifGenericXml, + ApplicationVndDvbNotifIaMsglistXml, + ApplicationVndDvbNotifIaRegistrationRequestXml, + ApplicationVndDvbNotifIaRegistrationResponseXml, + ApplicationVndDvbNotifInitXml, + ApplicationVndDvbPfr, + ApplicationVndDvbService, + ApplicationVndDxr, + ApplicationVndDynageo, + ApplicationVndDzr, + ApplicationVndEasykaraokeCdgdownload, + ApplicationVndEcipRlp, + ApplicationVndEcdisUpdate, + ApplicationVndEclipseDittoJson, + ApplicationVndEcowinChart, + ApplicationVndEcowinFilerequest, + ApplicationVndEcowinFileupdate, + ApplicationVndEcowinSeries, + ApplicationVndEcowinSeriesrequest, + ApplicationVndEcowinSeriesupdate, + ApplicationVndEfiImg, + ApplicationVndEfiIso, + ApplicationVndElnZip, + ApplicationVndEmclientAccessrequestXml, + ApplicationVndEnliven, + ApplicationVndEnphaseEnvoy, + ApplicationVndEprintsDataXml, + ApplicationVndEpsonEsf, + ApplicationVndEpsonMsf, + ApplicationVndEpsonQuickanime, + ApplicationVndEpsonSalt, + ApplicationVndEpsonSsf, + ApplicationVndEricssonQuickcall, + ApplicationVndEspassEspassZip, + ApplicationVndEszigno3Xml, + ApplicationVndEtsiAocXml, + ApplicationVndEtsiAsicSZip, + ApplicationVndEtsiAsicEZip, + ApplicationVndEtsiCugXml, + ApplicationVndEtsiIptvcommandXml, + ApplicationVndEtsiIptvdiscoveryXml, + ApplicationVndEtsiIptvprofileXml, + ApplicationVndEtsiIptvsadBcXml, + ApplicationVndEtsiIptvsadCodXml, + ApplicationVndEtsiIptvsadNpvrXml, + ApplicationVndEtsiIptvserviceXml, + ApplicationVndEtsiIptvsyncXml, + ApplicationVndEtsiIptvueprofileXml, + ApplicationVndEtsiMcidXml, + ApplicationVndEtsiMheg5, + ApplicationVndEtsiOverloadControlPolicyDatasetXml, + ApplicationVndEtsiPstnXml, + ApplicationVndEtsiSciXml, + ApplicationVndEtsiSimservsXml, + ApplicationVndEtsiTimestampToken, + ApplicationVndEtsiTslXml, + ApplicationVndEtsiTslDer, + ApplicationVndEuKasparianCarJson, + ApplicationVndEudoraData, + ApplicationVndEvolvEcigProfile, + ApplicationVndEvolvEcigSettings, + ApplicationVndEvolvEcigTheme, + ApplicationVndExstreamEmpowerZip, + ApplicationVndExstreamPackage, + ApplicationVndEzpixAlbum, + ApplicationVndEzpixPackage, + ApplicationVndFSecureMobile, + ApplicationVndFastcopyDiskImage, + ApplicationVndFamilysearchGedcomZip, + ApplicationVndFdsnMseed, + ApplicationVndFdsnSeed, + ApplicationVndFfsns, + ApplicationVndFiclabFlbZip, + ApplicationVndFilmitZfc, + ApplicationVndFints, + ApplicationVndFiremonkeysCloudcell, + ApplicationVndFlographit, + ApplicationVndFluxtimeClip, + ApplicationVndFontFontforgeSfd, + ApplicationVndFramemaker, + ApplicationVndFscWeblaunch, + ApplicationVndFujifilmFbDocuworks, + ApplicationVndFujifilmFbDocuworksBinder, + ApplicationVndFujifilmFbDocuworksContainer, + ApplicationVndFujifilmFbJfiXml, + ApplicationVndFujitsuOasys, + ApplicationVndFujitsuOasys2, + ApplicationVndFujitsuOasys3, + ApplicationVndFujitsuOasysgp, + ApplicationVndFujitsuOasysprs, + ApplicationVndFujixeroxArt4, + ApplicationVndFujixeroxArtEx, + ApplicationVndFujixeroxDdd, + ApplicationVndFujixeroxDocuworks, + ApplicationVndFujixeroxDocuworksBinder, + ApplicationVndFujixeroxDocuworksContainer, + ApplicationVndFujixeroxHbpl, + ApplicationVndFutMisnet, + ApplicationVndFutoinCbor, + ApplicationVndFutoinJson, + ApplicationVndFuzzysheet, + ApplicationVndGenomatixTuxedo, + ApplicationVndGenozip, + ApplicationVndGenticsGrdJson, + ApplicationVndGentooCatmetadataXml, + ApplicationVndGentooEbuild, + ApplicationVndGentooEclass, + ApplicationVndGentooGpkg, + ApplicationVndGentooManifest, + ApplicationVndGentooXpak, + ApplicationVndGentooPkgmetadataXml, + ApplicationVndGeogebraFile, + ApplicationVndGeogebraSlides, + ApplicationVndGeogebraTool, + ApplicationVndGeometryExplorer, + ApplicationVndGeonext, + ApplicationVndGeoplan, + ApplicationVndGeospace, + ApplicationVndGerber, + ApplicationVndGlobalplatformCardContentMgt, + ApplicationVndGlobalplatformCardContentMgtResponse, + ApplicationVndGnuTalerExchangeJson, + ApplicationVndGnuTalerMerchantJson, + ApplicationVndGoogleEarthKmlXml, + ApplicationVndGoogleEarthKmz, + ApplicationVndGovSkEFormXml, + ApplicationVndGovSkEFormZip, + ApplicationVndGovSkXmldatacontainerXml, + ApplicationVndGpxseeMapXml, + ApplicationVndGrafeq, + ApplicationVndGridmp, + ApplicationVndGrooveAccount, + ApplicationVndGrooveHelp, + ApplicationVndGrooveIdentityMessage, + ApplicationVndGrooveInjector, + ApplicationVndGrooveToolMessage, + ApplicationVndGrooveToolTemplate, + ApplicationVndGrooveVcard, + ApplicationVndHalJson, + ApplicationVndHalXml, + ApplicationVndHandheldEntertainmentXml, + ApplicationVndHbci, + ApplicationVndHcJson, + ApplicationVndHclBireports, + ApplicationVndHdt, + ApplicationVndHerokuJson, + ApplicationVndHheLessonPlayer, + ApplicationVndHpHpgl, + ApplicationVndHpHpid, + ApplicationVndHpHps, + ApplicationVndHpJlyt, + ApplicationVndHpPcl, + ApplicationVndHpPclxl, + ApplicationVndHsl, + ApplicationVndHttphone, + ApplicationVndHydrostatixSofData, + ApplicationVndHyperItemJson, + ApplicationVndHyperJson, + ApplicationVndHyperdriveJson, + ApplicationVndHzn3dCrossword, + ApplicationVndIbmElectronicMedia, + ApplicationVndIbmMinipay, + ApplicationVndIbmRightsManagement, + ApplicationVndIbmSecureContainer, + ApplicationVndIccprofile, + ApplicationVndIeee1905, + ApplicationVndIgloader, + ApplicationVndImagemeterFolderZip, + ApplicationVndImagemeterImageZip, + ApplicationVndImmervisionIvp, + ApplicationVndImmervisionIvu, + ApplicationVndImsImsccv1p1, + ApplicationVndImsImsccv1p2, + ApplicationVndImsImsccv1p3, + ApplicationVndImsLisV2ResultJson, + ApplicationVndImsLtiV2ToolconsumerprofileJson, + ApplicationVndImsLtiV2ToolproxyIdJson, + ApplicationVndImsLtiV2ToolproxyJson, + ApplicationVndImsLtiV2ToolsettingsJson, + ApplicationVndImsLtiV2ToolsettingsSimpleJson, + ApplicationVndInformedcontrolRmsXml, + ApplicationVndInfotechProject, + ApplicationVndInfotechProjectXml, + ApplicationVndInnopathWampNotification, + ApplicationVndInsorsIgm, + ApplicationVndInterconFormnet, + ApplicationVndIntergeo, + ApplicationVndIntertrustDigibox, + ApplicationVndIntertrustNncp, + ApplicationVndIntuQbo, + ApplicationVndIntuQfx, + ApplicationVndIpfsIpnsRecord, + ApplicationVndIpldCar, + ApplicationVndIpldDagCbor, + ApplicationVndIpldDagJson, + ApplicationVndIpldRaw, + ApplicationVndIptcG2CatalogitemXml, + ApplicationVndIptcG2ConceptitemXml, + ApplicationVndIptcG2KnowledgeitemXml, + ApplicationVndIptcG2NewsitemXml, + ApplicationVndIptcG2NewsmessageXml, + ApplicationVndIptcG2PackageitemXml, + ApplicationVndIptcG2PlanningitemXml, + ApplicationVndIpunpluggedRcprofile, + ApplicationVndIrepositoryPackageXml, + ApplicationVndIsXpr, + ApplicationVndIsacFcs, + ApplicationVndJam, + ApplicationVndIso1178310Zip, + ApplicationVndJapannetDirectoryService, + ApplicationVndJapannetJpnstoreWakeup, + ApplicationVndJapannetPaymentWakeup, + ApplicationVndJapannetRegistration, + ApplicationVndJapannetRegistrationWakeup, + ApplicationVndJapannetSetstoreWakeup, + ApplicationVndJapannetVerification, + ApplicationVndJapannetVerificationWakeup, + ApplicationVndJcpJavameMidletRms, + ApplicationVndJisp, + ApplicationVndJoostJodaArchive, + ApplicationVndJskIsdnNgn, + ApplicationVndKahootz, + ApplicationVndKdeKarbon, + ApplicationVndKdeKchart, + ApplicationVndKdeKformula, + ApplicationVndKdeKivio, + ApplicationVndKdeKontour, + ApplicationVndKdeKpresenter, + ApplicationVndKdeKspread, + ApplicationVndKdeKword, + ApplicationVndKenameaapp, + ApplicationVndKidspiration, + ApplicationVndKinar, + ApplicationVndKoan, + ApplicationVndKodakDescriptor, + ApplicationVndLas, + ApplicationVndLasLasJson, + ApplicationVndLasLasXml, + ApplicationVndLaszip, + ApplicationVndLeapJson, + ApplicationVndLibertyRequestXml, + ApplicationVndLlamagraphicsLifeBalanceDesktop, + ApplicationVndLlamagraphicsLifeBalanceExchangeXml, + ApplicationVndLogipipeCircuitZip, + ApplicationVndLoom, + ApplicationVndLotus123, + ApplicationVndLotusApproach, + ApplicationVndLotusFreelance, + ApplicationVndLotusNotes, + ApplicationVndLotusOrganizer, + ApplicationVndLotusScreencam, + ApplicationVndLotusWordpro, + ApplicationVndMacportsPortpkg, + ApplicationVndMapboxVectorTile, + ApplicationVndMarlinDrmActiontokenXml, + ApplicationVndMarlinDrmConftokenXml, + ApplicationVndMarlinDrmLicenseXml, + ApplicationVndMarlinDrmMdcf, + ApplicationVndMasonJson, + ApplicationVndMaxarArchive3tzZip, + ApplicationVndMaxmindMaxmindDb, + ApplicationVndMcd, + ApplicationVndMdl, + ApplicationVndMdlMbsdf, + ApplicationVndMedcalcdata, + ApplicationVndMediastationCdkey, + ApplicationVndMedicalholodeckRecordxr, + ApplicationVndMeridianSlingshot, + ApplicationVndMfer, + ApplicationVndMfmp, + ApplicationVndMicroJson, + ApplicationVndMicrografxFlo, + ApplicationVndMicrografxIgx, + ApplicationVndMicrosoftPortableExecutable, + ApplicationVndMicrosoftWindowsThumbnailCache, + ApplicationVndMieleJson, + ApplicationVndMif, + ApplicationVndMinisoftHp3000Save, + ApplicationVndMitsubishiMistyGuardTrustweb, + ApplicationVndMobiusDaf, + ApplicationVndMobiusDis, + ApplicationVndMobiusMbk, + ApplicationVndMobiusMqy, + ApplicationVndMobiusMsl, + ApplicationVndMobiusPlc, + ApplicationVndMobiusTxf, + ApplicationVndModl, + ApplicationVndMophunApplication, + ApplicationVndMophunCertificate, + ApplicationVndMotorolaFlexsuite, + ApplicationVndMotorolaFlexsuiteAdsi, + ApplicationVndMotorolaFlexsuiteFis, + ApplicationVndMotorolaFlexsuiteGotap, + ApplicationVndMotorolaFlexsuiteKmr, + ApplicationVndMotorolaFlexsuiteTtc, + ApplicationVndMotorolaFlexsuiteWem, + ApplicationVndMotorolaIprm, + ApplicationVndMozillaXulXml, + ApplicationVndMsArtgalry, + ApplicationVndMsAsf, + ApplicationVndMsCabCompressed, + ApplicationVndMs3mfdocument, + ApplicationVndMsExcel, + ApplicationVndMsExcelAddinMacroenabled12, + ApplicationVndMsExcelSheetBinaryMacroenabled12, + ApplicationVndMsExcelSheetMacroenabled12, + ApplicationVndMsExcelTemplateMacroenabled12, + ApplicationVndMsFontobject, + ApplicationVndMsHtmlhelp, + ApplicationVndMsIms, + ApplicationVndMsLrm, + ApplicationVndMsOfficeActivexXml, + ApplicationVndMsOfficetheme, + ApplicationVndMsPlayreadyInitiatorXml, + ApplicationVndMsPowerpoint, + ApplicationVndMsPowerpointAddinMacroenabled12, + ApplicationVndMsPowerpointPresentationMacroenabled12, + ApplicationVndMsPowerpointSlideMacroenabled12, + ApplicationVndMsPowerpointSlideshowMacroenabled12, + ApplicationVndMsPowerpointTemplateMacroenabled12, + ApplicationVndMsPrintdevicecapabilitiesXml, + ApplicationVndMsPrintschematicketXml, + ApplicationVndMsProject, + ApplicationVndMsTnef, + ApplicationVndMsWindowsDevicepairing, + ApplicationVndMsWindowsNwprintingOob, + ApplicationVndMsWindowsPrinterpairing, + ApplicationVndMsWindowsWsdOob, + ApplicationVndMsWmdrmLicChlgReq, + ApplicationVndMsWmdrmLicResp, + ApplicationVndMsWmdrmMeterChlgReq, + ApplicationVndMsWmdrmMeterResp, + ApplicationVndMsWordDocumentMacroenabled12, + ApplicationVndMsWordTemplateMacroenabled12, + ApplicationVndMsWorks, + ApplicationVndMsWpl, + ApplicationVndMsXpsdocument, + ApplicationVndMsaDiskImage, + ApplicationVndMseq, + ApplicationVndMsign, + ApplicationVndMultiadCreator, + ApplicationVndMultiadCreatorCif, + ApplicationVndMusician, + ApplicationVndMusicNiff, + ApplicationVndMuveeStyle, + ApplicationVndMynfc, + ApplicationVndNacamarYbridJson, + ApplicationVndNcdControl, + ApplicationVndNcdReference, + ApplicationVndNearstInvJson, + ApplicationVndNebumindLine, + ApplicationVndNervana, + ApplicationVndNetfpx, + ApplicationVndNeurolanguageNlu, + ApplicationVndNimn, + ApplicationVndNintendoSnesRom, + ApplicationVndNintendoNitroRom, + ApplicationVndNitf, + ApplicationVndNoblenetDirectory, + ApplicationVndNoblenetSealer, + ApplicationVndNoblenetWeb, + ApplicationVndNokiaCatalogs, + ApplicationVndNokiaConmlWbxml, + ApplicationVndNokiaConmlXml, + ApplicationVndNokiaIptvConfigXml, + ApplicationVndNokiaIsdsRadioPresets, + ApplicationVndNokiaLandmarkWbxml, + ApplicationVndNokiaLandmarkXml, + ApplicationVndNokiaLandmarkcollectionXml, + ApplicationVndNokiaNcd, + ApplicationVndNokiaNGageAcXml, + ApplicationVndNokiaNGageData, + ApplicationVndNokiaPcdWbxml, + ApplicationVndNokiaPcdXml, + ApplicationVndNokiaRadioPreset, + ApplicationVndNokiaRadioPresets, + ApplicationVndNovadigmEdm, + ApplicationVndNovadigmEdx, + ApplicationVndNovadigmExt, + ApplicationVndNttLocalContentShare, + ApplicationVndNttLocalFileTransfer, + ApplicationVndNttLocalOgwRemoteAccess, + ApplicationVndNttLocalSipTaRemote, + ApplicationVndNttLocalSipTaTcpStream, + ApplicationVndOasisOpendocumentBase, + ApplicationVndOasisOpendocumentChart, + ApplicationVndOasisOpendocumentChartTemplate, + ApplicationVndOasisOpendocumentFormula, + ApplicationVndOasisOpendocumentFormulaTemplate, + ApplicationVndOasisOpendocumentGraphics, + ApplicationVndOasisOpendocumentGraphicsTemplate, + ApplicationVndOasisOpendocumentImage, + ApplicationVndOasisOpendocumentImageTemplate, + ApplicationVndOasisOpendocumentPresentation, + ApplicationVndOasisOpendocumentPresentationTemplate, + ApplicationVndOasisOpendocumentSpreadsheet, + ApplicationVndOasisOpendocumentSpreadsheetTemplate, + ApplicationVndOasisOpendocumentText, + ApplicationVndOasisOpendocumentTextMaster, + ApplicationVndOasisOpendocumentTextMasterTemplate, + ApplicationVndOasisOpendocumentTextTemplate, + ApplicationVndOasisOpendocumentTextWeb, + ApplicationVndObn, + ApplicationVndOcfCbor, + ApplicationVndOciImageManifestV1Json, + ApplicationVndOftnL10nJson, + ApplicationVndOipfContentaccessdownloadXml, + ApplicationVndOipfContentaccessstreamingXml, + ApplicationVndOipfCspgHexbinary, + ApplicationVndOipfDaeSvgXml, + ApplicationVndOipfDaeXhtmlXml, + ApplicationVndOipfMippvcontrolmessageXml, + ApplicationVndOipfPaeGem, + ApplicationVndOipfSpdiscoveryXml, + ApplicationVndOipfSpdlistXml, + ApplicationVndOipfUeprofileXml, + ApplicationVndOipfUserprofileXml, + ApplicationVndOlpcSugar, + ApplicationVndOmaBcastAssociatedProcedureParameterXml, + ApplicationVndOmaBcastDrmTriggerXml, + ApplicationVndOmaBcastImdXml, + ApplicationVndOmaBcastLtkm, + ApplicationVndOmaBcastNotificationXml, + ApplicationVndOmaBcastProvisioningtrigger, + ApplicationVndOmaBcastSgboot, + ApplicationVndOmaBcastSgddXml, + ApplicationVndOmaBcastSgdu, + ApplicationVndOmaBcastSimpleSymbolContainer, + ApplicationVndOmaBcastSmartcardTriggerXml, + ApplicationVndOmaBcastSprovXml, + ApplicationVndOmaBcastStkm, + ApplicationVndOmaCabAddressBookXml, + ApplicationVndOmaCabFeatureHandlerXml, + ApplicationVndOmaCabPccXml, + ApplicationVndOmaCabSubsInviteXml, + ApplicationVndOmaCabUserPrefsXml, + ApplicationVndOmaDcd, + ApplicationVndOmaDcdc, + ApplicationVndOmaDd2Xml, + ApplicationVndOmaDrmRisdXml, + ApplicationVndOmaGroupUsageListXml, + ApplicationVndOmaLwm2mCbor, + ApplicationVndOmaLwm2mJson, + ApplicationVndOmaLwm2mTlv, + ApplicationVndOmaPalXml, + ApplicationVndOmaPocDetailedProgressReportXml, + ApplicationVndOmaPocFinalReportXml, + ApplicationVndOmaPocGroupsXml, + ApplicationVndOmaPocInvocationDescriptorXml, + ApplicationVndOmaPocOptimizedProgressReportXml, + ApplicationVndOmaPush, + ApplicationVndOmaScidmMessagesXml, + ApplicationVndOmaXcapDirectoryXml, + ApplicationVndOmadsEmailXml, + ApplicationVndOmadsFileXml, + ApplicationVndOmadsFolderXml, + ApplicationVndOmalocSuplInit, + ApplicationVndOmaScwsConfig, + ApplicationVndOmaScwsHttpRequest, + ApplicationVndOmaScwsHttpResponse, + ApplicationVndOnepager, + ApplicationVndOnepagertamp, + ApplicationVndOnepagertamx, + ApplicationVndOnepagertat, + ApplicationVndOnepagertatp, + ApplicationVndOnepagertatx, + ApplicationVndOnvifMetadata, + ApplicationVndOpenbloxGameBinary, + ApplicationVndOpenbloxGameXml, + ApplicationVndOpeneyeOeb, + ApplicationVndOpenstreetmapDataXml, + ApplicationVndOpentimestampsOts, + ApplicationVndOpenxmlformatsOfficedocumentCustomPropertiesXml, + ApplicationVndOpenxmlformatsOfficedocumentCustomxmlpropertiesXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartshapesXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramcolorsXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramdataXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramlayoutXml, + ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramstyleXml, + ApplicationVndOpenxmlformatsOfficedocumentExtendedPropertiesXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentauthorsXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentsXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlHandoutmasterXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesmasterXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesslideXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentation, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentationMainXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPrespropsXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlide, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidelayoutXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidemasterXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshow, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshowMainXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideupdateinfoXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTablestylesXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTagsXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplate, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplateMainXml, + ApplicationVndOpenxmlformatsOfficedocumentPresentationmlViewpropsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCalcchainXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlChartsheetXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCommentsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlConnectionsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlDialogsheetXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlExternallinkXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcachedefinitionXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcacherecordsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivottableXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlQuerytableXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionheadersXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionlogXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSharedstringsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheet, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetMainXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetmetadataXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlStylesXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTableXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTablesinglecellsXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplateMainXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlUsernamesXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlVolatiledependenciesXml, + ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlWorksheetXml, + ApplicationVndOpenxmlformatsOfficedocumentThemeXml, + ApplicationVndOpenxmlformatsOfficedocumentThemeoverrideXml, + ApplicationVndOpenxmlformatsOfficedocumentVmldrawing, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlCommentsXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocument, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentGlossaryXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentMainXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlEndnotesXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFonttableXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFooterXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFootnotesXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlNumberingXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlSettingsXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlStylesXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplate, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplateMainXml, + ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlWebsettingsXml, + ApplicationVndOpenxmlformatsPackageCorePropertiesXml, + ApplicationVndOpenxmlformatsPackageDigitalSignatureXmlsignatureXml, + ApplicationVndOpenxmlformatsPackageRelationshipsXml, + ApplicationVndOracleResourceJson, + ApplicationVndOrangeIndata, + ApplicationVndOsaNetdeploy, + ApplicationVndOsgeoMapguidePackage, + ApplicationVndOsgiBundle, + ApplicationVndOsgiDp, + ApplicationVndOsgiSubsystem, + ApplicationVndOtpsCtKipXml, + ApplicationVndOxliCountgraph, + ApplicationVndPagerdutyJson, + ApplicationVndPalm, + ApplicationVndPanoply, + ApplicationVndPaosXml, + ApplicationVndPatentdive, + ApplicationVndPatientecommsdoc, + ApplicationVndPawaafile, + ApplicationVndPcos, + ApplicationVndPgFormat, + ApplicationVndPgOsasli, + ApplicationVndPiaccessApplicationLicence, + ApplicationVndPicsel, + ApplicationVndPmiWidget, + ApplicationVndPocGroupAdvertisementXml, + ApplicationVndPocketlearn, + ApplicationVndPowerbuilder6, + ApplicationVndPowerbuilder6S, + ApplicationVndPowerbuilder7, + ApplicationVndPowerbuilder75, + ApplicationVndPowerbuilder75S, + ApplicationVndPowerbuilder7S, + ApplicationVndPreminet, + ApplicationVndPreviewsystemsBox, + ApplicationVndProteusMagazine, + ApplicationVndPsfs, + ApplicationVndPtMundusmundi, + ApplicationVndPublishareDeltaTree, + ApplicationVndPviPtid1, + ApplicationVndPwgMultiplexed, + ApplicationVndPwgXhtmlPrintXml, + ApplicationVndQualcommBrewAppRes, + ApplicationVndQuarantainenet, + ApplicationVndQuarkQuarkxpress, + ApplicationVndQuobjectQuoxdocument, + ApplicationVndRadisysMomlXml, + ApplicationVndRadisysMsmlAuditConfXml, + ApplicationVndRadisysMsmlAuditConnXml, + ApplicationVndRadisysMsmlAuditDialogXml, + ApplicationVndRadisysMsmlAuditStreamXml, + ApplicationVndRadisysMsmlAuditXml, + ApplicationVndRadisysMsmlConfXml, + ApplicationVndRadisysMsmlDialogBaseXml, + ApplicationVndRadisysMsmlDialogFaxDetectXml, + ApplicationVndRadisysMsmlDialogFaxSendrecvXml, + ApplicationVndRadisysMsmlDialogGroupXml, + ApplicationVndRadisysMsmlDialogSpeechXml, + ApplicationVndRadisysMsmlDialogTransformXml, + ApplicationVndRadisysMsmlDialogXml, + ApplicationVndRadisysMsmlXml, + ApplicationVndRainstorData, + ApplicationVndRapid, + ApplicationVndRar, + ApplicationVndRealvncBed, + ApplicationVndRecordareMusicxml, + ApplicationVndRecordareMusicxmlXml, + ApplicationVndRenlearnRlprint, + ApplicationVndResilientLogic, + ApplicationVndRestfulJson, + ApplicationVndRigCryptonote, + ApplicationVndRoute66Link66Xml, + ApplicationVndRs274x, + ApplicationVndRuckusDownload, + ApplicationVndS3sms, + ApplicationVndSailingtrackerTrack, + ApplicationVndSar, + ApplicationVndSbmCid, + ApplicationVndSbmMid2, + ApplicationVndScribus, + ApplicationVndSealed3df, + ApplicationVndSealedCsf, + ApplicationVndSealedDoc, + ApplicationVndSealedEml, + ApplicationVndSealedMht, + ApplicationVndSealedNet, + ApplicationVndSealedPpt, + ApplicationVndSealedTiff, + ApplicationVndSealedXls, + ApplicationVndSealedmediaSoftsealHtml, + ApplicationVndSealedmediaSoftsealPdf, + ApplicationVndSeemail, + ApplicationVndSeisJson, + ApplicationVndSema, + ApplicationVndSemd, + ApplicationVndSemf, + ApplicationVndShadeSaveFile, + ApplicationVndShanaInformedFormdata, + ApplicationVndShanaInformedFormtemplate, + ApplicationVndShanaInformedInterchange, + ApplicationVndShanaInformedPackage, + ApplicationVndShootproofJson, + ApplicationVndShopkickJson, + ApplicationVndShp, + ApplicationVndShx, + ApplicationVndSigrokSession, + ApplicationVndSimtechMindmapper, + ApplicationVndSirenJson, + ApplicationVndSmaf, + ApplicationVndSmartNotebook, + ApplicationVndSmartTeacher, + ApplicationVndSmintioPortalsArchive, + ApplicationVndSnesdevPageTable, + ApplicationVndSoftware602FillerFormXml, + ApplicationVndSoftware602FillerFormXmlZip, + ApplicationVndSolentSdkmXml, + ApplicationVndSpotfireDxp, + ApplicationVndSpotfireSfs, + ApplicationVndSqlite3, + ApplicationVndSssCod, + ApplicationVndSssDtf, + ApplicationVndSssNtf, + ApplicationVndStepmaniaPackage, + ApplicationVndStepmaniaStepchart, + ApplicationVndStreetStream, + ApplicationVndSunWadlXml, + ApplicationVndSusCalendar, + ApplicationVndSvd, + ApplicationVndSwiftviewIcs, + ApplicationVndSybylMol2, + ApplicationVndSycleXml, + ApplicationVndSyftJson, + ApplicationVndSyncmlDmNotification, + ApplicationVndSyncmlDmddfXml, + ApplicationVndSyncmlDmtndsWbxml, + ApplicationVndSyncmlDmtndsXml, + ApplicationVndSyncmlDmddfWbxml, + ApplicationVndSyncmlDmWbxml, + ApplicationVndSyncmlDmXml, + ApplicationVndSyncmlDsNotification, + ApplicationVndSyncmlXml, + ApplicationVndTableschemaJson, + ApplicationVndTaoIntentModuleArchive, + ApplicationVndTcpdumpPcap, + ApplicationVndThinkCellPpttcJson, + ApplicationVndTml, + ApplicationVndTmdMediaflexApiXml, + ApplicationVndTmobileLivetv, + ApplicationVndTriOnesource, + ApplicationVndTridTpt, + ApplicationVndTriscapeMxs, + ApplicationVndTrueapp, + ApplicationVndTruedoc, + ApplicationVndUbisoftWebplayer, + ApplicationVndUfdl, + ApplicationVndUiqTheme, + ApplicationVndUmajin, + ApplicationVndUnity, + ApplicationVndUomlXml, + ApplicationVndUplanetAlert, + ApplicationVndUplanetAlertWbxml, + ApplicationVndUplanetBearerChoice, + ApplicationVndUplanetBearerChoiceWbxml, + ApplicationVndUplanetCacheop, + ApplicationVndUplanetCacheopWbxml, + ApplicationVndUplanetChannel, + ApplicationVndUplanetChannelWbxml, + ApplicationVndUplanetList, + ApplicationVndUplanetListcmd, + ApplicationVndUplanetListcmdWbxml, + ApplicationVndUplanetListWbxml, + ApplicationVndUriMap, + ApplicationVndUplanetSignal, + ApplicationVndValveSourceMaterial, + ApplicationVndVcx, + ApplicationVndVdStudy, + ApplicationVndVectorworks, + ApplicationVndVelJson, + ApplicationVndVerimatrixVcas, + ApplicationVndVeritoneAionJson, + ApplicationVndVeryantThin, + ApplicationVndVesEncrypted, + ApplicationVndVidsoftVidconference, + ApplicationVndVisio, + ApplicationVndVisionary, + ApplicationVndVividenceScriptfile, + ApplicationVndVsf, + ApplicationVndWapSic, + ApplicationVndWapSlc, + ApplicationVndWapWbxml, + ApplicationVndWapWmlc, + ApplicationVndWapWmlscriptc, + ApplicationVndWasmflowWafl, + ApplicationVndWebturbo, + ApplicationVndWfaDpp, + ApplicationVndWfaP2p, + ApplicationVndWfaWsc, + ApplicationVndWindowsDevicepairing, + ApplicationVndWmc, + ApplicationVndWmfBootstrap, + ApplicationVndWolframMathematica, + ApplicationVndWolframMathematicaPackage, + ApplicationVndWolframPlayer, + ApplicationVndWordlift, + ApplicationVndWordperfect, + ApplicationVndWqd, + ApplicationVndWrqHp3000Labelled, + ApplicationVndWtStf, + ApplicationVndWvCspXml, + ApplicationVndWvCspWbxml, + ApplicationVndWvSspXml, + ApplicationVndXacmlJson, + ApplicationVndXara, + ApplicationVndXfdl, + ApplicationVndXfdlWebform, + ApplicationVndXmiXml, + ApplicationVndXmpieCpkg, + ApplicationVndXmpieDpkg, + ApplicationVndXmpiePlan, + ApplicationVndXmpiePpkg, + ApplicationVndXmpieXlim, + ApplicationVndYamahaHvDic, + ApplicationVndYamahaHvScript, + ApplicationVndYamahaHvVoice, + ApplicationVndYamahaOpenscoreformatOsfpvgXml, + ApplicationVndYamahaOpenscoreformat, + ApplicationVndYamahaRemoteSetup, + ApplicationVndYamahaSmafAudio, + ApplicationVndYamahaSmafPhrase, + ApplicationVndYamahaThroughNgn, + ApplicationVndYamahaTunnelUdpencap, + ApplicationVndYaoweme, + ApplicationVndYellowriverCustomMenu, + ApplicationVndZul, + ApplicationVndZzazzDeckXml, + ApplicationVoicexmlXml, + ApplicationVoucherCmsJson, + ApplicationVqRtcpxr, + ApplicationWasm, + ApplicationWatcherinfoXml, + ApplicationWebpushOptionsJson, + ApplicationWhoisppQuery, + ApplicationWhoisppResponse, + ApplicationWidget, + ApplicationWita, + ApplicationWordperfect51, + ApplicationWsdlXml, + ApplicationWspolicyXml, + ApplicationXPkiMessage, + ApplicationXWwwFormUrlencoded, + ApplicationXX509CaCert, + ApplicationXX509CaRaCert, + ApplicationXX509NextCaCert, + ApplicationX400Bp, + ApplicationXacmlXml, + ApplicationXcapAttXml, + ApplicationXcapCapsXml, + ApplicationXcapDiffXml, + ApplicationXcapElXml, + ApplicationXcapErrorXml, + ApplicationXcapNsXml, + ApplicationXconConferenceInfoDiffXml, + ApplicationXconConferenceInfoXml, + ApplicationXencXml, + ApplicationXfdf, + ApplicationXhtmlXml, + ApplicationXliffXml, + ApplicationXml, + ApplicationXmlDtd, + ApplicationXmlExternalParsedEntity, + ApplicationXmlPatchXml, + ApplicationXmppXml, + ApplicationXopXml, + ApplicationXsltXml, + ApplicationXvXml, + ApplicationYang, + ApplicationYangDataCbor, + ApplicationYangDataJson, + ApplicationYangDataXml, + ApplicationYangPatchJson, + ApplicationYangPatchXml, + ApplicationYinXml, + ApplicationZip, + ApplicationZlib, + ApplicationZstd, + Audio1dInterleavedParityfec, + Audio32kadpcm, + Audio3gpp, + Audio3gpp2, + AudioAac, + AudioAc3, + AudioAmr, + AudioAmrWbPlus, + AudioAmrWb, + AudioAptx, + AudioAsc, + AudioAtracAdvancedLossless, + AudioAtracX, + AudioAtrac3, + AudioBasic, + AudioBv16, + AudioBv32, + AudioClearmode, + AudioCn, + AudioDat12, + AudioDls, + AudioDsrEs201108, + AudioDsrEs202050, + AudioDsrEs202211, + AudioDsrEs202212, + AudioDv, + AudioDvi4, + AudioEac3, + AudioEncaprtp, + AudioEvrc, + AudioEvrcQcp, + AudioEvrc0, + AudioEvrc1, + AudioEvrcb, + AudioEvrcb0, + AudioEvrcb1, + AudioEvrcnw, + AudioEvrcnw0, + AudioEvrcnw1, + AudioEvrcwb, + AudioEvrcwb0, + AudioEvrcwb1, + AudioEvs, + AudioExample, + AudioFlexfec, + AudioFwdred, + AudioG7110, + AudioG719, + AudioG7221, + AudioG722, + AudioG723, + AudioG72616, + AudioG72624, + AudioG72632, + AudioG72640, + AudioG728, + AudioG729, + AudioG7291, + AudioG729d, + AudioG729e, + AudioGsm, + AudioGsmEfr, + AudioGsmHr08, + AudioIlbc, + AudioIpMrV25, + AudioL8, + AudioL16, + AudioL20, + AudioL24, + AudioLpc, + AudioMelp, + AudioMelp600, + AudioMelp1200, + AudioMelp2400, + AudioMhas, + AudioMobileXmf, + AudioMpa, + AudioMp4, + AudioMp4aLatm, + AudioMpaRobust, + AudioMpeg, + AudioMpeg4Generic, + AudioOgg, + AudioOpus, + AudioParityfec, + AudioPcma, + AudioPcmaWb, + AudioPcmu, + AudioPcmuWb, + AudioPrsSid, + AudioQcelp, + AudioRaptorfec, + AudioRed, + AudioRtpEncAescm128, + AudioRtploopback, + AudioRtpMidi, + AudioRtx, + AudioScip, + AudioSmv, + AudioSmv0, + AudioSmvQcp, + AudioSofa, + AudioSpMidi, + AudioSpeex, + AudioT140c, + AudioT38, + AudioTelephoneEvent, + AudioTetraAcelp, + AudioTetraAcelpBb, + AudioTone, + AudioTsvcis, + AudioUemclip, + AudioUlpfec, + AudioUsac, + AudioVdvi, + AudioVmrWb, + AudioVnd3gppIufp, + AudioVnd4sb, + AudioVndAudiokoz, + AudioVndCelp, + AudioVndCiscoNse, + AudioVndCmlesRadioEvents, + AudioVndCnsAnp1, + AudioVndCnsInf1, + AudioVndDeceAudio, + AudioVndDigitalWinds, + AudioVndDlnaAdts, + AudioVndDolbyHeaac1, + AudioVndDolbyHeaac2, + AudioVndDolbyMlp, + AudioVndDolbyMps, + AudioVndDolbyPl2, + AudioVndDolbyPl2x, + AudioVndDolbyPl2z, + AudioVndDolbyPulse1, + AudioVndDra, + AudioVndDts, + AudioVndDtsHd, + AudioVndDtsUhd, + AudioVndDvbFile, + AudioVndEveradPlj, + AudioVndHnsAudio, + AudioVndLucentVoice, + AudioVndMsPlayreadyMediaPya, + AudioVndNokiaMobileXmf, + AudioVndNortelVbk, + AudioVndNueraEcelp4800, + AudioVndNueraEcelp7470, + AudioVndNueraEcelp9600, + AudioVndOctelSbc, + AudioVndPresonusMultitrack, + AudioVndRhetorex32kadpcm, + AudioVndRip, + AudioVndSealedmediaSoftsealMpeg, + AudioVndVmxCvsd, + AudioVorbis, + AudioVorbisConfig, + FontCollection, + FontOtf, + FontSfnt, + FontTtf, + FontWoff, + FontWoff2, + ImageAces, + ImageApng, + ImageAvci, + ImageAvcs, + ImageAvif, + ImageBmp, + ImageCgm, + ImageDicomRle, + ImageDpx, + ImageEmf, + ImageExample, + ImageFits, + ImageG3fax, + ImageHeic, + ImageHeicSequence, + ImageHeif, + ImageHeifSequence, + ImageHej2k, + ImageHsj2, + ImageJls, + ImageJp2, + ImageJpeg, + ImageJph, + ImageJphc, + ImageJpm, + ImageJpx, + ImageJxr, + ImageJxra, + ImageJxrs, + ImageJxs, + ImageJxsc, + ImageJxsi, + ImageJxss, + ImageKtx, + ImageKtx2, + ImageNaplps, + ImagePng, + ImagePrsBtif, + ImagePrsPti, + ImagePwgRaster, + ImageSvgXml, + ImageT38, + ImageTiff, + ImageTiffFx, + ImageVndAdobePhotoshop, + ImageVndAirzipAcceleratorAzv, + ImageVndCnsInf2, + ImageVndDeceGraphic, + ImageVndDjvu, + ImageVndDwg, + ImageVndDxf, + ImageVndDvbSubtitle, + ImageVndFastbidsheet, + ImageVndFpx, + ImageVndFst, + ImageVndFujixeroxEdmicsMmr, + ImageVndFujixeroxEdmicsRlc, + ImageVndGlobalgraphicsPgb, + ImageVndMicrosoftIcon, + ImageVndMix, + ImageVndMsModi, + ImageVndMozillaApng, + ImageVndNetFpx, + ImageVndPcoB16, + ImageVndRadiance, + ImageVndSealedPng, + ImageVndSealedmediaSoftsealGif, + ImageVndSealedmediaSoftsealJpg, + ImageVndSvf, + ImageVndTencentTap, + ImageVndValveSourceTexture, + ImageVndWapWbmp, + ImageVndXiff, + ImageVndZbrushPcx, + ImageWebp, + ImageWmf, + MessageBhttp, + MessageCpim, + MessageDeliveryStatus, + MessageDispositionNotification, + MessageExample, + MessageFeedbackReport, + MessageGlobal, + MessageGlobalDeliveryStatus, + MessageGlobalDispositionNotification, + MessageGlobalHeaders, + MessageHttp, + MessageImdnXml, + MessageMls, + MessageOhttpReq, + MessageOhttpRes, + MessageSip, + MessageSipfrag, + MessageTrackingStatus, + MessageVndWfaWsc, + Model3mf, + ModelE57, + ModelExample, + ModelGltfBinary, + ModelGltfJson, + ModelJt, + ModelIges, + ModelMtl, + ModelObj, + ModelPrc, + ModelStep, + ModelStepXml, + ModelStepZip, + ModelStepXmlZip, + ModelStl, + ModelU3d, + ModelVndBary, + ModelVndCld, + ModelVndColladaXml, + ModelVndDwf, + ModelVndFlatland3dml, + ModelVndGdl, + ModelVndGsGdl, + ModelVndGtw, + ModelVndMomlXml, + ModelVndMts, + ModelVndOpengex, + ModelVndParasolidTransmitBinary, + ModelVndParasolidTransmitText, + ModelVndPythaPyox, + ModelVndRosetteAnnotatedDataModel, + ModelVndSapVds, + ModelVndUsda, + ModelVndUsdzZip, + ModelVndValveSourceCompiledMap, + ModelVndVtu, + ModelX3dVrml, + ModelX3dFastinfoset, + ModelX3dXml, + MultipartAppledouble, + MultipartByteranges, + MultipartEncrypted, + MultipartExample, + MultipartFormData, + MultipartHeaderSet, + MultipartMultilingual, + MultipartRelated, + MultipartReport, + MultipartSigned, + MultipartVndBintMedPlus, + MultipartVoiceMessage, + MultipartXMixedReplace, + Text1dInterleavedParityfec, + TextCacheManifest, + TextCalendar, + TextCql, + TextCqlExpression, + TextCqlIdentifier, + TextCss, + TextCsv, + TextCsvSchema, + TextDns, + TextEncaprtp, + TextExample, + TextFhirpath, + TextFlexfec, + TextFwdred, + TextGff3, + TextGrammarRefList, + TextHl7v2, + TextHtml, + TextJavascript, + TextJcrCnd, + TextMarkdown, + TextMizar, + TextN3, + TextParameters, + TextParityfec, + TextPlain, + TextProvenanceNotation, + TextPrsFallensteinRst, + TextPrsLinesTag, + TextPrsPropLogic, + TextRaptorfec, + TextRed, + TextRfc822Headers, + TextRtf, + TextRtpEncAescm128, + TextRtploopback, + TextRtx, + TextSgml, + TextShaclc, + TextShex, + TextSpdx, + TextStrings, + TextT140, + TextTabSeparatedValues, + TextTroff, + TextTurtle, + TextUlpfec, + TextUriList, + TextVcard, + TextVndA, + TextVndAbc, + TextVndAsciiArt, + TextVndCurl, + TextVndDebianCopyright, + TextVndDmclientscript, + TextVndDvbSubtitle, + TextVndEsmertecThemeDescriptor, + TextVndExchangeable, + TextVndFamilysearchGedcom, + TextVndFiclabFlt, + TextVndFly, + TextVndFmiFlexstor, + TextVndGml, + TextVndGraphviz, + TextVndHans, + TextVndHgl, + TextVndIn3d3dml, + TextVndIn3dSpot, + TextVndIptcNewsml, + TextVndIptcNitf, + TextVndLatexZ, + TextVndMotorolaReflex, + TextVndMsMediapackage, + TextVndNet2phoneCommcenterCommand, + TextVndRadisysMsmlBasicLayout, + TextVndSenxWarpscript, + TextVndSunJ2meAppDescriptor, + TextVndSosi, + TextVndTrolltechLinguist, + TextVndWapSi, + TextVndWapSl, + TextVndWapWml, + TextVndWapWmlscript, + TextVtt, + TextWgsl, + TextXml, + TextXmlExternalParsedEntity, + Video1dInterleavedParityfec, + Video3gpp, + Video3gpp2, + Video3gppTt, + VideoAv1, + VideoBmpeg, + VideoBt656, + VideoCelb, + VideoDv, + VideoEncaprtp, + VideoExample, + VideoFfv1, + VideoFlexfec, + VideoH261, + VideoH263, + VideoH2631998, + VideoH2632000, + VideoH264, + VideoH264Rcdo, + VideoH264Svc, + VideoH265, + VideoH266, + VideoIsoSegment, + VideoJpeg, + VideoJpeg2000, + VideoJxsv, + VideoMj2, + VideoMp1s, + VideoMp2p, + VideoMp2t, + VideoMp4, + VideoMp4vEs, + VideoMpv, + VideoMpeg4Generic, + VideoNv, + VideoOgg, + VideoParityfec, + VideoPointer, + VideoQuicktime, + VideoRaptorfec, + VideoRaw, + VideoRtpEncAescm128, + VideoRtploopback, + VideoRtx, + VideoScip, + VideoSmpte291, + VideoSmpte292m, + VideoUlpfec, + VideoVc1, + VideoVc2, + VideoVndCctv, + VideoVndDeceHd, + VideoVndDeceMobile, + VideoVndDeceMp4, + VideoVndDecePd, + VideoVndDeceSd, + VideoVndDeceVideo, + VideoVndDirectvMpeg, + VideoVndDirectvMpegTts, + VideoVndDlnaMpegTts, + VideoVndDvbFile, + VideoVndFvt, + VideoVndHnsVideo, + VideoVndIptvforum1dparityfec1010, + VideoVndIptvforum1dparityfec2005, + VideoVndIptvforum2dparityfec1010, + VideoVndIptvforum2dparityfec2005, + VideoVndIptvforumTtsavc, + VideoVndIptvforumTtsmpeg2, + VideoVndMotorolaVideo, + VideoVndMotorolaVideop, + VideoVndMpegurl, + VideoVndMsPlayreadyMediaPyv, + VideoVndNokiaInterleavedMultimedia, + VideoVndNokiaMp4vr, + VideoVndNokiaVideovoip, + VideoVndObjectvideo, + VideoVndRadgamettoolsBink, + VideoVndRadgamettoolsSmacker, + VideoVndSealedMpeg1, + VideoVndSealedMpeg4, + VideoVndSealedSwf, + VideoVndSealedmediaSoftsealMov, + VideoVndUvvuMp4, + VideoVndYoutubeYt, + VideoVndVivo, + VideoVp8, + VideoVp9, +} + +impl std::fmt::Display for Mime { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Mime::Application1dInterleavedParityfec => write!(f, "application/1d-interleaved-parityfec"), + Mime::Application3gpdashQoeReportXml => write!(f, "application/3gpdash-qoe-report+xml"), + Mime::Application3gpphalJson => write!(f, "application/3gppHal+json"), + Mime::Application3gpphalformsJson => write!(f, "application/3gppHalForms+json"), + Mime::Application3gppImsXml => write!(f, "application/3gpp-ims+xml"), + Mime::ApplicationA2l => write!(f, "application/A2L"), + Mime::ApplicationAceCbor => write!(f, "application/ace+cbor"), + Mime::ApplicationAceJson => write!(f, "application/ace+json"), + Mime::ApplicationActivemessage => write!(f, "application/activemessage"), + Mime::ApplicationActivityJson => write!(f, "application/activity+json"), + Mime::ApplicationAifCbor => write!(f, "application/aif+cbor"), + Mime::ApplicationAifJson => write!(f, "application/aif+json"), + Mime::ApplicationAltoCdniJson => write!(f, "application/alto-cdni+json"), + Mime::ApplicationAltoCdnifilterJson => write!(f, "application/alto-cdnifilter+json"), + Mime::ApplicationAltoCostmapJson => write!(f, "application/alto-costmap+json"), + Mime::ApplicationAltoCostmapfilterJson => write!(f, "application/alto-costmapfilter+json"), + Mime::ApplicationAltoDirectoryJson => write!(f, "application/alto-directory+json"), + Mime::ApplicationAltoEndpointpropJson => write!(f, "application/alto-endpointprop+json"), + Mime::ApplicationAltoEndpointpropparamsJson => write!(f, "application/alto-endpointpropparams+json"), + Mime::ApplicationAltoEndpointcostJson => write!(f, "application/alto-endpointcost+json"), + Mime::ApplicationAltoEndpointcostparamsJson => write!(f, "application/alto-endpointcostparams+json"), + Mime::ApplicationAltoErrorJson => write!(f, "application/alto-error+json"), + Mime::ApplicationAltoNetworkmapfilterJson => write!(f, "application/alto-networkmapfilter+json"), + Mime::ApplicationAltoNetworkmapJson => write!(f, "application/alto-networkmap+json"), + Mime::ApplicationAltoPropmapJson => write!(f, "application/alto-propmap+json"), + Mime::ApplicationAltoPropmapparamsJson => write!(f, "application/alto-propmapparams+json"), + Mime::ApplicationAltoUpdatestreamcontrolJson => write!(f, "application/alto-updatestreamcontrol+json"), + Mime::ApplicationAltoUpdatestreamparamsJson => write!(f, "application/alto-updatestreamparams+json"), + Mime::ApplicationAml => write!(f, "application/AML"), + Mime::ApplicationAndrewInset => write!(f, "application/andrew-inset"), + Mime::ApplicationApplefile => write!(f, "application/applefile"), + Mime::ApplicationAtJwt => write!(f, "application/at+jwt"), + Mime::ApplicationAtf => write!(f, "application/ATF"), + Mime::ApplicationAtfx => write!(f, "application/ATFX"), + Mime::ApplicationAtomXml => write!(f, "application/atom+xml"), + Mime::ApplicationAtomcatXml => write!(f, "application/atomcat+xml"), + Mime::ApplicationAtomdeletedXml => write!(f, "application/atomdeleted+xml"), + Mime::ApplicationAtomicmail => write!(f, "application/atomicmail"), + Mime::ApplicationAtomsvcXml => write!(f, "application/atomsvc+xml"), + Mime::ApplicationAtscDwdXml => write!(f, "application/atsc-dwd+xml"), + Mime::ApplicationAtscDynamicEventMessage => write!(f, "application/atsc-dynamic-event-message"), + Mime::ApplicationAtscHeldXml => write!(f, "application/atsc-held+xml"), + Mime::ApplicationAtscRdtJson => write!(f, "application/atsc-rdt+json"), + Mime::ApplicationAtscRsatXml => write!(f, "application/atsc-rsat+xml"), + Mime::ApplicationAtxml => write!(f, "application/ATXML"), + Mime::ApplicationAuthPolicyXml => write!(f, "application/auth-policy+xml"), + Mime::ApplicationAutomationmlAmlXml => write!(f, "application/automationml-aml+xml"), + Mime::ApplicationAutomationmlAmlxZip => write!(f, "application/automationml-amlx+zip"), + Mime::ApplicationBacnetXddZip => write!(f, "application/bacnet-xdd+zip"), + Mime::ApplicationBatchSmtp => write!(f, "application/batch-SMTP"), + Mime::ApplicationBeepXml => write!(f, "application/beep+xml"), + Mime::ApplicationCalendarJson => write!(f, "application/calendar+json"), + Mime::ApplicationCalendarXml => write!(f, "application/calendar+xml"), + Mime::ApplicationCallCompletion => write!(f, "application/call-completion"), + Mime::ApplicationCals1840 => write!(f, "application/CALS-1840"), + Mime::ApplicationCaptiveJson => write!(f, "application/captive+json"), + Mime::ApplicationCbor => write!(f, "application/cbor"), + Mime::ApplicationCborSeq => write!(f, "application/cbor-seq"), + Mime::ApplicationCccex => write!(f, "application/cccex"), + Mime::ApplicationCcmpXml => write!(f, "application/ccmp+xml"), + Mime::ApplicationCcxmlXml => write!(f, "application/ccxml+xml"), + Mime::ApplicationCdaXml => write!(f, "application/cda+xml"), + Mime::ApplicationCdfxXml => write!(f, "application/CDFX+XML"), + Mime::ApplicationCdmiCapability => write!(f, "application/cdmi-capability"), + Mime::ApplicationCdmiContainer => write!(f, "application/cdmi-container"), + Mime::ApplicationCdmiDomain => write!(f, "application/cdmi-domain"), + Mime::ApplicationCdmiObject => write!(f, "application/cdmi-object"), + Mime::ApplicationCdmiQueue => write!(f, "application/cdmi-queue"), + Mime::ApplicationCdni => write!(f, "application/cdni"), + Mime::ApplicationCea => write!(f, "application/CEA"), + Mime::ApplicationCea2018Xml => write!(f, "application/cea-2018+xml"), + Mime::ApplicationCellmlXml => write!(f, "application/cellml+xml"), + Mime::ApplicationCfw => write!(f, "application/cfw"), + Mime::ApplicationCityJson => write!(f, "application/city+json"), + Mime::ApplicationClr => write!(f, "application/clr"), + Mime::ApplicationClueInfoXml => write!(f, "application/clue_info+xml"), + Mime::ApplicationClueXml => write!(f, "application/clue+xml"), + Mime::ApplicationCms => write!(f, "application/cms"), + Mime::ApplicationCnrpXml => write!(f, "application/cnrp+xml"), + Mime::ApplicationCoapGroupJson => write!(f, "application/coap-group+json"), + Mime::ApplicationCoapPayload => write!(f, "application/coap-payload"), + Mime::ApplicationCommonground => write!(f, "application/commonground"), + Mime::ApplicationConciseProblemDetailsCbor => write!(f, "application/concise-problem-details+cbor"), + Mime::ApplicationConferenceInfoXml => write!(f, "application/conference-info+xml"), + Mime::ApplicationCplXml => write!(f, "application/cpl+xml"), + Mime::ApplicationCose => write!(f, "application/cose"), + Mime::ApplicationCoseKey => write!(f, "application/cose-key"), + Mime::ApplicationCoseKeySet => write!(f, "application/cose-key-set"), + Mime::ApplicationCoseX509 => write!(f, "application/cose-x509"), + Mime::ApplicationCsrattrs => write!(f, "application/csrattrs"), + Mime::ApplicationCstaXml => write!(f, "application/csta+xml"), + Mime::ApplicationCstadataXml => write!(f, "application/CSTAdata+xml"), + Mime::ApplicationCsvmJson => write!(f, "application/csvm+json"), + Mime::ApplicationCwl => write!(f, "application/cwl"), + Mime::ApplicationCwlJson => write!(f, "application/cwl+json"), + Mime::ApplicationCwt => write!(f, "application/cwt"), + Mime::ApplicationCybercash => write!(f, "application/cybercash"), + Mime::ApplicationDashXml => write!(f, "application/dash+xml"), + Mime::ApplicationDashPatchXml => write!(f, "application/dash-patch+xml"), + Mime::ApplicationDashdelta => write!(f, "application/dashdelta"), + Mime::ApplicationDavmountXml => write!(f, "application/davmount+xml"), + Mime::ApplicationDcaRft => write!(f, "application/dca-rft"), + Mime::ApplicationDcd => write!(f, "application/DCD"), + Mime::ApplicationDecDx => write!(f, "application/dec-dx"), + Mime::ApplicationDialogInfoXml => write!(f, "application/dialog-info+xml"), + Mime::ApplicationDicom => write!(f, "application/dicom"), + Mime::ApplicationDicomJson => write!(f, "application/dicom+json"), + Mime::ApplicationDicomXml => write!(f, "application/dicom+xml"), + Mime::ApplicationDii => write!(f, "application/DII"), + Mime::ApplicationDit => write!(f, "application/DIT"), + Mime::ApplicationDns => write!(f, "application/dns"), + Mime::ApplicationDnsJson => write!(f, "application/dns+json"), + Mime::ApplicationDnsMessage => write!(f, "application/dns-message"), + Mime::ApplicationDotsCbor => write!(f, "application/dots+cbor"), + Mime::ApplicationDpopJwt => write!(f, "application/dpop+jwt"), + Mime::ApplicationDskppXml => write!(f, "application/dskpp+xml"), + Mime::ApplicationDsscDer => write!(f, "application/dssc+der"), + Mime::ApplicationDsscXml => write!(f, "application/dssc+xml"), + Mime::ApplicationDvcs => write!(f, "application/dvcs"), + Mime::ApplicationEdiConsent => write!(f, "application/EDI-consent"), + Mime::ApplicationEdifact => write!(f, "application/EDIFACT"), + Mime::ApplicationEdiX12 => write!(f, "application/EDI-X12"), + Mime::ApplicationEfi => write!(f, "application/efi"), + Mime::ApplicationElmJson => write!(f, "application/elm+json"), + Mime::ApplicationElmXml => write!(f, "application/elm+xml"), + Mime::ApplicationEmergencycalldataCapXml => write!(f, "application/EmergencyCallData.cap+xml"), + Mime::ApplicationEmergencycalldataCommentXml => write!(f, "application/EmergencyCallData.Comment+xml"), + Mime::ApplicationEmergencycalldataControlXml => write!(f, "application/EmergencyCallData.Control+xml"), + Mime::ApplicationEmergencycalldataDeviceinfoXml => write!(f, "application/EmergencyCallData.DeviceInfo+xml"), + Mime::ApplicationEmergencycalldataEcallMsd => write!(f, "application/EmergencyCallData.eCall.MSD"), + Mime::ApplicationEmergencycalldataLegacyesnJson => write!(f, "application/EmergencyCallData.LegacyESN+json"), + Mime::ApplicationEmergencycalldataProviderinfoXml => write!(f, "application/EmergencyCallData.ProviderInfo+xml"), + Mime::ApplicationEmergencycalldataServiceinfoXml => write!(f, "application/EmergencyCallData.ServiceInfo+xml"), + Mime::ApplicationEmergencycalldataSubscriberinfoXml => write!(f, "application/EmergencyCallData.SubscriberInfo+xml"), + Mime::ApplicationEmergencycalldataVedsXml => write!(f, "application/EmergencyCallData.VEDS+xml"), + Mime::ApplicationEmmaXml => write!(f, "application/emma+xml"), + Mime::ApplicationEmotionmlXml => write!(f, "application/emotionml+xml"), + Mime::ApplicationEncaprtp => write!(f, "application/encaprtp"), + Mime::ApplicationEppXml => write!(f, "application/epp+xml"), + Mime::ApplicationEpubZip => write!(f, "application/epub+zip"), + Mime::ApplicationEshop => write!(f, "application/eshop"), + Mime::ApplicationExample => write!(f, "application/example"), + Mime::ApplicationExi => write!(f, "application/exi"), + Mime::ApplicationExpectCtReportJson => write!(f, "application/expect-ct-report+json"), + Mime::ApplicationExpress => write!(f, "application/express"), + Mime::ApplicationFastinfoset => write!(f, "application/fastinfoset"), + Mime::ApplicationFastsoap => write!(f, "application/fastsoap"), + Mime::ApplicationFdf => write!(f, "application/fdf"), + Mime::ApplicationFdtXml => write!(f, "application/fdt+xml"), + Mime::ApplicationFhirJson => write!(f, "application/fhir+json"), + Mime::ApplicationFhirXml => write!(f, "application/fhir+xml"), + Mime::ApplicationFits => write!(f, "application/fits"), + Mime::ApplicationFlexfec => write!(f, "application/flexfec"), + Mime::ApplicationFontTdpfr => write!(f, "application/font-tdpfr"), + Mime::ApplicationFrameworkAttributesXml => write!(f, "application/framework-attributes+xml"), + Mime::ApplicationGeoJson => write!(f, "application/geo+json"), + Mime::ApplicationGeoJsonSeq => write!(f, "application/geo+json-seq"), + Mime::ApplicationGeopackageSqlite3 => write!(f, "application/geopackage+sqlite3"), + Mime::ApplicationGeoxacmlXml => write!(f, "application/geoxacml+xml"), + Mime::ApplicationGltfBuffer => write!(f, "application/gltf-buffer"), + Mime::ApplicationGmlXml => write!(f, "application/gml+xml"), + Mime::ApplicationGzip => write!(f, "application/gzip"), + Mime::ApplicationH224 => write!(f, "application/H224"), + Mime::ApplicationHeldXml => write!(f, "application/held+xml"), + Mime::ApplicationHl7v2Xml => write!(f, "application/hl7v2+xml"), + Mime::ApplicationHttp => write!(f, "application/http"), + Mime::ApplicationHyperstudio => write!(f, "application/hyperstudio"), + Mime::ApplicationIbeKeyRequestXml => write!(f, "application/ibe-key-request+xml"), + Mime::ApplicationIbePkgReplyXml => write!(f, "application/ibe-pkg-reply+xml"), + Mime::ApplicationIbePpData => write!(f, "application/ibe-pp-data"), + Mime::ApplicationIges => write!(f, "application/iges"), + Mime::ApplicationImIscomposingXml => write!(f, "application/im-iscomposing+xml"), + Mime::ApplicationIndex => write!(f, "application/index"), + Mime::ApplicationIndexCmd => write!(f, "application/index.cmd"), + Mime::ApplicationIndexObj => write!(f, "application/index.obj"), + Mime::ApplicationIndexResponse => write!(f, "application/index.response"), + Mime::ApplicationIndexVnd => write!(f, "application/index.vnd"), + Mime::ApplicationInkmlXml => write!(f, "application/inkml+xml"), + Mime::ApplicationIotp => write!(f, "application/IOTP"), + Mime::ApplicationIpfix => write!(f, "application/ipfix"), + Mime::ApplicationIpp => write!(f, "application/ipp"), + Mime::ApplicationIsup => write!(f, "application/ISUP"), + Mime::ApplicationItsXml => write!(f, "application/its+xml"), + Mime::ApplicationJavaArchive => write!(f, "application/java-archive"), + Mime::ApplicationJf2feedJson => write!(f, "application/jf2feed+json"), + Mime::ApplicationJose => write!(f, "application/jose"), + Mime::ApplicationJoseJson => write!(f, "application/jose+json"), + Mime::ApplicationJrdJson => write!(f, "application/jrd+json"), + Mime::ApplicationJscalendarJson => write!(f, "application/jscalendar+json"), + Mime::ApplicationJson => write!(f, "application/json"), + Mime::ApplicationJsonPatchJson => write!(f, "application/json-patch+json"), + Mime::ApplicationJsonSeq => write!(f, "application/json-seq"), + Mime::ApplicationJwkJson => write!(f, "application/jwk+json"), + Mime::ApplicationJwkSetJson => write!(f, "application/jwk-set+json"), + Mime::ApplicationJwt => write!(f, "application/jwt"), + Mime::ApplicationKpmlRequestXml => write!(f, "application/kpml-request+xml"), + Mime::ApplicationKpmlResponseXml => write!(f, "application/kpml-response+xml"), + Mime::ApplicationLdJson => write!(f, "application/ld+json"), + Mime::ApplicationLgrXml => write!(f, "application/lgr+xml"), + Mime::ApplicationLinkFormat => write!(f, "application/link-format"), + Mime::ApplicationLinkset => write!(f, "application/linkset"), + Mime::ApplicationLinksetJson => write!(f, "application/linkset+json"), + Mime::ApplicationLoadControlXml => write!(f, "application/load-control+xml"), + Mime::ApplicationLogoutJwt => write!(f, "application/logout+jwt"), + Mime::ApplicationLostXml => write!(f, "application/lost+xml"), + Mime::ApplicationLostsyncXml => write!(f, "application/lostsync+xml"), + Mime::ApplicationLpfZip => write!(f, "application/lpf+zip"), + Mime::ApplicationLxf => write!(f, "application/LXF"), + Mime::ApplicationMacBinhex40 => write!(f, "application/mac-binhex40"), + Mime::ApplicationMacwriteii => write!(f, "application/macwriteii"), + Mime::ApplicationMadsXml => write!(f, "application/mads+xml"), + Mime::ApplicationManifestJson => write!(f, "application/manifest+json"), + Mime::ApplicationMarc => write!(f, "application/marc"), + Mime::ApplicationMarcxmlXml => write!(f, "application/marcxml+xml"), + Mime::ApplicationMathematica => write!(f, "application/mathematica"), + Mime::ApplicationMathmlXml => write!(f, "application/mathml+xml"), + Mime::ApplicationMathmlContentXml => write!(f, "application/mathml-content+xml"), + Mime::ApplicationMathmlPresentationXml => write!(f, "application/mathml-presentation+xml"), + Mime::ApplicationMbmsAssociatedProcedureDescriptionXml => write!(f, "application/mbms-associated-procedure-description+xml"), + Mime::ApplicationMbmsDeregisterXml => write!(f, "application/mbms-deregister+xml"), + Mime::ApplicationMbmsEnvelopeXml => write!(f, "application/mbms-envelope+xml"), + Mime::ApplicationMbmsMskResponseXml => write!(f, "application/mbms-msk-response+xml"), + Mime::ApplicationMbmsMskXml => write!(f, "application/mbms-msk+xml"), + Mime::ApplicationMbmsProtectionDescriptionXml => write!(f, "application/mbms-protection-description+xml"), + Mime::ApplicationMbmsReceptionReportXml => write!(f, "application/mbms-reception-report+xml"), + Mime::ApplicationMbmsRegisterResponseXml => write!(f, "application/mbms-register-response+xml"), + Mime::ApplicationMbmsRegisterXml => write!(f, "application/mbms-register+xml"), + Mime::ApplicationMbmsScheduleXml => write!(f, "application/mbms-schedule+xml"), + Mime::ApplicationMbmsUserServiceDescriptionXml => write!(f, "application/mbms-user-service-description+xml"), + Mime::ApplicationMbox => write!(f, "application/mbox"), + Mime::ApplicationMediaControlXml => write!(f, "application/media_control+xml"), + Mime::ApplicationMediaPolicyDatasetXml => write!(f, "application/media-policy-dataset+xml"), + Mime::ApplicationMediaservercontrolXml => write!(f, "application/mediaservercontrol+xml"), + Mime::ApplicationMergePatchJson => write!(f, "application/merge-patch+json"), + Mime::ApplicationMetalink4Xml => write!(f, "application/metalink4+xml"), + Mime::ApplicationMetsXml => write!(f, "application/mets+xml"), + Mime::ApplicationMf4 => write!(f, "application/MF4"), + Mime::ApplicationMikey => write!(f, "application/mikey"), + Mime::ApplicationMipc => write!(f, "application/mipc"), + Mime::ApplicationMissingBlocksCborSeq => write!(f, "application/missing-blocks+cbor-seq"), + Mime::ApplicationMmtAeiXml => write!(f, "application/mmt-aei+xml"), + Mime::ApplicationMmtUsdXml => write!(f, "application/mmt-usd+xml"), + Mime::ApplicationModsXml => write!(f, "application/mods+xml"), + Mime::ApplicationMossKeys => write!(f, "application/moss-keys"), + Mime::ApplicationMossSignature => write!(f, "application/moss-signature"), + Mime::ApplicationMosskeyData => write!(f, "application/mosskey-data"), + Mime::ApplicationMosskeyRequest => write!(f, "application/mosskey-request"), + Mime::ApplicationMp21 => write!(f, "application/mp21"), + Mime::ApplicationMp4 => write!(f, "application/mp4"), + Mime::ApplicationMpeg4Generic => write!(f, "application/mpeg4-generic"), + Mime::ApplicationMpeg4Iod => write!(f, "application/mpeg4-iod"), + Mime::ApplicationMpeg4IodXmt => write!(f, "application/mpeg4-iod-xmt"), + Mime::ApplicationMrbConsumerXml => write!(f, "application/mrb-consumer+xml"), + Mime::ApplicationMrbPublishXml => write!(f, "application/mrb-publish+xml"), + Mime::ApplicationMscIvrXml => write!(f, "application/msc-ivr+xml"), + Mime::ApplicationMscMixerXml => write!(f, "application/msc-mixer+xml"), + Mime::ApplicationMsword => write!(f, "application/msword"), + Mime::ApplicationMudJson => write!(f, "application/mud+json"), + Mime::ApplicationMultipartCore => write!(f, "application/multipart-core"), + Mime::ApplicationMxf => write!(f, "application/mxf"), + Mime::ApplicationNQuads => write!(f, "application/n-quads"), + Mime::ApplicationNTriples => write!(f, "application/n-triples"), + Mime::ApplicationNasdata => write!(f, "application/nasdata"), + Mime::ApplicationNewsCheckgroups => write!(f, "application/news-checkgroups"), + Mime::ApplicationNewsGroupinfo => write!(f, "application/news-groupinfo"), + Mime::ApplicationNewsTransmission => write!(f, "application/news-transmission"), + Mime::ApplicationNlsmlXml => write!(f, "application/nlsml+xml"), + Mime::ApplicationNode => write!(f, "application/node"), + Mime::ApplicationNss => write!(f, "application/nss"), + Mime::ApplicationOauthAuthzReqJwt => write!(f, "application/oauth-authz-req+jwt"), + Mime::ApplicationObliviousDnsMessage => write!(f, "application/oblivious-dns-message"), + Mime::ApplicationOcspRequest => write!(f, "application/ocsp-request"), + Mime::ApplicationOcspResponse => write!(f, "application/ocsp-response"), + Mime::ApplicationOctetStream => write!(f, "application/octet-stream"), + Mime::ApplicationOda => write!(f, "application/ODA"), + Mime::ApplicationOdmXml => write!(f, "application/odm+xml"), + Mime::ApplicationOdx => write!(f, "application/ODX"), + Mime::ApplicationOebpsPackageXml => write!(f, "application/oebps-package+xml"), + Mime::ApplicationOgg => write!(f, "application/ogg"), + Mime::ApplicationOhttpKeys => write!(f, "application/ohttp-keys"), + Mime::ApplicationOpcNodesetXml => write!(f, "application/opc-nodeset+xml"), + Mime::ApplicationOscore => write!(f, "application/oscore"), + Mime::ApplicationOxps => write!(f, "application/oxps"), + Mime::ApplicationP21 => write!(f, "application/p21"), + Mime::ApplicationP21Zip => write!(f, "application/p21+zip"), + Mime::ApplicationP2pOverlayXml => write!(f, "application/p2p-overlay+xml"), + Mime::ApplicationParityfec => write!(f, "application/parityfec"), + Mime::ApplicationPassport => write!(f, "application/passport"), + Mime::ApplicationPatchOpsErrorXml => write!(f, "application/patch-ops-error+xml"), + Mime::ApplicationPdf => write!(f, "application/pdf"), + Mime::ApplicationPdx => write!(f, "application/PDX"), + Mime::ApplicationPemCertificateChain => write!(f, "application/pem-certificate-chain"), + Mime::ApplicationPgpEncrypted => write!(f, "application/pgp-encrypted"), + Mime::ApplicationPgpKeys => write!(f, "application/pgp-keys"), + Mime::ApplicationPgpSignature => write!(f, "application/pgp-signature"), + Mime::ApplicationPidfDiffXml => write!(f, "application/pidf-diff+xml"), + Mime::ApplicationPidfXml => write!(f, "application/pidf+xml"), + Mime::ApplicationPkcs10 => write!(f, "application/pkcs10"), + Mime::ApplicationPkcs7Mime => write!(f, "application/pkcs7-mime"), + Mime::ApplicationPkcs7Signature => write!(f, "application/pkcs7-signature"), + Mime::ApplicationPkcs8 => write!(f, "application/pkcs8"), + Mime::ApplicationPkcs8Encrypted => write!(f, "application/pkcs8-encrypted"), + Mime::ApplicationPkcs12 => write!(f, "application/pkcs12"), + Mime::ApplicationPkixAttrCert => write!(f, "application/pkix-attr-cert"), + Mime::ApplicationPkixCert => write!(f, "application/pkix-cert"), + Mime::ApplicationPkixCrl => write!(f, "application/pkix-crl"), + Mime::ApplicationPkixPkipath => write!(f, "application/pkix-pkipath"), + Mime::ApplicationPkixcmp => write!(f, "application/pkixcmp"), + Mime::ApplicationPlsXml => write!(f, "application/pls+xml"), + Mime::ApplicationPocSettingsXml => write!(f, "application/poc-settings+xml"), + Mime::ApplicationPostscript => write!(f, "application/postscript"), + Mime::ApplicationPpspTrackerJson => write!(f, "application/ppsp-tracker+json"), + Mime::ApplicationProblemJson => write!(f, "application/problem+json"), + Mime::ApplicationProblemXml => write!(f, "application/problem+xml"), + Mime::ApplicationProvenanceXml => write!(f, "application/provenance+xml"), + Mime::ApplicationPrsAlvestrandTitraxSheet => write!(f, "application/prs.alvestrand.titrax-sheet"), + Mime::ApplicationPrsCww => write!(f, "application/prs.cww"), + Mime::ApplicationPrsCyn => write!(f, "application/prs.cyn"), + Mime::ApplicationPrsHpubZip => write!(f, "application/prs.hpub+zip"), + Mime::ApplicationPrsImpliedDocumentXml => write!(f, "application/prs.implied-document+xml"), + Mime::ApplicationPrsImpliedExecutable => write!(f, "application/prs.implied-executable"), + Mime::ApplicationPrsImpliedStructure => write!(f, "application/prs.implied-structure"), + Mime::ApplicationPrsNprend => write!(f, "application/prs.nprend"), + Mime::ApplicationPrsPlucker => write!(f, "application/prs.plucker"), + Mime::ApplicationPrsRdfXmlCrypt => write!(f, "application/prs.rdf-xml-crypt"), + Mime::ApplicationPrsXsfXml => write!(f, "application/prs.xsf+xml"), + Mime::ApplicationPskcXml => write!(f, "application/pskc+xml"), + Mime::ApplicationPvdJson => write!(f, "application/pvd+json"), + Mime::ApplicationRdfXml => write!(f, "application/rdf+xml"), + Mime::ApplicationRouteApdXml => write!(f, "application/route-apd+xml"), + Mime::ApplicationRouteSTsidXml => write!(f, "application/route-s-tsid+xml"), + Mime::ApplicationRouteUsdXml => write!(f, "application/route-usd+xml"), + Mime::ApplicationQsig => write!(f, "application/QSIG"), + Mime::ApplicationRaptorfec => write!(f, "application/raptorfec"), + Mime::ApplicationRdapJson => write!(f, "application/rdap+json"), + Mime::ApplicationReginfoXml => write!(f, "application/reginfo+xml"), + Mime::ApplicationRelaxNgCompactSyntax => write!(f, "application/relax-ng-compact-syntax"), + Mime::ApplicationReputonJson => write!(f, "application/reputon+json"), + Mime::ApplicationResourceListsDiffXml => write!(f, "application/resource-lists-diff+xml"), + Mime::ApplicationResourceListsXml => write!(f, "application/resource-lists+xml"), + Mime::ApplicationRfcXml => write!(f, "application/rfc+xml"), + Mime::ApplicationRiscos => write!(f, "application/riscos"), + Mime::ApplicationRlmiXml => write!(f, "application/rlmi+xml"), + Mime::ApplicationRlsServicesXml => write!(f, "application/rls-services+xml"), + Mime::ApplicationRpkiChecklist => write!(f, "application/rpki-checklist"), + Mime::ApplicationRpkiGhostbusters => write!(f, "application/rpki-ghostbusters"), + Mime::ApplicationRpkiManifest => write!(f, "application/rpki-manifest"), + Mime::ApplicationRpkiPublication => write!(f, "application/rpki-publication"), + Mime::ApplicationRpkiRoa => write!(f, "application/rpki-roa"), + Mime::ApplicationRpkiUpdown => write!(f, "application/rpki-updown"), + Mime::ApplicationRtf => write!(f, "application/rtf"), + Mime::ApplicationRtploopback => write!(f, "application/rtploopback"), + Mime::ApplicationRtx => write!(f, "application/rtx"), + Mime::ApplicationSamlassertionXml => write!(f, "application/samlassertion+xml"), + Mime::ApplicationSamlmetadataXml => write!(f, "application/samlmetadata+xml"), + Mime::ApplicationSarifExternalPropertiesJson => write!(f, "application/sarif-external-properties+json"), + Mime::ApplicationSarifJson => write!(f, "application/sarif+json"), + Mime::ApplicationSbe => write!(f, "application/sbe"), + Mime::ApplicationSbmlXml => write!(f, "application/sbml+xml"), + Mime::ApplicationScaipXml => write!(f, "application/scaip+xml"), + Mime::ApplicationScimJson => write!(f, "application/scim+json"), + Mime::ApplicationScvpCvRequest => write!(f, "application/scvp-cv-request"), + Mime::ApplicationScvpCvResponse => write!(f, "application/scvp-cv-response"), + Mime::ApplicationScvpVpRequest => write!(f, "application/scvp-vp-request"), + Mime::ApplicationScvpVpResponse => write!(f, "application/scvp-vp-response"), + Mime::ApplicationSdp => write!(f, "application/sdp"), + Mime::ApplicationSeceventJwt => write!(f, "application/secevent+jwt"), + Mime::ApplicationSenmlEtchCbor => write!(f, "application/senml-etch+cbor"), + Mime::ApplicationSenmlEtchJson => write!(f, "application/senml-etch+json"), + Mime::ApplicationSenmlExi => write!(f, "application/senml-exi"), + Mime::ApplicationSenmlCbor => write!(f, "application/senml+cbor"), + Mime::ApplicationSenmlJson => write!(f, "application/senml+json"), + Mime::ApplicationSenmlXml => write!(f, "application/senml+xml"), + Mime::ApplicationSensmlExi => write!(f, "application/sensml-exi"), + Mime::ApplicationSensmlCbor => write!(f, "application/sensml+cbor"), + Mime::ApplicationSensmlJson => write!(f, "application/sensml+json"), + Mime::ApplicationSensmlXml => write!(f, "application/sensml+xml"), + Mime::ApplicationSepExi => write!(f, "application/sep-exi"), + Mime::ApplicationSepXml => write!(f, "application/sep+xml"), + Mime::ApplicationSessionInfo => write!(f, "application/session-info"), + Mime::ApplicationSetPayment => write!(f, "application/set-payment"), + Mime::ApplicationSetPaymentInitiation => write!(f, "application/set-payment-initiation"), + Mime::ApplicationSetRegistration => write!(f, "application/set-registration"), + Mime::ApplicationSetRegistrationInitiation => write!(f, "application/set-registration-initiation"), + Mime::ApplicationSgml => write!(f, "application/SGML"), + Mime::ApplicationSgmlOpenCatalog => write!(f, "application/sgml-open-catalog"), + Mime::ApplicationShfXml => write!(f, "application/shf+xml"), + Mime::ApplicationSieve => write!(f, "application/sieve"), + Mime::ApplicationSimpleFilterXml => write!(f, "application/simple-filter+xml"), + Mime::ApplicationSimpleMessageSummary => write!(f, "application/simple-message-summary"), + Mime::ApplicationSimplesymbolcontainer => write!(f, "application/simpleSymbolContainer"), + Mime::ApplicationSipc => write!(f, "application/sipc"), + Mime::ApplicationSlate => write!(f, "application/slate"), + Mime::ApplicationSmilXml => write!(f, "application/smil+xml"), + Mime::ApplicationSmpte336m => write!(f, "application/smpte336m"), + Mime::ApplicationSoapFastinfoset => write!(f, "application/soap+fastinfoset"), + Mime::ApplicationSoapXml => write!(f, "application/soap+xml"), + Mime::ApplicationSparqlQuery => write!(f, "application/sparql-query"), + Mime::ApplicationSpdxJson => write!(f, "application/spdx+json"), + Mime::ApplicationSparqlResultsXml => write!(f, "application/sparql-results+xml"), + Mime::ApplicationSpiritsEventXml => write!(f, "application/spirits-event+xml"), + Mime::ApplicationSql => write!(f, "application/sql"), + Mime::ApplicationSrgs => write!(f, "application/srgs"), + Mime::ApplicationSrgsXml => write!(f, "application/srgs+xml"), + Mime::ApplicationSruXml => write!(f, "application/sru+xml"), + Mime::ApplicationSsmlXml => write!(f, "application/ssml+xml"), + Mime::ApplicationStixJson => write!(f, "application/stix+json"), + Mime::ApplicationSwidCbor => write!(f, "application/swid+cbor"), + Mime::ApplicationSwidXml => write!(f, "application/swid+xml"), + Mime::ApplicationTampApexUpdate => write!(f, "application/tamp-apex-update"), + Mime::ApplicationTampApexUpdateConfirm => write!(f, "application/tamp-apex-update-confirm"), + Mime::ApplicationTampCommunityUpdate => write!(f, "application/tamp-community-update"), + Mime::ApplicationTampCommunityUpdateConfirm => write!(f, "application/tamp-community-update-confirm"), + Mime::ApplicationTampError => write!(f, "application/tamp-error"), + Mime::ApplicationTampSequenceAdjust => write!(f, "application/tamp-sequence-adjust"), + Mime::ApplicationTampSequenceAdjustConfirm => write!(f, "application/tamp-sequence-adjust-confirm"), + Mime::ApplicationTampStatusQuery => write!(f, "application/tamp-status-query"), + Mime::ApplicationTampStatusResponse => write!(f, "application/tamp-status-response"), + Mime::ApplicationTampUpdate => write!(f, "application/tamp-update"), + Mime::ApplicationTampUpdateConfirm => write!(f, "application/tamp-update-confirm"), + Mime::ApplicationTaxiiJson => write!(f, "application/taxii+json"), + Mime::ApplicationTdJson => write!(f, "application/td+json"), + Mime::ApplicationTeiXml => write!(f, "application/tei+xml"), + Mime::ApplicationTetraIsi => write!(f, "application/TETRA_ISI"), + Mime::ApplicationThraudXml => write!(f, "application/thraud+xml"), + Mime::ApplicationTimestampQuery => write!(f, "application/timestamp-query"), + Mime::ApplicationTimestampReply => write!(f, "application/timestamp-reply"), + Mime::ApplicationTimestampedData => write!(f, "application/timestamped-data"), + Mime::ApplicationTlsrptGzip => write!(f, "application/tlsrpt+gzip"), + Mime::ApplicationTlsrptJson => write!(f, "application/tlsrpt+json"), + Mime::ApplicationTmJson => write!(f, "application/tm+json"), + Mime::ApplicationTnauthlist => write!(f, "application/tnauthlist"), + Mime::ApplicationTokenIntrospectionJwt => write!(f, "application/token-introspection+jwt"), + Mime::ApplicationTrickleIceSdpfrag => write!(f, "application/trickle-ice-sdpfrag"), + Mime::ApplicationTrig => write!(f, "application/trig"), + Mime::ApplicationTtmlXml => write!(f, "application/ttml+xml"), + Mime::ApplicationTveTrigger => write!(f, "application/tve-trigger"), + Mime::ApplicationTzif => write!(f, "application/tzif"), + Mime::ApplicationTzifLeap => write!(f, "application/tzif-leap"), + Mime::ApplicationUlpfec => write!(f, "application/ulpfec"), + Mime::ApplicationUrcGrpsheetXml => write!(f, "application/urc-grpsheet+xml"), + Mime::ApplicationUrcRessheetXml => write!(f, "application/urc-ressheet+xml"), + Mime::ApplicationUrcTargetdescXml => write!(f, "application/urc-targetdesc+xml"), + Mime::ApplicationUrcUisocketdescXml => write!(f, "application/urc-uisocketdesc+xml"), + Mime::ApplicationVcardJson => write!(f, "application/vcard+json"), + Mime::ApplicationVcardXml => write!(f, "application/vcard+xml"), + Mime::ApplicationVemmi => write!(f, "application/vemmi"), + Mime::ApplicationVnd1000mindsDecisionModelXml => write!(f, "application/vnd.1000minds.decision-model+xml"), + Mime::ApplicationVnd1ob => write!(f, "application/vnd.1ob"), + Mime::ApplicationVnd3gpp5gnas => write!(f, "application/vnd.3gpp.5gnas"), + Mime::ApplicationVnd3gppAccessTransferEventsXml => write!(f, "application/vnd.3gpp.access-transfer-events+xml"), + Mime::ApplicationVnd3gppBsfXml => write!(f, "application/vnd.3gpp.bsf+xml"), + Mime::ApplicationVnd3gppCrsXml => write!(f, "application/vnd.3gpp.crs+xml"), + Mime::ApplicationVnd3gppCurrentLocationDiscoveryXml => write!(f, "application/vnd.3gpp.current-location-discovery+xml"), + Mime::ApplicationVnd3gppGmopXml => write!(f, "application/vnd.3gpp.GMOP+xml"), + Mime::ApplicationVnd3gppGtpc => write!(f, "application/vnd.3gpp.gtpc"), + Mime::ApplicationVnd3gppInterworkingData => write!(f, "application/vnd.3gpp.interworking-data"), + Mime::ApplicationVnd3gppLpp => write!(f, "application/vnd.3gpp.lpp"), + Mime::ApplicationVnd3gppMcSignallingEar => write!(f, "application/vnd.3gpp.mc-signalling-ear"), + Mime::ApplicationVnd3gppMcdataAffiliationCommandXml => write!(f, "application/vnd.3gpp.mcdata-affiliation-command+xml"), + Mime::ApplicationVnd3gppMcdataInfoXml => write!(f, "application/vnd.3gpp.mcdata-info+xml"), + Mime::ApplicationVnd3gppMcdataMsgstoreCtrlRequestXml => write!(f, "application/vnd.3gpp.mcdata-msgstore-ctrl-request+xml"), + Mime::ApplicationVnd3gppMcdataPayload => write!(f, "application/vnd.3gpp.mcdata-payload"), + Mime::ApplicationVnd3gppMcdataRegroupXml => write!(f, "application/vnd.3gpp.mcdata-regroup+xml"), + Mime::ApplicationVnd3gppMcdataServiceConfigXml => write!(f, "application/vnd.3gpp.mcdata-service-config+xml"), + Mime::ApplicationVnd3gppMcdataSignalling => write!(f, "application/vnd.3gpp.mcdata-signalling"), + Mime::ApplicationVnd3gppMcdataUeConfigXml => write!(f, "application/vnd.3gpp.mcdata-ue-config+xml"), + Mime::ApplicationVnd3gppMcdataUserProfileXml => write!(f, "application/vnd.3gpp.mcdata-user-profile+xml"), + Mime::ApplicationVnd3gppMcpttAffiliationCommandXml => write!(f, "application/vnd.3gpp.mcptt-affiliation-command+xml"), + Mime::ApplicationVnd3gppMcpttFloorRequestXml => write!(f, "application/vnd.3gpp.mcptt-floor-request+xml"), + Mime::ApplicationVnd3gppMcpttInfoXml => write!(f, "application/vnd.3gpp.mcptt-info+xml"), + Mime::ApplicationVnd3gppMcpttLocationInfoXml => write!(f, "application/vnd.3gpp.mcptt-location-info+xml"), + Mime::ApplicationVnd3gppMcpttMbmsUsageInfoXml => write!(f, "application/vnd.3gpp.mcptt-mbms-usage-info+xml"), + Mime::ApplicationVnd3gppMcpttRegroupXml => write!(f, "application/vnd.3gpp.mcptt-regroup+xml"), + Mime::ApplicationVnd3gppMcpttServiceConfigXml => write!(f, "application/vnd.3gpp.mcptt-service-config+xml"), + Mime::ApplicationVnd3gppMcpttSignedXml => write!(f, "application/vnd.3gpp.mcptt-signed+xml"), + Mime::ApplicationVnd3gppMcpttUeConfigXml => write!(f, "application/vnd.3gpp.mcptt-ue-config+xml"), + Mime::ApplicationVnd3gppMcpttUeInitConfigXml => write!(f, "application/vnd.3gpp.mcptt-ue-init-config+xml"), + Mime::ApplicationVnd3gppMcpttUserProfileXml => write!(f, "application/vnd.3gpp.mcptt-user-profile+xml"), + Mime::ApplicationVnd3gppMcvideoAffiliationCommandXml => write!(f, "application/vnd.3gpp.mcvideo-affiliation-command+xml"), + Mime::ApplicationVnd3gppMcvideoInfoXml => write!(f, "application/vnd.3gpp.mcvideo-info+xml"), + Mime::ApplicationVnd3gppMcvideoLocationInfoXml => write!(f, "application/vnd.3gpp.mcvideo-location-info+xml"), + Mime::ApplicationVnd3gppMcvideoMbmsUsageInfoXml => write!(f, "application/vnd.3gpp.mcvideo-mbms-usage-info+xml"), + Mime::ApplicationVnd3gppMcvideoRegroupXml => write!(f, "application/vnd.3gpp.mcvideo-regroup+xml"), + Mime::ApplicationVnd3gppMcvideoServiceConfigXml => write!(f, "application/vnd.3gpp.mcvideo-service-config+xml"), + Mime::ApplicationVnd3gppMcvideoTransmissionRequestXml => write!(f, "application/vnd.3gpp.mcvideo-transmission-request+xml"), + Mime::ApplicationVnd3gppMcvideoUeConfigXml => write!(f, "application/vnd.3gpp.mcvideo-ue-config+xml"), + Mime::ApplicationVnd3gppMcvideoUserProfileXml => write!(f, "application/vnd.3gpp.mcvideo-user-profile+xml"), + Mime::ApplicationVnd3gppMidCallXml => write!(f, "application/vnd.3gpp.mid-call+xml"), + Mime::ApplicationVnd3gppNgap => write!(f, "application/vnd.3gpp.ngap"), + Mime::ApplicationVnd3gppPfcp => write!(f, "application/vnd.3gpp.pfcp"), + Mime::ApplicationVnd3gppPicBwLarge => write!(f, "application/vnd.3gpp.pic-bw-large"), + Mime::ApplicationVnd3gppPicBwSmall => write!(f, "application/vnd.3gpp.pic-bw-small"), + Mime::ApplicationVnd3gppPicBwVar => write!(f, "application/vnd.3gpp.pic-bw-var"), + Mime::ApplicationVnd3gppProsePc3aXml => write!(f, "application/vnd.3gpp-prose-pc3a+xml"), + Mime::ApplicationVnd3gppProsePc3achXml => write!(f, "application/vnd.3gpp-prose-pc3ach+xml"), + Mime::ApplicationVnd3gppProsePc3chXml => write!(f, "application/vnd.3gpp-prose-pc3ch+xml"), + Mime::ApplicationVnd3gppProsePc8Xml => write!(f, "application/vnd.3gpp-prose-pc8+xml"), + Mime::ApplicationVnd3gppProseXml => write!(f, "application/vnd.3gpp-prose+xml"), + Mime::ApplicationVnd3gppS1ap => write!(f, "application/vnd.3gpp.s1ap"), + Mime::ApplicationVnd3gppSealGroupDocXml => write!(f, "application/vnd.3gpp.seal-group-doc+xml"), + Mime::ApplicationVnd3gppSealInfoXml => write!(f, "application/vnd.3gpp.seal-info+xml"), + Mime::ApplicationVnd3gppSealLocationInfoXml => write!(f, "application/vnd.3gpp.seal-location-info+xml"), + Mime::ApplicationVnd3gppSealMbmsUsageInfoXml => write!(f, "application/vnd.3gpp.seal-mbms-usage-info+xml"), + Mime::ApplicationVnd3gppSealNetworkQosManagementInfoXml => write!(f, "application/vnd.3gpp.seal-network-QoS-management-info+xml"), + Mime::ApplicationVnd3gppSealUeConfigInfoXml => write!(f, "application/vnd.3gpp.seal-ue-config-info+xml"), + Mime::ApplicationVnd3gppSealUnicastInfoXml => write!(f, "application/vnd.3gpp.seal-unicast-info+xml"), + Mime::ApplicationVnd3gppSealUserProfileInfoXml => write!(f, "application/vnd.3gpp.seal-user-profile-info+xml"), + Mime::ApplicationVnd3gppSms => write!(f, "application/vnd.3gpp.sms"), + Mime::ApplicationVnd3gppSmsXml => write!(f, "application/vnd.3gpp.sms+xml"), + Mime::ApplicationVnd3gppSrvccExtXml => write!(f, "application/vnd.3gpp.srvcc-ext+xml"), + Mime::ApplicationVnd3gppSrvccInfoXml => write!(f, "application/vnd.3gpp.SRVCC-info+xml"), + Mime::ApplicationVnd3gppStateAndEventInfoXml => write!(f, "application/vnd.3gpp.state-and-event-info+xml"), + Mime::ApplicationVnd3gppUssdXml => write!(f, "application/vnd.3gpp.ussd+xml"), + Mime::ApplicationVnd3gppVaeInfoXml => write!(f, "application/vnd.3gpp.vae-info+xml"), + Mime::ApplicationVnd3gppV2xLocalServiceInformation => write!(f, "application/vnd.3gpp-v2x-local-service-information"), + Mime::ApplicationVnd3gpp2BcmcsinfoXml => write!(f, "application/vnd.3gpp2.bcmcsinfo+xml"), + Mime::ApplicationVnd3gpp2Sms => write!(f, "application/vnd.3gpp2.sms"), + Mime::ApplicationVnd3gpp2Tcap => write!(f, "application/vnd.3gpp2.tcap"), + Mime::ApplicationVnd3gppV2x => write!(f, "application/vnd.3gpp.v2x"), + Mime::ApplicationVnd3lightssoftwareImagescal => write!(f, "application/vnd.3lightssoftware.imagescal"), + Mime::ApplicationVnd3mPostItNotes => write!(f, "application/vnd.3M.Post-it-Notes"), + Mime::ApplicationVndAccpacSimplyAso => write!(f, "application/vnd.accpac.simply.aso"), + Mime::ApplicationVndAccpacSimplyImp => write!(f, "application/vnd.accpac.simply.imp"), + Mime::ApplicationVndAcmAddressxferJson => write!(f, "application/vnd.acm.addressxfer+json"), + Mime::ApplicationVndAcucobol => write!(f, "application/vnd.acucobol"), + Mime::ApplicationVndAcucorp => write!(f, "application/vnd.acucorp"), + Mime::ApplicationVndAdobeFlashMovie => write!(f, "application/vnd.adobe.flash.movie"), + Mime::ApplicationVndAdobeFormscentralFcdt => write!(f, "application/vnd.adobe.formscentral.fcdt"), + Mime::ApplicationVndAdobeFxp => write!(f, "application/vnd.adobe.fxp"), + Mime::ApplicationVndAdobePartialUpload => write!(f, "application/vnd.adobe.partial-upload"), + Mime::ApplicationVndAdobeXdpXml => write!(f, "application/vnd.adobe.xdp+xml"), + Mime::ApplicationVndAetherImp => write!(f, "application/vnd.aether.imp"), + Mime::ApplicationVndAfpcAfplinedata => write!(f, "application/vnd.afpc.afplinedata"), + Mime::ApplicationVndAfpcAfplinedataPagedef => write!(f, "application/vnd.afpc.afplinedata-pagedef"), + Mime::ApplicationVndAfpcCmocaCmresource => write!(f, "application/vnd.afpc.cmoca-cmresource"), + Mime::ApplicationVndAfpcFocaCharset => write!(f, "application/vnd.afpc.foca-charset"), + Mime::ApplicationVndAfpcFocaCodedfont => write!(f, "application/vnd.afpc.foca-codedfont"), + Mime::ApplicationVndAfpcFocaCodepage => write!(f, "application/vnd.afpc.foca-codepage"), + Mime::ApplicationVndAfpcModca => write!(f, "application/vnd.afpc.modca"), + Mime::ApplicationVndAfpcModcaCmtable => write!(f, "application/vnd.afpc.modca-cmtable"), + Mime::ApplicationVndAfpcModcaFormdef => write!(f, "application/vnd.afpc.modca-formdef"), + Mime::ApplicationVndAfpcModcaMediummap => write!(f, "application/vnd.afpc.modca-mediummap"), + Mime::ApplicationVndAfpcModcaObjectcontainer => write!(f, "application/vnd.afpc.modca-objectcontainer"), + Mime::ApplicationVndAfpcModcaOverlay => write!(f, "application/vnd.afpc.modca-overlay"), + Mime::ApplicationVndAfpcModcaPagesegment => write!(f, "application/vnd.afpc.modca-pagesegment"), + Mime::ApplicationVndAge => write!(f, "application/vnd.age"), + Mime::ApplicationVndAhBarcode => write!(f, "application/vnd.ah-barcode"), + Mime::ApplicationVndAheadSpace => write!(f, "application/vnd.ahead.space"), + Mime::ApplicationVndAirzipFilesecureAzf => write!(f, "application/vnd.airzip.filesecure.azf"), + Mime::ApplicationVndAirzipFilesecureAzs => write!(f, "application/vnd.airzip.filesecure.azs"), + Mime::ApplicationVndAmadeusJson => write!(f, "application/vnd.amadeus+json"), + Mime::ApplicationVndAmazonMobi8Ebook => write!(f, "application/vnd.amazon.mobi8-ebook"), + Mime::ApplicationVndAmericandynamicsAcc => write!(f, "application/vnd.americandynamics.acc"), + Mime::ApplicationVndAmigaAmi => write!(f, "application/vnd.amiga.ami"), + Mime::ApplicationVndAmundsenMazeXml => write!(f, "application/vnd.amundsen.maze+xml"), + Mime::ApplicationVndAndroidOta => write!(f, "application/vnd.android.ota"), + Mime::ApplicationVndAnki => write!(f, "application/vnd.anki"), + Mime::ApplicationVndAnserWebCertificateIssueInitiation => write!(f, "application/vnd.anser-web-certificate-issue-initiation"), + Mime::ApplicationVndAntixGameComponent => write!(f, "application/vnd.antix.game-component"), + Mime::ApplicationVndApacheArrowFile => write!(f, "application/vnd.apache.arrow.file"), + Mime::ApplicationVndApacheArrowStream => write!(f, "application/vnd.apache.arrow.stream"), + Mime::ApplicationVndApacheThriftBinary => write!(f, "application/vnd.apache.thrift.binary"), + Mime::ApplicationVndApacheThriftCompact => write!(f, "application/vnd.apache.thrift.compact"), + Mime::ApplicationVndApacheThriftJson => write!(f, "application/vnd.apache.thrift.json"), + Mime::ApplicationVndApexlang => write!(f, "application/vnd.apexlang"), + Mime::ApplicationVndApiJson => write!(f, "application/vnd.api+json"), + Mime::ApplicationVndAplextorWarrpJson => write!(f, "application/vnd.aplextor.warrp+json"), + Mime::ApplicationVndApothekendeReservationJson => write!(f, "application/vnd.apothekende.reservation+json"), + Mime::ApplicationVndAppleInstallerXml => write!(f, "application/vnd.apple.installer+xml"), + Mime::ApplicationVndAppleKeynote => write!(f, "application/vnd.apple.keynote"), + Mime::ApplicationVndAppleMpegurl => write!(f, "application/vnd.apple.mpegurl"), + Mime::ApplicationVndAppleNumbers => write!(f, "application/vnd.apple.numbers"), + Mime::ApplicationVndApplePages => write!(f, "application/vnd.apple.pages"), + Mime::ApplicationVndAristanetworksSwi => write!(f, "application/vnd.aristanetworks.swi"), + Mime::ApplicationVndArtisanJson => write!(f, "application/vnd.artisan+json"), + Mime::ApplicationVndArtsquare => write!(f, "application/vnd.artsquare"), + Mime::ApplicationVndAstraeaSoftwareIota => write!(f, "application/vnd.astraea-software.iota"), + Mime::ApplicationVndAudiograph => write!(f, "application/vnd.audiograph"), + Mime::ApplicationVndAutopackage => write!(f, "application/vnd.autopackage"), + Mime::ApplicationVndAvalonJson => write!(f, "application/vnd.avalon+json"), + Mime::ApplicationVndAvistarXml => write!(f, "application/vnd.avistar+xml"), + Mime::ApplicationVndBalsamiqBmmlXml => write!(f, "application/vnd.balsamiq.bmml+xml"), + Mime::ApplicationVndBananaAccounting => write!(f, "application/vnd.banana-accounting"), + Mime::ApplicationVndBbfUspError => write!(f, "application/vnd.bbf.usp.error"), + Mime::ApplicationVndBbfUspMsg => write!(f, "application/vnd.bbf.usp.msg"), + Mime::ApplicationVndBbfUspMsgJson => write!(f, "application/vnd.bbf.usp.msg+json"), + Mime::ApplicationVndBalsamiqBmpr => write!(f, "application/vnd.balsamiq.bmpr"), + Mime::ApplicationVndBekitzurStechJson => write!(f, "application/vnd.bekitzur-stech+json"), + Mime::ApplicationVndBelightsoftLhzdZip => write!(f, "application/vnd.belightsoft.lhzd+zip"), + Mime::ApplicationVndBelightsoftLhzlZip => write!(f, "application/vnd.belightsoft.lhzl+zip"), + Mime::ApplicationVndBintMedContent => write!(f, "application/vnd.bint.med-content"), + Mime::ApplicationVndBiopaxRdfXml => write!(f, "application/vnd.biopax.rdf+xml"), + Mime::ApplicationVndBlinkIdbValueWrapper => write!(f, "application/vnd.blink-idb-value-wrapper"), + Mime::ApplicationVndBlueiceMultipass => write!(f, "application/vnd.blueice.multipass"), + Mime::ApplicationVndBluetoothEpOob => write!(f, "application/vnd.bluetooth.ep.oob"), + Mime::ApplicationVndBluetoothLeOob => write!(f, "application/vnd.bluetooth.le.oob"), + Mime::ApplicationVndBmi => write!(f, "application/vnd.bmi"), + Mime::ApplicationVndBpf => write!(f, "application/vnd.bpf"), + Mime::ApplicationVndBpf3 => write!(f, "application/vnd.bpf3"), + Mime::ApplicationVndBusinessobjects => write!(f, "application/vnd.businessobjects"), + Mime::ApplicationVndByuUapiJson => write!(f, "application/vnd.byu.uapi+json"), + Mime::ApplicationVndCabJscript => write!(f, "application/vnd.cab-jscript"), + Mime::ApplicationVndCanonCpdl => write!(f, "application/vnd.canon-cpdl"), + Mime::ApplicationVndCanonLips => write!(f, "application/vnd.canon-lips"), + Mime::ApplicationVndCapasystemsPgJson => write!(f, "application/vnd.capasystems-pg+json"), + Mime::ApplicationVndCendioThinlincClientconf => write!(f, "application/vnd.cendio.thinlinc.clientconf"), + Mime::ApplicationVndCenturySystemsTcpStream => write!(f, "application/vnd.century-systems.tcp_stream"), + Mime::ApplicationVndChemdrawXml => write!(f, "application/vnd.chemdraw+xml"), + Mime::ApplicationVndChessPgn => write!(f, "application/vnd.chess-pgn"), + Mime::ApplicationVndChipnutsKaraokeMmd => write!(f, "application/vnd.chipnuts.karaoke-mmd"), + Mime::ApplicationVndCiedi => write!(f, "application/vnd.ciedi"), + Mime::ApplicationVndCinderella => write!(f, "application/vnd.cinderella"), + Mime::ApplicationVndCirpackIsdnExt => write!(f, "application/vnd.cirpack.isdn-ext"), + Mime::ApplicationVndCitationstylesStyleXml => write!(f, "application/vnd.citationstyles.style+xml"), + Mime::ApplicationVndClaymore => write!(f, "application/vnd.claymore"), + Mime::ApplicationVndCloantoRp9 => write!(f, "application/vnd.cloanto.rp9"), + Mime::ApplicationVndClonkC4group => write!(f, "application/vnd.clonk.c4group"), + Mime::ApplicationVndCluetrustCartomobileConfig => write!(f, "application/vnd.cluetrust.cartomobile-config"), + Mime::ApplicationVndCluetrustCartomobileConfigPkg => write!(f, "application/vnd.cluetrust.cartomobile-config-pkg"), + Mime::ApplicationVndCncfHelmChartContentV1TarGzip => write!(f, "application/vnd.cncf.helm.chart.content.v1.tar+gzip"), + Mime::ApplicationVndCncfHelmChartProvenanceV1Prov => write!(f, "application/vnd.cncf.helm.chart.provenance.v1.prov"), + Mime::ApplicationVndCncfHelmConfigV1Json => write!(f, "application/vnd.cncf.helm.config.v1+json"), + Mime::ApplicationVndCoffeescript => write!(f, "application/vnd.coffeescript"), + Mime::ApplicationVndCollabioXodocumentsDocument => write!(f, "application/vnd.collabio.xodocuments.document"), + Mime::ApplicationVndCollabioXodocumentsDocumentTemplate => write!(f, "application/vnd.collabio.xodocuments.document-template"), + Mime::ApplicationVndCollabioXodocumentsPresentation => write!(f, "application/vnd.collabio.xodocuments.presentation"), + Mime::ApplicationVndCollabioXodocumentsPresentationTemplate => write!(f, "application/vnd.collabio.xodocuments.presentation-template"), + Mime::ApplicationVndCollabioXodocumentsSpreadsheet => write!(f, "application/vnd.collabio.xodocuments.spreadsheet"), + Mime::ApplicationVndCollabioXodocumentsSpreadsheetTemplate => write!(f, "application/vnd.collabio.xodocuments.spreadsheet-template"), + Mime::ApplicationVndCollectionDocJson => write!(f, "application/vnd.collection.doc+json"), + Mime::ApplicationVndCollectionJson => write!(f, "application/vnd.collection+json"), + Mime::ApplicationVndCollectionNextJson => write!(f, "application/vnd.collection.next+json"), + Mime::ApplicationVndComicbookRar => write!(f, "application/vnd.comicbook-rar"), + Mime::ApplicationVndComicbookZip => write!(f, "application/vnd.comicbook+zip"), + Mime::ApplicationVndCommerceBattelle => write!(f, "application/vnd.commerce-battelle"), + Mime::ApplicationVndCommonspace => write!(f, "application/vnd.commonspace"), + Mime::ApplicationVndCoreosIgnitionJson => write!(f, "application/vnd.coreos.ignition+json"), + Mime::ApplicationVndCosmocaller => write!(f, "application/vnd.cosmocaller"), + Mime::ApplicationVndContactCmsg => write!(f, "application/vnd.contact.cmsg"), + Mime::ApplicationVndCrickClicker => write!(f, "application/vnd.crick.clicker"), + Mime::ApplicationVndCrickClickerKeyboard => write!(f, "application/vnd.crick.clicker.keyboard"), + Mime::ApplicationVndCrickClickerPalette => write!(f, "application/vnd.crick.clicker.palette"), + Mime::ApplicationVndCrickClickerTemplate => write!(f, "application/vnd.crick.clicker.template"), + Mime::ApplicationVndCrickClickerWordbank => write!(f, "application/vnd.crick.clicker.wordbank"), + Mime::ApplicationVndCriticaltoolsWbsXml => write!(f, "application/vnd.criticaltools.wbs+xml"), + Mime::ApplicationVndCryptiiPipeJson => write!(f, "application/vnd.cryptii.pipe+json"), + Mime::ApplicationVndCryptoShadeFile => write!(f, "application/vnd.crypto-shade-file"), + Mime::ApplicationVndCryptomatorEncrypted => write!(f, "application/vnd.cryptomator.encrypted"), + Mime::ApplicationVndCryptomatorVault => write!(f, "application/vnd.cryptomator.vault"), + Mime::ApplicationVndCtcPosml => write!(f, "application/vnd.ctc-posml"), + Mime::ApplicationVndCtctWsXml => write!(f, "application/vnd.ctct.ws+xml"), + Mime::ApplicationVndCupsPdf => write!(f, "application/vnd.cups-pdf"), + Mime::ApplicationVndCupsPostscript => write!(f, "application/vnd.cups-postscript"), + Mime::ApplicationVndCupsPpd => write!(f, "application/vnd.cups-ppd"), + Mime::ApplicationVndCupsRaster => write!(f, "application/vnd.cups-raster"), + Mime::ApplicationVndCupsRaw => write!(f, "application/vnd.cups-raw"), + Mime::ApplicationVndCurl => write!(f, "application/vnd.curl"), + Mime::ApplicationVndCyanDeanRootXml => write!(f, "application/vnd.cyan.dean.root+xml"), + Mime::ApplicationVndCybank => write!(f, "application/vnd.cybank"), + Mime::ApplicationVndCyclonedxJson => write!(f, "application/vnd.cyclonedx+json"), + Mime::ApplicationVndCyclonedxXml => write!(f, "application/vnd.cyclonedx+xml"), + Mime::ApplicationVndD2lCoursepackage1p0Zip => write!(f, "application/vnd.d2l.coursepackage1p0+zip"), + Mime::ApplicationVndD3mDataset => write!(f, "application/vnd.d3m-dataset"), + Mime::ApplicationVndD3mProblem => write!(f, "application/vnd.d3m-problem"), + Mime::ApplicationVndDart => write!(f, "application/vnd.dart"), + Mime::ApplicationVndDataVisionRdz => write!(f, "application/vnd.data-vision.rdz"), + Mime::ApplicationVndDatalog => write!(f, "application/vnd.datalog"), + Mime::ApplicationVndDatapackageJson => write!(f, "application/vnd.datapackage+json"), + Mime::ApplicationVndDataresourceJson => write!(f, "application/vnd.dataresource+json"), + Mime::ApplicationVndDbf => write!(f, "application/vnd.dbf"), + Mime::ApplicationVndDebianBinaryPackage => write!(f, "application/vnd.debian.binary-package"), + Mime::ApplicationVndDeceData => write!(f, "application/vnd.dece.data"), + Mime::ApplicationVndDeceTtmlXml => write!(f, "application/vnd.dece.ttml+xml"), + Mime::ApplicationVndDeceUnspecified => write!(f, "application/vnd.dece.unspecified"), + Mime::ApplicationVndDeceZip => write!(f, "application/vnd.dece.zip"), + Mime::ApplicationVndDenovoFcselayoutLink => write!(f, "application/vnd.denovo.fcselayout-link"), + Mime::ApplicationVndDesmumeMovie => write!(f, "application/vnd.desmume.movie"), + Mime::ApplicationVndDirBiPlateDlNosuffix => write!(f, "application/vnd.dir-bi.plate-dl-nosuffix"), + Mime::ApplicationVndDmDelegationXml => write!(f, "application/vnd.dm.delegation+xml"), + Mime::ApplicationVndDna => write!(f, "application/vnd.dna"), + Mime::ApplicationVndDocumentJson => write!(f, "application/vnd.document+json"), + Mime::ApplicationVndDolbyMobile1 => write!(f, "application/vnd.dolby.mobile.1"), + Mime::ApplicationVndDolbyMobile2 => write!(f, "application/vnd.dolby.mobile.2"), + Mime::ApplicationVndDoremirScorecloudBinaryDocument => write!(f, "application/vnd.doremir.scorecloud-binary-document"), + Mime::ApplicationVndDpgraph => write!(f, "application/vnd.dpgraph"), + Mime::ApplicationVndDreamfactory => write!(f, "application/vnd.dreamfactory"), + Mime::ApplicationVndDriveJson => write!(f, "application/vnd.drive+json"), + Mime::ApplicationVndDtgLocal => write!(f, "application/vnd.dtg.local"), + Mime::ApplicationVndDtgLocalFlash => write!(f, "application/vnd.dtg.local.flash"), + Mime::ApplicationVndDtgLocalHtml => write!(f, "application/vnd.dtg.local.html"), + Mime::ApplicationVndDvbAit => write!(f, "application/vnd.dvb.ait"), + Mime::ApplicationVndDvbDvbislXml => write!(f, "application/vnd.dvb.dvbisl+xml"), + Mime::ApplicationVndDvbDvbj => write!(f, "application/vnd.dvb.dvbj"), + Mime::ApplicationVndDvbEsgcontainer => write!(f, "application/vnd.dvb.esgcontainer"), + Mime::ApplicationVndDvbIpdcdftnotifaccess => write!(f, "application/vnd.dvb.ipdcdftnotifaccess"), + Mime::ApplicationVndDvbIpdcesgaccess => write!(f, "application/vnd.dvb.ipdcesgaccess"), + Mime::ApplicationVndDvbIpdcesgaccess2 => write!(f, "application/vnd.dvb.ipdcesgaccess2"), + Mime::ApplicationVndDvbIpdcesgpdd => write!(f, "application/vnd.dvb.ipdcesgpdd"), + Mime::ApplicationVndDvbIpdcroaming => write!(f, "application/vnd.dvb.ipdcroaming"), + Mime::ApplicationVndDvbIptvAlfecBase => write!(f, "application/vnd.dvb.iptv.alfec-base"), + Mime::ApplicationVndDvbIptvAlfecEnhancement => write!(f, "application/vnd.dvb.iptv.alfec-enhancement"), + Mime::ApplicationVndDvbNotifAggregateRootXml => write!(f, "application/vnd.dvb.notif-aggregate-root+xml"), + Mime::ApplicationVndDvbNotifContainerXml => write!(f, "application/vnd.dvb.notif-container+xml"), + Mime::ApplicationVndDvbNotifGenericXml => write!(f, "application/vnd.dvb.notif-generic+xml"), + Mime::ApplicationVndDvbNotifIaMsglistXml => write!(f, "application/vnd.dvb.notif-ia-msglist+xml"), + Mime::ApplicationVndDvbNotifIaRegistrationRequestXml => write!(f, "application/vnd.dvb.notif-ia-registration-request+xml"), + Mime::ApplicationVndDvbNotifIaRegistrationResponseXml => write!(f, "application/vnd.dvb.notif-ia-registration-response+xml"), + Mime::ApplicationVndDvbNotifInitXml => write!(f, "application/vnd.dvb.notif-init+xml"), + Mime::ApplicationVndDvbPfr => write!(f, "application/vnd.dvb.pfr"), + Mime::ApplicationVndDvbService => write!(f, "application/vnd.dvb.service"), + Mime::ApplicationVndDxr => write!(f, "application/vnd.dxr"), + Mime::ApplicationVndDynageo => write!(f, "application/vnd.dynageo"), + Mime::ApplicationVndDzr => write!(f, "application/vnd.dzr"), + Mime::ApplicationVndEasykaraokeCdgdownload => write!(f, "application/vnd.easykaraoke.cdgdownload"), + Mime::ApplicationVndEcipRlp => write!(f, "application/vnd.ecip.rlp"), + Mime::ApplicationVndEcdisUpdate => write!(f, "application/vnd.ecdis-update"), + Mime::ApplicationVndEclipseDittoJson => write!(f, "application/vnd.eclipse.ditto+json"), + Mime::ApplicationVndEcowinChart => write!(f, "application/vnd.ecowin.chart"), + Mime::ApplicationVndEcowinFilerequest => write!(f, "application/vnd.ecowin.filerequest"), + Mime::ApplicationVndEcowinFileupdate => write!(f, "application/vnd.ecowin.fileupdate"), + Mime::ApplicationVndEcowinSeries => write!(f, "application/vnd.ecowin.series"), + Mime::ApplicationVndEcowinSeriesrequest => write!(f, "application/vnd.ecowin.seriesrequest"), + Mime::ApplicationVndEcowinSeriesupdate => write!(f, "application/vnd.ecowin.seriesupdate"), + Mime::ApplicationVndEfiImg => write!(f, "application/vnd.efi.img"), + Mime::ApplicationVndEfiIso => write!(f, "application/vnd.efi.iso"), + Mime::ApplicationVndElnZip => write!(f, "application/vnd.eln+zip"), + Mime::ApplicationVndEmclientAccessrequestXml => write!(f, "application/vnd.emclient.accessrequest+xml"), + Mime::ApplicationVndEnliven => write!(f, "application/vnd.enliven"), + Mime::ApplicationVndEnphaseEnvoy => write!(f, "application/vnd.enphase.envoy"), + Mime::ApplicationVndEprintsDataXml => write!(f, "application/vnd.eprints.data+xml"), + Mime::ApplicationVndEpsonEsf => write!(f, "application/vnd.epson.esf"), + Mime::ApplicationVndEpsonMsf => write!(f, "application/vnd.epson.msf"), + Mime::ApplicationVndEpsonQuickanime => write!(f, "application/vnd.epson.quickanime"), + Mime::ApplicationVndEpsonSalt => write!(f, "application/vnd.epson.salt"), + Mime::ApplicationVndEpsonSsf => write!(f, "application/vnd.epson.ssf"), + Mime::ApplicationVndEricssonQuickcall => write!(f, "application/vnd.ericsson.quickcall"), + Mime::ApplicationVndEspassEspassZip => write!(f, "application/vnd.espass-espass+zip"), + Mime::ApplicationVndEszigno3Xml => write!(f, "application/vnd.eszigno3+xml"), + Mime::ApplicationVndEtsiAocXml => write!(f, "application/vnd.etsi.aoc+xml"), + Mime::ApplicationVndEtsiAsicSZip => write!(f, "application/vnd.etsi.asic-s+zip"), + Mime::ApplicationVndEtsiAsicEZip => write!(f, "application/vnd.etsi.asic-e+zip"), + Mime::ApplicationVndEtsiCugXml => write!(f, "application/vnd.etsi.cug+xml"), + Mime::ApplicationVndEtsiIptvcommandXml => write!(f, "application/vnd.etsi.iptvcommand+xml"), + Mime::ApplicationVndEtsiIptvdiscoveryXml => write!(f, "application/vnd.etsi.iptvdiscovery+xml"), + Mime::ApplicationVndEtsiIptvprofileXml => write!(f, "application/vnd.etsi.iptvprofile+xml"), + Mime::ApplicationVndEtsiIptvsadBcXml => write!(f, "application/vnd.etsi.iptvsad-bc+xml"), + Mime::ApplicationVndEtsiIptvsadCodXml => write!(f, "application/vnd.etsi.iptvsad-cod+xml"), + Mime::ApplicationVndEtsiIptvsadNpvrXml => write!(f, "application/vnd.etsi.iptvsad-npvr+xml"), + Mime::ApplicationVndEtsiIptvserviceXml => write!(f, "application/vnd.etsi.iptvservice+xml"), + Mime::ApplicationVndEtsiIptvsyncXml => write!(f, "application/vnd.etsi.iptvsync+xml"), + Mime::ApplicationVndEtsiIptvueprofileXml => write!(f, "application/vnd.etsi.iptvueprofile+xml"), + Mime::ApplicationVndEtsiMcidXml => write!(f, "application/vnd.etsi.mcid+xml"), + Mime::ApplicationVndEtsiMheg5 => write!(f, "application/vnd.etsi.mheg5"), + Mime::ApplicationVndEtsiOverloadControlPolicyDatasetXml => write!(f, "application/vnd.etsi.overload-control-policy-dataset+xml"), + Mime::ApplicationVndEtsiPstnXml => write!(f, "application/vnd.etsi.pstn+xml"), + Mime::ApplicationVndEtsiSciXml => write!(f, "application/vnd.etsi.sci+xml"), + Mime::ApplicationVndEtsiSimservsXml => write!(f, "application/vnd.etsi.simservs+xml"), + Mime::ApplicationVndEtsiTimestampToken => write!(f, "application/vnd.etsi.timestamp-token"), + Mime::ApplicationVndEtsiTslXml => write!(f, "application/vnd.etsi.tsl+xml"), + Mime::ApplicationVndEtsiTslDer => write!(f, "application/vnd.etsi.tsl.der"), + Mime::ApplicationVndEuKasparianCarJson => write!(f, "application/vnd.eu.kasparian.car+json"), + Mime::ApplicationVndEudoraData => write!(f, "application/vnd.eudora.data"), + Mime::ApplicationVndEvolvEcigProfile => write!(f, "application/vnd.evolv.ecig.profile"), + Mime::ApplicationVndEvolvEcigSettings => write!(f, "application/vnd.evolv.ecig.settings"), + Mime::ApplicationVndEvolvEcigTheme => write!(f, "application/vnd.evolv.ecig.theme"), + Mime::ApplicationVndExstreamEmpowerZip => write!(f, "application/vnd.exstream-empower+zip"), + Mime::ApplicationVndExstreamPackage => write!(f, "application/vnd.exstream-package"), + Mime::ApplicationVndEzpixAlbum => write!(f, "application/vnd.ezpix-album"), + Mime::ApplicationVndEzpixPackage => write!(f, "application/vnd.ezpix-package"), + Mime::ApplicationVndFSecureMobile => write!(f, "application/vnd.f-secure.mobile"), + Mime::ApplicationVndFastcopyDiskImage => write!(f, "application/vnd.fastcopy-disk-image"), + Mime::ApplicationVndFamilysearchGedcomZip => write!(f, "application/vnd.familysearch.gedcom+zip"), + Mime::ApplicationVndFdsnMseed => write!(f, "application/vnd.fdsn.mseed"), + Mime::ApplicationVndFdsnSeed => write!(f, "application/vnd.fdsn.seed"), + Mime::ApplicationVndFfsns => write!(f, "application/vnd.ffsns"), + Mime::ApplicationVndFiclabFlbZip => write!(f, "application/vnd.ficlab.flb+zip"), + Mime::ApplicationVndFilmitZfc => write!(f, "application/vnd.filmit.zfc"), + Mime::ApplicationVndFints => write!(f, "application/vnd.fints"), + Mime::ApplicationVndFiremonkeysCloudcell => write!(f, "application/vnd.firemonkeys.cloudcell"), + Mime::ApplicationVndFlographit => write!(f, "application/vnd.FloGraphIt"), + Mime::ApplicationVndFluxtimeClip => write!(f, "application/vnd.fluxtime.clip"), + Mime::ApplicationVndFontFontforgeSfd => write!(f, "application/vnd.font-fontforge-sfd"), + Mime::ApplicationVndFramemaker => write!(f, "application/vnd.framemaker"), + Mime::ApplicationVndFscWeblaunch => write!(f, "application/vnd.fsc.weblaunch"), + Mime::ApplicationVndFujifilmFbDocuworks => write!(f, "application/vnd.fujifilm.fb.docuworks"), + Mime::ApplicationVndFujifilmFbDocuworksBinder => write!(f, "application/vnd.fujifilm.fb.docuworks.binder"), + Mime::ApplicationVndFujifilmFbDocuworksContainer => write!(f, "application/vnd.fujifilm.fb.docuworks.container"), + Mime::ApplicationVndFujifilmFbJfiXml => write!(f, "application/vnd.fujifilm.fb.jfi+xml"), + Mime::ApplicationVndFujitsuOasys => write!(f, "application/vnd.fujitsu.oasys"), + Mime::ApplicationVndFujitsuOasys2 => write!(f, "application/vnd.fujitsu.oasys2"), + Mime::ApplicationVndFujitsuOasys3 => write!(f, "application/vnd.fujitsu.oasys3"), + Mime::ApplicationVndFujitsuOasysgp => write!(f, "application/vnd.fujitsu.oasysgp"), + Mime::ApplicationVndFujitsuOasysprs => write!(f, "application/vnd.fujitsu.oasysprs"), + Mime::ApplicationVndFujixeroxArt4 => write!(f, "application/vnd.fujixerox.ART4"), + Mime::ApplicationVndFujixeroxArtEx => write!(f, "application/vnd.fujixerox.ART-EX"), + Mime::ApplicationVndFujixeroxDdd => write!(f, "application/vnd.fujixerox.ddd"), + Mime::ApplicationVndFujixeroxDocuworks => write!(f, "application/vnd.fujixerox.docuworks"), + Mime::ApplicationVndFujixeroxDocuworksBinder => write!(f, "application/vnd.fujixerox.docuworks.binder"), + Mime::ApplicationVndFujixeroxDocuworksContainer => write!(f, "application/vnd.fujixerox.docuworks.container"), + Mime::ApplicationVndFujixeroxHbpl => write!(f, "application/vnd.fujixerox.HBPL"), + Mime::ApplicationVndFutMisnet => write!(f, "application/vnd.fut-misnet"), + Mime::ApplicationVndFutoinCbor => write!(f, "application/vnd.futoin+cbor"), + Mime::ApplicationVndFutoinJson => write!(f, "application/vnd.futoin+json"), + Mime::ApplicationVndFuzzysheet => write!(f, "application/vnd.fuzzysheet"), + Mime::ApplicationVndGenomatixTuxedo => write!(f, "application/vnd.genomatix.tuxedo"), + Mime::ApplicationVndGenozip => write!(f, "application/vnd.genozip"), + Mime::ApplicationVndGenticsGrdJson => write!(f, "application/vnd.gentics.grd+json"), + Mime::ApplicationVndGentooCatmetadataXml => write!(f, "application/vnd.gentoo.catmetadata+xml"), + Mime::ApplicationVndGentooEbuild => write!(f, "application/vnd.gentoo.ebuild"), + Mime::ApplicationVndGentooEclass => write!(f, "application/vnd.gentoo.eclass"), + Mime::ApplicationVndGentooGpkg => write!(f, "application/vnd.gentoo.gpkg"), + Mime::ApplicationVndGentooManifest => write!(f, "application/vnd.gentoo.manifest"), + Mime::ApplicationVndGentooXpak => write!(f, "application/vnd.gentoo.xpak"), + Mime::ApplicationVndGentooPkgmetadataXml => write!(f, "application/vnd.gentoo.pkgmetadata+xml"), + Mime::ApplicationVndGeogebraFile => write!(f, "application/vnd.geogebra.file"), + Mime::ApplicationVndGeogebraSlides => write!(f, "application/vnd.geogebra.slides"), + Mime::ApplicationVndGeogebraTool => write!(f, "application/vnd.geogebra.tool"), + Mime::ApplicationVndGeometryExplorer => write!(f, "application/vnd.geometry-explorer"), + Mime::ApplicationVndGeonext => write!(f, "application/vnd.geonext"), + Mime::ApplicationVndGeoplan => write!(f, "application/vnd.geoplan"), + Mime::ApplicationVndGeospace => write!(f, "application/vnd.geospace"), + Mime::ApplicationVndGerber => write!(f, "application/vnd.gerber"), + Mime::ApplicationVndGlobalplatformCardContentMgt => write!(f, "application/vnd.globalplatform.card-content-mgt"), + Mime::ApplicationVndGlobalplatformCardContentMgtResponse => write!(f, "application/vnd.globalplatform.card-content-mgt-response"), + Mime::ApplicationVndGnuTalerExchangeJson => write!(f, "application/vnd.gnu.taler.exchange+json"), + Mime::ApplicationVndGnuTalerMerchantJson => write!(f, "application/vnd.gnu.taler.merchant+json"), + Mime::ApplicationVndGoogleEarthKmlXml => write!(f, "application/vnd.google-earth.kml+xml"), + Mime::ApplicationVndGoogleEarthKmz => write!(f, "application/vnd.google-earth.kmz"), + Mime::ApplicationVndGovSkEFormXml => write!(f, "application/vnd.gov.sk.e-form+xml"), + Mime::ApplicationVndGovSkEFormZip => write!(f, "application/vnd.gov.sk.e-form+zip"), + Mime::ApplicationVndGovSkXmldatacontainerXml => write!(f, "application/vnd.gov.sk.xmldatacontainer+xml"), + Mime::ApplicationVndGpxseeMapXml => write!(f, "application/vnd.gpxsee.map+xml"), + Mime::ApplicationVndGrafeq => write!(f, "application/vnd.grafeq"), + Mime::ApplicationVndGridmp => write!(f, "application/vnd.gridmp"), + Mime::ApplicationVndGrooveAccount => write!(f, "application/vnd.groove-account"), + Mime::ApplicationVndGrooveHelp => write!(f, "application/vnd.groove-help"), + Mime::ApplicationVndGrooveIdentityMessage => write!(f, "application/vnd.groove-identity-message"), + Mime::ApplicationVndGrooveInjector => write!(f, "application/vnd.groove-injector"), + Mime::ApplicationVndGrooveToolMessage => write!(f, "application/vnd.groove-tool-message"), + Mime::ApplicationVndGrooveToolTemplate => write!(f, "application/vnd.groove-tool-template"), + Mime::ApplicationVndGrooveVcard => write!(f, "application/vnd.groove-vcard"), + Mime::ApplicationVndHalJson => write!(f, "application/vnd.hal+json"), + Mime::ApplicationVndHalXml => write!(f, "application/vnd.hal+xml"), + Mime::ApplicationVndHandheldEntertainmentXml => write!(f, "application/vnd.HandHeld-Entertainment+xml"), + Mime::ApplicationVndHbci => write!(f, "application/vnd.hbci"), + Mime::ApplicationVndHcJson => write!(f, "application/vnd.hc+json"), + Mime::ApplicationVndHclBireports => write!(f, "application/vnd.hcl-bireports"), + Mime::ApplicationVndHdt => write!(f, "application/vnd.hdt"), + Mime::ApplicationVndHerokuJson => write!(f, "application/vnd.heroku+json"), + Mime::ApplicationVndHheLessonPlayer => write!(f, "application/vnd.hhe.lesson-player"), + Mime::ApplicationVndHpHpgl => write!(f, "application/vnd.hp-HPGL"), + Mime::ApplicationVndHpHpid => write!(f, "application/vnd.hp-hpid"), + Mime::ApplicationVndHpHps => write!(f, "application/vnd.hp-hps"), + Mime::ApplicationVndHpJlyt => write!(f, "application/vnd.hp-jlyt"), + Mime::ApplicationVndHpPcl => write!(f, "application/vnd.hp-PCL"), + Mime::ApplicationVndHpPclxl => write!(f, "application/vnd.hp-PCLXL"), + Mime::ApplicationVndHsl => write!(f, "application/vnd.hsl"), + Mime::ApplicationVndHttphone => write!(f, "application/vnd.httphone"), + Mime::ApplicationVndHydrostatixSofData => write!(f, "application/vnd.hydrostatix.sof-data"), + Mime::ApplicationVndHyperItemJson => write!(f, "application/vnd.hyper-item+json"), + Mime::ApplicationVndHyperJson => write!(f, "application/vnd.hyper+json"), + Mime::ApplicationVndHyperdriveJson => write!(f, "application/vnd.hyperdrive+json"), + Mime::ApplicationVndHzn3dCrossword => write!(f, "application/vnd.hzn-3d-crossword"), + Mime::ApplicationVndIbmElectronicMedia => write!(f, "application/vnd.ibm.electronic-media"), + Mime::ApplicationVndIbmMinipay => write!(f, "application/vnd.ibm.MiniPay"), + Mime::ApplicationVndIbmRightsManagement => write!(f, "application/vnd.ibm.rights-management"), + Mime::ApplicationVndIbmSecureContainer => write!(f, "application/vnd.ibm.secure-container"), + Mime::ApplicationVndIccprofile => write!(f, "application/vnd.iccprofile"), + Mime::ApplicationVndIeee1905 => write!(f, "application/vnd.ieee.1905"), + Mime::ApplicationVndIgloader => write!(f, "application/vnd.igloader"), + Mime::ApplicationVndImagemeterFolderZip => write!(f, "application/vnd.imagemeter.folder+zip"), + Mime::ApplicationVndImagemeterImageZip => write!(f, "application/vnd.imagemeter.image+zip"), + Mime::ApplicationVndImmervisionIvp => write!(f, "application/vnd.immervision-ivp"), + Mime::ApplicationVndImmervisionIvu => write!(f, "application/vnd.immervision-ivu"), + Mime::ApplicationVndImsImsccv1p1 => write!(f, "application/vnd.ims.imsccv1p1"), + Mime::ApplicationVndImsImsccv1p2 => write!(f, "application/vnd.ims.imsccv1p2"), + Mime::ApplicationVndImsImsccv1p3 => write!(f, "application/vnd.ims.imsccv1p3"), + Mime::ApplicationVndImsLisV2ResultJson => write!(f, "application/vnd.ims.lis.v2.result+json"), + Mime::ApplicationVndImsLtiV2ToolconsumerprofileJson => write!(f, "application/vnd.ims.lti.v2.toolconsumerprofile+json"), + Mime::ApplicationVndImsLtiV2ToolproxyIdJson => write!(f, "application/vnd.ims.lti.v2.toolproxy.id+json"), + Mime::ApplicationVndImsLtiV2ToolproxyJson => write!(f, "application/vnd.ims.lti.v2.toolproxy+json"), + Mime::ApplicationVndImsLtiV2ToolsettingsJson => write!(f, "application/vnd.ims.lti.v2.toolsettings+json"), + Mime::ApplicationVndImsLtiV2ToolsettingsSimpleJson => write!(f, "application/vnd.ims.lti.v2.toolsettings.simple+json"), + Mime::ApplicationVndInformedcontrolRmsXml => write!(f, "application/vnd.informedcontrol.rms+xml"), + Mime::ApplicationVndInfotechProject => write!(f, "application/vnd.infotech.project"), + Mime::ApplicationVndInfotechProjectXml => write!(f, "application/vnd.infotech.project+xml"), + Mime::ApplicationVndInnopathWampNotification => write!(f, "application/vnd.innopath.wamp.notification"), + Mime::ApplicationVndInsorsIgm => write!(f, "application/vnd.insors.igm"), + Mime::ApplicationVndInterconFormnet => write!(f, "application/vnd.intercon.formnet"), + Mime::ApplicationVndIntergeo => write!(f, "application/vnd.intergeo"), + Mime::ApplicationVndIntertrustDigibox => write!(f, "application/vnd.intertrust.digibox"), + Mime::ApplicationVndIntertrustNncp => write!(f, "application/vnd.intertrust.nncp"), + Mime::ApplicationVndIntuQbo => write!(f, "application/vnd.intu.qbo"), + Mime::ApplicationVndIntuQfx => write!(f, "application/vnd.intu.qfx"), + Mime::ApplicationVndIpfsIpnsRecord => write!(f, "application/vnd.ipfs.ipns-record"), + Mime::ApplicationVndIpldCar => write!(f, "application/vnd.ipld.car"), + Mime::ApplicationVndIpldDagCbor => write!(f, "application/vnd.ipld.dag-cbor"), + Mime::ApplicationVndIpldDagJson => write!(f, "application/vnd.ipld.dag-json"), + Mime::ApplicationVndIpldRaw => write!(f, "application/vnd.ipld.raw"), + Mime::ApplicationVndIptcG2CatalogitemXml => write!(f, "application/vnd.iptc.g2.catalogitem+xml"), + Mime::ApplicationVndIptcG2ConceptitemXml => write!(f, "application/vnd.iptc.g2.conceptitem+xml"), + Mime::ApplicationVndIptcG2KnowledgeitemXml => write!(f, "application/vnd.iptc.g2.knowledgeitem+xml"), + Mime::ApplicationVndIptcG2NewsitemXml => write!(f, "application/vnd.iptc.g2.newsitem+xml"), + Mime::ApplicationVndIptcG2NewsmessageXml => write!(f, "application/vnd.iptc.g2.newsmessage+xml"), + Mime::ApplicationVndIptcG2PackageitemXml => write!(f, "application/vnd.iptc.g2.packageitem+xml"), + Mime::ApplicationVndIptcG2PlanningitemXml => write!(f, "application/vnd.iptc.g2.planningitem+xml"), + Mime::ApplicationVndIpunpluggedRcprofile => write!(f, "application/vnd.ipunplugged.rcprofile"), + Mime::ApplicationVndIrepositoryPackageXml => write!(f, "application/vnd.irepository.package+xml"), + Mime::ApplicationVndIsXpr => write!(f, "application/vnd.is-xpr"), + Mime::ApplicationVndIsacFcs => write!(f, "application/vnd.isac.fcs"), + Mime::ApplicationVndJam => write!(f, "application/vnd.jam"), + Mime::ApplicationVndIso1178310Zip => write!(f, "application/vnd.iso11783-10+zip"), + Mime::ApplicationVndJapannetDirectoryService => write!(f, "application/vnd.japannet-directory-service"), + Mime::ApplicationVndJapannetJpnstoreWakeup => write!(f, "application/vnd.japannet-jpnstore-wakeup"), + Mime::ApplicationVndJapannetPaymentWakeup => write!(f, "application/vnd.japannet-payment-wakeup"), + Mime::ApplicationVndJapannetRegistration => write!(f, "application/vnd.japannet-registration"), + Mime::ApplicationVndJapannetRegistrationWakeup => write!(f, "application/vnd.japannet-registration-wakeup"), + Mime::ApplicationVndJapannetSetstoreWakeup => write!(f, "application/vnd.japannet-setstore-wakeup"), + Mime::ApplicationVndJapannetVerification => write!(f, "application/vnd.japannet-verification"), + Mime::ApplicationVndJapannetVerificationWakeup => write!(f, "application/vnd.japannet-verification-wakeup"), + Mime::ApplicationVndJcpJavameMidletRms => write!(f, "application/vnd.jcp.javame.midlet-rms"), + Mime::ApplicationVndJisp => write!(f, "application/vnd.jisp"), + Mime::ApplicationVndJoostJodaArchive => write!(f, "application/vnd.joost.joda-archive"), + Mime::ApplicationVndJskIsdnNgn => write!(f, "application/vnd.jsk.isdn-ngn"), + Mime::ApplicationVndKahootz => write!(f, "application/vnd.kahootz"), + Mime::ApplicationVndKdeKarbon => write!(f, "application/vnd.kde.karbon"), + Mime::ApplicationVndKdeKchart => write!(f, "application/vnd.kde.kchart"), + Mime::ApplicationVndKdeKformula => write!(f, "application/vnd.kde.kformula"), + Mime::ApplicationVndKdeKivio => write!(f, "application/vnd.kde.kivio"), + Mime::ApplicationVndKdeKontour => write!(f, "application/vnd.kde.kontour"), + Mime::ApplicationVndKdeKpresenter => write!(f, "application/vnd.kde.kpresenter"), + Mime::ApplicationVndKdeKspread => write!(f, "application/vnd.kde.kspread"), + Mime::ApplicationVndKdeKword => write!(f, "application/vnd.kde.kword"), + Mime::ApplicationVndKenameaapp => write!(f, "application/vnd.kenameaapp"), + Mime::ApplicationVndKidspiration => write!(f, "application/vnd.kidspiration"), + Mime::ApplicationVndKinar => write!(f, "application/vnd.Kinar"), + Mime::ApplicationVndKoan => write!(f, "application/vnd.koan"), + Mime::ApplicationVndKodakDescriptor => write!(f, "application/vnd.kodak-descriptor"), + Mime::ApplicationVndLas => write!(f, "application/vnd.las"), + Mime::ApplicationVndLasLasJson => write!(f, "application/vnd.las.las+json"), + Mime::ApplicationVndLasLasXml => write!(f, "application/vnd.las.las+xml"), + Mime::ApplicationVndLaszip => write!(f, "application/vnd.laszip"), + Mime::ApplicationVndLeapJson => write!(f, "application/vnd.leap+json"), + Mime::ApplicationVndLibertyRequestXml => write!(f, "application/vnd.liberty-request+xml"), + Mime::ApplicationVndLlamagraphicsLifeBalanceDesktop => write!(f, "application/vnd.llamagraphics.life-balance.desktop"), + Mime::ApplicationVndLlamagraphicsLifeBalanceExchangeXml => write!(f, "application/vnd.llamagraphics.life-balance.exchange+xml"), + Mime::ApplicationVndLogipipeCircuitZip => write!(f, "application/vnd.logipipe.circuit+zip"), + Mime::ApplicationVndLoom => write!(f, "application/vnd.loom"), + Mime::ApplicationVndLotus123 => write!(f, "application/vnd.lotus-1-2-3"), + Mime::ApplicationVndLotusApproach => write!(f, "application/vnd.lotus-approach"), + Mime::ApplicationVndLotusFreelance => write!(f, "application/vnd.lotus-freelance"), + Mime::ApplicationVndLotusNotes => write!(f, "application/vnd.lotus-notes"), + Mime::ApplicationVndLotusOrganizer => write!(f, "application/vnd.lotus-organizer"), + Mime::ApplicationVndLotusScreencam => write!(f, "application/vnd.lotus-screencam"), + Mime::ApplicationVndLotusWordpro => write!(f, "application/vnd.lotus-wordpro"), + Mime::ApplicationVndMacportsPortpkg => write!(f, "application/vnd.macports.portpkg"), + Mime::ApplicationVndMapboxVectorTile => write!(f, "application/vnd.mapbox-vector-tile"), + Mime::ApplicationVndMarlinDrmActiontokenXml => write!(f, "application/vnd.marlin.drm.actiontoken+xml"), + Mime::ApplicationVndMarlinDrmConftokenXml => write!(f, "application/vnd.marlin.drm.conftoken+xml"), + Mime::ApplicationVndMarlinDrmLicenseXml => write!(f, "application/vnd.marlin.drm.license+xml"), + Mime::ApplicationVndMarlinDrmMdcf => write!(f, "application/vnd.marlin.drm.mdcf"), + Mime::ApplicationVndMasonJson => write!(f, "application/vnd.mason+json"), + Mime::ApplicationVndMaxarArchive3tzZip => write!(f, "application/vnd.maxar.archive.3tz+zip"), + Mime::ApplicationVndMaxmindMaxmindDb => write!(f, "application/vnd.maxmind.maxmind-db"), + Mime::ApplicationVndMcd => write!(f, "application/vnd.mcd"), + Mime::ApplicationVndMdl => write!(f, "application/vnd.mdl"), + Mime::ApplicationVndMdlMbsdf => write!(f, "application/vnd.mdl-mbsdf"), + Mime::ApplicationVndMedcalcdata => write!(f, "application/vnd.medcalcdata"), + Mime::ApplicationVndMediastationCdkey => write!(f, "application/vnd.mediastation.cdkey"), + Mime::ApplicationVndMedicalholodeckRecordxr => write!(f, "application/vnd.medicalholodeck.recordxr"), + Mime::ApplicationVndMeridianSlingshot => write!(f, "application/vnd.meridian-slingshot"), + Mime::ApplicationVndMfer => write!(f, "application/vnd.MFER"), + Mime::ApplicationVndMfmp => write!(f, "application/vnd.mfmp"), + Mime::ApplicationVndMicroJson => write!(f, "application/vnd.micro+json"), + Mime::ApplicationVndMicrografxFlo => write!(f, "application/vnd.micrografx.flo"), + Mime::ApplicationVndMicrografxIgx => write!(f, "application/vnd.micrografx.igx"), + Mime::ApplicationVndMicrosoftPortableExecutable => write!(f, "application/vnd.microsoft.portable-executable"), + Mime::ApplicationVndMicrosoftWindowsThumbnailCache => write!(f, "application/vnd.microsoft.windows.thumbnail-cache"), + Mime::ApplicationVndMieleJson => write!(f, "application/vnd.miele+json"), + Mime::ApplicationVndMif => write!(f, "application/vnd.mif"), + Mime::ApplicationVndMinisoftHp3000Save => write!(f, "application/vnd.minisoft-hp3000-save"), + Mime::ApplicationVndMitsubishiMistyGuardTrustweb => write!(f, "application/vnd.mitsubishi.misty-guard.trustweb"), + Mime::ApplicationVndMobiusDaf => write!(f, "application/vnd.Mobius.DAF"), + Mime::ApplicationVndMobiusDis => write!(f, "application/vnd.Mobius.DIS"), + Mime::ApplicationVndMobiusMbk => write!(f, "application/vnd.Mobius.MBK"), + Mime::ApplicationVndMobiusMqy => write!(f, "application/vnd.Mobius.MQY"), + Mime::ApplicationVndMobiusMsl => write!(f, "application/vnd.Mobius.MSL"), + Mime::ApplicationVndMobiusPlc => write!(f, "application/vnd.Mobius.PLC"), + Mime::ApplicationVndMobiusTxf => write!(f, "application/vnd.Mobius.TXF"), + Mime::ApplicationVndModl => write!(f, "application/vnd.modl"), + Mime::ApplicationVndMophunApplication => write!(f, "application/vnd.mophun.application"), + Mime::ApplicationVndMophunCertificate => write!(f, "application/vnd.mophun.certificate"), + Mime::ApplicationVndMotorolaFlexsuite => write!(f, "application/vnd.motorola.flexsuite"), + Mime::ApplicationVndMotorolaFlexsuiteAdsi => write!(f, "application/vnd.motorola.flexsuite.adsi"), + Mime::ApplicationVndMotorolaFlexsuiteFis => write!(f, "application/vnd.motorola.flexsuite.fis"), + Mime::ApplicationVndMotorolaFlexsuiteGotap => write!(f, "application/vnd.motorola.flexsuite.gotap"), + Mime::ApplicationVndMotorolaFlexsuiteKmr => write!(f, "application/vnd.motorola.flexsuite.kmr"), + Mime::ApplicationVndMotorolaFlexsuiteTtc => write!(f, "application/vnd.motorola.flexsuite.ttc"), + Mime::ApplicationVndMotorolaFlexsuiteWem => write!(f, "application/vnd.motorola.flexsuite.wem"), + Mime::ApplicationVndMotorolaIprm => write!(f, "application/vnd.motorola.iprm"), + Mime::ApplicationVndMozillaXulXml => write!(f, "application/vnd.mozilla.xul+xml"), + Mime::ApplicationVndMsArtgalry => write!(f, "application/vnd.ms-artgalry"), + Mime::ApplicationVndMsAsf => write!(f, "application/vnd.ms-asf"), + Mime::ApplicationVndMsCabCompressed => write!(f, "application/vnd.ms-cab-compressed"), + Mime::ApplicationVndMs3mfdocument => write!(f, "application/vnd.ms-3mfdocument"), + Mime::ApplicationVndMsExcel => write!(f, "application/vnd.ms-excel"), + Mime::ApplicationVndMsExcelAddinMacroenabled12 => write!(f, "application/vnd.ms-excel.addin.macroEnabled.12"), + Mime::ApplicationVndMsExcelSheetBinaryMacroenabled12 => write!(f, "application/vnd.ms-excel.sheet.binary.macroEnabled.12"), + Mime::ApplicationVndMsExcelSheetMacroenabled12 => write!(f, "application/vnd.ms-excel.sheet.macroEnabled.12"), + Mime::ApplicationVndMsExcelTemplateMacroenabled12 => write!(f, "application/vnd.ms-excel.template.macroEnabled.12"), + Mime::ApplicationVndMsFontobject => write!(f, "application/vnd.ms-fontobject"), + Mime::ApplicationVndMsHtmlhelp => write!(f, "application/vnd.ms-htmlhelp"), + Mime::ApplicationVndMsIms => write!(f, "application/vnd.ms-ims"), + Mime::ApplicationVndMsLrm => write!(f, "application/vnd.ms-lrm"), + Mime::ApplicationVndMsOfficeActivexXml => write!(f, "application/vnd.ms-office.activeX+xml"), + Mime::ApplicationVndMsOfficetheme => write!(f, "application/vnd.ms-officetheme"), + Mime::ApplicationVndMsPlayreadyInitiatorXml => write!(f, "application/vnd.ms-playready.initiator+xml"), + Mime::ApplicationVndMsPowerpoint => write!(f, "application/vnd.ms-powerpoint"), + Mime::ApplicationVndMsPowerpointAddinMacroenabled12 => write!(f, "application/vnd.ms-powerpoint.addin.macroEnabled.12"), + Mime::ApplicationVndMsPowerpointPresentationMacroenabled12 => write!(f, "application/vnd.ms-powerpoint.presentation.macroEnabled.12"), + Mime::ApplicationVndMsPowerpointSlideMacroenabled12 => write!(f, "application/vnd.ms-powerpoint.slide.macroEnabled.12"), + Mime::ApplicationVndMsPowerpointSlideshowMacroenabled12 => write!(f, "application/vnd.ms-powerpoint.slideshow.macroEnabled.12"), + Mime::ApplicationVndMsPowerpointTemplateMacroenabled12 => write!(f, "application/vnd.ms-powerpoint.template.macroEnabled.12"), + Mime::ApplicationVndMsPrintdevicecapabilitiesXml => write!(f, "application/vnd.ms-PrintDeviceCapabilities+xml"), + Mime::ApplicationVndMsPrintschematicketXml => write!(f, "application/vnd.ms-PrintSchemaTicket+xml"), + Mime::ApplicationVndMsProject => write!(f, "application/vnd.ms-project"), + Mime::ApplicationVndMsTnef => write!(f, "application/vnd.ms-tnef"), + Mime::ApplicationVndMsWindowsDevicepairing => write!(f, "application/vnd.ms-windows.devicepairing"), + Mime::ApplicationVndMsWindowsNwprintingOob => write!(f, "application/vnd.ms-windows.nwprinting.oob"), + Mime::ApplicationVndMsWindowsPrinterpairing => write!(f, "application/vnd.ms-windows.printerpairing"), + Mime::ApplicationVndMsWindowsWsdOob => write!(f, "application/vnd.ms-windows.wsd.oob"), + Mime::ApplicationVndMsWmdrmLicChlgReq => write!(f, "application/vnd.ms-wmdrm.lic-chlg-req"), + Mime::ApplicationVndMsWmdrmLicResp => write!(f, "application/vnd.ms-wmdrm.lic-resp"), + Mime::ApplicationVndMsWmdrmMeterChlgReq => write!(f, "application/vnd.ms-wmdrm.meter-chlg-req"), + Mime::ApplicationVndMsWmdrmMeterResp => write!(f, "application/vnd.ms-wmdrm.meter-resp"), + Mime::ApplicationVndMsWordDocumentMacroenabled12 => write!(f, "application/vnd.ms-word.document.macroEnabled.12"), + Mime::ApplicationVndMsWordTemplateMacroenabled12 => write!(f, "application/vnd.ms-word.template.macroEnabled.12"), + Mime::ApplicationVndMsWorks => write!(f, "application/vnd.ms-works"), + Mime::ApplicationVndMsWpl => write!(f, "application/vnd.ms-wpl"), + Mime::ApplicationVndMsXpsdocument => write!(f, "application/vnd.ms-xpsdocument"), + Mime::ApplicationVndMsaDiskImage => write!(f, "application/vnd.msa-disk-image"), + Mime::ApplicationVndMseq => write!(f, "application/vnd.mseq"), + Mime::ApplicationVndMsign => write!(f, "application/vnd.msign"), + Mime::ApplicationVndMultiadCreator => write!(f, "application/vnd.multiad.creator"), + Mime::ApplicationVndMultiadCreatorCif => write!(f, "application/vnd.multiad.creator.cif"), + Mime::ApplicationVndMusician => write!(f, "application/vnd.musician"), + Mime::ApplicationVndMusicNiff => write!(f, "application/vnd.music-niff"), + Mime::ApplicationVndMuveeStyle => write!(f, "application/vnd.muvee.style"), + Mime::ApplicationVndMynfc => write!(f, "application/vnd.mynfc"), + Mime::ApplicationVndNacamarYbridJson => write!(f, "application/vnd.nacamar.ybrid+json"), + Mime::ApplicationVndNcdControl => write!(f, "application/vnd.ncd.control"), + Mime::ApplicationVndNcdReference => write!(f, "application/vnd.ncd.reference"), + Mime::ApplicationVndNearstInvJson => write!(f, "application/vnd.nearst.inv+json"), + Mime::ApplicationVndNebumindLine => write!(f, "application/vnd.nebumind.line"), + Mime::ApplicationVndNervana => write!(f, "application/vnd.nervana"), + Mime::ApplicationVndNetfpx => write!(f, "application/vnd.netfpx"), + Mime::ApplicationVndNeurolanguageNlu => write!(f, "application/vnd.neurolanguage.nlu"), + Mime::ApplicationVndNimn => write!(f, "application/vnd.nimn"), + Mime::ApplicationVndNintendoSnesRom => write!(f, "application/vnd.nintendo.snes.rom"), + Mime::ApplicationVndNintendoNitroRom => write!(f, "application/vnd.nintendo.nitro.rom"), + Mime::ApplicationVndNitf => write!(f, "application/vnd.nitf"), + Mime::ApplicationVndNoblenetDirectory => write!(f, "application/vnd.noblenet-directory"), + Mime::ApplicationVndNoblenetSealer => write!(f, "application/vnd.noblenet-sealer"), + Mime::ApplicationVndNoblenetWeb => write!(f, "application/vnd.noblenet-web"), + Mime::ApplicationVndNokiaCatalogs => write!(f, "application/vnd.nokia.catalogs"), + Mime::ApplicationVndNokiaConmlWbxml => write!(f, "application/vnd.nokia.conml+wbxml"), + Mime::ApplicationVndNokiaConmlXml => write!(f, "application/vnd.nokia.conml+xml"), + Mime::ApplicationVndNokiaIptvConfigXml => write!(f, "application/vnd.nokia.iptv.config+xml"), + Mime::ApplicationVndNokiaIsdsRadioPresets => write!(f, "application/vnd.nokia.iSDS-radio-presets"), + Mime::ApplicationVndNokiaLandmarkWbxml => write!(f, "application/vnd.nokia.landmark+wbxml"), + Mime::ApplicationVndNokiaLandmarkXml => write!(f, "application/vnd.nokia.landmark+xml"), + Mime::ApplicationVndNokiaLandmarkcollectionXml => write!(f, "application/vnd.nokia.landmarkcollection+xml"), + Mime::ApplicationVndNokiaNcd => write!(f, "application/vnd.nokia.ncd"), + Mime::ApplicationVndNokiaNGageAcXml => write!(f, "application/vnd.nokia.n-gage.ac+xml"), + Mime::ApplicationVndNokiaNGageData => write!(f, "application/vnd.nokia.n-gage.data"), + Mime::ApplicationVndNokiaPcdWbxml => write!(f, "application/vnd.nokia.pcd+wbxml"), + Mime::ApplicationVndNokiaPcdXml => write!(f, "application/vnd.nokia.pcd+xml"), + Mime::ApplicationVndNokiaRadioPreset => write!(f, "application/vnd.nokia.radio-preset"), + Mime::ApplicationVndNokiaRadioPresets => write!(f, "application/vnd.nokia.radio-presets"), + Mime::ApplicationVndNovadigmEdm => write!(f, "application/vnd.novadigm.EDM"), + Mime::ApplicationVndNovadigmEdx => write!(f, "application/vnd.novadigm.EDX"), + Mime::ApplicationVndNovadigmExt => write!(f, "application/vnd.novadigm.EXT"), + Mime::ApplicationVndNttLocalContentShare => write!(f, "application/vnd.ntt-local.content-share"), + Mime::ApplicationVndNttLocalFileTransfer => write!(f, "application/vnd.ntt-local.file-transfer"), + Mime::ApplicationVndNttLocalOgwRemoteAccess => write!(f, "application/vnd.ntt-local.ogw_remote-access"), + Mime::ApplicationVndNttLocalSipTaRemote => write!(f, "application/vnd.ntt-local.sip-ta_remote"), + Mime::ApplicationVndNttLocalSipTaTcpStream => write!(f, "application/vnd.ntt-local.sip-ta_tcp_stream"), + Mime::ApplicationVndOasisOpendocumentBase => write!(f, "application/vnd.oasis.opendocument.base"), + Mime::ApplicationVndOasisOpendocumentChart => write!(f, "application/vnd.oasis.opendocument.chart"), + Mime::ApplicationVndOasisOpendocumentChartTemplate => write!(f, "application/vnd.oasis.opendocument.chart-template"), + Mime::ApplicationVndOasisOpendocumentFormula => write!(f, "application/vnd.oasis.opendocument.formula"), + Mime::ApplicationVndOasisOpendocumentFormulaTemplate => write!(f, "application/vnd.oasis.opendocument.formula-template"), + Mime::ApplicationVndOasisOpendocumentGraphics => write!(f, "application/vnd.oasis.opendocument.graphics"), + Mime::ApplicationVndOasisOpendocumentGraphicsTemplate => write!(f, "application/vnd.oasis.opendocument.graphics-template"), + Mime::ApplicationVndOasisOpendocumentImage => write!(f, "application/vnd.oasis.opendocument.image"), + Mime::ApplicationVndOasisOpendocumentImageTemplate => write!(f, "application/vnd.oasis.opendocument.image-template"), + Mime::ApplicationVndOasisOpendocumentPresentation => write!(f, "application/vnd.oasis.opendocument.presentation"), + Mime::ApplicationVndOasisOpendocumentPresentationTemplate => write!(f, "application/vnd.oasis.opendocument.presentation-template"), + Mime::ApplicationVndOasisOpendocumentSpreadsheet => write!(f, "application/vnd.oasis.opendocument.spreadsheet"), + Mime::ApplicationVndOasisOpendocumentSpreadsheetTemplate => write!(f, "application/vnd.oasis.opendocument.spreadsheet-template"), + Mime::ApplicationVndOasisOpendocumentText => write!(f, "application/vnd.oasis.opendocument.text"), + Mime::ApplicationVndOasisOpendocumentTextMaster => write!(f, "application/vnd.oasis.opendocument.text-master"), + Mime::ApplicationVndOasisOpendocumentTextMasterTemplate => write!(f, "application/vnd.oasis.opendocument.text-master-template"), + Mime::ApplicationVndOasisOpendocumentTextTemplate => write!(f, "application/vnd.oasis.opendocument.text-template"), + Mime::ApplicationVndOasisOpendocumentTextWeb => write!(f, "application/vnd.oasis.opendocument.text-web"), + Mime::ApplicationVndObn => write!(f, "application/vnd.obn"), + Mime::ApplicationVndOcfCbor => write!(f, "application/vnd.ocf+cbor"), + Mime::ApplicationVndOciImageManifestV1Json => write!(f, "application/vnd.oci.image.manifest.v1+json"), + Mime::ApplicationVndOftnL10nJson => write!(f, "application/vnd.oftn.l10n+json"), + Mime::ApplicationVndOipfContentaccessdownloadXml => write!(f, "application/vnd.oipf.contentaccessdownload+xml"), + Mime::ApplicationVndOipfContentaccessstreamingXml => write!(f, "application/vnd.oipf.contentaccessstreaming+xml"), + Mime::ApplicationVndOipfCspgHexbinary => write!(f, "application/vnd.oipf.cspg-hexbinary"), + Mime::ApplicationVndOipfDaeSvgXml => write!(f, "application/vnd.oipf.dae.svg+xml"), + Mime::ApplicationVndOipfDaeXhtmlXml => write!(f, "application/vnd.oipf.dae.xhtml+xml"), + Mime::ApplicationVndOipfMippvcontrolmessageXml => write!(f, "application/vnd.oipf.mippvcontrolmessage+xml"), + Mime::ApplicationVndOipfPaeGem => write!(f, "application/vnd.oipf.pae.gem"), + Mime::ApplicationVndOipfSpdiscoveryXml => write!(f, "application/vnd.oipf.spdiscovery+xml"), + Mime::ApplicationVndOipfSpdlistXml => write!(f, "application/vnd.oipf.spdlist+xml"), + Mime::ApplicationVndOipfUeprofileXml => write!(f, "application/vnd.oipf.ueprofile+xml"), + Mime::ApplicationVndOipfUserprofileXml => write!(f, "application/vnd.oipf.userprofile+xml"), + Mime::ApplicationVndOlpcSugar => write!(f, "application/vnd.olpc-sugar"), + Mime::ApplicationVndOmaBcastAssociatedProcedureParameterXml => write!(f, "application/vnd.oma.bcast.associated-procedure-parameter+xml"), + Mime::ApplicationVndOmaBcastDrmTriggerXml => write!(f, "application/vnd.oma.bcast.drm-trigger+xml"), + Mime::ApplicationVndOmaBcastImdXml => write!(f, "application/vnd.oma.bcast.imd+xml"), + Mime::ApplicationVndOmaBcastLtkm => write!(f, "application/vnd.oma.bcast.ltkm"), + Mime::ApplicationVndOmaBcastNotificationXml => write!(f, "application/vnd.oma.bcast.notification+xml"), + Mime::ApplicationVndOmaBcastProvisioningtrigger => write!(f, "application/vnd.oma.bcast.provisioningtrigger"), + Mime::ApplicationVndOmaBcastSgboot => write!(f, "application/vnd.oma.bcast.sgboot"), + Mime::ApplicationVndOmaBcastSgddXml => write!(f, "application/vnd.oma.bcast.sgdd+xml"), + Mime::ApplicationVndOmaBcastSgdu => write!(f, "application/vnd.oma.bcast.sgdu"), + Mime::ApplicationVndOmaBcastSimpleSymbolContainer => write!(f, "application/vnd.oma.bcast.simple-symbol-container"), + Mime::ApplicationVndOmaBcastSmartcardTriggerXml => write!(f, "application/vnd.oma.bcast.smartcard-trigger+xml"), + Mime::ApplicationVndOmaBcastSprovXml => write!(f, "application/vnd.oma.bcast.sprov+xml"), + Mime::ApplicationVndOmaBcastStkm => write!(f, "application/vnd.oma.bcast.stkm"), + Mime::ApplicationVndOmaCabAddressBookXml => write!(f, "application/vnd.oma.cab-address-book+xml"), + Mime::ApplicationVndOmaCabFeatureHandlerXml => write!(f, "application/vnd.oma.cab-feature-handler+xml"), + Mime::ApplicationVndOmaCabPccXml => write!(f, "application/vnd.oma.cab-pcc+xml"), + Mime::ApplicationVndOmaCabSubsInviteXml => write!(f, "application/vnd.oma.cab-subs-invite+xml"), + Mime::ApplicationVndOmaCabUserPrefsXml => write!(f, "application/vnd.oma.cab-user-prefs+xml"), + Mime::ApplicationVndOmaDcd => write!(f, "application/vnd.oma.dcd"), + Mime::ApplicationVndOmaDcdc => write!(f, "application/vnd.oma.dcdc"), + Mime::ApplicationVndOmaDd2Xml => write!(f, "application/vnd.oma.dd2+xml"), + Mime::ApplicationVndOmaDrmRisdXml => write!(f, "application/vnd.oma.drm.risd+xml"), + Mime::ApplicationVndOmaGroupUsageListXml => write!(f, "application/vnd.oma.group-usage-list+xml"), + Mime::ApplicationVndOmaLwm2mCbor => write!(f, "application/vnd.oma.lwm2m+cbor"), + Mime::ApplicationVndOmaLwm2mJson => write!(f, "application/vnd.oma.lwm2m+json"), + Mime::ApplicationVndOmaLwm2mTlv => write!(f, "application/vnd.oma.lwm2m+tlv"), + Mime::ApplicationVndOmaPalXml => write!(f, "application/vnd.oma.pal+xml"), + Mime::ApplicationVndOmaPocDetailedProgressReportXml => write!(f, "application/vnd.oma.poc.detailed-progress-report+xml"), + Mime::ApplicationVndOmaPocFinalReportXml => write!(f, "application/vnd.oma.poc.final-report+xml"), + Mime::ApplicationVndOmaPocGroupsXml => write!(f, "application/vnd.oma.poc.groups+xml"), + Mime::ApplicationVndOmaPocInvocationDescriptorXml => write!(f, "application/vnd.oma.poc.invocation-descriptor+xml"), + Mime::ApplicationVndOmaPocOptimizedProgressReportXml => write!(f, "application/vnd.oma.poc.optimized-progress-report+xml"), + Mime::ApplicationVndOmaPush => write!(f, "application/vnd.oma.push"), + Mime::ApplicationVndOmaScidmMessagesXml => write!(f, "application/vnd.oma.scidm.messages+xml"), + Mime::ApplicationVndOmaXcapDirectoryXml => write!(f, "application/vnd.oma.xcap-directory+xml"), + Mime::ApplicationVndOmadsEmailXml => write!(f, "application/vnd.omads-email+xml"), + Mime::ApplicationVndOmadsFileXml => write!(f, "application/vnd.omads-file+xml"), + Mime::ApplicationVndOmadsFolderXml => write!(f, "application/vnd.omads-folder+xml"), + Mime::ApplicationVndOmalocSuplInit => write!(f, "application/vnd.omaloc-supl-init"), + Mime::ApplicationVndOmaScwsConfig => write!(f, "application/vnd.oma-scws-config"), + Mime::ApplicationVndOmaScwsHttpRequest => write!(f, "application/vnd.oma-scws-http-request"), + Mime::ApplicationVndOmaScwsHttpResponse => write!(f, "application/vnd.oma-scws-http-response"), + Mime::ApplicationVndOnepager => write!(f, "application/vnd.onepager"), + Mime::ApplicationVndOnepagertamp => write!(f, "application/vnd.onepagertamp"), + Mime::ApplicationVndOnepagertamx => write!(f, "application/vnd.onepagertamx"), + Mime::ApplicationVndOnepagertat => write!(f, "application/vnd.onepagertat"), + Mime::ApplicationVndOnepagertatp => write!(f, "application/vnd.onepagertatp"), + Mime::ApplicationVndOnepagertatx => write!(f, "application/vnd.onepagertatx"), + Mime::ApplicationVndOnvifMetadata => write!(f, "application/vnd.onvif.metadata"), + Mime::ApplicationVndOpenbloxGameBinary => write!(f, "application/vnd.openblox.game-binary"), + Mime::ApplicationVndOpenbloxGameXml => write!(f, "application/vnd.openblox.game+xml"), + Mime::ApplicationVndOpeneyeOeb => write!(f, "application/vnd.openeye.oeb"), + Mime::ApplicationVndOpenstreetmapDataXml => write!(f, "application/vnd.openstreetmap.data+xml"), + Mime::ApplicationVndOpentimestampsOts => write!(f, "application/vnd.opentimestamps.ots"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentCustomPropertiesXml => write!(f, "application/vnd.openxmlformats-officedocument.custom-properties+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentCustomxmlpropertiesXml => write!(f, "application/vnd.openxmlformats-officedocument.customXmlProperties+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingXml => write!(f, "application/vnd.openxmlformats-officedocument.drawing+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlChartshapesXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramcolorsXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramdataXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramlayoutXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentDrawingmlDiagramstyleXml => write!(f, "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentExtendedPropertiesXml => write!(f, "application/vnd.openxmlformats-officedocument.extended-properties+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentauthorsXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlCommentsXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.comments+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlHandoutmasterXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesmasterXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlNotesslideXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentation => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPresentationMainXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlPrespropsXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlide => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slide"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidelayoutXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlidemasterXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshow => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideshowMainXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlSlideupdateinfoXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTablestylesXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTagsXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.tags+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplate => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.template"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlTemplateMainXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentPresentationmlViewpropsXml => write!(f, "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCalcchainXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlChartsheetXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlCommentsXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlConnectionsXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlDialogsheetXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlExternallinkXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcachedefinitionXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivotcacherecordsXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlPivottableXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlQuerytableXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionheadersXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlRevisionlogXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSharedstringsXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheet => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetMainXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlSheetmetadataXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlStylesXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTableXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTablesinglecellsXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplate => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.template"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlTemplateMainXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlUsernamesXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlVolatiledependenciesXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentSpreadsheetmlWorksheetXml => write!(f, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentThemeXml => write!(f, "application/vnd.openxmlformats-officedocument.theme+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentThemeoverrideXml => write!(f, "application/vnd.openxmlformats-officedocument.themeOverride+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentVmldrawing => write!(f, "application/vnd.openxmlformats-officedocument.vmlDrawing"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlCommentsXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocument => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentGlossaryXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlDocumentMainXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlEndnotesXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFonttableXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFooterXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlFootnotesXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlNumberingXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlSettingsXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlStylesXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplate => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlTemplateMainXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml"), + Mime::ApplicationVndOpenxmlformatsOfficedocumentWordprocessingmlWebsettingsXml => write!(f, "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"), + Mime::ApplicationVndOpenxmlformatsPackageCorePropertiesXml => write!(f, "application/vnd.openxmlformats-package.core-properties+xml"), + Mime::ApplicationVndOpenxmlformatsPackageDigitalSignatureXmlsignatureXml => write!(f, "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"), + Mime::ApplicationVndOpenxmlformatsPackageRelationshipsXml => write!(f, "application/vnd.openxmlformats-package.relationships+xml"), + Mime::ApplicationVndOracleResourceJson => write!(f, "application/vnd.oracle.resource+json"), + Mime::ApplicationVndOrangeIndata => write!(f, "application/vnd.orange.indata"), + Mime::ApplicationVndOsaNetdeploy => write!(f, "application/vnd.osa.netdeploy"), + Mime::ApplicationVndOsgeoMapguidePackage => write!(f, "application/vnd.osgeo.mapguide.package"), + Mime::ApplicationVndOsgiBundle => write!(f, "application/vnd.osgi.bundle"), + Mime::ApplicationVndOsgiDp => write!(f, "application/vnd.osgi.dp"), + Mime::ApplicationVndOsgiSubsystem => write!(f, "application/vnd.osgi.subsystem"), + Mime::ApplicationVndOtpsCtKipXml => write!(f, "application/vnd.otps.ct-kip+xml"), + Mime::ApplicationVndOxliCountgraph => write!(f, "application/vnd.oxli.countgraph"), + Mime::ApplicationVndPagerdutyJson => write!(f, "application/vnd.pagerduty+json"), + Mime::ApplicationVndPalm => write!(f, "application/vnd.palm"), + Mime::ApplicationVndPanoply => write!(f, "application/vnd.panoply"), + Mime::ApplicationVndPaosXml => write!(f, "application/vnd.paos.xml"), + Mime::ApplicationVndPatentdive => write!(f, "application/vnd.patentdive"), + Mime::ApplicationVndPatientecommsdoc => write!(f, "application/vnd.patientecommsdoc"), + Mime::ApplicationVndPawaafile => write!(f, "application/vnd.pawaafile"), + Mime::ApplicationVndPcos => write!(f, "application/vnd.pcos"), + Mime::ApplicationVndPgFormat => write!(f, "application/vnd.pg.format"), + Mime::ApplicationVndPgOsasli => write!(f, "application/vnd.pg.osasli"), + Mime::ApplicationVndPiaccessApplicationLicence => write!(f, "application/vnd.piaccess.application-licence"), + Mime::ApplicationVndPicsel => write!(f, "application/vnd.picsel"), + Mime::ApplicationVndPmiWidget => write!(f, "application/vnd.pmi.widget"), + Mime::ApplicationVndPocGroupAdvertisementXml => write!(f, "application/vnd.poc.group-advertisement+xml"), + Mime::ApplicationVndPocketlearn => write!(f, "application/vnd.pocketlearn"), + Mime::ApplicationVndPowerbuilder6 => write!(f, "application/vnd.powerbuilder6"), + Mime::ApplicationVndPowerbuilder6S => write!(f, "application/vnd.powerbuilder6-s"), + Mime::ApplicationVndPowerbuilder7 => write!(f, "application/vnd.powerbuilder7"), + Mime::ApplicationVndPowerbuilder75 => write!(f, "application/vnd.powerbuilder75"), + Mime::ApplicationVndPowerbuilder75S => write!(f, "application/vnd.powerbuilder75-s"), + Mime::ApplicationVndPowerbuilder7S => write!(f, "application/vnd.powerbuilder7-s"), + Mime::ApplicationVndPreminet => write!(f, "application/vnd.preminet"), + Mime::ApplicationVndPreviewsystemsBox => write!(f, "application/vnd.previewsystems.box"), + Mime::ApplicationVndProteusMagazine => write!(f, "application/vnd.proteus.magazine"), + Mime::ApplicationVndPsfs => write!(f, "application/vnd.psfs"), + Mime::ApplicationVndPtMundusmundi => write!(f, "application/vnd.pt.mundusmundi"), + Mime::ApplicationVndPublishareDeltaTree => write!(f, "application/vnd.publishare-delta-tree"), + Mime::ApplicationVndPviPtid1 => write!(f, "application/vnd.pvi.ptid1"), + Mime::ApplicationVndPwgMultiplexed => write!(f, "application/vnd.pwg-multiplexed"), + Mime::ApplicationVndPwgXhtmlPrintXml => write!(f, "application/vnd.pwg-xhtml-print+xml"), + Mime::ApplicationVndQualcommBrewAppRes => write!(f, "application/vnd.qualcomm.brew-app-res"), + Mime::ApplicationVndQuarantainenet => write!(f, "application/vnd.quarantainenet"), + Mime::ApplicationVndQuarkQuarkxpress => write!(f, "application/vnd.Quark.QuarkXPress"), + Mime::ApplicationVndQuobjectQuoxdocument => write!(f, "application/vnd.quobject-quoxdocument"), + Mime::ApplicationVndRadisysMomlXml => write!(f, "application/vnd.radisys.moml+xml"), + Mime::ApplicationVndRadisysMsmlAuditConfXml => write!(f, "application/vnd.radisys.msml-audit-conf+xml"), + Mime::ApplicationVndRadisysMsmlAuditConnXml => write!(f, "application/vnd.radisys.msml-audit-conn+xml"), + Mime::ApplicationVndRadisysMsmlAuditDialogXml => write!(f, "application/vnd.radisys.msml-audit-dialog+xml"), + Mime::ApplicationVndRadisysMsmlAuditStreamXml => write!(f, "application/vnd.radisys.msml-audit-stream+xml"), + Mime::ApplicationVndRadisysMsmlAuditXml => write!(f, "application/vnd.radisys.msml-audit+xml"), + Mime::ApplicationVndRadisysMsmlConfXml => write!(f, "application/vnd.radisys.msml-conf+xml"), + Mime::ApplicationVndRadisysMsmlDialogBaseXml => write!(f, "application/vnd.radisys.msml-dialog-base+xml"), + Mime::ApplicationVndRadisysMsmlDialogFaxDetectXml => write!(f, "application/vnd.radisys.msml-dialog-fax-detect+xml"), + Mime::ApplicationVndRadisysMsmlDialogFaxSendrecvXml => write!(f, "application/vnd.radisys.msml-dialog-fax-sendrecv+xml"), + Mime::ApplicationVndRadisysMsmlDialogGroupXml => write!(f, "application/vnd.radisys.msml-dialog-group+xml"), + Mime::ApplicationVndRadisysMsmlDialogSpeechXml => write!(f, "application/vnd.radisys.msml-dialog-speech+xml"), + Mime::ApplicationVndRadisysMsmlDialogTransformXml => write!(f, "application/vnd.radisys.msml-dialog-transform+xml"), + Mime::ApplicationVndRadisysMsmlDialogXml => write!(f, "application/vnd.radisys.msml-dialog+xml"), + Mime::ApplicationVndRadisysMsmlXml => write!(f, "application/vnd.radisys.msml+xml"), + Mime::ApplicationVndRainstorData => write!(f, "application/vnd.rainstor.data"), + Mime::ApplicationVndRapid => write!(f, "application/vnd.rapid"), + Mime::ApplicationVndRar => write!(f, "application/vnd.rar"), + Mime::ApplicationVndRealvncBed => write!(f, "application/vnd.realvnc.bed"), + Mime::ApplicationVndRecordareMusicxml => write!(f, "application/vnd.recordare.musicxml"), + Mime::ApplicationVndRecordareMusicxmlXml => write!(f, "application/vnd.recordare.musicxml+xml"), + Mime::ApplicationVndRenlearnRlprint => write!(f, "application/vnd.RenLearn.rlprint"), + Mime::ApplicationVndResilientLogic => write!(f, "application/vnd.resilient.logic"), + Mime::ApplicationVndRestfulJson => write!(f, "application/vnd.restful+json"), + Mime::ApplicationVndRigCryptonote => write!(f, "application/vnd.rig.cryptonote"), + Mime::ApplicationVndRoute66Link66Xml => write!(f, "application/vnd.route66.link66+xml"), + Mime::ApplicationVndRs274x => write!(f, "application/vnd.rs-274x"), + Mime::ApplicationVndRuckusDownload => write!(f, "application/vnd.ruckus.download"), + Mime::ApplicationVndS3sms => write!(f, "application/vnd.s3sms"), + Mime::ApplicationVndSailingtrackerTrack => write!(f, "application/vnd.sailingtracker.track"), + Mime::ApplicationVndSar => write!(f, "application/vnd.sar"), + Mime::ApplicationVndSbmCid => write!(f, "application/vnd.sbm.cid"), + Mime::ApplicationVndSbmMid2 => write!(f, "application/vnd.sbm.mid2"), + Mime::ApplicationVndScribus => write!(f, "application/vnd.scribus"), + Mime::ApplicationVndSealed3df => write!(f, "application/vnd.sealed.3df"), + Mime::ApplicationVndSealedCsf => write!(f, "application/vnd.sealed.csf"), + Mime::ApplicationVndSealedDoc => write!(f, "application/vnd.sealed.doc"), + Mime::ApplicationVndSealedEml => write!(f, "application/vnd.sealed.eml"), + Mime::ApplicationVndSealedMht => write!(f, "application/vnd.sealed.mht"), + Mime::ApplicationVndSealedNet => write!(f, "application/vnd.sealed.net"), + Mime::ApplicationVndSealedPpt => write!(f, "application/vnd.sealed.ppt"), + Mime::ApplicationVndSealedTiff => write!(f, "application/vnd.sealed.tiff"), + Mime::ApplicationVndSealedXls => write!(f, "application/vnd.sealed.xls"), + Mime::ApplicationVndSealedmediaSoftsealHtml => write!(f, "application/vnd.sealedmedia.softseal.html"), + Mime::ApplicationVndSealedmediaSoftsealPdf => write!(f, "application/vnd.sealedmedia.softseal.pdf"), + Mime::ApplicationVndSeemail => write!(f, "application/vnd.seemail"), + Mime::ApplicationVndSeisJson => write!(f, "application/vnd.seis+json"), + Mime::ApplicationVndSema => write!(f, "application/vnd.sema"), + Mime::ApplicationVndSemd => write!(f, "application/vnd.semd"), + Mime::ApplicationVndSemf => write!(f, "application/vnd.semf"), + Mime::ApplicationVndShadeSaveFile => write!(f, "application/vnd.shade-save-file"), + Mime::ApplicationVndShanaInformedFormdata => write!(f, "application/vnd.shana.informed.formdata"), + Mime::ApplicationVndShanaInformedFormtemplate => write!(f, "application/vnd.shana.informed.formtemplate"), + Mime::ApplicationVndShanaInformedInterchange => write!(f, "application/vnd.shana.informed.interchange"), + Mime::ApplicationVndShanaInformedPackage => write!(f, "application/vnd.shana.informed.package"), + Mime::ApplicationVndShootproofJson => write!(f, "application/vnd.shootproof+json"), + Mime::ApplicationVndShopkickJson => write!(f, "application/vnd.shopkick+json"), + Mime::ApplicationVndShp => write!(f, "application/vnd.shp"), + Mime::ApplicationVndShx => write!(f, "application/vnd.shx"), + Mime::ApplicationVndSigrokSession => write!(f, "application/vnd.sigrok.session"), + Mime::ApplicationVndSimtechMindmapper => write!(f, "application/vnd.SimTech-MindMapper"), + Mime::ApplicationVndSirenJson => write!(f, "application/vnd.siren+json"), + Mime::ApplicationVndSmaf => write!(f, "application/vnd.smaf"), + Mime::ApplicationVndSmartNotebook => write!(f, "application/vnd.smart.notebook"), + Mime::ApplicationVndSmartTeacher => write!(f, "application/vnd.smart.teacher"), + Mime::ApplicationVndSmintioPortalsArchive => write!(f, "application/vnd.smintio.portals.archive"), + Mime::ApplicationVndSnesdevPageTable => write!(f, "application/vnd.snesdev-page-table"), + Mime::ApplicationVndSoftware602FillerFormXml => write!(f, "application/vnd.software602.filler.form+xml"), + Mime::ApplicationVndSoftware602FillerFormXmlZip => write!(f, "application/vnd.software602.filler.form-xml-zip"), + Mime::ApplicationVndSolentSdkmXml => write!(f, "application/vnd.solent.sdkm+xml"), + Mime::ApplicationVndSpotfireDxp => write!(f, "application/vnd.spotfire.dxp"), + Mime::ApplicationVndSpotfireSfs => write!(f, "application/vnd.spotfire.sfs"), + Mime::ApplicationVndSqlite3 => write!(f, "application/vnd.sqlite3"), + Mime::ApplicationVndSssCod => write!(f, "application/vnd.sss-cod"), + Mime::ApplicationVndSssDtf => write!(f, "application/vnd.sss-dtf"), + Mime::ApplicationVndSssNtf => write!(f, "application/vnd.sss-ntf"), + Mime::ApplicationVndStepmaniaPackage => write!(f, "application/vnd.stepmania.package"), + Mime::ApplicationVndStepmaniaStepchart => write!(f, "application/vnd.stepmania.stepchart"), + Mime::ApplicationVndStreetStream => write!(f, "application/vnd.street-stream"), + Mime::ApplicationVndSunWadlXml => write!(f, "application/vnd.sun.wadl+xml"), + Mime::ApplicationVndSusCalendar => write!(f, "application/vnd.sus-calendar"), + Mime::ApplicationVndSvd => write!(f, "application/vnd.svd"), + Mime::ApplicationVndSwiftviewIcs => write!(f, "application/vnd.swiftview-ics"), + Mime::ApplicationVndSybylMol2 => write!(f, "application/vnd.sybyl.mol2"), + Mime::ApplicationVndSycleXml => write!(f, "application/vnd.sycle+xml"), + Mime::ApplicationVndSyftJson => write!(f, "application/vnd.syft+json"), + Mime::ApplicationVndSyncmlDmNotification => write!(f, "application/vnd.syncml.dm.notification"), + Mime::ApplicationVndSyncmlDmddfXml => write!(f, "application/vnd.syncml.dmddf+xml"), + Mime::ApplicationVndSyncmlDmtndsWbxml => write!(f, "application/vnd.syncml.dmtnds+wbxml"), + Mime::ApplicationVndSyncmlDmtndsXml => write!(f, "application/vnd.syncml.dmtnds+xml"), + Mime::ApplicationVndSyncmlDmddfWbxml => write!(f, "application/vnd.syncml.dmddf+wbxml"), + Mime::ApplicationVndSyncmlDmWbxml => write!(f, "application/vnd.syncml.dm+wbxml"), + Mime::ApplicationVndSyncmlDmXml => write!(f, "application/vnd.syncml.dm+xml"), + Mime::ApplicationVndSyncmlDsNotification => write!(f, "application/vnd.syncml.ds.notification"), + Mime::ApplicationVndSyncmlXml => write!(f, "application/vnd.syncml+xml"), + Mime::ApplicationVndTableschemaJson => write!(f, "application/vnd.tableschema+json"), + Mime::ApplicationVndTaoIntentModuleArchive => write!(f, "application/vnd.tao.intent-module-archive"), + Mime::ApplicationVndTcpdumpPcap => write!(f, "application/vnd.tcpdump.pcap"), + Mime::ApplicationVndThinkCellPpttcJson => write!(f, "application/vnd.think-cell.ppttc+json"), + Mime::ApplicationVndTml => write!(f, "application/vnd.tml"), + Mime::ApplicationVndTmdMediaflexApiXml => write!(f, "application/vnd.tmd.mediaflex.api+xml"), + Mime::ApplicationVndTmobileLivetv => write!(f, "application/vnd.tmobile-livetv"), + Mime::ApplicationVndTriOnesource => write!(f, "application/vnd.tri.onesource"), + Mime::ApplicationVndTridTpt => write!(f, "application/vnd.trid.tpt"), + Mime::ApplicationVndTriscapeMxs => write!(f, "application/vnd.triscape.mxs"), + Mime::ApplicationVndTrueapp => write!(f, "application/vnd.trueapp"), + Mime::ApplicationVndTruedoc => write!(f, "application/vnd.truedoc"), + Mime::ApplicationVndUbisoftWebplayer => write!(f, "application/vnd.ubisoft.webplayer"), + Mime::ApplicationVndUfdl => write!(f, "application/vnd.ufdl"), + Mime::ApplicationVndUiqTheme => write!(f, "application/vnd.uiq.theme"), + Mime::ApplicationVndUmajin => write!(f, "application/vnd.umajin"), + Mime::ApplicationVndUnity => write!(f, "application/vnd.unity"), + Mime::ApplicationVndUomlXml => write!(f, "application/vnd.uoml+xml"), + Mime::ApplicationVndUplanetAlert => write!(f, "application/vnd.uplanet.alert"), + Mime::ApplicationVndUplanetAlertWbxml => write!(f, "application/vnd.uplanet.alert-wbxml"), + Mime::ApplicationVndUplanetBearerChoice => write!(f, "application/vnd.uplanet.bearer-choice"), + Mime::ApplicationVndUplanetBearerChoiceWbxml => write!(f, "application/vnd.uplanet.bearer-choice-wbxml"), + Mime::ApplicationVndUplanetCacheop => write!(f, "application/vnd.uplanet.cacheop"), + Mime::ApplicationVndUplanetCacheopWbxml => write!(f, "application/vnd.uplanet.cacheop-wbxml"), + Mime::ApplicationVndUplanetChannel => write!(f, "application/vnd.uplanet.channel"), + Mime::ApplicationVndUplanetChannelWbxml => write!(f, "application/vnd.uplanet.channel-wbxml"), + Mime::ApplicationVndUplanetList => write!(f, "application/vnd.uplanet.list"), + Mime::ApplicationVndUplanetListcmd => write!(f, "application/vnd.uplanet.listcmd"), + Mime::ApplicationVndUplanetListcmdWbxml => write!(f, "application/vnd.uplanet.listcmd-wbxml"), + Mime::ApplicationVndUplanetListWbxml => write!(f, "application/vnd.uplanet.list-wbxml"), + Mime::ApplicationVndUriMap => write!(f, "application/vnd.uri-map"), + Mime::ApplicationVndUplanetSignal => write!(f, "application/vnd.uplanet.signal"), + Mime::ApplicationVndValveSourceMaterial => write!(f, "application/vnd.valve.source.material"), + Mime::ApplicationVndVcx => write!(f, "application/vnd.vcx"), + Mime::ApplicationVndVdStudy => write!(f, "application/vnd.vd-study"), + Mime::ApplicationVndVectorworks => write!(f, "application/vnd.vectorworks"), + Mime::ApplicationVndVelJson => write!(f, "application/vnd.vel+json"), + Mime::ApplicationVndVerimatrixVcas => write!(f, "application/vnd.verimatrix.vcas"), + Mime::ApplicationVndVeritoneAionJson => write!(f, "application/vnd.veritone.aion+json"), + Mime::ApplicationVndVeryantThin => write!(f, "application/vnd.veryant.thin"), + Mime::ApplicationVndVesEncrypted => write!(f, "application/vnd.ves.encrypted"), + Mime::ApplicationVndVidsoftVidconference => write!(f, "application/vnd.vidsoft.vidconference"), + Mime::ApplicationVndVisio => write!(f, "application/vnd.visio"), + Mime::ApplicationVndVisionary => write!(f, "application/vnd.visionary"), + Mime::ApplicationVndVividenceScriptfile => write!(f, "application/vnd.vividence.scriptfile"), + Mime::ApplicationVndVsf => write!(f, "application/vnd.vsf"), + Mime::ApplicationVndWapSic => write!(f, "application/vnd.wap.sic"), + Mime::ApplicationVndWapSlc => write!(f, "application/vnd.wap.slc"), + Mime::ApplicationVndWapWbxml => write!(f, "application/vnd.wap.wbxml"), + Mime::ApplicationVndWapWmlc => write!(f, "application/vnd.wap.wmlc"), + Mime::ApplicationVndWapWmlscriptc => write!(f, "application/vnd.wap.wmlscriptc"), + Mime::ApplicationVndWasmflowWafl => write!(f, "application/vnd.wasmflow.wafl"), + Mime::ApplicationVndWebturbo => write!(f, "application/vnd.webturbo"), + Mime::ApplicationVndWfaDpp => write!(f, "application/vnd.wfa.dpp"), + Mime::ApplicationVndWfaP2p => write!(f, "application/vnd.wfa.p2p"), + Mime::ApplicationVndWfaWsc => write!(f, "application/vnd.wfa.wsc"), + Mime::ApplicationVndWindowsDevicepairing => write!(f, "application/vnd.windows.devicepairing"), + Mime::ApplicationVndWmc => write!(f, "application/vnd.wmc"), + Mime::ApplicationVndWmfBootstrap => write!(f, "application/vnd.wmf.bootstrap"), + Mime::ApplicationVndWolframMathematica => write!(f, "application/vnd.wolfram.mathematica"), + Mime::ApplicationVndWolframMathematicaPackage => write!(f, "application/vnd.wolfram.mathematica.package"), + Mime::ApplicationVndWolframPlayer => write!(f, "application/vnd.wolfram.player"), + Mime::ApplicationVndWordlift => write!(f, "application/vnd.wordlift"), + Mime::ApplicationVndWordperfect => write!(f, "application/vnd.wordperfect"), + Mime::ApplicationVndWqd => write!(f, "application/vnd.wqd"), + Mime::ApplicationVndWrqHp3000Labelled => write!(f, "application/vnd.wrq-hp3000-labelled"), + Mime::ApplicationVndWtStf => write!(f, "application/vnd.wt.stf"), + Mime::ApplicationVndWvCspXml => write!(f, "application/vnd.wv.csp+xml"), + Mime::ApplicationVndWvCspWbxml => write!(f, "application/vnd.wv.csp+wbxml"), + Mime::ApplicationVndWvSspXml => write!(f, "application/vnd.wv.ssp+xml"), + Mime::ApplicationVndXacmlJson => write!(f, "application/vnd.xacml+json"), + Mime::ApplicationVndXara => write!(f, "application/vnd.xara"), + Mime::ApplicationVndXfdl => write!(f, "application/vnd.xfdl"), + Mime::ApplicationVndXfdlWebform => write!(f, "application/vnd.xfdl.webform"), + Mime::ApplicationVndXmiXml => write!(f, "application/vnd.xmi+xml"), + Mime::ApplicationVndXmpieCpkg => write!(f, "application/vnd.xmpie.cpkg"), + Mime::ApplicationVndXmpieDpkg => write!(f, "application/vnd.xmpie.dpkg"), + Mime::ApplicationVndXmpiePlan => write!(f, "application/vnd.xmpie.plan"), + Mime::ApplicationVndXmpiePpkg => write!(f, "application/vnd.xmpie.ppkg"), + Mime::ApplicationVndXmpieXlim => write!(f, "application/vnd.xmpie.xlim"), + Mime::ApplicationVndYamahaHvDic => write!(f, "application/vnd.yamaha.hv-dic"), + Mime::ApplicationVndYamahaHvScript => write!(f, "application/vnd.yamaha.hv-script"), + Mime::ApplicationVndYamahaHvVoice => write!(f, "application/vnd.yamaha.hv-voice"), + Mime::ApplicationVndYamahaOpenscoreformatOsfpvgXml => write!(f, "application/vnd.yamaha.openscoreformat.osfpvg+xml"), + Mime::ApplicationVndYamahaOpenscoreformat => write!(f, "application/vnd.yamaha.openscoreformat"), + Mime::ApplicationVndYamahaRemoteSetup => write!(f, "application/vnd.yamaha.remote-setup"), + Mime::ApplicationVndYamahaSmafAudio => write!(f, "application/vnd.yamaha.smaf-audio"), + Mime::ApplicationVndYamahaSmafPhrase => write!(f, "application/vnd.yamaha.smaf-phrase"), + Mime::ApplicationVndYamahaThroughNgn => write!(f, "application/vnd.yamaha.through-ngn"), + Mime::ApplicationVndYamahaTunnelUdpencap => write!(f, "application/vnd.yamaha.tunnel-udpencap"), + Mime::ApplicationVndYaoweme => write!(f, "application/vnd.yaoweme"), + Mime::ApplicationVndYellowriverCustomMenu => write!(f, "application/vnd.yellowriver-custom-menu"), + Mime::ApplicationVndZul => write!(f, "application/vnd.zul"), + Mime::ApplicationVndZzazzDeckXml => write!(f, "application/vnd.zzazz.deck+xml"), + Mime::ApplicationVoicexmlXml => write!(f, "application/voicexml+xml"), + Mime::ApplicationVoucherCmsJson => write!(f, "application/voucher-cms+json"), + Mime::ApplicationVqRtcpxr => write!(f, "application/vq-rtcpxr"), + Mime::ApplicationWasm => write!(f, "application/wasm"), + Mime::ApplicationWatcherinfoXml => write!(f, "application/watcherinfo+xml"), + Mime::ApplicationWebpushOptionsJson => write!(f, "application/webpush-options+json"), + Mime::ApplicationWhoisppQuery => write!(f, "application/whoispp-query"), + Mime::ApplicationWhoisppResponse => write!(f, "application/whoispp-response"), + Mime::ApplicationWidget => write!(f, "application/widget"), + Mime::ApplicationWita => write!(f, "application/wita"), + Mime::ApplicationWordperfect51 => write!(f, "application/wordperfect5.1"), + Mime::ApplicationWsdlXml => write!(f, "application/wsdl+xml"), + Mime::ApplicationWspolicyXml => write!(f, "application/wspolicy+xml"), + Mime::ApplicationXPkiMessage => write!(f, "application/x-pki-message"), + Mime::ApplicationXWwwFormUrlencoded => write!(f, "application/x-www-form-urlencoded"), + Mime::ApplicationXX509CaCert => write!(f, "application/x-x509-ca-cert"), + Mime::ApplicationXX509CaRaCert => write!(f, "application/x-x509-ca-ra-cert"), + Mime::ApplicationXX509NextCaCert => write!(f, "application/x-x509-next-ca-cert"), + Mime::ApplicationX400Bp => write!(f, "application/x400-bp"), + Mime::ApplicationXacmlXml => write!(f, "application/xacml+xml"), + Mime::ApplicationXcapAttXml => write!(f, "application/xcap-att+xml"), + Mime::ApplicationXcapCapsXml => write!(f, "application/xcap-caps+xml"), + Mime::ApplicationXcapDiffXml => write!(f, "application/xcap-diff+xml"), + Mime::ApplicationXcapElXml => write!(f, "application/xcap-el+xml"), + Mime::ApplicationXcapErrorXml => write!(f, "application/xcap-error+xml"), + Mime::ApplicationXcapNsXml => write!(f, "application/xcap-ns+xml"), + Mime::ApplicationXconConferenceInfoDiffXml => write!(f, "application/xcon-conference-info-diff+xml"), + Mime::ApplicationXconConferenceInfoXml => write!(f, "application/xcon-conference-info+xml"), + Mime::ApplicationXencXml => write!(f, "application/xenc+xml"), + Mime::ApplicationXfdf => write!(f, "application/xfdf"), + Mime::ApplicationXhtmlXml => write!(f, "application/xhtml+xml"), + Mime::ApplicationXliffXml => write!(f, "application/xliff+xml"), + Mime::ApplicationXml => write!(f, "application/xml"), + Mime::ApplicationXmlDtd => write!(f, "application/xml-dtd"), + Mime::ApplicationXmlExternalParsedEntity => write!(f, "application/xml-external-parsed-entity"), + Mime::ApplicationXmlPatchXml => write!(f, "application/xml-patch+xml"), + Mime::ApplicationXmppXml => write!(f, "application/xmpp+xml"), + Mime::ApplicationXopXml => write!(f, "application/xop+xml"), + Mime::ApplicationXsltXml => write!(f, "application/xslt+xml"), + Mime::ApplicationXvXml => write!(f, "application/xv+xml"), + Mime::ApplicationYang => write!(f, "application/yang"), + Mime::ApplicationYangDataCbor => write!(f, "application/yang-data+cbor"), + Mime::ApplicationYangDataJson => write!(f, "application/yang-data+json"), + Mime::ApplicationYangDataXml => write!(f, "application/yang-data+xml"), + Mime::ApplicationYangPatchJson => write!(f, "application/yang-patch+json"), + Mime::ApplicationYangPatchXml => write!(f, "application/yang-patch+xml"), + Mime::ApplicationYinXml => write!(f, "application/yin+xml"), + Mime::ApplicationZip => write!(f, "application/zip"), + Mime::ApplicationZlib => write!(f, "application/zlib"), + Mime::ApplicationZstd => write!(f, "application/zstd"), + Mime::Audio1dInterleavedParityfec => write!(f, "audio/1d-interleaved-parityfec"), + Mime::Audio32kadpcm => write!(f, "audio/32kadpcm"), + Mime::Audio3gpp => write!(f, "audio/3gpp"), + Mime::Audio3gpp2 => write!(f, "audio/3gpp2"), + Mime::AudioAac => write!(f, "audio/aac"), + Mime::AudioAc3 => write!(f, "audio/ac3"), + Mime::AudioAmr => write!(f, "audio/AMR"), + Mime::AudioAmrWb => write!(f, "audio/AMR-WB"), + Mime::AudioAmrWbPlus => write!(f, "audio/amr-wb+"), + Mime::AudioAptx => write!(f, "audio/aptx"), + Mime::AudioAsc => write!(f, "audio/asc"), + Mime::AudioAtracAdvancedLossless => write!(f, "audio/ATRAC-ADVANCED-LOSSLESS"), + Mime::AudioAtracX => write!(f, "audio/ATRAC-X"), + Mime::AudioAtrac3 => write!(f, "audio/ATRAC3"), + Mime::AudioBasic => write!(f, "audio/basic"), + Mime::AudioBv16 => write!(f, "audio/BV16"), + Mime::AudioBv32 => write!(f, "audio/BV32"), + Mime::AudioClearmode => write!(f, "audio/clearmode"), + Mime::AudioCn => write!(f, "audio/CN"), + Mime::AudioDat12 => write!(f, "audio/DAT12"), + Mime::AudioDls => write!(f, "audio/dls"), + Mime::AudioDsrEs201108 => write!(f, "audio/dsr-es201108"), + Mime::AudioDsrEs202050 => write!(f, "audio/dsr-es202050"), + Mime::AudioDsrEs202211 => write!(f, "audio/dsr-es202211"), + Mime::AudioDsrEs202212 => write!(f, "audio/dsr-es202212"), + Mime::AudioDv => write!(f, "audio/DV"), + Mime::AudioDvi4 => write!(f, "audio/DVI4"), + Mime::AudioEac3 => write!(f, "audio/eac3"), + Mime::AudioEncaprtp => write!(f, "audio/encaprtp"), + Mime::AudioEvrc => write!(f, "audio/EVRC"), + Mime::AudioEvrcQcp => write!(f, "audio/EVRC-QCP"), + Mime::AudioEvrc0 => write!(f, "audio/EVRC0"), + Mime::AudioEvrc1 => write!(f, "audio/EVRC1"), + Mime::AudioEvrcb => write!(f, "audio/EVRCB"), + Mime::AudioEvrcb0 => write!(f, "audio/EVRCB0"), + Mime::AudioEvrcb1 => write!(f, "audio/EVRCB1"), + Mime::AudioEvrcnw => write!(f, "audio/EVRCNW"), + Mime::AudioEvrcnw0 => write!(f, "audio/EVRCNW0"), + Mime::AudioEvrcnw1 => write!(f, "audio/EVRCNW1"), + Mime::AudioEvrcwb => write!(f, "audio/EVRCWB"), + Mime::AudioEvrcwb0 => write!(f, "audio/EVRCWB0"), + Mime::AudioEvrcwb1 => write!(f, "audio/EVRCWB1"), + Mime::AudioEvs => write!(f, "audio/EVS"), + Mime::AudioExample => write!(f, "audio/example"), + Mime::AudioFlexfec => write!(f, "audio/flexfec"), + Mime::AudioFwdred => write!(f, "audio/fwdred"), + Mime::AudioG7110 => write!(f, "audio/G711-0"), + Mime::AudioG719 => write!(f, "audio/G719"), + Mime::AudioG7221 => write!(f, "audio/G7221"), + Mime::AudioG722 => write!(f, "audio/G722"), + Mime::AudioG723 => write!(f, "audio/G723"), + Mime::AudioG72616 => write!(f, "audio/G726-16"), + Mime::AudioG72624 => write!(f, "audio/G726-24"), + Mime::AudioG72632 => write!(f, "audio/G726-32"), + Mime::AudioG72640 => write!(f, "audio/G726-40"), + Mime::AudioG728 => write!(f, "audio/G728"), + Mime::AudioG729 => write!(f, "audio/G729"), + Mime::AudioG7291 => write!(f, "audio/G7291"), + Mime::AudioG729d => write!(f, "audio/G729D"), + Mime::AudioG729e => write!(f, "audio/G729E"), + Mime::AudioGsm => write!(f, "audio/GSM"), + Mime::AudioGsmEfr => write!(f, "audio/GSM-EFR"), + Mime::AudioGsmHr08 => write!(f, "audio/GSM-HR-08"), + Mime::AudioIlbc => write!(f, "audio/iLBC"), + Mime::AudioIpMrV25 => write!(f, "audio/ip-mr_v2.5"), + Mime::AudioL8 => write!(f, "audio/L8"), + Mime::AudioL16 => write!(f, "audio/L16"), + Mime::AudioL20 => write!(f, "audio/L20"), + Mime::AudioL24 => write!(f, "audio/L24"), + Mime::AudioLpc => write!(f, "audio/LPC"), + Mime::AudioMelp => write!(f, "audio/MELP"), + Mime::AudioMelp600 => write!(f, "audio/MELP600"), + Mime::AudioMelp1200 => write!(f, "audio/MELP1200"), + Mime::AudioMelp2400 => write!(f, "audio/MELP2400"), + Mime::AudioMhas => write!(f, "audio/mhas"), + Mime::AudioMobileXmf => write!(f, "audio/mobile-xmf"), + Mime::AudioMpa => write!(f, "audio/MPA"), + Mime::AudioMp4 => write!(f, "audio/mp4"), + Mime::AudioMp4aLatm => write!(f, "audio/MP4A-LATM"), + Mime::AudioMpaRobust => write!(f, "audio/mpa-robust"), + Mime::AudioMpeg => write!(f, "audio/mpeg"), + Mime::AudioMpeg4Generic => write!(f, "audio/mpeg4-generic"), + Mime::AudioOgg => write!(f, "audio/ogg"), + Mime::AudioOpus => write!(f, "audio/opus"), + Mime::AudioParityfec => write!(f, "audio/parityfec"), + Mime::AudioPcma => write!(f, "audio/PCMA"), + Mime::AudioPcmaWb => write!(f, "audio/PCMA-WB"), + Mime::AudioPcmu => write!(f, "audio/PCMU"), + Mime::AudioPcmuWb => write!(f, "audio/PCMU-WB"), + Mime::AudioPrsSid => write!(f, "audio/prs.sid"), + Mime::AudioQcelp => write!(f, "audio/QCELP"), + Mime::AudioRaptorfec => write!(f, "audio/raptorfec"), + Mime::AudioRed => write!(f, "audio/RED"), + Mime::AudioRtpEncAescm128 => write!(f, "audio/rtp-enc-aescm128"), + Mime::AudioRtploopback => write!(f, "audio/rtploopback"), + Mime::AudioRtpMidi => write!(f, "audio/rtp-midi"), + Mime::AudioRtx => write!(f, "audio/rtx"), + Mime::AudioScip => write!(f, "audio/scip"), + Mime::AudioSmv => write!(f, "audio/SMV"), + Mime::AudioSmv0 => write!(f, "audio/SMV0"), + Mime::AudioSmvQcp => write!(f, "audio/SMV-QCP"), + Mime::AudioSofa => write!(f, "audio/sofa"), + Mime::AudioSpMidi => write!(f, "audio/sp-midi"), + Mime::AudioSpeex => write!(f, "audio/speex"), + Mime::AudioT140c => write!(f, "audio/t140c"), + Mime::AudioT38 => write!(f, "audio/t38"), + Mime::AudioTelephoneEvent => write!(f, "audio/telephone-event"), + Mime::AudioTetraAcelp => write!(f, "audio/TETRA_ACELP"), + Mime::AudioTetraAcelpBb => write!(f, "audio/TETRA_ACELP_BB"), + Mime::AudioTone => write!(f, "audio/tone"), + Mime::AudioTsvcis => write!(f, "audio/TSVCIS"), + Mime::AudioUemclip => write!(f, "audio/UEMCLIP"), + Mime::AudioUlpfec => write!(f, "audio/ulpfec"), + Mime::AudioUsac => write!(f, "audio/usac"), + Mime::AudioVdvi => write!(f, "audio/VDVI"), + Mime::AudioVmrWb => write!(f, "audio/VMR-WB"), + Mime::AudioVnd3gppIufp => write!(f, "audio/vnd.3gpp.iufp"), + Mime::AudioVnd4sb => write!(f, "audio/vnd.4SB"), + Mime::AudioVndAudiokoz => write!(f, "audio/vnd.audiokoz"), + Mime::AudioVndCelp => write!(f, "audio/vnd.CELP"), + Mime::AudioVndCiscoNse => write!(f, "audio/vnd.cisco.nse"), + Mime::AudioVndCmlesRadioEvents => write!(f, "audio/vnd.cmles.radio-events"), + Mime::AudioVndCnsAnp1 => write!(f, "audio/vnd.cns.anp1"), + Mime::AudioVndCnsInf1 => write!(f, "audio/vnd.cns.inf1"), + Mime::AudioVndDeceAudio => write!(f, "audio/vnd.dece.audio"), + Mime::AudioVndDigitalWinds => write!(f, "audio/vnd.digital-winds"), + Mime::AudioVndDlnaAdts => write!(f, "audio/vnd.dlna.adts"), + Mime::AudioVndDolbyHeaac1 => write!(f, "audio/vnd.dolby.heaac.1"), + Mime::AudioVndDolbyHeaac2 => write!(f, "audio/vnd.dolby.heaac.2"), + Mime::AudioVndDolbyMlp => write!(f, "audio/vnd.dolby.mlp"), + Mime::AudioVndDolbyMps => write!(f, "audio/vnd.dolby.mps"), + Mime::AudioVndDolbyPl2 => write!(f, "audio/vnd.dolby.pl2"), + Mime::AudioVndDolbyPl2x => write!(f, "audio/vnd.dolby.pl2x"), + Mime::AudioVndDolbyPl2z => write!(f, "audio/vnd.dolby.pl2z"), + Mime::AudioVndDolbyPulse1 => write!(f, "audio/vnd.dolby.pulse.1"), + Mime::AudioVndDra => write!(f, "audio/vnd.dra"), + Mime::AudioVndDts => write!(f, "audio/vnd.dts"), + Mime::AudioVndDtsHd => write!(f, "audio/vnd.dts.hd"), + Mime::AudioVndDtsUhd => write!(f, "audio/vnd.dts.uhd"), + Mime::AudioVndDvbFile => write!(f, "audio/vnd.dvb.file"), + Mime::AudioVndEveradPlj => write!(f, "audio/vnd.everad.plj"), + Mime::AudioVndHnsAudio => write!(f, "audio/vnd.hns.audio"), + Mime::AudioVndLucentVoice => write!(f, "audio/vnd.lucent.voice"), + Mime::AudioVndMsPlayreadyMediaPya => write!(f, "audio/vnd.ms-playready.media.pya"), + Mime::AudioVndNokiaMobileXmf => write!(f, "audio/vnd.nokia.mobile-xmf"), + Mime::AudioVndNortelVbk => write!(f, "audio/vnd.nortel.vbk"), + Mime::AudioVndNueraEcelp4800 => write!(f, "audio/vnd.nuera.ecelp4800"), + Mime::AudioVndNueraEcelp7470 => write!(f, "audio/vnd.nuera.ecelp7470"), + Mime::AudioVndNueraEcelp9600 => write!(f, "audio/vnd.nuera.ecelp9600"), + Mime::AudioVndOctelSbc => write!(f, "audio/vnd.octel.sbc"), + Mime::AudioVndPresonusMultitrack => write!(f, "audio/vnd.presonus.multitrack"), + Mime::AudioVndRhetorex32kadpcm => write!(f, "audio/vnd.rhetorex.32kadpcm"), + Mime::AudioVndRip => write!(f, "audio/vnd.rip"), + Mime::AudioVndSealedmediaSoftsealMpeg => write!(f, "audio/vnd.sealedmedia.softseal.mpeg"), + Mime::AudioVndVmxCvsd => write!(f, "audio/vnd.vmx.cvsd"), + Mime::AudioVorbis => write!(f, "audio/vorbis"), + Mime::AudioVorbisConfig => write!(f, "audio/vorbis-config"), + Mime::FontCollection => write!(f, "font/collection"), + Mime::FontOtf => write!(f, "font/otf"), + Mime::FontSfnt => write!(f, "font/sfnt"), + Mime::FontTtf => write!(f, "font/ttf"), + Mime::FontWoff => write!(f, "font/woff"), + Mime::FontWoff2 => write!(f, "font/woff2"), + Mime::ImageAces => write!(f, "image/aces"), + Mime::ImageApng => write!(f, "image/apng"), + Mime::ImageAvci => write!(f, "image/avci"), + Mime::ImageAvcs => write!(f, "image/avcs"), + Mime::ImageAvif => write!(f, "image/avif"), + Mime::ImageBmp => write!(f, "image/bmp"), + Mime::ImageCgm => write!(f, "image/cgm"), + Mime::ImageDicomRle => write!(f, "image/dicom-rle"), + Mime::ImageDpx => write!(f, "image/dpx"), + Mime::ImageEmf => write!(f, "image/emf"), + Mime::ImageExample => write!(f, "image/example"), + Mime::ImageFits => write!(f, "image/fits"), + Mime::ImageG3fax => write!(f, "image/g3fax"), + Mime::ImageHeic => write!(f, "image/heic"), + Mime::ImageHeicSequence => write!(f, "image/heic-sequence"), + Mime::ImageHeif => write!(f, "image/heif"), + Mime::ImageHeifSequence => write!(f, "image/heif-sequence"), + Mime::ImageHej2k => write!(f, "image/hej2k"), + Mime::ImageHsj2 => write!(f, "image/hsj2"), + Mime::ImageJls => write!(f, "image/jls"), + Mime::ImageJp2 => write!(f, "image/jp2"), + Mime::ImageJpeg => write!(f, "image/jpeg"), + Mime::ImageJph => write!(f, "image/jph"), + Mime::ImageJphc => write!(f, "image/jphc"), + Mime::ImageJpm => write!(f, "image/jpm"), + Mime::ImageJpx => write!(f, "image/jpx"), + Mime::ImageJxr => write!(f, "image/jxr"), + Mime::ImageJxra => write!(f, "image/jxrA"), + Mime::ImageJxrs => write!(f, "image/jxrS"), + Mime::ImageJxs => write!(f, "image/jxs"), + Mime::ImageJxsc => write!(f, "image/jxsc"), + Mime::ImageJxsi => write!(f, "image/jxsi"), + Mime::ImageJxss => write!(f, "image/jxss"), + Mime::ImageKtx => write!(f, "image/ktx"), + Mime::ImageKtx2 => write!(f, "image/ktx2"), + Mime::ImageNaplps => write!(f, "image/naplps"), + Mime::ImagePng => write!(f, "image/png"), + Mime::ImagePrsBtif => write!(f, "image/prs.btif"), + Mime::ImagePrsPti => write!(f, "image/prs.pti"), + Mime::ImagePwgRaster => write!(f, "image/pwg-raster"), + Mime::ImageSvgXml => write!(f, "image/svg+xml"), + Mime::ImageT38 => write!(f, "image/t38"), + Mime::ImageTiff => write!(f, "image/tiff"), + Mime::ImageTiffFx => write!(f, "image/tiff-fx"), + Mime::ImageVndAdobePhotoshop => write!(f, "image/vnd.adobe.photoshop"), + Mime::ImageVndAirzipAcceleratorAzv => write!(f, "image/vnd.airzip.accelerator.azv"), + Mime::ImageVndCnsInf2 => write!(f, "image/vnd.cns.inf2"), + Mime::ImageVndDeceGraphic => write!(f, "image/vnd.dece.graphic"), + Mime::ImageVndDjvu => write!(f, "image/vnd.djvu"), + Mime::ImageVndDwg => write!(f, "image/vnd.dwg"), + Mime::ImageVndDxf => write!(f, "image/vnd.dxf"), + Mime::ImageVndDvbSubtitle => write!(f, "image/vnd.dvb.subtitle"), + Mime::ImageVndFastbidsheet => write!(f, "image/vnd.fastbidsheet"), + Mime::ImageVndFpx => write!(f, "image/vnd.fpx"), + Mime::ImageVndFst => write!(f, "image/vnd.fst"), + Mime::ImageVndFujixeroxEdmicsMmr => write!(f, "image/vnd.fujixerox.edmics-mmr"), + Mime::ImageVndFujixeroxEdmicsRlc => write!(f, "image/vnd.fujixerox.edmics-rlc"), + Mime::ImageVndGlobalgraphicsPgb => write!(f, "image/vnd.globalgraphics.pgb"), + Mime::ImageVndMicrosoftIcon => write!(f, "image/vnd.microsoft.icon"), + Mime::ImageVndMix => write!(f, "image/vnd.mix"), + Mime::ImageVndMsModi => write!(f, "image/vnd.ms-modi"), + Mime::ImageVndMozillaApng => write!(f, "image/vnd.mozilla.apng"), + Mime::ImageVndNetFpx => write!(f, "image/vnd.net-fpx"), + Mime::ImageVndPcoB16 => write!(f, "image/vnd.pco.b16"), + Mime::ImageVndRadiance => write!(f, "image/vnd.radiance"), + Mime::ImageVndSealedPng => write!(f, "image/vnd.sealed.png"), + Mime::ImageVndSealedmediaSoftsealGif => write!(f, "image/vnd.sealedmedia.softseal.gif"), + Mime::ImageVndSealedmediaSoftsealJpg => write!(f, "image/vnd.sealedmedia.softseal.jpg"), + Mime::ImageVndSvf => write!(f, "image/vnd.svf"), + Mime::ImageVndTencentTap => write!(f, "image/vnd.tencent.tap"), + Mime::ImageVndValveSourceTexture => write!(f, "image/vnd.valve.source.texture"), + Mime::ImageVndWapWbmp => write!(f, "image/vnd.wap.wbmp"), + Mime::ImageVndXiff => write!(f, "image/vnd.xiff"), + Mime::ImageVndZbrushPcx => write!(f, "image/vnd.zbrush.pcx"), + Mime::ImageWebp => write!(f, "image/webp"), + Mime::ImageWmf => write!(f, "image/wmf"), + Mime::MessageBhttp => write!(f, "message/bhttp"), + Mime::MessageCpim => write!(f, "message/CPIM"), + Mime::MessageDeliveryStatus => write!(f, "message/delivery-status"), + Mime::MessageDispositionNotification => write!(f, "message/disposition-notification"), + Mime::MessageExample => write!(f, "message/example"), + Mime::MessageFeedbackReport => write!(f, "message/feedback-report"), + Mime::MessageGlobal => write!(f, "message/global"), + Mime::MessageGlobalDeliveryStatus => write!(f, "message/global-delivery-status"), + Mime::MessageGlobalDispositionNotification => write!(f, "message/global-disposition-notification"), + Mime::MessageGlobalHeaders => write!(f, "message/global-headers"), + Mime::MessageHttp => write!(f, "message/http"), + Mime::MessageImdnXml => write!(f, "message/imdn+xml"), + Mime::MessageMls => write!(f, "message/mls"), + Mime::MessageOhttpReq => write!(f, "message/ohttp-req"), + Mime::MessageOhttpRes => write!(f, "message/ohttp-res"), + Mime::MessageSip => write!(f, "message/sip"), + Mime::MessageSipfrag => write!(f, "message/sipfrag"), + Mime::MessageTrackingStatus => write!(f, "message/tracking-status"), + Mime::MessageVndWfaWsc => write!(f, "message/vnd.wfa.wsc"), + Mime::Model3mf => write!(f, "model/3mf"), + Mime::ModelE57 => write!(f, "model/e57"), + Mime::ModelExample => write!(f, "model/example"), + Mime::ModelGltfBinary => write!(f, "model/gltf-binary"), + Mime::ModelGltfJson => write!(f, "model/gltf+json"), + Mime::ModelJt => write!(f, "model/JT"), + Mime::ModelIges => write!(f, "model/iges"), + Mime::ModelMtl => write!(f, "model/mtl"), + Mime::ModelObj => write!(f, "model/obj"), + Mime::ModelPrc => write!(f, "model/prc"), + Mime::ModelStep => write!(f, "model/step"), + Mime::ModelStepXml => write!(f, "model/step+xml"), + Mime::ModelStepZip => write!(f, "model/step+zip"), + Mime::ModelStepXmlZip => write!(f, "model/step-xml+zip"), + Mime::ModelStl => write!(f, "model/stl"), + Mime::ModelU3d => write!(f, "model/u3d"), + Mime::ModelVndBary => write!(f, "model/vnd.bary"), + Mime::ModelVndCld => write!(f, "model/vnd.cld"), + Mime::ModelVndColladaXml => write!(f, "model/vnd.collada+xml"), + Mime::ModelVndDwf => write!(f, "model/vnd.dwf"), + Mime::ModelVndFlatland3dml => write!(f, "model/vnd.flatland.3dml"), + Mime::ModelVndGdl => write!(f, "model/vnd.gdl"), + Mime::ModelVndGsGdl => write!(f, "model/vnd.gs-gdl"), + Mime::ModelVndGtw => write!(f, "model/vnd.gtw"), + Mime::ModelVndMomlXml => write!(f, "model/vnd.moml+xml"), + Mime::ModelVndMts => write!(f, "model/vnd.mts"), + Mime::ModelVndOpengex => write!(f, "model/vnd.opengex"), + Mime::ModelVndParasolidTransmitBinary => write!(f, "model/vnd.parasolid.transmit.binary"), + Mime::ModelVndParasolidTransmitText => write!(f, "model/vnd.parasolid.transmit.text"), + Mime::ModelVndPythaPyox => write!(f, "model/vnd.pytha.pyox"), + Mime::ModelVndRosetteAnnotatedDataModel => write!(f, "model/vnd.rosette.annotated-data-model"), + Mime::ModelVndSapVds => write!(f, "model/vnd.sap.vds"), + Mime::ModelVndUsda => write!(f, "model/vnd.usda"), + Mime::ModelVndUsdzZip => write!(f, "model/vnd.usdz+zip"), + Mime::ModelVndValveSourceCompiledMap => write!(f, "model/vnd.valve.source.compiled-map"), + Mime::ModelVndVtu => write!(f, "model/vnd.vtu"), + Mime::ModelX3dVrml => write!(f, "model/x3d-vrml"), + Mime::ModelX3dFastinfoset => write!(f, "model/x3d+fastinfoset"), + Mime::ModelX3dXml => write!(f, "model/x3d+xml"), + Mime::MultipartAppledouble => write!(f, "multipart/appledouble"), + Mime::MultipartByteranges => write!(f, "multipart/byteranges"), + Mime::MultipartEncrypted => write!(f, "multipart/encrypted"), + Mime::MultipartExample => write!(f, "multipart/example"), + Mime::MultipartFormData => write!(f, "multipart/form-data"), + Mime::MultipartHeaderSet => write!(f, "multipart/header-set"), + Mime::MultipartMultilingual => write!(f, "multipart/multilingual"), + Mime::MultipartRelated => write!(f, "multipart/related"), + Mime::MultipartReport => write!(f, "multipart/report"), + Mime::MultipartSigned => write!(f, "multipart/signed"), + Mime::MultipartVndBintMedPlus => write!(f, "multipart/vnd.bint.med-plus"), + Mime::MultipartVoiceMessage => write!(f, "multipart/voice-message"), + Mime::MultipartXMixedReplace => write!(f, "multipart/x-mixed-replace"), + Mime::Text1dInterleavedParityfec => write!(f, "text/1d-interleaved-parityfec"), + Mime::TextCacheManifest => write!(f, "text/cache-manifest"), + Mime::TextCalendar => write!(f, "text/calendar"), + Mime::TextCql => write!(f, "text/cql"), + Mime::TextCqlExpression => write!(f, "text/cql-expression"), + Mime::TextCqlIdentifier => write!(f, "text/cql-identifier"), + Mime::TextCss => write!(f, "text/css"), + Mime::TextCsv => write!(f, "text/csv"), + Mime::TextCsvSchema => write!(f, "text/csv-schema"), + Mime::TextDns => write!(f, "text/dns"), + Mime::TextEncaprtp => write!(f, "text/encaprtp"), + Mime::TextExample => write!(f, "text/example"), + Mime::TextFhirpath => write!(f, "text/fhirpath"), + Mime::TextFlexfec => write!(f, "text/flexfec"), + Mime::TextFwdred => write!(f, "text/fwdred"), + Mime::TextGff3 => write!(f, "text/gff3"), + Mime::TextGrammarRefList => write!(f, "text/grammar-ref-list"), + Mime::TextHl7v2 => write!(f, "text/hl7v2"), + Mime::TextHtml => write!(f, "text/html"), + Mime::TextJavascript => write!(f, "text/javascript"), + Mime::TextJcrCnd => write!(f, "text/jcr-cnd"), + Mime::TextMarkdown => write!(f, "text/markdown"), + Mime::TextMizar => write!(f, "text/mizar"), + Mime::TextN3 => write!(f, "text/n3"), + Mime::TextParameters => write!(f, "text/parameters"), + Mime::TextParityfec => write!(f, "text/parityfec"), + Mime::TextPlain => write!(f, "text/plain"), + Mime::TextProvenanceNotation => write!(f, "text/provenance-notation"), + Mime::TextPrsFallensteinRst => write!(f, "text/prs.fallenstein.rst"), + Mime::TextPrsLinesTag => write!(f, "text/prs.lines.tag"), + Mime::TextPrsPropLogic => write!(f, "text/prs.prop.logic"), + Mime::TextRaptorfec => write!(f, "text/raptorfec"), + Mime::TextRed => write!(f, "text/RED"), + Mime::TextRfc822Headers => write!(f, "text/rfc822-headers"), + Mime::TextRtf => write!(f, "text/rtf"), + Mime::TextRtpEncAescm128 => write!(f, "text/rtp-enc-aescm128"), + Mime::TextRtploopback => write!(f, "text/rtploopback"), + Mime::TextRtx => write!(f, "text/rtx"), + Mime::TextSgml => write!(f, "text/SGML"), + Mime::TextShaclc => write!(f, "text/shaclc"), + Mime::TextShex => write!(f, "text/shex"), + Mime::TextSpdx => write!(f, "text/spdx"), + Mime::TextStrings => write!(f, "text/strings"), + Mime::TextT140 => write!(f, "text/t140"), + Mime::TextTabSeparatedValues => write!(f, "text/tab-separated-values"), + Mime::TextTroff => write!(f, "text/troff"), + Mime::TextTurtle => write!(f, "text/turtle"), + Mime::TextUlpfec => write!(f, "text/ulpfec"), + Mime::TextUriList => write!(f, "text/uri-list"), + Mime::TextVcard => write!(f, "text/vcard"), + Mime::TextVndA => write!(f, "text/vnd.a"), + Mime::TextVndAbc => write!(f, "text/vnd.abc"), + Mime::TextVndAsciiArt => write!(f, "text/vnd.ascii-art"), + Mime::TextVndCurl => write!(f, "text/vnd.curl"), + Mime::TextVndDebianCopyright => write!(f, "text/vnd.debian.copyright"), + Mime::TextVndDmclientscript => write!(f, "text/vnd.DMClientScript"), + Mime::TextVndDvbSubtitle => write!(f, "text/vnd.dvb.subtitle"), + Mime::TextVndEsmertecThemeDescriptor => write!(f, "text/vnd.esmertec.theme-descriptor"), + Mime::TextVndExchangeable => write!(f, "text/vnd.exchangeable"), + Mime::TextVndFamilysearchGedcom => write!(f, "text/vnd.familysearch.gedcom"), + Mime::TextVndFiclabFlt => write!(f, "text/vnd.ficlab.flt"), + Mime::TextVndFly => write!(f, "text/vnd.fly"), + Mime::TextVndFmiFlexstor => write!(f, "text/vnd.fmi.flexstor"), + Mime::TextVndGml => write!(f, "text/vnd.gml"), + Mime::TextVndGraphviz => write!(f, "text/vnd.graphviz"), + Mime::TextVndHans => write!(f, "text/vnd.hans"), + Mime::TextVndHgl => write!(f, "text/vnd.hgl"), + Mime::TextVndIn3d3dml => write!(f, "text/vnd.in3d.3dml"), + Mime::TextVndIn3dSpot => write!(f, "text/vnd.in3d.spot"), + Mime::TextVndIptcNewsml => write!(f, "text/vnd.IPTC.NewsML"), + Mime::TextVndIptcNitf => write!(f, "text/vnd.IPTC.NITF"), + Mime::TextVndLatexZ => write!(f, "text/vnd.latex-z"), + Mime::TextVndMotorolaReflex => write!(f, "text/vnd.motorola.reflex"), + Mime::TextVndMsMediapackage => write!(f, "text/vnd.ms-mediapackage"), + Mime::TextVndNet2phoneCommcenterCommand => write!(f, "text/vnd.net2phone.commcenter.command"), + Mime::TextVndRadisysMsmlBasicLayout => write!(f, "text/vnd.radisys.msml-basic-layout"), + Mime::TextVndSenxWarpscript => write!(f, "text/vnd.senx.warpscript"), + Mime::TextVndSunJ2meAppDescriptor => write!(f, "text/vnd.sun.j2me.app-descriptor"), + Mime::TextVndSosi => write!(f, "text/vnd.sosi"), + Mime::TextVndTrolltechLinguist => write!(f, "text/vnd.trolltech.linguist"), + Mime::TextVndWapSi => write!(f, "text/vnd.wap.si"), + Mime::TextVndWapSl => write!(f, "text/vnd.wap.sl"), + Mime::TextVndWapWml => write!(f, "text/vnd.wap.wml"), + Mime::TextVndWapWmlscript => write!(f, "text/vnd.wap.wmlscript"), + Mime::TextVtt => write!(f, "text/vtt"), + Mime::TextWgsl => write!(f, "text/wgsl"), + Mime::TextXml => write!(f, "text/xml"), + Mime::TextXmlExternalParsedEntity => write!(f, "text/xml-external-parsed-entity"), + Mime::Video1dInterleavedParityfec => write!(f, "video/1d-interleaved-parityfec"), + Mime::Video3gpp => write!(f, "video/3gpp"), + Mime::Video3gpp2 => write!(f, "video/3gpp2"), + Mime::Video3gppTt => write!(f, "video/3gpp-tt"), + Mime::VideoAv1 => write!(f, "video/AV1"), + Mime::VideoBmpeg => write!(f, "video/BMPEG"), + Mime::VideoBt656 => write!(f, "video/BT656"), + Mime::VideoCelb => write!(f, "video/CelB"), + Mime::VideoDv => write!(f, "video/DV"), + Mime::VideoEncaprtp => write!(f, "video/encaprtp"), + Mime::VideoExample => write!(f, "video/example"), + Mime::VideoFfv1 => write!(f, "video/FFV1"), + Mime::VideoFlexfec => write!(f, "video/flexfec"), + Mime::VideoH261 => write!(f, "video/H261"), + Mime::VideoH263 => write!(f, "video/H263"), + Mime::VideoH2631998 => write!(f, "video/H263-1998"), + Mime::VideoH2632000 => write!(f, "video/H263-2000"), + Mime::VideoH264 => write!(f, "video/H264"), + Mime::VideoH264Rcdo => write!(f, "video/H264-RCDO"), + Mime::VideoH264Svc => write!(f, "video/H264-SVC"), + Mime::VideoH265 => write!(f, "video/H265"), + Mime::VideoH266 => write!(f, "video/H266"), + Mime::VideoIsoSegment => write!(f, "video/iso.segment"), + Mime::VideoJpeg => write!(f, "video/JPEG"), + Mime::VideoJpeg2000 => write!(f, "video/jpeg2000"), + Mime::VideoJxsv => write!(f, "video/jxsv"), + Mime::VideoMj2 => write!(f, "video/mj2"), + Mime::VideoMp1s => write!(f, "video/MP1S"), + Mime::VideoMp2p => write!(f, "video/MP2P"), + Mime::VideoMp2t => write!(f, "video/MP2T"), + Mime::VideoMp4 => write!(f, "video/mp4"), + Mime::VideoMp4vEs => write!(f, "video/MP4V-ES"), + Mime::VideoMpv => write!(f, "video/MPV"), + Mime::VideoMpeg4Generic => write!(f, "video/mpeg4-generic"), + Mime::VideoNv => write!(f, "video/nv"), + Mime::VideoOgg => write!(f, "video/ogg"), + Mime::VideoParityfec => write!(f, "video/parityfec"), + Mime::VideoPointer => write!(f, "video/pointer"), + Mime::VideoQuicktime => write!(f, "video/quicktime"), + Mime::VideoRaptorfec => write!(f, "video/raptorfec"), + Mime::VideoRaw => write!(f, "video/raw"), + Mime::VideoRtpEncAescm128 => write!(f, "video/rtp-enc-aescm128"), + Mime::VideoRtploopback => write!(f, "video/rtploopback"), + Mime::VideoRtx => write!(f, "video/rtx"), + Mime::VideoScip => write!(f, "video/scip"), + Mime::VideoSmpte291 => write!(f, "video/smpte291"), + Mime::VideoSmpte292m => write!(f, "video/SMPTE292M"), + Mime::VideoUlpfec => write!(f, "video/ulpfec"), + Mime::VideoVc1 => write!(f, "video/vc1"), + Mime::VideoVc2 => write!(f, "video/vc2"), + Mime::VideoVndCctv => write!(f, "video/vnd.CCTV"), + Mime::VideoVndDeceHd => write!(f, "video/vnd.dece.hd"), + Mime::VideoVndDeceMobile => write!(f, "video/vnd.dece.mobile"), + Mime::VideoVndDeceMp4 => write!(f, "video/vnd.dece.mp4"), + Mime::VideoVndDecePd => write!(f, "video/vnd.dece.pd"), + Mime::VideoVndDeceSd => write!(f, "video/vnd.dece.sd"), + Mime::VideoVndDeceVideo => write!(f, "video/vnd.dece.video"), + Mime::VideoVndDirectvMpeg => write!(f, "video/vnd.directv.mpeg"), + Mime::VideoVndDirectvMpegTts => write!(f, "video/vnd.directv.mpeg-tts"), + Mime::VideoVndDlnaMpegTts => write!(f, "video/vnd.dlna.mpeg-tts"), + Mime::VideoVndDvbFile => write!(f, "video/vnd.dvb.file"), + Mime::VideoVndFvt => write!(f, "video/vnd.fvt"), + Mime::VideoVndHnsVideo => write!(f, "video/vnd.hns.video"), + Mime::VideoVndIptvforum1dparityfec1010 => write!(f, "video/vnd.iptvforum.1dparityfec-1010"), + Mime::VideoVndIptvforum1dparityfec2005 => write!(f, "video/vnd.iptvforum.1dparityfec-2005"), + Mime::VideoVndIptvforum2dparityfec1010 => write!(f, "video/vnd.iptvforum.2dparityfec-1010"), + Mime::VideoVndIptvforum2dparityfec2005 => write!(f, "video/vnd.iptvforum.2dparityfec-2005"), + Mime::VideoVndIptvforumTtsavc => write!(f, "video/vnd.iptvforum.ttsavc"), + Mime::VideoVndIptvforumTtsmpeg2 => write!(f, "video/vnd.iptvforum.ttsmpeg2"), + Mime::VideoVndMotorolaVideo => write!(f, "video/vnd.motorola.video"), + Mime::VideoVndMotorolaVideop => write!(f, "video/vnd.motorola.videop"), + Mime::VideoVndMpegurl => write!(f, "video/vnd.mpegurl"), + Mime::VideoVndMsPlayreadyMediaPyv => write!(f, "video/vnd.ms-playready.media.pyv"), + Mime::VideoVndNokiaInterleavedMultimedia => write!(f, "video/vnd.nokia.interleaved-multimedia"), + Mime::VideoVndNokiaMp4vr => write!(f, "video/vnd.nokia.mp4vr"), + Mime::VideoVndNokiaVideovoip => write!(f, "video/vnd.nokia.videovoip"), + Mime::VideoVndObjectvideo => write!(f, "video/vnd.objectvideo"), + Mime::VideoVndRadgamettoolsBink => write!(f, "video/vnd.radgamettools.bink"), + Mime::VideoVndRadgamettoolsSmacker => write!(f, "video/vnd.radgamettools.smacker"), + Mime::VideoVndSealedMpeg1 => write!(f, "video/vnd.sealed.mpeg1"), + Mime::VideoVndSealedMpeg4 => write!(f, "video/vnd.sealed.mpeg4"), + Mime::VideoVndSealedSwf => write!(f, "video/vnd.sealed.swf"), + Mime::VideoVndSealedmediaSoftsealMov => write!(f, "video/vnd.sealedmedia.softseal.mov"), + Mime::VideoVndUvvuMp4 => write!(f, "video/vnd.uvvu.mp4"), + Mime::VideoVndYoutubeYt => write!(f, "video/vnd.youtube.yt"), + Mime::VideoVndVivo => write!(f, "video/vnd.vivo"), + Mime::VideoVp8 => write!(f, "video/VP8"), + Mime::VideoVp9 => write!(f, "video/VP9"), + } + } +} + +#[derive(Debug)] +pub struct ParseMimeError(i32); + +impl std::fmt::Display for ParseMimeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} +impl std::error::Error for ParseMimeError {} + +impl std::str::FromStr for Mime { + type Err = ParseMimeError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match MIME_MAP.get(s).copied() { + Some(mimetype) => Ok(mimetype), + None => Err(ParseMimeError(1)), + } + } +} + +impl Mime { + pub fn from_filename(filename: &str) -> Self { + match filename.split('.').last() { + Some(v) => match v { + "png" => Mime::ImagePng, + "jpg" => Mime::ImageJpeg, + "json" => Mime::ApplicationJson, + "html" => Mime::TextHtml, + "css" => Mime::TextCss, + &_ => Mime::TextPlain, + }, + None => Mime::TextPlain, + } + } +} + +#[cfg(test)] +mod test { + use crate::utils::mime::mime_enum::Mime; + + #[test] + fn get_mime_test() { + assert_eq!("text/plain".parse::<Mime>().unwrap(), Mime::TextPlain) + } +} diff --git a/core/http/src/utils/mime/mod.rs b/core/http/src/utils/mime/mod.rs new file mode 100644 index 0000000..bbf3a87 --- /dev/null +++ b/core/http/src/utils/mime/mod.rs @@ -0,0 +1,2 @@ +mod map; +pub mod mime_enum; diff --git a/generatersenum.py b/generatersenum.py new file mode 100644 index 0000000..2c686bd --- /dev/null +++ b/generatersenum.py @@ -0,0 +1,73 @@ +import csv + + +def generate_rust_enum(csv_file): + rust_enum = "use phf::phf_map;\n\nenum Mime {\n" + + with open(csv_file, "r") as file: + csv_data = csv.reader(file) + next(csv_data) # Skip the header row + + for row in csv_data: + if row[1] == "": + continue + if "DEPRECATED" in row[0]: + continue + name = format_enum_member(row[0:2]) + rust_enum += f"\t{name},\n" + + rust_enum += "}\n\n" + rust_enum += "impl std::fmt::Display for Mime {\n" + rust_enum += "\tfn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n" + rust_enum += "\t\tmatch self {\n" + + with open(csv_file, "r") as file: + csv_data = csv.reader(file) + next(csv_data) # Skip the header row + for row in csv_data: + if row[1] == "": + continue + if "DEPRECATED" in row[0]: + continue + name = format_enum_member(row[0:2]) + rust_enum += f'\t\t\tMime::{name} => write!(f, "{row[1]}"),\n' + + rust_enum += "\t\t}\n\t}\n}\n\n" + + rust_enum += "static MimeMap: phf::Map<&'static str, Mime> = phf_map! {\n" + with open(csv_file, "r") as file: + csv_data = csv.reader(file) + next(csv_data) + for row in csv_data: + if row[1] == "": + continue + if "DEPRECATED" in row[0]: + continue + key = row[1] + value = format_enum_member(row[0:2]) + rust_enum += f'\t"{key}" => Mime::{value},\n' + rust_enum += "};" + + return rust_enum + + +def format_enum_member(name): + prefix = "".join(name[1].split("/")[0:-1]) + name = name[0].split("-") + words = [] + parts = [] + for part in name: + parts += part.split("+") + for part in parts: + words += part.split(".") + + formatted_name = "".join(word.capitalize() for word in words) + if not formatted_name.startswith(prefix.capitalize()): + formatted_name = prefix.capitalize() + formatted_name + return formatted_name + + +# Usage example +csv_file = "mimes.csv" +rust_enum_code = generate_rust_enum(csv_file) +print(rust_enum_code) diff --git a/site/Cargo.lock b/site/Cargo.lock index c714959..84f675c 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ name = "http" version = "0.1.0" dependencies = [ - "mime", + "phf", "tokio", ] @@ -59,12 +59,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mio" version = "0.8.7" @@ -109,6 +103,48 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -133,6 +169,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -157,6 +208,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "site" version = "0.1.0" @@ -181,6 +238,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.18" @@ -219,7 +287,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] diff --git a/small.csv b/small.csv new file mode 100644 index 0000000..aa3809c --- /dev/null +++ b/small.csv @@ -0,0 +1,20 @@ +Name, format, asdf +vnd.motorola.video,video/vnd.motorola.video,[Tom_McGinty] +vnd.motorola.videop,video/vnd.motorola.videop,[Tom_McGinty] +vnd.mpegurl,video/vnd.mpegurl,[Heiko_Recktenwald] +vnd.ms-playready.media.pyv,video/vnd.ms-playready.media.pyv,[Steve_DiAcetis] +vnd.nokia.interleaved-multimedia,video/vnd.nokia.interleaved-multimedia,[Petteri_Kangaslampi] +vnd.nokia.mp4vr,video/vnd.nokia.mp4vr,[Miska_M._Hannuksela] +vnd.nokia.videovoip,video/vnd.nokia.videovoip,[Nokia] +vnd.objectvideo,video/vnd.objectvideo,[John_Clark] +vnd.radgamettools.bink,video/vnd.radgamettools.bink,[Henrik_Andersson] +vnd.radgamettools.smacker,video/vnd.radgamettools.smacker,[Henrik_Andersson] +vnd.sealed.mpeg1,video/vnd.sealed.mpeg1,[David_Petersen] +vnd.sealed.mpeg4,video/vnd.sealed.mpeg4,[David_Petersen] +vnd.sealed.swf,video/vnd.sealed.swf,[David_Petersen] +vnd.sealedmedia.softseal.mov,video/vnd.sealedmedia.softseal.mov,[David_Petersen] +vnd.uvvu.mp4,video/vnd.uvvu.mp4,[Michael_A_Dolan] +vnd.youtube.yt,video/vnd.youtube.yt,[Google] +vnd.vivo,video/vnd.vivo,[John_Wolfe] +VP8,video/VP8,[RFC7741] +VP9,video/VP9,[RFC-ietf-payload-vp9-16] -- GitLab From fe8dba588850c16b28b955a51c871504094e6fdc Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 4 Jun 2023 20:58:42 +0200 Subject: [PATCH 25/65] Add multipart post text form support, check for `Content-Length` Header --- core/http/src/handlers/handlers.rs | 45 ++++----- core/http/src/handling/request.rs | 140 ++++++++++++++++++++++++-- core/http/src/handling/response.rs | 4 +- core/http/src/handling/routes.rs | 32 +++++- core/http/src/setup.rs | 1 + core/http/src/utils/mime/mime_enum.rs | 5 + site/404.html | 5 +- site/src/main.rs | 8 +- 8 files changed, 196 insertions(+), 44 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index f81441c..63172c5 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -12,7 +12,7 @@ use crate::setup::MountPoint; pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); - let mut http_request: Vec<String> = Vec::with_capacity(100); + let mut http_request: Vec<String> = Vec::with_capacity(30); loop { let mut buffer = String::new(); if let Err(_) = buf_reader.read_line(&mut buffer).await { @@ -74,26 +74,28 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin if let Ok(size) = len { size } else { - 0 + eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); + len_not_defined(stream, Status::LengthRequired).await; + return; } } else { + if request.mandatory_body() { + eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); + len_not_defined(stream, Status::LengthRequired).await; + return; + } 0 }; - - let mut buffer: Vec<u8> = vec![]; - buf_reader.read_buf(&mut buffer).await.unwrap(); - if buffer.len() != length { - let respone = len_not_defined(Status::LengthRequired); - let mut response = respone.0.as_bytes().to_vec(); - response.extend_from_slice(&respone.1); - if let Err(e) = stream.write_all(&response).await { - eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); + if length != 0 { + let mut buffer: Vec<u8> = vec![]; + buf_reader.read_buf(&mut buffer).await.unwrap(); + if buffer.len() != length { + len_not_defined(stream, Status::LengthRequired).await; return; } - return; + data.is_complete = true; + data.buffer = buffer; } - data.is_complete = true; - data.buffer = buffer; } let mut handled_response: Option<Outcome<Response, Status, Data>> = None; @@ -165,14 +167,11 @@ fn failure_handler(status: Status) -> (String, Vec<u8>) { ) } -fn len_not_defined(status: Status) -> (String, Vec<u8>) { +async fn len_not_defined(mut stream: TcpStream, status: Status) { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); - ( - format!( - "HTTP/1.1 {status}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - page_411.get_len(), - page_411.get_mime() - ), - page_411.get_data(), - ) + let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec(); + response.extend_from_slice(&page_411.get_data()); + if let Err(e) = stream.write_all(&response).await { + eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); + } } diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index f90bacb..5f02d3d 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,10 +2,7 @@ use std::{collections::HashMap, error::Error, fmt::Display}; -trait FromPost {} - -impl FromPost for &str {} -impl FromPost for Vec<u8> {} +use crate::utils::mime::mime_enum::Mime; use super::{ methods::Method, @@ -68,6 +65,12 @@ impl Request<'_> { _ => false, } } + pub fn mandatory_body(&self) -> bool { + match self.method { + Method::Post | Method::Put | Method::Patch => true, + _ => false, + } + } // pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {} pub fn get_get_form_keys( &self, @@ -108,7 +111,132 @@ impl Request<'_> { } Ok(response) } - pub fn get_post_form_key(&self, key: &str, data: Data) { - todo!() + pub fn get_post_text_form_key(&self, key: &str, data: &Data) -> Result<String, ()> { + let mut post_type = self + .headers + .iter() + .find(|header| header.contains("Content-Type: ")) + .unwrap() + .to_string(); + + post_type = post_type + .strip_prefix("Content-Type: ") + .unwrap() + .to_string(); + + let post_type: Vec<&str> = post_type.trim().split(';').collect(); + let mime_type = post_type[0].parse::<Mime>().unwrap(); + + match mime_type { + Mime::ApplicationXWwwFormUrlencoded => { + let data = String::from_utf8(data.buffer.clone()).unwrap(); + let kvps = data + .split("&") + .map(|kvp| kvp.split_once("=").unwrap()) + .collect::<HashMap<&str, &str>>(); + if let Some(val) = kvps.get(key) { + Ok(val.to_string()) + } else { + Err(()) + } + } + Mime::MultipartFormData => { + let from_req = post_type[1..] + .iter() + .find(|val| val.contains("boundary=")) + .unwrap() + .strip_prefix("boundary=") + .unwrap(); + let mut boundary = "--".as_bytes().to_vec(); + boundary.extend_from_slice(from_req.trim_matches('"').as_bytes()); + let mut end_boundary = boundary.clone(); + end_boundary.extend_from_slice(b"--"); + boundary.extend_from_slice(&[b'\r']); + let parts = data + .buffer + .split(|byte| byte == &b'\n') + .collect::<Vec<&[u8]>>(); + + let mut boundary_found = false; + let mut key_found = false; + let mut value = vec![]; + for part in parts { + if part == [] { + continue; + } + if (key_found && part == boundary) || part == end_boundary { + break; + } + if !boundary_found && part == boundary { + boundary_found = true; + continue; + } + if part.starts_with(b"Content-Disposition: form-data; name=") { + let headers = part + .split(|byte| byte == &b';') + .filter(|header| !header.is_empty()) + .collect::<Vec<_>>(); + if headers.len() < 2 { + continue; + } + let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); + if name.len() != 2 { + continue; + } + let mkey = String::from_utf8_lossy(name[1]) + .to_string() + .trim_end() + .trim_matches('"') + .to_owned(); + key_found = key == mkey; + } else if key_found == true { + value.extend_from_slice(part); + value.extend_from_slice(&[b'\n']); + } + } + Ok(String::from_utf8_lossy(&value) + .to_owned() + .trim() + .to_string()) + } + _ => Err(()), + } + } +} + +#[cfg(test)] +mod test { + use crate::handling::routes::Data; + + use super::Request; + + #[test] + fn try_post_text() { + let req = Request { + uri: "", + headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], + method: crate::handling::methods::Method::Post, + }; + let data = Data { + buffer: b"--boundary\r +Content-Disposition: form-data; name=\"field1\"\r +\r +value1\r +--boundary\r +Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\n\r +\r +value2\r +--boundary--" + .to_vec(), + is_complete: true, + }; + assert_eq!( + "value1", + req.get_post_text_form_key("field1", &data).unwrap() + ); + assert_eq!( + "value2", + req.get_post_text_form_key("field2", &data).unwrap() + ); } } diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response.rs index 2f7fdcf..81a54af 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response.rs @@ -21,10 +21,10 @@ pub trait ResponseBody: Send { impl ResponseBody for Body { fn get_data(&self) -> Vec<u8> { - self.body.clone() + self.body() } fn get_mime(&self) -> Mime { - Mime::TextPlain + self.mime_type() } fn get_len(&self) -> usize { diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index 9ded1ce..9fd3f77 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -1,7 +1,10 @@ -use super::{ - methods::Method, - request::{MediaType, Request}, - response::{Outcome, Response, Status}, +use crate::{ + handling::{ + methods::Method, + request::{MediaType, Request}, + response::{Outcome, Response, Status}, + }, + utils::mime::mime_enum::Mime, }; pub struct RoutInfo { @@ -63,7 +66,26 @@ pub type Uri<'a> = &'a str; #[derive(Debug, Clone)] pub struct Body { - pub body: Vec<u8>, + body: Vec<u8>, + mime_type: Mime, +} + +impl Body { + pub fn new(body: Vec<u8>, mime_type: Mime) -> Self { + Self { body, mime_type } + } + pub fn set_mime_type(&mut self, mime_type: Mime) { + self.mime_type = mime_type; + } + pub fn set_body(&mut self, body: Vec<u8>) { + self.body = body; + } + pub fn mime_type(&self) -> Mime { + self.mime_type + } + pub fn body(&self) -> Vec<u8> { + self.body.clone() + } } #[derive(Debug, Clone)] diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 9f57e76..50f5144 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -66,6 +66,7 @@ impl<'a> Config { self } pub async fn launch(self) { + println!("Server launched from http://{}", self.address.local_addr().unwrap()); let mut sigint = signal(SignalKind::interrupt()).unwrap(); loop { select! { diff --git a/core/http/src/utils/mime/mime_enum.rs b/core/http/src/utils/mime/mime_enum.rs index fbf4415..0a6a068 100644 --- a/core/http/src/utils/mime/mime_enum.rs +++ b/core/http/src/utils/mime/mime_enum.rs @@ -4061,6 +4061,11 @@ impl std::str::FromStr for Mime { type Err = ParseMimeError; fn from_str(s: &str) -> Result<Self, Self::Err> { + let s = if let Some(str) = s.split_once(';') { + str.0 + } else { + s + }; match MIME_MAP.get(s).copied() { Some(mimetype) => Ok(mimetype), None => Err(ParseMimeError(1)), diff --git a/site/404.html b/site/404.html index 0a1a4ed..7934c61 100644 --- a/site/404.html +++ b/site/404.html @@ -7,13 +7,10 @@ <body> <h1>404</h1> <p>Hi from Rust</p> - <form action="/" method="POST"> + <form action="/post/post" method="post"> <label for="message">Enter your message:</label> <input type="text" id="message" name="message" /> <button type="submit">Send</button> - <label for="message">Enter your message:</label> - <input type="text" id="asdf" name="message" /> - <button type="submit">Send</button> </form> <form action="/static/hi" method="get"> <label for="jump">Enter asdf</label> diff --git a/site/src/main.rs b/site/src/main.rs index deb31d6..1005312 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -58,12 +58,12 @@ fn fileserver(path: &str) -> Result<NamedFile, Status> { NamedFile::open(PathBuf::from("static/".to_string() + path)) } -fn post_hi_handler(_request: Request, data: Data) -> Outcome<Response, Status, Data> { +fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> { if data.is_empty() { return Outcome::Forward(data); } - let data = if let Ok(str) = String::from_utf8(data.buffer) { - str + let data = if let Ok(val) = request.get_post_text_form_key("message", &data) { + val } else { return Outcome::Failure(Status::BadRequest); }; @@ -111,7 +111,7 @@ async fn main() { rank: 0, }; - http::build("192.168.178.32:8000") + http::build("127.0.0.1:8000") .await .mount("/", vec![fileserver, post_test, static_hi]) .mount("/post/", vec![post_test]) -- GitLab From 374dcc3e1e454421e0d18bd39f1cbe37410bb53b Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 5 Jun 2023 21:19:48 +0200 Subject: [PATCH 26/65] Update `get_post_form_text` to allow for an array of keys to search --- core/http/src/handling/request.rs | 70 +++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 5f02d3d..12947a6 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -72,9 +72,9 @@ impl Request<'_> { } } // pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {} - pub fn get_get_form_keys( - &self, - keys: Vec<&str>, + pub fn get_get_form_keys<'a>( + &'a self, + keys: &'a [&str], ) -> Result<HashMap<&str, &str>, ParseFormError> { let data = if let Some(val) = self.uri.split_once("?") { val @@ -111,7 +111,11 @@ impl Request<'_> { } Ok(response) } - pub fn get_post_text_form_key(&self, key: &str, data: &Data) -> Result<String, ()> { + pub fn get_post_text_form_key( + &self, + keys: &[&str], + data: &Data, + ) -> Result<Vec<String>, ParseFormError> { let mut post_type = self .headers .iter() @@ -134,11 +138,18 @@ impl Request<'_> { .split("&") .map(|kvp| kvp.split_once("=").unwrap()) .collect::<HashMap<&str, &str>>(); - if let Some(val) = kvps.get(key) { - Ok(val.to_string()) - } else { - Err(()) + + let mut result: Vec<String> = Vec::with_capacity(keys.len()); + for key in keys { + if let Some(val) = kvps.get(key) { + result.push(val.to_string()); + } else { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); + } } + Ok(result) } Mime::MultipartFormData => { let from_req = post_type[1..] @@ -158,16 +169,17 @@ impl Request<'_> { .collect::<Vec<&[u8]>>(); let mut boundary_found = false; - let mut key_found = false; - let mut value = vec![]; + let mut current_key: Option<usize> = None; + let mut result: Vec<Vec<u8>> = vec!["".into(); keys.len()]; for part in parts { if part == [] { continue; } - if (key_found && part == boundary) || part == end_boundary { + if part == end_boundary { break; } if !boundary_found && part == boundary { + current_key = None; boundary_found = true; continue; } @@ -188,18 +200,27 @@ impl Request<'_> { .trim_end() .trim_matches('"') .to_owned(); - key_found = key == mkey; - } else if key_found == true { - value.extend_from_slice(part); - value.extend_from_slice(&[b'\n']); + let mut index = 0; + for i in keys { + if *i == mkey { + current_key = Some(index); + } + index += 1; + } + boundary_found = false; + } else if let Some(key) = current_key { + result[key].extend_from_slice(part); + result[key].extend_from_slice(&[b'\n']); } } - Ok(String::from_utf8_lossy(&value) - .to_owned() - .trim() - .to_string()) + Ok(result + .iter() + .map(|arr| String::from_utf8(arr.to_vec()).unwrap().trim().into()) + .collect()) } - _ => Err(()), + _ => Err(ParseFormError { + error: ParseErrors::BadData, + }), } } } @@ -232,11 +253,16 @@ value2\r }; assert_eq!( "value1", - req.get_post_text_form_key("field1", &data).unwrap() + req.get_post_text_form_key(&["field1"], &data).unwrap()[0] ); assert_eq!( "value2", - req.get_post_text_form_key("field2", &data).unwrap() + req.get_post_text_form_key(&["field2"], &data).unwrap()[0] ); + assert_eq!( + vec!["value1", "value2"], + req.get_post_text_form_key(&["field1", "field2"], &data) + .unwrap() + ) } } -- GitLab From 91fb02ce5510ca1a8884d0f7d846471b6ae72d7d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 6 Jun 2023 20:49:49 +0200 Subject: [PATCH 27/65] update the `get_get_form_data()` to return a Result<Hasmap<&str, Result<&str, ParseFormError>>, ParseFormError> for easier handling of not existant keys --- core/http/src/handling/request.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 12947a6..6fa46de 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -75,7 +75,7 @@ impl Request<'_> { pub fn get_get_form_keys<'a>( &'a self, keys: &'a [&str], - ) -> Result<HashMap<&str, &str>, ParseFormError> { + ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { let data = if let Some(val) = self.uri.split_once("?") { val } else { @@ -100,14 +100,14 @@ impl Request<'_> { } let mut response = HashMap::new(); for key in keys { - let entry = if let Some(val) = values.get_key_value(key) { - val + let entry = if let Some(val) = values.get(key) { + Ok(*val) } else { - return Err(ParseFormError { + Err(ParseFormError { error: ParseErrors::NoData, - }); + }) }; - response.insert(*entry.0, *entry.1); + response.insert((*key).into(), entry); } Ok(response) } -- GitLab From 4fe55b1268cf02b2b6433c05d51227cc23b58a89 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 6 Jun 2023 22:29:36 +0200 Subject: [PATCH 28/65] Some rather small changes --- core/http/src/handling/request.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 6fa46de..09f5db6 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -71,7 +71,6 @@ impl Request<'_> { _ => false, } } - // pub fn get_post_form_key<T: FromRequest>(&self, data: Data) -> T {} pub fn get_get_form_keys<'a>( &'a self, keys: &'a [&str], @@ -115,7 +114,7 @@ impl Request<'_> { &self, keys: &[&str], data: &Data, - ) -> Result<Vec<String>, ParseFormError> { + ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { let mut post_type = self .headers .iter() @@ -131,6 +130,7 @@ impl Request<'_> { let post_type: Vec<&str> = post_type.trim().split(';').collect(); let mime_type = post_type[0].parse::<Mime>().unwrap(); + let mut result = HashMap::new(); match mime_type { Mime::ApplicationXWwwFormUrlencoded => { let data = String::from_utf8(data.buffer.clone()).unwrap(); @@ -139,15 +139,15 @@ impl Request<'_> { .map(|kvp| kvp.split_once("=").unwrap()) .collect::<HashMap<&str, &str>>(); - let mut result: Vec<String> = Vec::with_capacity(keys.len()); for key in keys { - if let Some(val) = kvps.get(key) { - result.push(val.to_string()); + let entry = if let Some(val) = kvps.get(key) { + Ok(*val) } else { - return Err(ParseFormError { + Err(ParseFormError { error: ParseErrors::NoData, - }); - } + }) + }; + result.insert(*key, entry); } Ok(result) } @@ -158,7 +158,7 @@ impl Request<'_> { .unwrap() .strip_prefix("boundary=") .unwrap(); - let mut boundary = "--".as_bytes().to_vec(); + let mut boundary = b"--".to_vec(); boundary.extend_from_slice(from_req.trim_matches('"').as_bytes()); let mut end_boundary = boundary.clone(); end_boundary.extend_from_slice(b"--"); -- GitLab From 39889425fb4f96aef011ec7b4cb7b7eb5085de76 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 9 Jun 2023 21:17:37 +0200 Subject: [PATCH 29/65] Working on new get post data function --- core/http/src/handling/request.rs | 327 ++++++++++++++++++------------ site/src/main.rs | 20 +- 2 files changed, 202 insertions(+), 145 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 09f5db6..8605de4 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -110,119 +110,176 @@ impl Request<'_> { } Ok(response) } - pub fn get_post_text_form_key( + + pub fn get_post_data( &self, keys: &[&str], data: &Data, - ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - let mut post_type = self + ) -> Result<HashMap<&str, Result<Vec<u8>, ParseFormError>>, ParseFormError> { + let mut post_type = if let Some(val) = self .headers .iter() .find(|header| header.contains("Content-Type: ")) - .unwrap() - .to_string(); - - post_type = post_type - .strip_prefix("Content-Type: ") - .unwrap() - .to_string(); - - let post_type: Vec<&str> = post_type.trim().split(';').collect(); - let mime_type = post_type[0].parse::<Mime>().unwrap(); - - let mut result = HashMap::new(); - match mime_type { - Mime::ApplicationXWwwFormUrlencoded => { - let data = String::from_utf8(data.buffer.clone()).unwrap(); - let kvps = data - .split("&") - .map(|kvp| kvp.split_once("=").unwrap()) - .collect::<HashMap<&str, &str>>(); - - for key in keys { - let entry = if let Some(val) = kvps.get(key) { - Ok(*val) - } else { - Err(ParseFormError { - error: ParseErrors::NoData, - }) - }; - result.insert(*key, entry); - } - Ok(result) + { + if let Ok(Type) = val.strip_prefix("Content-Type: ").unwrap().trim().parse() { + Type + } else { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); } - Mime::MultipartFormData => { - let from_req = post_type[1..] - .iter() - .find(|val| val.contains("boundary=")) - .unwrap() - .strip_prefix("boundary=") - .unwrap(); - let mut boundary = b"--".to_vec(); - boundary.extend_from_slice(from_req.trim_matches('"').as_bytes()); - let mut end_boundary = boundary.clone(); - end_boundary.extend_from_slice(b"--"); - boundary.extend_from_slice(&[b'\r']); - let parts = data - .buffer - .split(|byte| byte == &b'\n') - .collect::<Vec<&[u8]>>(); + } else { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); + }; - let mut boundary_found = false; - let mut current_key: Option<usize> = None; - let mut result: Vec<Vec<u8>> = vec!["".into(); keys.len()]; - for part in parts { - if part == [] { - continue; - } - if part == end_boundary { - break; - } - if !boundary_found && part == boundary { - current_key = None; - boundary_found = true; - continue; - } - if part.starts_with(b"Content-Disposition: form-data; name=") { - let headers = part - .split(|byte| byte == &b';') - .filter(|header| !header.is_empty()) - .collect::<Vec<_>>(); - if headers.len() < 2 { - continue; - } - let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); - if name.len() != 2 { - continue; - } - let mkey = String::from_utf8_lossy(name[1]) - .to_string() - .trim_end() - .trim_matches('"') - .to_owned(); - let mut index = 0; - for i in keys { - if *i == mkey { - current_key = Some(index); - } - index += 1; - } - boundary_found = false; - } else if let Some(key) = current_key { - result[key].extend_from_slice(part); - result[key].extend_from_slice(&[b'\n']); - } - } - Ok(result - .iter() - .map(|arr| String::from_utf8(arr.to_vec()).unwrap().trim().into()) - .collect()) - } + let data = data.buffer.as_slice(); + let mut keymap: HashMap<&str, Option<&[u8]>> = HashMap::with_capacity(keys.len()); + for i in keys { + keymap.entry(i).or_default(); + } + match post_type { + Mime::ApplicationXWwwFormUrlencoded => Ok(()), _ => Err(ParseFormError { error: ParseErrors::BadData, }), - } + }; + todo!() } + + // pub fn get_post_text_form_key( + // &self, + // keys: &[&str], + // data: &Data, + // ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { + // let mut post_type = self + // .headers + // .iter() + // .find(|header| header.contains("Content-Type: ")) + // .unwrap() + // .to_string(); + // + // post_type = post_type + // .strip_prefix("Content-Type: ") + // .unwrap() + // .to_string(); + // + // let post_type: Vec<&str> = post_type.trim().split(';').collect(); + // let mime_type = post_type[0].parse().unwrap(); + // + // // let data = String::from_utf8(vec) + // + // let mut result = HashMap::new(); + // match mime_type { + // Mime::ApplicationXWwwFormUrlencoded => { + // let data = String::from_utf8(data.buffer.clone()).unwrap(); + // let kvps = data + // .split("&") + // .map(|kvp| kvp.split_once("=").unwrap()) + // .collect::<HashMap<&str, &str>>(); + // + // for key in keys { + // let entry = if let Some(val) = kvps.get(key) { + // Ok(*val) + // } else { + // Err(ParseFormError { + // error: ParseErrors::NoData, + // }) + // }; + // result.insert(*key, entry); + // } + // Ok(result) + // } + // Mime::MultipartFormData => { + // let from_req = post_type[1..] + // .iter() + // .find(|val| val.contains("boundary=")) + // .unwrap() + // .strip_prefix("boundary=") + // .unwrap(); + // let mut boundary = b"--".to_vec(); + // boundary.extend_from_slice(from_req.trim_matches('"').as_bytes()); + // let mut end_boundary = boundary.clone(); + // end_boundary.extend_from_slice(b"--"); + // boundary.extend_from_slice(&[b'\r']); + // let parts = data + // .buffer + // .split(|byte| byte == &b'\n') + // .collect::<Vec<&[u8]>>(); + // + // let mut boundary_found = false; + // let mut current_key: Option<&str> = None; + // let mut result: HashMap<&str, Vec<u8>> = HashMap::new(); + // for part in parts { + // if part == [] { + // continue; + // } + // if part == end_boundary { + // break; + // } + // if !boundary_found && part == boundary { + // current_key = None; + // boundary_found = true; + // continue; + // } + // if part.starts_with(b"Content-Disposition: form-data; name=") { + // let headers = part + // .split(|byte| byte == &b';') + // .filter(|header| !header.is_empty()) + // .collect::<Vec<_>>(); + // if headers.len() < 2 { + // continue; + // } + // let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); + // if name.len() != 2 { + // continue; + // } + // let mkey = String::from_utf8_lossy(name[1]) + // .as_ref() + // .trim_end() + // .trim_matches('"') + // .to_owned(); + // for i in keys { + // if *i == mkey { + // current_key = Some(&mkey); + // } + // } + // boundary_found = false; + // } else if let Some(key) = current_key { + // if None == result.get(key) { + // result.insert(key, part.to_vec()); + // continue; + // } + // result.get_mut(key).unwrap().extend_from_slice(part); + // } + // } + // if result.len() == 0 { + // return Err(ParseFormError { + // error: ParseErrors::NoData, + // }); + // } + // let return_result: HashMap<&str, Result<&str, ParseErrors>> = + // HashMap::with_capacity(keys.len()); + // + // for key in keys { + // let val = result.get(key).ok_or(ParseFormError { + // error: ParseErrors::NoData, + // }).map(|value| String::from_utf8(value)) + // let val = if let Ok(str) = String::from_utf8(val) { + // Ok(str) + // } else { + // Err(ParseFormError { + // error: ParseErrors::BadData, + // }) + // }; + // } + // } + // _ => Err(ParseFormError { + // error: ParseErrors::BadData, + // }), + // } + // } } #[cfg(test)] @@ -231,38 +288,38 @@ mod test { use super::Request; - #[test] - fn try_post_text() { - let req = Request { - uri: "", - headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], - method: crate::handling::methods::Method::Post, - }; - let data = Data { - buffer: b"--boundary\r -Content-Disposition: form-data; name=\"field1\"\r -\r -value1\r ---boundary\r -Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\n\r -\r -value2\r ---boundary--" - .to_vec(), - is_complete: true, - }; - assert_eq!( - "value1", - req.get_post_text_form_key(&["field1"], &data).unwrap()[0] - ); - assert_eq!( - "value2", - req.get_post_text_form_key(&["field2"], &data).unwrap()[0] - ); - assert_eq!( - vec!["value1", "value2"], - req.get_post_text_form_key(&["field1", "field2"], &data) - .unwrap() - ) - } + // #[test] + // fn try_post_text() { + // let req = Request { + // uri: "", + // headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], + // method: crate::handling::methods::Method::Post, + // }; + // let data = Data { + // buffer: b"--boundary\r + // Content-Disposition: form-data; name=\"field1\"\r + // \r + // value1\r + // --boundary\r + // Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\n\r + // \r + // value2\r + // --boundary--" + // .to_vec(), + // is_complete: true, + // }; + // assert_eq!( + // "value1", + // req.get_post_text_form_key(&["field1"], &data).unwrap()[0] + // ); + // assert_eq!( + // "value2", + // req.get_post_text_form_key(&["field2"], &data).unwrap()[0] + // ); + // assert_eq!( + // vec!["value1", "value2"], + // req.get_post_text_form_key(&["field1", "field2"], &data) + // .unwrap() + // ) + // } } diff --git a/site/src/main.rs b/site/src/main.rs index 1005312..2a16f43 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -3,17 +3,17 @@ use std::{collections::HashMap, path::PathBuf}; use http::handling::{ file_handlers::NamedFile, methods::Method, - request::Request, + request::{Request, ParseFormError}, response::{Outcome, Response, ResponseBody, Status}, routes::{Data, Route}, }; -fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { +fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> String { let mut result = String::new(); for (key, value) in map { result.push_str(key); result.push('='); - result.push_str(value); + result.push_str(value.as_ref().unwrap()); result.push(';'); } result.pop(); // Remove the trailing semicolon if desired @@ -21,7 +21,7 @@ fn hashmap_to_string(map: &HashMap<&str, &str>) -> String { } fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { - let keys = if let Ok(keys) = request.get_get_form_keys(vec!["asdf", "jkl"]) { + let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { keys } else { return Outcome::Forward(data); @@ -62,12 +62,12 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da if data.is_empty() { return Outcome::Forward(data); } - let data = if let Ok(val) = request.get_post_text_form_key("message", &data) { - val - } else { - return Outcome::Failure(Status::BadRequest); - }; - let dat = post_hi(data); + // let data = if let Ok(val) = request.get_post_text_form_key("message", &data) { + // val + // } else { + // return Outcome::Failure(Status::BadRequest); + // }; + let dat = post_hi(String::from_utf8(data.buffer).unwrap()); Outcome::Success(Response { headers: vec![ format!("Content-Length: {}", dat.len()), -- GitLab From d13f46590211d721046f7dadd233d6224b5674cb Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 10 Jun 2023 21:34:37 +0200 Subject: [PATCH 30/65] working on post --- core/http/src/handling/request.rs | 120 +++++++++++++++++------------- core/http/src/setup.rs | 2 +- 2 files changed, 71 insertions(+), 51 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 8605de4..a74685f 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -30,13 +30,13 @@ pub enum MediaType { Html, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ParseErrors { NoData, BadData, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct ParseFormError { pub error: ParseErrors, } @@ -111,18 +111,18 @@ impl Request<'_> { Ok(response) } - pub fn get_post_data( - &self, - keys: &[&str], + pub fn get_post_data<'a>( + &'a self, + keys: &[&'a str], data: &Data, ) -> Result<HashMap<&str, Result<Vec<u8>, ParseFormError>>, ParseFormError> { - let mut post_type = if let Some(val) = self + let post_type = if let Some(val) = self .headers .iter() .find(|header| header.contains("Content-Type: ")) { - if let Ok(Type) = val.strip_prefix("Content-Type: ").unwrap().trim().parse() { - Type + if let Ok(mime_type) = val.strip_prefix("Content-Type: ").unwrap().trim().parse() { + mime_type } else { return Err(ParseFormError { error: ParseErrors::NoData, @@ -135,17 +135,52 @@ impl Request<'_> { }; let data = data.buffer.as_slice(); - let mut keymap: HashMap<&str, Option<&[u8]>> = HashMap::with_capacity(keys.len()); - for i in keys { - keymap.entry(i).or_default(); + let mut keymap: HashMap<&str, Result<Vec<u8>, ParseFormError>> = + HashMap::with_capacity(keys.len()); + for key in keys { + keymap.entry(key).or_insert(Err(ParseFormError { + error: ParseErrors::NoData, + })); } match post_type { - Mime::ApplicationXWwwFormUrlencoded => Ok(()), - _ => Err(ParseFormError { - error: ParseErrors::BadData, - }), + Mime::ApplicationXWwwFormUrlencoded => { + for kvp in data.split(|byte| *byte == b'&') { + let kvp = kvp.split(|byte| *byte == b'=').collect::<Vec<&[u8]>>(); + let key = if let Some(kv) = kvp.get(0) { + kv + } else { + return Err(ParseFormError { + error: ParseErrors::BadData, + }); + }; + let key = if let Ok(kv) = String::from_utf8(key.to_vec()) { + kv + } else { + return Err(ParseFormError { + error: ParseErrors::BadData, + }); + }; + let value = kvp.get(1).ok_or(ParseFormError { + error: ParseErrors::NoData, + }); + let thing = if let Some(val) = keymap.get_mut(key.as_str()) { + val + } else { + continue; + }; + *thing = match value { + Ok(val) => Ok(val.to_vec()), + Err(err) => Err(err), + } + } + } + _ => { + return Err(ParseFormError { + error: ParseErrors::BadData, + }) + } }; - todo!() + Ok(keymap) } // pub fn get_post_text_form_key( @@ -288,38 +323,23 @@ mod test { use super::Request; - // #[test] - // fn try_post_text() { - // let req = Request { - // uri: "", - // headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], - // method: crate::handling::methods::Method::Post, - // }; - // let data = Data { - // buffer: b"--boundary\r - // Content-Disposition: form-data; name=\"field1\"\r - // \r - // value1\r - // --boundary\r - // Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\n\r - // \r - // value2\r - // --boundary--" - // .to_vec(), - // is_complete: true, - // }; - // assert_eq!( - // "value1", - // req.get_post_text_form_key(&["field1"], &data).unwrap()[0] - // ); - // assert_eq!( - // "value2", - // req.get_post_text_form_key(&["field2"], &data).unwrap()[0] - // ); - // assert_eq!( - // vec!["value1", "value2"], - // req.get_post_text_form_key(&["field1", "field2"], &data) - // .unwrap() - // ) - // } + #[test] + fn try_post_text() { + let req = Request { + uri: "", + headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], + method: crate::handling::methods::Method::Post, + }; + let data = Data { + buffer: b"message=23&message1=24".to_vec(), + is_complete: true, + }; + assert_eq!( + vec![&Ok(b"23".to_vec()), &Ok(b"24".to_vec())], + req.get_post_data(&["message", "message1"], &data) + .unwrap() + .values() + .collect::<Vec<_>>() + ); + } } diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 50f5144..4a3914b 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -96,7 +96,7 @@ pub async fn build(ip: &str) -> Config { let ip = ip[0]; let workers = available_parallelism().unwrap().get(); println!( - "\x1b[34mâš™ Configuration\x1b[0m +"\x1b[34mâš™ Configuration\x1b[0m >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} -- GitLab From 206fa32e55ebe2fa88abe91c8646d4880822b733 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 12 Jun 2023 20:57:22 +0200 Subject: [PATCH 31/65] finishing the `get_post_data()` function for binary Applicacation-x-www-urlencoded forms --- core/http/src/handling/request.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index a74685f..5267de1 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -145,7 +145,10 @@ impl Request<'_> { match post_type { Mime::ApplicationXWwwFormUrlencoded => { for kvp in data.split(|byte| *byte == b'&') { - let kvp = kvp.split(|byte| *byte == b'=').collect::<Vec<&[u8]>>(); + let kvp = kvp + .split(|byte| *byte == b'=') + .map(|list| list.to_vec()) + .collect::<Vec<Vec<u8>>>(); let key = if let Some(kv) = kvp.get(0) { kv } else { @@ -331,7 +334,7 @@ mod test { method: crate::handling::methods::Method::Post, }; let data = Data { - buffer: b"message=23&message1=24".to_vec(), + buffer: b"message=24&message1=23".to_vec(), is_complete: true, }; assert_eq!( -- GitLab From 9b412c1916f8532987dc070d0dfff90984ca5afc Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 13 Jun 2023 21:30:47 +0200 Subject: [PATCH 32/65] Add test for multipart form data and boundary getter in request.rs --- core/http/src/handling/request.rs | 64 ++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 5267de1..760bd3c 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -116,16 +116,26 @@ impl Request<'_> { keys: &[&'a str], data: &Data, ) -> Result<HashMap<&str, Result<Vec<u8>, ParseFormError>>, ParseFormError> { + let boundary; let post_type = if let Some(val) = self .headers .iter() .find(|header| header.contains("Content-Type: ")) { - if let Ok(mime_type) = val.strip_prefix("Content-Type: ").unwrap().trim().parse() { - mime_type + let content_type = val.trim().strip_prefix("Content-Type: ").unwrap(); + let type_vec = content_type.split(';').collect::<Vec<&str>>(); + + boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains("boundary=")) + { + bound.strip_prefix("boundary=").unwrap().trim_matches('"') + } else { + "" + }; + if let Ok(mime) = type_vec[0].trim().parse() { + mime } else { return Err(ParseFormError { - error: ParseErrors::NoData, + error: ParseErrors::BadData, }); } } else { @@ -177,6 +187,12 @@ impl Request<'_> { } } } + Mime::MultipartFormData => { + let mut temp_bound = "--".to_string(); + temp_bound.push_str(boundary); + let end_boundary = format!("{temp_bound}--").as_bytes().to_owned(); + let boundary = temp_bound.as_bytes(); + } _ => { return Err(ParseFormError { error: ParseErrors::BadData, @@ -334,15 +350,45 @@ mod test { method: crate::handling::methods::Method::Post, }; let data = Data { - buffer: b"message=24&message1=23".to_vec(), + buffer: b"message=23&message1=24".to_vec(), is_complete: true, }; + let map = req.get_post_data(&["message", "message1"], &data).unwrap(); + assert_eq!( + &b"23".to_vec(), + map.get("message").unwrap().as_ref().unwrap() + ); + assert_eq!( + &b"24".to_vec(), + map.get("message1").unwrap().as_ref().unwrap() + ); + let req = Request { + uri: "", + headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], + method: crate::handling::methods::Method::Post, + }; + let data = Data { + buffer: b"--boundary +Content-Disposition: form-data; name=\"field1\" + +value1 +--boundary +Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\" + +value2 +--boundary-- +" + .to_vec(), + is_complete: true, + }; + let map = req.get_post_data(&["field1, field2"], &data).unwrap(); + assert_eq!( + &b"value1".to_vec(), + map.get("field1").unwrap().as_ref().unwrap() + ); assert_eq!( - vec![&Ok(b"23".to_vec()), &Ok(b"24".to_vec())], - req.get_post_data(&["message", "message1"], &data) - .unwrap() - .values() - .collect::<Vec<_>>() + &b"value2".to_vec(), + map.get("field2").unwrap().as_ref().unwrap() ); } } -- GitLab From 663495413df6bf632a63e574c9d1e7562145d3e7 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 14 Jun 2023 22:48:37 +0200 Subject: [PATCH 33/65] Finishing `get_post_data()` TODO: Add '\n' detector in thing --- core/http/src/handling/request.rs | 203 ++++++++++-------------------- 1 file changed, 63 insertions(+), 140 deletions(-) diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 760bd3c..030188e 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -115,7 +115,7 @@ impl Request<'_> { &'a self, keys: &[&'a str], data: &Data, - ) -> Result<HashMap<&str, Result<Vec<u8>, ParseFormError>>, ParseFormError> { + ) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> { let boundary; let post_type = if let Some(val) = self .headers @@ -145,13 +145,15 @@ impl Request<'_> { }; let data = data.buffer.as_slice(); - let mut keymap: HashMap<&str, Result<Vec<u8>, ParseFormError>> = + let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> = HashMap::with_capacity(keys.len()); + for key in keys { - keymap.entry(key).or_insert(Err(ParseFormError { + keymap.entry(key.to_string()).or_insert(Err(ParseFormError { error: ParseErrors::NoData, })); } + match post_type { Mime::ApplicationXWwwFormUrlencoded => { for kvp in data.split(|byte| *byte == b'&') { @@ -189,9 +191,63 @@ impl Request<'_> { } Mime::MultipartFormData => { let mut temp_bound = "--".to_string(); - temp_bound.push_str(boundary); + temp_bound.push_str(&format!("{boundary}")); let end_boundary = format!("{temp_bound}--").as_bytes().to_owned(); let boundary = temp_bound.as_bytes(); + let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>(); + + let mut current_key: Option<String> = None; + let mut boundary_found = true; + for part in parts { + if part == [] { + continue; + } + if part == end_boundary { + break; + } + if !boundary_found && part == boundary { + boundary_found = true; + current_key = None; + continue; + } + if part.starts_with(b"Content-Disposition: form-data; name=") { + let headers = part + .split(|byte| byte == &b';') + .filter(|header| !header.is_empty()) + .collect::<Vec<_>>(); + if headers.len() < 2 { + continue; + } + let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); + if name.len() != 2 { + continue; + } + let mkey = String::from_utf8_lossy(name[1]) + .as_ref() + .trim_end() + .trim_matches('"') + .to_owned(); + + if keymap.contains_key::<str>(&mkey) { + current_key = Some(mkey.to_owned()); + } + boundary_found = false; + continue; + } else if let Some(key) = ¤t_key { + if let Some(val) = keymap.get::<str>(&key) { + if let Err(_) = val { + keymap.insert(key.to_string(), Ok(part.to_vec())); + continue; + } + keymap + .get_mut(key) + .unwrap() + .as_mut() + .unwrap() + .extend_from_slice(part); + } + } + } } _ => { return Err(ParseFormError { @@ -201,139 +257,6 @@ impl Request<'_> { }; Ok(keymap) } - - // pub fn get_post_text_form_key( - // &self, - // keys: &[&str], - // data: &Data, - // ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - // let mut post_type = self - // .headers - // .iter() - // .find(|header| header.contains("Content-Type: ")) - // .unwrap() - // .to_string(); - // - // post_type = post_type - // .strip_prefix("Content-Type: ") - // .unwrap() - // .to_string(); - // - // let post_type: Vec<&str> = post_type.trim().split(';').collect(); - // let mime_type = post_type[0].parse().unwrap(); - // - // // let data = String::from_utf8(vec) - // - // let mut result = HashMap::new(); - // match mime_type { - // Mime::ApplicationXWwwFormUrlencoded => { - // let data = String::from_utf8(data.buffer.clone()).unwrap(); - // let kvps = data - // .split("&") - // .map(|kvp| kvp.split_once("=").unwrap()) - // .collect::<HashMap<&str, &str>>(); - // - // for key in keys { - // let entry = if let Some(val) = kvps.get(key) { - // Ok(*val) - // } else { - // Err(ParseFormError { - // error: ParseErrors::NoData, - // }) - // }; - // result.insert(*key, entry); - // } - // Ok(result) - // } - // Mime::MultipartFormData => { - // let from_req = post_type[1..] - // .iter() - // .find(|val| val.contains("boundary=")) - // .unwrap() - // .strip_prefix("boundary=") - // .unwrap(); - // let mut boundary = b"--".to_vec(); - // boundary.extend_from_slice(from_req.trim_matches('"').as_bytes()); - // let mut end_boundary = boundary.clone(); - // end_boundary.extend_from_slice(b"--"); - // boundary.extend_from_slice(&[b'\r']); - // let parts = data - // .buffer - // .split(|byte| byte == &b'\n') - // .collect::<Vec<&[u8]>>(); - // - // let mut boundary_found = false; - // let mut current_key: Option<&str> = None; - // let mut result: HashMap<&str, Vec<u8>> = HashMap::new(); - // for part in parts { - // if part == [] { - // continue; - // } - // if part == end_boundary { - // break; - // } - // if !boundary_found && part == boundary { - // current_key = None; - // boundary_found = true; - // continue; - // } - // if part.starts_with(b"Content-Disposition: form-data; name=") { - // let headers = part - // .split(|byte| byte == &b';') - // .filter(|header| !header.is_empty()) - // .collect::<Vec<_>>(); - // if headers.len() < 2 { - // continue; - // } - // let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); - // if name.len() != 2 { - // continue; - // } - // let mkey = String::from_utf8_lossy(name[1]) - // .as_ref() - // .trim_end() - // .trim_matches('"') - // .to_owned(); - // for i in keys { - // if *i == mkey { - // current_key = Some(&mkey); - // } - // } - // boundary_found = false; - // } else if let Some(key) = current_key { - // if None == result.get(key) { - // result.insert(key, part.to_vec()); - // continue; - // } - // result.get_mut(key).unwrap().extend_from_slice(part); - // } - // } - // if result.len() == 0 { - // return Err(ParseFormError { - // error: ParseErrors::NoData, - // }); - // } - // let return_result: HashMap<&str, Result<&str, ParseErrors>> = - // HashMap::with_capacity(keys.len()); - // - // for key in keys { - // let val = result.get(key).ok_or(ParseFormError { - // error: ParseErrors::NoData, - // }).map(|value| String::from_utf8(value)) - // let val = if let Ok(str) = String::from_utf8(val) { - // Ok(str) - // } else { - // Err(ParseFormError { - // error: ParseErrors::BadData, - // }) - // }; - // } - // } - // _ => Err(ParseFormError { - // error: ParseErrors::BadData, - // }), - // } - // } } #[cfg(test)] @@ -375,19 +298,19 @@ value1 --boundary Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\" -value2 +value2\n --boundary-- " .to_vec(), is_complete: true, }; - let map = req.get_post_data(&["field1, field2"], &data).unwrap(); + let map = req.get_post_data(&["field1", "field2"], &data).unwrap(); assert_eq!( &b"value1".to_vec(), map.get("field1").unwrap().as_ref().unwrap() ); assert_eq!( - &b"value2".to_vec(), + &b"value2\n".to_vec(), map.get("field2").unwrap().as_ref().unwrap() ); } -- GitLab From f85cfb4d5ea1f05a03dcbafbf88c14e6f2d5c4b1 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 15 Jun 2023 16:20:18 +0200 Subject: [PATCH 34/65] Complete `get_post_data()` implementation set maximum post body size to 4196 bytes --- core/http/src/handlers/handlers.rs | 9 +- core/http/src/handling/request.rs | 162 +++++++++++++++++------------ site/404.html | 2 +- site/src/main.rs | 11 +- 4 files changed, 106 insertions(+), 78 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 63172c5..4459968 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -10,6 +10,8 @@ use crate::handling::{ }; use crate::setup::MountPoint; +static MAX_HTTPMESSAGE_SIZE: u16 = 4196; + pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(30); @@ -61,6 +63,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin buffer: vec![], }; if request.can_have_body() { + println!("{:#?}", request.headers); let length = if let Some(len) = request .headers .iter() @@ -87,9 +90,9 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin 0 }; if length != 0 { - let mut buffer: Vec<u8> = vec![]; - buf_reader.read_buf(&mut buffer).await.unwrap(); - if buffer.len() != length { + let mut buffer = vec![0u8; MAX_HTTPMESSAGE_SIZE.into()]; + let read = buf_reader.read(&mut buffer).await.unwrap(); + if read != length { len_not_defined(stream, Status::LengthRequired).await; return; } diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 030188e..ff7e338 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -4,6 +4,8 @@ use std::{collections::HashMap, error::Error, fmt::Display}; use crate::utils::mime::mime_enum::Mime; +static TWO_NEWLINES: u8 = 3; + use super::{ methods::Method, routes::{Data, Uri}, @@ -125,9 +127,13 @@ impl Request<'_> { let content_type = val.trim().strip_prefix("Content-Type: ").unwrap(); let type_vec = content_type.split(';').collect::<Vec<&str>>(); - boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains("boundary=")) + boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains(" boundary=")) { - bound.strip_prefix("boundary=").unwrap().trim_matches('"') + bound + .strip_prefix(" boundary=") + .unwrap() + .trim_end() + .trim_matches('"') } else { "" }; @@ -192,62 +198,10 @@ impl Request<'_> { Mime::MultipartFormData => { let mut temp_bound = "--".to_string(); temp_bound.push_str(&format!("{boundary}")); - let end_boundary = format!("{temp_bound}--").as_bytes().to_owned(); + let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); + temp_bound.push('\r'); let boundary = temp_bound.as_bytes(); - let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>(); - - let mut current_key: Option<String> = None; - let mut boundary_found = true; - for part in parts { - if part == [] { - continue; - } - if part == end_boundary { - break; - } - if !boundary_found && part == boundary { - boundary_found = true; - current_key = None; - continue; - } - if part.starts_with(b"Content-Disposition: form-data; name=") { - let headers = part - .split(|byte| byte == &b';') - .filter(|header| !header.is_empty()) - .collect::<Vec<_>>(); - if headers.len() < 2 { - continue; - } - let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); - if name.len() != 2 { - continue; - } - let mkey = String::from_utf8_lossy(name[1]) - .as_ref() - .trim_end() - .trim_matches('"') - .to_owned(); - - if keymap.contains_key::<str>(&mkey) { - current_key = Some(mkey.to_owned()); - } - boundary_found = false; - continue; - } else if let Some(key) = ¤t_key { - if let Some(val) = keymap.get::<str>(&key) { - if let Err(_) = val { - keymap.insert(key.to_string(), Ok(part.to_vec())); - continue; - } - keymap - .get_mut(key) - .unwrap() - .as_mut() - .unwrap() - .extend_from_slice(part); - } - } - } + Request::get_multipart(data, boundary, &end_boundary, &mut keymap); } _ => { return Err(ParseFormError { @@ -257,6 +211,77 @@ impl Request<'_> { }; Ok(keymap) } + fn get_multipart( + data: &[u8], + boundary: &[u8], + end_boundary: &[u8], + map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>, + ) { + let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>(); + let mut current_part: Vec<&[u8]> = vec![]; + let mut current_key: Option<String> = None; + let mut ignore_line = 0; + for part in parts { + if part == &[b'\r'] { + if let Some(_) = current_key { + if ignore_line >= TWO_NEWLINES { + current_part.push(&[b'\n']); + continue; + } + ignore_line += 1; + } + continue; + } + if part == end_boundary { + if let Some(key) = ¤t_key { + let mut part = current_part.join(&b'\n'); + if part.ends_with(&[b'\r']) { + part.pop(); + } + map.insert(key.to_string(), Ok(part)); + } + break; + } + if part == boundary { + if let Some(key) = ¤t_key { + let mut part = current_part.join(&b'\n'); + if part.ends_with(&[b'\r']) { + part.pop(); + } + map.insert(key.to_string(), Ok(part)); + } + current_part = vec![]; + current_key = None; + ignore_line = 0; + continue; + } + if part.starts_with(b"Content-Disposition: form-data; name=") { + let headers = part + .split(|byte| byte == &b';') + .filter(|header| !header.is_empty()) + .collect::<Vec<_>>(); + if headers.len() < 2 { + continue; + } + let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); + if name.len() != 2 { + continue; + } + let mkey = String::from_utf8_lossy(name[1]) + .as_ref() + .trim_end() + .trim_matches('"') + .to_owned(); + + if map.contains_key::<str>(&mkey) { + current_key = Some(mkey.to_owned()); + } + continue; + } else if let Some(_) = ¤t_key { + current_part.push(part); + } + } + } } #[cfg(test)] @@ -287,30 +312,31 @@ mod test { ); let req = Request { uri: "", - headers: vec!["Content-Type: multipart/form-data;boundary=\"boundary\"".to_string()], + headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], method: crate::handling::methods::Method::Post, }; let data = Data { - buffer: b"--boundary -Content-Disposition: form-data; name=\"field1\" - -value1 ---boundary -Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\" - -value2\n ---boundary-- + buffer: b"--boundary\r +Content-Disposition: form-data; name=\"field1\"\r +\r +value1\r +--boundary\r +Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r +\r +va\nlue2\r +--boundary--\r " .to_vec(), is_complete: true, }; let map = req.get_post_data(&["field1", "field2"], &data).unwrap(); + assert_eq!( &b"value1".to_vec(), map.get("field1").unwrap().as_ref().unwrap() ); assert_eq!( - &b"value2\n".to_vec(), + &b"va\nlue2".to_vec(), map.get("field2").unwrap().as_ref().unwrap() ); } diff --git a/site/404.html b/site/404.html index 7934c61..b5a9b4a 100644 --- a/site/404.html +++ b/site/404.html @@ -7,7 +7,7 @@ <body> <h1>404</h1> <p>Hi from Rust</p> - <form action="/post/post" method="post"> + <form action="/post/post" method="post" enctype="multipart/form-data"> <label for="message">Enter your message:</label> <input type="text" id="message" name="message" /> <button type="submit">Send</button> diff --git a/site/src/main.rs b/site/src/main.rs index 2a16f43..af47897 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -62,12 +62,11 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da if data.is_empty() { return Outcome::Forward(data); } - // let data = if let Ok(val) = request.get_post_text_form_key("message", &data) { - // val - // } else { - // return Outcome::Failure(Status::BadRequest); - // }; - let dat = post_hi(String::from_utf8(data.buffer).unwrap()); + let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { + post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) + } else { + return Outcome::Failure(Status::BadRequest); + }; Outcome::Success(Response { headers: vec![ format!("Content-Length: {}", dat.len()), -- GitLab From f29d3004ef1279097b0f55bef27fe95daf940425 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 16 Jun 2023 18:51:51 +0200 Subject: [PATCH 35/65] Started on working with Multipart data in multipart forms --- core/http/src/handlers/handlers.rs | 4 +--- core/http/src/handling/request.rs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 4459968..0f724cd 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -14,7 +14,7 @@ static MAX_HTTPMESSAGE_SIZE: u16 = 4196; pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); - let mut http_request: Vec<String> = Vec::with_capacity(30); + let mut http_request: Vec<String> = Vec::with_capacity(10); loop { let mut buffer = String::new(); if let Err(_) = buf_reader.read_line(&mut buffer).await { @@ -27,8 +27,6 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin http_request.push(buffer); } - // println!("{:#?}", http_request); - let request_status_line = if let Some(status_line) = http_request.get(0).cloned() { status_line } else { diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index ff7e338..ff4db83 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,6 +2,8 @@ use std::{collections::HashMap, error::Error, fmt::Display}; +use tokio::io::BufReader; + use crate::utils::mime::mime_enum::Mime; static TWO_NEWLINES: u8 = 3; @@ -211,7 +213,16 @@ impl Request<'_> { }; Ok(keymap) } - fn get_multipart( + fn decode_multipart<'a>( + data: &Data, + keys: &[&'a str], + ) -> HashMap<String, Result<Vec<u8>, ParseFormError>> { + let mut data = data.buffer.as_ref(); + let mut buf_reader = BufReader::new(&mut data); + let mut http_request: Vec<String> = Vec::with_capacity(2); + todo!() + } + fn get_multipart_data( data: &[u8], boundary: &[u8], end_boundary: &[u8], -- GitLab From 61af39db1025cc51a83e04eaaf24d769a8aaaa7c Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 17 Jun 2023 18:22:05 +0200 Subject: [PATCH 36/65] Create the FromRequest trait --- core/http/src/handlers/handlers.rs | 4 ++-- core/http/src/handling/request.rs | 33 +++++++++++++++++++----------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 0f724cd..17812fc 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -10,7 +10,7 @@ use crate::handling::{ }; use crate::setup::MountPoint; -static MAX_HTTPMESSAGE_SIZE: u16 = 4196; +static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); @@ -88,7 +88,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin 0 }; if length != 0 { - let mut buffer = vec![0u8; MAX_HTTPMESSAGE_SIZE.into()]; + let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { len_not_defined(stream, Status::LengthRequired).await; diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index ff4db83..171798a 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -2,8 +2,6 @@ use std::{collections::HashMap, error::Error, fmt::Display}; -use tokio::io::BufReader; - use crate::utils::mime::mime_enum::Mime; static TWO_NEWLINES: u8 = 3; @@ -13,7 +11,27 @@ use super::{ routes::{Data, Uri}, }; +pub trait FromRequest: Send { + fn get_data(&self) -> &Self; + fn set_data(&mut self, data: &Self); + fn append(&mut self, data: &Self); +} + +impl FromRequest for Vec<u8> { + fn get_data(&self) -> &Self { + &self + } + + fn set_data(&mut self, data: &Self) { + *self = data.to_vec(); + } + fn append(&mut self, data: &Self) { + self.extend_from_slice(data); + } +} + type HeaderMap = Vec<String>; + #[derive(Clone)] pub struct Request<'a> { pub uri: Uri<'a>, @@ -203,7 +221,7 @@ impl Request<'_> { let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); temp_bound.push('\r'); let boundary = temp_bound.as_bytes(); - Request::get_multipart(data, boundary, &end_boundary, &mut keymap); + Request::get_multipart_data(data, boundary, &end_boundary, &mut keymap); } _ => { return Err(ParseFormError { @@ -213,15 +231,6 @@ impl Request<'_> { }; Ok(keymap) } - fn decode_multipart<'a>( - data: &Data, - keys: &[&'a str], - ) -> HashMap<String, Result<Vec<u8>, ParseFormError>> { - let mut data = data.buffer.as_ref(); - let mut buf_reader = BufReader::new(&mut data); - let mut http_request: Vec<String> = Vec::with_capacity(2); - todo!() - } fn get_multipart_data( data: &[u8], boundary: &[u8], -- GitLab From 94944f321e09b9828101b2f5b953fd54bc2191fe Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 20 Jun 2023 22:22:21 +0200 Subject: [PATCH 37/65] Remove unnecessary println! and updated some of the code Added TODO: file --- TODO.md | 9 +++++++++ core/http/src/handlers/handlers.rs | 1 - core/http/src/handling/request.rs | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c15fe15 --- /dev/null +++ b/TODO.md @@ -0,0 +1,9 @@ +1. If you collect an iterator which you don't index in, you don't need to collect it +2. avoid temporary hashmaps +3. rewrite POST request stuff TICK +4. Client struct +5. Mime-Display new implemented +6. Remove unwraps +7. Reusable allocations + +API design 3. No decisions for the caller diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index 17812fc..b0b2c19 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -61,7 +61,6 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin buffer: vec![], }; if request.can_have_body() { - println!("{:#?}", request.headers); let length = if let Some(len) = request .headers .iter() diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 171798a..5731425 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -221,7 +221,7 @@ impl Request<'_> { let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); temp_bound.push('\r'); let boundary = temp_bound.as_bytes(); - Request::get_multipart_data(data, boundary, &end_boundary, &mut keymap); + Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap); } _ => { return Err(ParseFormError { -- GitLab From 31f29ad0df0af5bedad173bfa5e4114b33aabe86 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 21 Jun 2023 22:03:45 +0200 Subject: [PATCH 38/65] Start working on cookies --- core/http/src/handlers/handlers.rs | 5 ++++- core/http/src/handling/request.rs | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index b0b2c19..df6ec34 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -26,6 +26,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } http_request.push(buffer); } + println!("{:?}", http_request); let request_status_line = if let Some(status_line) = http_request.get(0).cloned() { status_line @@ -33,6 +34,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin eprintln!("\x1b[31mAborting due to missing headers\x1b[0m"); return; }; + let request = Request { uri: if let Some(uri) = &request_status_line.split(" ").nth(1) { *uri @@ -40,6 +42,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; }, + cookies: None, headers: http_request, method: if let Some(method) = request_status_line .split(" ") @@ -53,7 +56,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } else { eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; - } + }, }; let mut data = Data { diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request.rs index 5731425..8841901 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request.rs @@ -37,6 +37,7 @@ pub struct Request<'a> { pub uri: Uri<'a>, pub headers: HeaderMap, pub method: Method, + pub cookies: Option<Vec<&'a str>>, // pub connection: ConnectionMeta, } @@ -316,6 +317,7 @@ mod test { uri: "", headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], method: crate::handling::methods::Method::Post, + cookies: None, }; let data = Data { buffer: b"message=23&message1=24".to_vec(), @@ -334,6 +336,7 @@ mod test { uri: "", headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], method: crate::handling::methods::Method::Post, + cookies: None, }; let data = Data { buffer: b"--boundary\r -- GitLab From af4ab2e39df8ba1d49209303095bfcf3c969d0d9 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 23 Jun 2023 14:41:15 +0200 Subject: [PATCH 39/65] refactoring the request module, add cookie functionality --- core/http/src/handlers/handlers.rs | 4 +- core/http/src/handling/request/cookies.rs | 54 ++++++++++++ core/http/src/handling/request/datatypes.rs | 73 ++++++++++++++++ core/http/src/handling/request/mod.rs | 5 ++ .../{request.rs => request/request_impl.rs} | 84 ++----------------- 5 files changed, 141 insertions(+), 79 deletions(-) create mode 100644 core/http/src/handling/request/cookies.rs create mode 100644 core/http/src/handling/request/datatypes.rs create mode 100644 core/http/src/handling/request/mod.rs rename core/http/src/handling/{request.rs => request/request_impl.rs} (84%) diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handlers.rs index df6ec34..32376bc 100755 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handlers.rs @@ -4,7 +4,7 @@ use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net:: use crate::handling::{ file_handlers::NamedFile, - request::Request, + request::{Request, extract_cookies_from_vec}, response::{Outcome, Response, ResponseBody, Status}, routes::Data, methods::Method, }; @@ -42,7 +42,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; }, - cookies: None, + cookies: extract_cookies_from_vec(&mut http_request), headers: http_request, method: if let Some(method) = request_status_line .split(" ") diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs new file mode 100644 index 0000000..c9d0fcd --- /dev/null +++ b/core/http/src/handling/request/cookies.rs @@ -0,0 +1,54 @@ +use std::collections::HashMap; + +pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> { + let mut cookies: HashMap<String, String> = HashMap::new(); + let mut cookies_string = if let Some(index) = headers + .iter() + .position(|header| header.starts_with("Cookie: ")) + { + headers.remove(index) + } else { + return None; + }; + cookies_string = cookies_string + .strip_prefix("Cookie: ") + .unwrap() + .trim() + .to_string(); + for cookie in cookies_string.split(';') { + let Some((name, cookie)) = cookie.split_once('=') else { + return None; + }; + cookies + .entry(name.trim().to_string()) + .or_insert(cookie.trim().to_string()); + } + Some(cookies) +} + +#[cfg(test)] +mod test { + use crate::handling::request::extract_cookies_from_vec; + + #[test] + fn test_cookies() { + let mut request_vec = vec![ + "GET / HTTP/1.1".to_string(), + "Accept: sdf".to_string(), + "Cookie: io=23; f=as".to_string(), + "Format: gzip".to_string(), + ]; + let right_finished_vec = vec![ + "GET / HTTP/1.1".to_string(), + "Accept: sdf".to_string(), + "Format: gzip".to_string(), + ]; + + let mut wrong = vec!["GET / HTTP/1.1".to_string()]; + assert_eq!(None, extract_cookies_from_vec(&mut wrong)); + let cookies = extract_cookies_from_vec(&mut request_vec); + assert_eq!(right_finished_vec, request_vec); + assert_eq!("23", cookies.clone().unwrap().get("io").unwrap()); + assert_eq!("as", cookies.unwrap().get("f").unwrap()); + } +} diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs new file mode 100644 index 0000000..68e32d5 --- /dev/null +++ b/core/http/src/handling/request/datatypes.rs @@ -0,0 +1,73 @@ +use std::{collections::HashMap, error::Error, fmt::Display}; + +use crate::handling::{methods::Method, routes::Uri}; + +pub trait FromRequest: Send { + fn get_data(&self) -> &Self; + fn set_data(&mut self, data: &Self); + fn append(&mut self, data: &Self); +} + +impl FromRequest for Vec<u8> { + fn get_data(&self) -> &Self { + &self + } + + fn set_data(&mut self, data: &Self) { + *self = data.to_vec(); + } + fn append(&mut self, data: &Self) { + self.extend_from_slice(data); + } +} + +type HeaderMap = Vec<String>; + +#[derive(Clone)] +pub struct Request<'a> { + pub uri: Uri<'a>, + pub headers: HeaderMap, + pub method: Method, + pub cookies: Option<HashMap<String, String>>, + // pub connection: ConnectionMeta, +} + +// struct ConnectionMeta { +// remote: Option<SocketAddr>, +// // certificates +// } + +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum MediaType { + Json, + Plain, + Html, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ParseErrors { + NoData, + BadData, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct ParseFormError { + pub error: ParseErrors, +} + +impl Display for ParseFormError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.error) + } +} + +impl Display for ParseErrors { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseErrors::NoData => write!(f, "No Data at key"), + ParseErrors::BadData => write!(f, "Bad Data at key"), + } + } +} + +impl Error for ParseFormError {} diff --git a/core/http/src/handling/request/mod.rs b/core/http/src/handling/request/mod.rs new file mode 100644 index 0000000..23c9f1a --- /dev/null +++ b/core/http/src/handling/request/mod.rs @@ -0,0 +1,5 @@ +mod cookies; +mod datatypes; +mod request_impl; +pub use cookies::extract_cookies_from_vec; +pub use datatypes::{MediaType, ParseFormError, Request}; diff --git a/core/http/src/handling/request.rs b/core/http/src/handling/request/request_impl.rs similarity index 84% rename from core/http/src/handling/request.rs rename to core/http/src/handling/request/request_impl.rs index 8841901..2c582ea 100644 --- a/core/http/src/handling/request.rs +++ b/core/http/src/handling/request/request_impl.rs @@ -1,85 +1,15 @@ // use std::net::SocketAddr; -use std::{collections::HashMap, error::Error, fmt::Display}; +use std::collections::HashMap; -use crate::utils::mime::mime_enum::Mime; - -static TWO_NEWLINES: u8 = 3; - -use super::{ - methods::Method, - routes::{Data, Uri}, +use crate::{ + handling::{methods::Method, routes::Data}, + utils::mime::mime_enum::Mime, }; -pub trait FromRequest: Send { - fn get_data(&self) -> &Self; - fn set_data(&mut self, data: &Self); - fn append(&mut self, data: &Self); -} - -impl FromRequest for Vec<u8> { - fn get_data(&self) -> &Self { - &self - } - - fn set_data(&mut self, data: &Self) { - *self = data.to_vec(); - } - fn append(&mut self, data: &Self) { - self.extend_from_slice(data); - } -} - -type HeaderMap = Vec<String>; - -#[derive(Clone)] -pub struct Request<'a> { - pub uri: Uri<'a>, - pub headers: HeaderMap, - pub method: Method, - pub cookies: Option<Vec<&'a str>>, - // pub connection: ConnectionMeta, -} - -// struct ConnectionMeta { -// remote: Option<SocketAddr>, -// // certificates -// } +use super::datatypes::{ParseErrors, ParseFormError, Request}; -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum MediaType { - Json, - Plain, - Html, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum ParseErrors { - NoData, - BadData, -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct ParseFormError { - pub error: ParseErrors, -} - -impl Display for ParseFormError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.error) - } -} - -impl Display for ParseErrors { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParseErrors::NoData => write!(f, "No Data at key"), - ParseErrors::BadData => write!(f, "Bad Data at key"), - } - } -} - -impl Error for ParseFormError {} +static TWO_NEWLINES: u8 = 3; impl Request<'_> { pub fn can_have_body(&self) -> bool { @@ -98,7 +28,7 @@ impl Request<'_> { &'a self, keys: &'a [&str], ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - let data = if let Some(val) = self.uri.split_once("?") { + let data = if let Some(val) = self.uri.split_once('?') { val } else { return Err(ParseFormError { -- GitLab From 3a080227419c8ba78515be1c1717e3dcfd61919b Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 23 Jun 2023 15:14:24 +0200 Subject: [PATCH 40/65] Add some documentation --- core/http/src/handling/request/cookies.rs | 1 + core/http/src/setup.rs | 28 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) mode change 100644 => 100755 core/http/src/setup.rs diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs index c9d0fcd..c11bd45 100644 --- a/core/http/src/handling/request/cookies.rs +++ b/core/http/src/handling/request/cookies.rs @@ -45,6 +45,7 @@ mod test { ]; let mut wrong = vec!["GET / HTTP/1.1".to_string()]; + assert_eq!(None, extract_cookies_from_vec(&mut wrong)); let cookies = extract_cookies_from_vec(&mut request_vec); assert_eq!(right_finished_vec, request_vec); diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs old mode 100644 new mode 100755 index 4a3914b..c7f4522 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -65,6 +65,18 @@ impl<'a> Config { } self } + /// # Launches/Starts the webserver + /// Launches a Webserver Configuration + /// + /// Is Async + /// + /// Is Blocking -> Can be interrupted with ^C + /// + /// # Panics + /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there + /// is no interrupt signal + /// + /// Panics if . pub async fn launch(self) { println!("Server launched from http://{}", self.address.local_addr().unwrap()); let mut sigint = signal(SignalKind::interrupt()).unwrap(); @@ -82,6 +94,22 @@ impl<'a> Config { } } } +/// # Creates a Webserver Config which can be launched with the launch function +/// Takes the IP and Port as an argument +/// +/// Prints out the configuration test +/// +/// Is async +/// +/// # Example +/// ``` +/// async fn example() { +/// let _ = http::build("127.0.0.1:8000"); +/// } +/// ``` +/// # Panics +/// Panics if the IP is not bindable, or other forms of system errors or it's not a valid +/// IP-Address pub async fn build(ip: &str) -> Config { let listener = if let Ok(listener) = TcpListener::bind(ip).await { listener -- GitLab From 29bec6fc8f7380dda275cae4e02e91cf040a970a Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 24 Jun 2023 13:27:50 +0200 Subject: [PATCH 41/65] Refactoring the Request struct into it's own seperated module with seprate impl's, fixing some code, ... --- .../src/handlers/{handlers.rs => handler.rs} | 13 +- core/http/src/handlers/mod.rs | 2 +- core/http/src/handling/request/cookies.rs | 52 ++-- core/http/src/handling/request/datatypes.rs | 6 +- core/http/src/handling/request/form_utils.rs | 250 ++++++++++++++++ core/http/src/handling/request/mod.rs | 3 +- .../http/src/handling/request/request_impl.rs | 282 +----------------- .../http/src/handling/request/request_mime.rs | 59 ++++ core/http/src/setup.rs | 4 +- site/src/main.rs | 1 + 10 files changed, 355 insertions(+), 317 deletions(-) rename core/http/src/handlers/{handlers.rs => handler.rs} (96%) mode change 100755 => 100644 create mode 100644 core/http/src/handling/request/form_utils.rs create mode 100644 core/http/src/handling/request/request_mime.rs mode change 100755 => 100644 core/http/src/setup.rs diff --git a/core/http/src/handlers/handlers.rs b/core/http/src/handlers/handler.rs old mode 100755 new mode 100644 similarity index 96% rename from core/http/src/handlers/handlers.rs rename to core/http/src/handlers/handler.rs index 32376bc..67d5117 --- a/core/http/src/handlers/handlers.rs +++ b/core/http/src/handlers/handler.rs @@ -4,7 +4,7 @@ use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net:: use crate::handling::{ file_handlers::NamedFile, - request::{Request, extract_cookies_from_vec}, + request::Request, response::{Outcome, Response, ResponseBody, Status}, routes::Data, methods::Method, }; @@ -26,7 +26,6 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } http_request.push(buffer); } - println!("{:?}", http_request); let request_status_line = if let Some(status_line) = http_request.get(0).cloned() { status_line @@ -35,15 +34,16 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin return; }; - let request = Request { + let mut request = Request { uri: if let Some(uri) = &request_status_line.split(" ").nth(1) { *uri } else { eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; }, - cookies: extract_cookies_from_vec(&mut http_request), + cookies: Request::extract_cookies_from_vec(&mut http_request), headers: http_request, + mime_type: None, method: if let Some(method) = request_status_line .split(" ") .next() { @@ -90,6 +90,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin 0 }; if length != 0 { + request.mime_type = Request::extract_mime_from_vec(&mut request.headers); let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { @@ -152,9 +153,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin resp.extend_from_slice(&response.1); if let Err(e) = stream.write_all(&resp).await { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); - return; - } - return; + } } fn failure_handler(status: Status) -> (String, Vec<u8>) { diff --git a/core/http/src/handlers/mod.rs b/core/http/src/handlers/mod.rs index c3d4495..062ae9d 100644 --- a/core/http/src/handlers/mod.rs +++ b/core/http/src/handlers/mod.rs @@ -1 +1 @@ -pub mod handlers; +pub mod handler; diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs index c11bd45..d078c76 100644 --- a/core/http/src/handling/request/cookies.rs +++ b/core/http/src/handling/request/cookies.rs @@ -1,34 +1,38 @@ use std::collections::HashMap; -pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> { - let mut cookies: HashMap<String, String> = HashMap::new(); - let mut cookies_string = if let Some(index) = headers - .iter() - .position(|header| header.starts_with("Cookie: ")) - { - headers.remove(index) - } else { - return None; - }; - cookies_string = cookies_string - .strip_prefix("Cookie: ") - .unwrap() - .trim() - .to_string(); - for cookie in cookies_string.split(';') { - let Some((name, cookie)) = cookie.split_once('=') else { +use super::Request; + +impl Request<'_> { + pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> { + let mut cookies: HashMap<String, String> = HashMap::new(); + let mut cookies_string = if let Some(index) = headers + .iter() + .position(|header| header.starts_with("Cookie: ")) + { + headers.remove(index) + } else { return None; }; - cookies - .entry(name.trim().to_string()) - .or_insert(cookie.trim().to_string()); + cookies_string = cookies_string + .strip_prefix("Cookie: ") + .unwrap() + .trim() + .to_string(); + for cookie in cookies_string.split(';') { + let Some((name, cookie)) = cookie.split_once('=') else { + return None; + }; + cookies + .entry(name.trim().to_string()) + .or_insert(cookie.trim().to_string()); + } + Some(cookies) } - Some(cookies) } #[cfg(test)] mod test { - use crate::handling::request::extract_cookies_from_vec; + use crate::handling::request::Request; #[test] fn test_cookies() { @@ -46,8 +50,8 @@ mod test { let mut wrong = vec!["GET / HTTP/1.1".to_string()]; - assert_eq!(None, extract_cookies_from_vec(&mut wrong)); - let cookies = extract_cookies_from_vec(&mut request_vec); + assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong)); + let cookies = Request::extract_cookies_from_vec(&mut request_vec); assert_eq!(right_finished_vec, request_vec); assert_eq!("23", cookies.clone().unwrap().get("io").unwrap()); assert_eq!("as", cookies.unwrap().get("f").unwrap()); diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index 68e32d5..6b72da9 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -1,6 +1,9 @@ use std::{collections::HashMap, error::Error, fmt::Display}; -use crate::handling::{methods::Method, routes::Uri}; +use crate::{ + handling::{methods::Method, routes::Uri}, + utils::mime::mime_enum::Mime, +}; pub trait FromRequest: Send { fn get_data(&self) -> &Self; @@ -29,6 +32,7 @@ pub struct Request<'a> { pub headers: HeaderMap, pub method: Method, pub cookies: Option<HashMap<String, String>>, + pub mime_type: Option<Mime>, // pub connection: ConnectionMeta, } diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs new file mode 100644 index 0000000..fef8a4f --- /dev/null +++ b/core/http/src/handling/request/form_utils.rs @@ -0,0 +1,250 @@ +use std::collections::HashMap; + +use crate::{handling::routes::Data, utils::mime::mime_enum::Mime}; + +use super::{datatypes::ParseErrors, ParseFormError, Request}; +static TWO_NEWLINES: u8 = 3; + +impl Request<'_> { + pub fn get_get_form_keys<'a>( + &'a self, + keys: &'a [&str], + ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { + let data = if let Some(val) = self.uri.split_once('?') { + val + } else { + return Err(ParseFormError { + error: ParseErrors::NoData, + }); + }; + let data = data + .1 + .split("&") + .map(|kvp| kvp.split_once("=")) + .collect::<Vec<Option<(&str, &str)>>>(); + + let mut values: HashMap<&str, &str> = HashMap::new(); + for kvp in data { + let kvp = if let Some(kvp) = kvp { + kvp + } else { + continue; + }; + values.insert(kvp.0, kvp.1); + } + let mut response = HashMap::new(); + for key in keys { + let entry = if let Some(val) = values.get(key) { + Ok(*val) + } else { + Err(ParseFormError { + error: ParseErrors::NoData, + }) + }; + response.insert((*key).into(), entry); + } + Ok(response) + } + pub fn get_post_data<'a>( + &'a self, + keys: &[&'a str], + data: &Data, + ) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> { + let data = data.buffer.as_slice(); + let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> = + HashMap::with_capacity(keys.len()); + + for key in keys { + keymap.entry(key.to_string()).or_insert(Err(ParseFormError { + error: ParseErrors::NoData, + })); + } + let Some(mime_type) = self.mime_type else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + + match mime_type { + Mime::ApplicationXWwwFormUrlencoded => { + let Ok(data) = String::from_utf8(data.to_vec()) else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + for kvp in data.split('&') { + let Some(mut kvp) = kvp.split_once('=') else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + + let Some(thing) = keymap.get_mut(kvp.0) else { + continue; + }; + kvp.1 = kvp.1.trim_end_matches('\0'); + + *thing = Ok(kvp.1.as_bytes().to_vec()); + } + } + Mime::MultipartFormData => { + let Some(post_type) = self + .headers + .iter() + .find(|header| header.contains("Content-Type: ")) else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + + let content_type = post_type.trim().strip_prefix("Content-Type: ").unwrap(); + let Some(mut boundary) = content_type.split(';').find(|element| element.trim().starts_with("boundary=")) else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + boundary = boundary + .trim() + .strip_prefix("boundary=") + .unwrap() + .trim_matches('"'); + let mut temp_bound = "--".to_string(); + temp_bound.push_str(&format!("{boundary}")); + let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); + temp_bound.push('\r'); + let boundary = temp_bound.as_bytes(); + Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap); + } + _ => { + return Err(ParseFormError { + error: ParseErrors::BadData, + }) + } + }; + Ok(keymap) + } + fn get_multipart_data( + data: &[u8], + boundary: &[u8], + end_boundary: &[u8], + map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>, + ) { + let mut current_part: Vec<&[u8]> = vec![]; + let mut current_key: Option<String> = None; + let mut ignore_line = 0; + for part in data.split(|byte| byte == &b'\n') { + if part == &[b'\r'] { + if let Some(_) = current_key { + if ignore_line >= TWO_NEWLINES { + current_part.push(&[b'\n']); + continue; + } + ignore_line += 1; + } + continue; + } + if part == end_boundary { + if let Some(key) = ¤t_key { + let mut part = current_part.join(&b'\n'); + if part.ends_with(&[b'\r']) { + part.pop(); + } + map.insert(key.to_string(), Ok(part)); + } + break; + } + if part == boundary { + if let Some(key) = ¤t_key { + let mut part = current_part.join(&b'\n'); + if part.ends_with(&[b'\r']) { + part.pop(); + } + map.insert(key.to_string(), Ok(part)); + } + current_part = vec![]; + current_key = None; + ignore_line = 0; + continue; + } + if part.starts_with(b"Content-Disposition: form-data; name=") { + let headers = part + .split(|byte| byte == &b';') + .filter(|header| !header.is_empty()) + .collect::<Vec<_>>(); + if headers.len() < 2 { + continue; + } + let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); + if name.len() != 2 { + continue; + } + let mkey = String::from_utf8_lossy(name[1]) + .as_ref() + .trim_end() + .trim_matches('"') + .to_owned(); + + if map.contains_key::<str>(&mkey) { + current_key = Some(mkey.to_owned()); + } + continue; + } else if let Some(_) = ¤t_key { + current_part.push(part); + } + } + } +} +#[cfg(test)] +mod test { + use crate::{ + handling::routes::Data, + utils::mime::mime_enum::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, + }; + + use super::Request; + + #[test] + fn try_post_text() { + let req = Request { + uri: "", + headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], + method: crate::handling::methods::Method::Post, + cookies: None, + mime_type: Some(ApplicationXWwwFormUrlencoded), + }; + let data = Data { + buffer: b"message=23&message1=24".to_vec(), + is_complete: true, + }; + let map = req.get_post_data(&["message", "message1"], &data).unwrap(); + assert_eq!( + &b"23".to_vec(), + map.get("message").unwrap().as_ref().unwrap() + ); + assert_eq!( + &b"24".to_vec(), + map.get("message1").unwrap().as_ref().unwrap() + ); + let req = Request { + uri: "", + headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], + method: crate::handling::methods::Method::Post, + cookies: None, + mime_type: Some(MultipartFormData), + }; + let data = Data { + buffer: b"--boundary\r +Content-Disposition: form-data; name=\"field1\"\r +\r +value1\r +--boundary\r +Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r +\r +va\nlue2\r +--boundary--\r +" + .to_vec(), + is_complete: true, + }; + let map = req.get_post_data(&["field1", "field2"], &data).unwrap(); + + assert_eq!( + &b"value1".to_vec(), + map.get("field1").unwrap().as_ref().unwrap() + ); + assert_eq!( + &b"va\nlue2".to_vec(), + map.get("field2").unwrap().as_ref().unwrap() + ); + } +} diff --git a/core/http/src/handling/request/mod.rs b/core/http/src/handling/request/mod.rs index 23c9f1a..54da4ad 100644 --- a/core/http/src/handling/request/mod.rs +++ b/core/http/src/handling/request/mod.rs @@ -1,5 +1,6 @@ mod cookies; mod datatypes; +mod form_utils; mod request_impl; -pub use cookies::extract_cookies_from_vec; +mod request_mime; pub use datatypes::{MediaType, ParseFormError, Request}; diff --git a/core/http/src/handling/request/request_impl.rs b/core/http/src/handling/request/request_impl.rs index 2c582ea..747cd23 100644 --- a/core/http/src/handling/request/request_impl.rs +++ b/core/http/src/handling/request/request_impl.rs @@ -1,15 +1,6 @@ -// use std::net::SocketAddr; +use crate::handling::methods::Method; -use std::collections::HashMap; - -use crate::{ - handling::{methods::Method, routes::Data}, - utils::mime::mime_enum::Mime, -}; - -use super::datatypes::{ParseErrors, ParseFormError, Request}; - -static TWO_NEWLINES: u8 = 3; +use super::Request; impl Request<'_> { pub fn can_have_body(&self) -> bool { @@ -24,273 +15,4 @@ impl Request<'_> { _ => false, } } - pub fn get_get_form_keys<'a>( - &'a self, - keys: &'a [&str], - ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - let data = if let Some(val) = self.uri.split_once('?') { - val - } else { - return Err(ParseFormError { - error: ParseErrors::NoData, - }); - }; - let data = data - .1 - .split("&") - .map(|kvp| kvp.split_once("=")) - .collect::<Vec<Option<(&str, &str)>>>(); - - let mut values: HashMap<&str, &str> = HashMap::new(); - for kvp in data { - let kvp = if let Some(kvp) = kvp { - kvp - } else { - continue; - }; - values.insert(kvp.0, kvp.1); - } - let mut response = HashMap::new(); - for key in keys { - let entry = if let Some(val) = values.get(key) { - Ok(*val) - } else { - Err(ParseFormError { - error: ParseErrors::NoData, - }) - }; - response.insert((*key).into(), entry); - } - Ok(response) - } - - pub fn get_post_data<'a>( - &'a self, - keys: &[&'a str], - data: &Data, - ) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> { - let boundary; - let post_type = if let Some(val) = self - .headers - .iter() - .find(|header| header.contains("Content-Type: ")) - { - let content_type = val.trim().strip_prefix("Content-Type: ").unwrap(); - let type_vec = content_type.split(';').collect::<Vec<&str>>(); - - boundary = if let Some(bound) = type_vec.iter().find(|part| part.contains(" boundary=")) - { - bound - .strip_prefix(" boundary=") - .unwrap() - .trim_end() - .trim_matches('"') - } else { - "" - }; - if let Ok(mime) = type_vec[0].trim().parse() { - mime - } else { - return Err(ParseFormError { - error: ParseErrors::BadData, - }); - } - } else { - return Err(ParseFormError { - error: ParseErrors::NoData, - }); - }; - - let data = data.buffer.as_slice(); - let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> = - HashMap::with_capacity(keys.len()); - - for key in keys { - keymap.entry(key.to_string()).or_insert(Err(ParseFormError { - error: ParseErrors::NoData, - })); - } - - match post_type { - Mime::ApplicationXWwwFormUrlencoded => { - for kvp in data.split(|byte| *byte == b'&') { - let kvp = kvp - .split(|byte| *byte == b'=') - .map(|list| list.to_vec()) - .collect::<Vec<Vec<u8>>>(); - let key = if let Some(kv) = kvp.get(0) { - kv - } else { - return Err(ParseFormError { - error: ParseErrors::BadData, - }); - }; - let key = if let Ok(kv) = String::from_utf8(key.to_vec()) { - kv - } else { - return Err(ParseFormError { - error: ParseErrors::BadData, - }); - }; - let value = kvp.get(1).ok_or(ParseFormError { - error: ParseErrors::NoData, - }); - let thing = if let Some(val) = keymap.get_mut(key.as_str()) { - val - } else { - continue; - }; - *thing = match value { - Ok(val) => Ok(val.to_vec()), - Err(err) => Err(err), - } - } - } - Mime::MultipartFormData => { - let mut temp_bound = "--".to_string(); - temp_bound.push_str(&format!("{boundary}")); - let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); - temp_bound.push('\r'); - let boundary = temp_bound.as_bytes(); - Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap); - } - _ => { - return Err(ParseFormError { - error: ParseErrors::BadData, - }) - } - }; - Ok(keymap) - } - fn get_multipart_data( - data: &[u8], - boundary: &[u8], - end_boundary: &[u8], - map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>, - ) { - let parts = data.split(|byte| byte == &b'\n').collect::<Vec<&[u8]>>(); - let mut current_part: Vec<&[u8]> = vec![]; - let mut current_key: Option<String> = None; - let mut ignore_line = 0; - for part in parts { - if part == &[b'\r'] { - if let Some(_) = current_key { - if ignore_line >= TWO_NEWLINES { - current_part.push(&[b'\n']); - continue; - } - ignore_line += 1; - } - continue; - } - if part == end_boundary { - if let Some(key) = ¤t_key { - let mut part = current_part.join(&b'\n'); - if part.ends_with(&[b'\r']) { - part.pop(); - } - map.insert(key.to_string(), Ok(part)); - } - break; - } - if part == boundary { - if let Some(key) = ¤t_key { - let mut part = current_part.join(&b'\n'); - if part.ends_with(&[b'\r']) { - part.pop(); - } - map.insert(key.to_string(), Ok(part)); - } - current_part = vec![]; - current_key = None; - ignore_line = 0; - continue; - } - if part.starts_with(b"Content-Disposition: form-data; name=") { - let headers = part - .split(|byte| byte == &b';') - .filter(|header| !header.is_empty()) - .collect::<Vec<_>>(); - if headers.len() < 2 { - continue; - } - let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>(); - if name.len() != 2 { - continue; - } - let mkey = String::from_utf8_lossy(name[1]) - .as_ref() - .trim_end() - .trim_matches('"') - .to_owned(); - - if map.contains_key::<str>(&mkey) { - current_key = Some(mkey.to_owned()); - } - continue; - } else if let Some(_) = ¤t_key { - current_part.push(part); - } - } - } -} - -#[cfg(test)] -mod test { - use crate::handling::routes::Data; - - use super::Request; - - #[test] - fn try_post_text() { - let req = Request { - uri: "", - headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], - method: crate::handling::methods::Method::Post, - cookies: None, - }; - let data = Data { - buffer: b"message=23&message1=24".to_vec(), - is_complete: true, - }; - let map = req.get_post_data(&["message", "message1"], &data).unwrap(); - assert_eq!( - &b"23".to_vec(), - map.get("message").unwrap().as_ref().unwrap() - ); - assert_eq!( - &b"24".to_vec(), - map.get("message1").unwrap().as_ref().unwrap() - ); - let req = Request { - uri: "", - headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], - method: crate::handling::methods::Method::Post, - cookies: None, - }; - let data = Data { - buffer: b"--boundary\r -Content-Disposition: form-data; name=\"field1\"\r -\r -value1\r ---boundary\r -Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r -\r -va\nlue2\r ---boundary--\r -" - .to_vec(), - is_complete: true, - }; - let map = req.get_post_data(&["field1", "field2"], &data).unwrap(); - - assert_eq!( - &b"value1".to_vec(), - map.get("field1").unwrap().as_ref().unwrap() - ); - assert_eq!( - &b"va\nlue2".to_vec(), - map.get("field2").unwrap().as_ref().unwrap() - ); - } } diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs new file mode 100644 index 0000000..4fb9f3f --- /dev/null +++ b/core/http/src/handling/request/request_mime.rs @@ -0,0 +1,59 @@ +use crate::utils::mime::mime_enum::Mime; + +use super::datatypes::Request; + +impl Request<'_> { + pub fn extract_mime_from_vec(headers: &Vec<String>) -> Option<Mime> { + let Some(content_type_header) = headers + .iter() + .find(|header| header.starts_with("Content-Type: ")) else { + return None; + }; + let content_type_string = content_type_header + .strip_prefix("Content-Type: ") + .unwrap() + .to_string(); + + let mime; + match content_type_string.split_once(';') { + Some(sub) => { + mime = if let Ok(a) = sub.0.trim().parse() { + a + } else { + return None; + }; + } + None => { + mime = if let Ok(a) = content_type_string.trim().parse() { + a + } else { + return None; + }; + } + } + Some(mime) + } +} + +#[cfg(test)] +mod test { + use crate::{handling::request::Request, utils::mime::mime_enum::Mime}; + + #[test] + pub fn test_mime_parse_from_header_vec() { + let mut right = vec![ + "GET / 23".to_string(), + "SDF:LKJSD:F".to_string(), + "Content-Type: text/plain".to_string(), + "SDF".to_string(), + ]; + let mut wrong = vec!["SDF:LKJSD:F".to_string(), "SDF".to_string()]; + + assert_eq!(None, Request::extract_mime_from_vec(&mut wrong)); + assert_eq!( + Mime::TextPlain, + Request::extract_mime_from_vec(&mut right).unwrap() + ); + assert_eq!(None, Request::extract_mime_from_vec(&mut right)); + } +} diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs old mode 100755 new mode 100644 index c7f4522..30e2bf7 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -3,7 +3,7 @@ use std::thread::available_parallelism; use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, select}; use crate::{ - handlers::handlers::handle_connection, + handlers::handler::handle_connection, handling::routes::{Route, Uri}, }; @@ -75,8 +75,6 @@ impl<'a> Config { /// # Panics /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there /// is no interrupt signal - /// - /// Panics if . pub async fn launch(self) { println!("Server launched from http://{}", self.address.local_addr().unwrap()); let mut sigint = signal(SignalKind::interrupt()).unwrap(); diff --git a/site/src/main.rs b/site/src/main.rs index af47897..f1fda07 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -62,6 +62,7 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da if data.is_empty() { return Outcome::Forward(data); } + println!("{:?}", request.get_post_data(&["message"], &data)); let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) } else { -- GitLab From aaceb700447fce812554eb7417cf561561817858 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 24 Jun 2023 13:49:47 +0200 Subject: [PATCH 42/65] Fixing all `cargo clippy` warnings / making code better --- core/http/src/handlers/handler.rs | 10 +++++----- core/http/src/handling/file_handlers.rs | 6 +++--- core/http/src/handling/request/datatypes.rs | 2 +- core/http/src/handling/request/form_utils.rs | 14 +++++++------- core/http/src/handling/request/request_impl.rs | 13 +++++-------- core/http/src/handling/request/request_mime.rs | 15 +++++++-------- core/http/src/handling/routes.rs | 10 +++++----- core/http/src/setup.rs | 14 ++++++-------- 8 files changed, 39 insertions(+), 45 deletions(-) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 67d5117..99f8300 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -17,7 +17,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut http_request: Vec<String> = Vec::with_capacity(10); loop { let mut buffer = String::new(); - if let Err(_) = buf_reader.read_line(&mut buffer).await { + if buf_reader.read_line(&mut buffer).await.is_err() { eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m"); return; } @@ -35,8 +35,8 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin }; let mut request = Request { - uri: if let Some(uri) = &request_status_line.split(" ").nth(1) { - *uri + uri: if let Some(uri) = &request_status_line.split(' ').nth(1) { + uri } else { eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; @@ -45,7 +45,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin headers: http_request, mime_type: None, method: if let Some(method) = request_status_line - .split(" ") + .split(' ') .next() { if let Ok(ok) = method.parse() { ok @@ -90,7 +90,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin 0 }; if length != 0 { - request.mime_type = Request::extract_mime_from_vec(&mut request.headers); + request.mime_type = Request::extract_mime_from_vec(&request.headers); let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { diff --git a/core/http/src/handling/file_handlers.rs b/core/http/src/handling/file_handlers.rs index 9e34054..8ca0019 100644 --- a/core/http/src/handling/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -18,7 +18,7 @@ impl ResponseBody for NamedFile { } fn get_mime(&self) -> Mime { - self.content_type.clone() + self.content_type } fn get_len(&self) -> usize { @@ -48,8 +48,8 @@ fn proove_path(path: PathBuf) -> PathBuf { PathBuf::from( path.to_str() .unwrap() - .split("/") - .filter(|&val| val != ".." && val != "") + .split('/') + .filter(|&val| val != ".." && !val.is_empty()) .collect::<Vec<&str>>() .join("/"), ) diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index 6b72da9..45f7b7f 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -13,7 +13,7 @@ pub trait FromRequest: Send { impl FromRequest for Vec<u8> { fn get_data(&self) -> &Self { - &self + self } fn set_data(&mut self, data: &Self) { diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index fef8a4f..4163710 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -19,8 +19,8 @@ impl Request<'_> { }; let data = data .1 - .split("&") - .map(|kvp| kvp.split_once("=")) + .split('&') + .map(|kvp| kvp.split_once('=')) .collect::<Vec<Option<(&str, &str)>>>(); let mut values: HashMap<&str, &str> = HashMap::new(); @@ -41,7 +41,7 @@ impl Request<'_> { error: ParseErrors::NoData, }) }; - response.insert((*key).into(), entry); + response.insert(*key, entry); } Ok(response) } @@ -99,7 +99,7 @@ impl Request<'_> { .unwrap() .trim_matches('"'); let mut temp_bound = "--".to_string(); - temp_bound.push_str(&format!("{boundary}")); + temp_bound.push_str(boundary); let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); temp_bound.push('\r'); let boundary = temp_bound.as_bytes(); @@ -123,8 +123,8 @@ impl Request<'_> { let mut current_key: Option<String> = None; let mut ignore_line = 0; for part in data.split(|byte| byte == &b'\n') { - if part == &[b'\r'] { - if let Some(_) = current_key { + if part == [b'\r'] { + if current_key.is_some() { if ignore_line >= TWO_NEWLINES { current_part.push(&[b'\n']); continue; @@ -178,7 +178,7 @@ impl Request<'_> { current_key = Some(mkey.to_owned()); } continue; - } else if let Some(_) = ¤t_key { + } else if current_key.is_some() { current_part.push(part); } } diff --git a/core/http/src/handling/request/request_impl.rs b/core/http/src/handling/request/request_impl.rs index 747cd23..2d2f7cb 100644 --- a/core/http/src/handling/request/request_impl.rs +++ b/core/http/src/handling/request/request_impl.rs @@ -4,15 +4,12 @@ use super::Request; impl Request<'_> { pub fn can_have_body(&self) -> bool { - match self.method { - Method::Post | Method::Put | Method::Patch | Method::Delete => true, - _ => false, - } + matches!( + self.method, + Method::Post | Method::Put | Method::Patch | Method::Delete + ) } pub fn mandatory_body(&self) -> bool { - match self.method { - Method::Post | Method::Put | Method::Patch => true, - _ => false, - } + matches!(self.method, Method::Post | Method::Put | Method::Patch) } } diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 4fb9f3f..94ebc4c 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -3,7 +3,7 @@ use crate::utils::mime::mime_enum::Mime; use super::datatypes::Request; impl Request<'_> { - pub fn extract_mime_from_vec(headers: &Vec<String>) -> Option<Mime> { + pub fn extract_mime_from_vec(headers: &[String]) -> Option<Mime> { let Some(content_type_header) = headers .iter() .find(|header| header.starts_with("Content-Type: ")) else { @@ -14,23 +14,22 @@ impl Request<'_> { .unwrap() .to_string(); - let mime; - match content_type_string.split_once(';') { + let mime = match content_type_string.split_once(';') { Some(sub) => { - mime = if let Ok(a) = sub.0.trim().parse() { + if let Ok(a) = sub.0.trim().parse() { a } else { return None; - }; + } } None => { - mime = if let Ok(a) = content_type_string.trim().parse() { + if let Ok(a) = content_type_string.trim().parse() { a } else { return None; - }; + } } - } + }; Some(mime) } } diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index 9fd3f77..a85261a 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -39,19 +39,19 @@ impl Route<'_> { } } pub fn compare_uri(&self, uri: Uri) -> bool { - let mut iter_comp_str = uri.split("/"); - for true_str in self.uri.split("/") { + let mut iter_comp_str = uri.split('/'); + for true_str in self.uri.split('/') { let comp_str = if let Some(str) = iter_comp_str.next() { str } else { return false; }; - if (true_str.starts_with("<") && true_str.ends_with("..>")) - || (comp_str.starts_with(true_str) && comp_str.contains("?")) + if (true_str.starts_with('<') && true_str.ends_with("..>")) + || (comp_str.starts_with(true_str) && comp_str.contains('?')) { return true; } - if true_str.starts_with("<") && true_str.ends_with(">") { + if true_str.starts_with('<') && true_str.ends_with('>') { continue; } if true_str != comp_str { diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 30e2bf7..417cf3d 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -21,9 +21,8 @@ pub struct Config { impl<'a> Config { fn check_mountpoint_taken(&self, to_insert: Uri) -> bool { if let Some(to_check) = &self.mountpoints { - let len = to_check.len(); - for i in 0..len { - if to_check[i].mountpoint == to_insert { + for i in to_check.iter() { + if i.mountpoint == to_insert { return true; // Found a duplicate &str } } @@ -38,10 +37,9 @@ impl<'a> Config { routes.sort_by(|a, b| a.rank.cmp(&b.rank)); let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint); for (index, route) in routes.iter().enumerate() { - let indent_sign; - match index { - i if i == routes.len() - 1 => indent_sign = "└─", - _ => indent_sign = "├─", + let indent_sign = match index { + i if i == routes.len() - 1 => "└─", + _ => "├─", }; mount_message += &format!( @@ -114,7 +112,7 @@ pub async fn build(ip: &str) -> Config { } else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; - let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); + let ip = ip.splitn(2, ':').collect::<Vec<&str>>(); if ip.len() != 2 { panic!("Invalid IP Address"); } -- GitLab From 901adb2c57f7cfa256a59e53457db5d958450318 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 25 Jun 2023 21:53:57 +0200 Subject: [PATCH 43/65] Add documentation for `request.mime_from_headers()` and `extract_cookies_from_vec()`. Update `mime_from_headers()` to take a mutable reference to self --- core/http/src/handlers/handler.rs | 2 +- core/http/src/handling/request/cookies.rs | 35 ++++++ .../http/src/handling/request/request_mime.rs | 113 ++++++++++++------ core/http/src/lib.rs | 2 +- 4 files changed, 115 insertions(+), 37 deletions(-) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 99f8300..9596f52 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -90,7 +90,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin 0 }; if length != 0 { - request.mime_type = Request::extract_mime_from_vec(&request.headers); + request.mime_from_headers(); let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs index d078c76..b386086 100644 --- a/core/http/src/handling/request/cookies.rs +++ b/core/http/src/handling/request/cookies.rs @@ -3,6 +3,41 @@ use std::collections::HashMap; use super::Request; impl Request<'_> { + /// Extracts the cookies from a Vector and gives back an optional HashMap of Strings + /// + /// Returns none if there are no cookies or there is a problem with the cookies, for example + /// missing a value. + /// + /// Removes the `Cookie: ` Header from the Vector of headers + /// + /// # Examples + /// ``` + /// use http::handling::request::Request; + /// + /// + /// let mut request_vec = vec![ + /// "GET / HTTP/1.1".to_string(), + /// "Accept: sdf".to_string(), + /// "Cookie: io=23; f=as".to_string(), + /// "Format: gzip".to_string(), + /// ]; + /// let right_finished_vec = vec![ + /// "GET / HTTP/1.1".to_string(), + /// "Accept: sdf".to_string(), + /// "Format: gzip".to_string(), + /// ]; + /// + /// let mut wrong = vec!["GET / HTTP/1.1".to_string()]; + /// + /// assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong)); + /// let cookies = Request::extract_cookies_from_vec(&mut request_vec); + /// assert_eq!(right_finished_vec, request_vec); + /// assert_eq!("23", cookies.clone().unwrap().get("io").unwrap()); + /// assert_eq!("as", cookies.unwrap().get("f").unwrap()); + /// ``` + /// + /// # Panics + /// No Panics pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> { let mut cookies: HashMap<String, String> = HashMap::new(); let mut cookies_string = if let Some(index) = headers diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 94ebc4c..03ac55a 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -1,58 +1,101 @@ -use crate::utils::mime::mime_enum::Mime; - use super::datatypes::Request; impl Request<'_> { - pub fn extract_mime_from_vec(headers: &[String]) -> Option<Mime> { - let Some(content_type_header) = headers + /// Sets the `mime_type` of this [`Request`] from the headers. + /// + /// The mime_type can remain none if there isn't a `Content-Type: ` header + /// + /// # Example + /// ``` + /// use http::{ + /// handling::{methods::Method, request::Request}, + /// utils::mime::mime_enum::Mime, + /// }; + /// + /// let mut request = Request { + /// uri: "thing", + /// headers: vec![ + /// "GET / 23".to_string(), + /// "SDF:LKJSD:F".to_string(), + /// "Content-Type: text/plain".to_string(), + /// "SDF".to_string(), + /// ], + /// method: Method::Get, + /// cookies: None, + /// mime_type: None, + /// }; + /// let mut wrong = Request { + /// uri: "thing", + /// headers: vec![ + /// "GET / 23".to_string(), + /// "SDF:LKJSD:F".to_string(), + /// "SDF".to_string(), + /// ], + /// method: Method::Get, + /// cookies: None, + /// mime_type: None, + /// }; + /// request.mime_from_headers(); + /// wrong.mime_from_headers(); + /// assert_eq!(None, wrong.mime_type); + /// assert_eq!(Mime::TextPlain, request.mime_type.unwrap()); + /// ``` + /// # Panics + /// No Panics + pub fn mime_from_headers(&mut self) { + let Some(content_type_header) = self.headers .iter() .find(|header| header.starts_with("Content-Type: ")) else { - return None; + return; }; let content_type_string = content_type_header .strip_prefix("Content-Type: ") .unwrap() .to_string(); - let mime = match content_type_string.split_once(';') { - Some(sub) => { - if let Ok(a) = sub.0.trim().parse() { - a - } else { - return None; - } - } - None => { - if let Ok(a) = content_type_string.trim().parse() { - a - } else { - return None; - } - } + self.mime_type = match content_type_string.split_once(';') { + Some(sub) => sub.0.trim().parse().ok(), + None => content_type_string.trim().parse().ok(), }; - Some(mime) } } #[cfg(test)] mod test { - use crate::{handling::request::Request, utils::mime::mime_enum::Mime}; + use crate::{ + handling::{methods::Method, request::Request}, + utils::mime::mime_enum::Mime, + }; #[test] pub fn test_mime_parse_from_header_vec() { - let mut right = vec![ - "GET / 23".to_string(), - "SDF:LKJSD:F".to_string(), - "Content-Type: text/plain".to_string(), - "SDF".to_string(), - ]; - let mut wrong = vec!["SDF:LKJSD:F".to_string(), "SDF".to_string()]; + let mut request = Request { + uri: "thing", + headers: vec![ + "GET / 23".to_string(), + "SDF:LKJSD:F".to_string(), + "Content-Type: text/plain".to_string(), + "SDF".to_string(), + ], + method: Method::Get, + cookies: None, + mime_type: None, + }; - assert_eq!(None, Request::extract_mime_from_vec(&mut wrong)); - assert_eq!( - Mime::TextPlain, - Request::extract_mime_from_vec(&mut right).unwrap() - ); - assert_eq!(None, Request::extract_mime_from_vec(&mut right)); + let mut wrong = Request { + uri: "thing", + headers: vec![ + "GET / 23".to_string(), + "SDF:LKJSD:F".to_string(), + "SDF".to_string(), + ], + method: Method::Get, + cookies: None, + mime_type: None, + }; + request.mime_from_headers(); + wrong.mime_from_headers(); + assert_eq!(None, wrong.mime_type); + assert_eq!(Mime::TextPlain, request.mime_type.unwrap()); } } diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index a51c899..1d6353a 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -2,7 +2,7 @@ pub mod handlers; pub mod handling; mod setup; -mod utils; +pub mod utils; #[cfg(test)] mod tests {} -- GitLab From 9e2911847edd441a82d68660b5d871e8d8d7ae67 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 29 Jun 2023 21:58:41 +0200 Subject: [PATCH 44/65] New Documentation and changing Request mime type to (Mime, String) for the whole data of the content-type --- core/http/src/handling/request/datatypes.rs | 9 +- core/http/src/handling/request/form_utils.rs | 117 +++++++++++++++--- .../http/src/handling/request/request_mime.rs | 25 ++-- 3 files changed, 125 insertions(+), 26 deletions(-) diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index 45f7b7f..f490137 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -26,13 +26,20 @@ impl FromRequest for Vec<u8> { type HeaderMap = Vec<String>; +/// A struct to handle Requests +/// #[derive(Clone)] pub struct Request<'a> { + /// The requested Uri pub uri: Uri<'a>, + /// All headers of the request that haven't been parsed pub headers: HeaderMap, + /// The methods Request represented with the [Method] pub method: Method, + /// An optional HashMap representation of all Cookies of the request pub cookies: Option<HashMap<String, String>>, - pub mime_type: Option<Mime>, + /// If the has a body it represents the [Mime]-type of the body + pub mime_type: Option<(Mime, String)>, // pub connection: ConnectionMeta, } diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index 4163710..d70b2bc 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -6,6 +6,55 @@ use super::{datatypes::ParseErrors, ParseFormError, Request}; static TWO_NEWLINES: u8 = 3; impl Request<'_> { + /// # Gets data from a get_form as a HashMap + /// + /// # Errors + /// Gives back a [ParseFormError], top level, if there is lacking data + /// + /// If everything is fine on the top level it gives back a HashMap of keys and Results, that + /// indicate wether the key exists with the [ParseFormError] with an error of + /// [ParseErrors::NoData] or wether the key is corrupt with the [ParseErrors::BadData]-Variant + /// + /// # Examples + /// ``` + /// let request = Request { + /// uri: "/form?name=Name&age=Age", + /// headers: vec![], + /// method: Method::Get, + /// cookies: None, + /// mime_type: None, + /// }; + /// let right = request.get_get_form_keys(&["name", "age"]).unwrap(); + /// assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap()); + /// assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap()); + /// + /// let wrong_request = Request { + /// uri: "/form", + /// ..request.clone() + /// }; + /// assert_eq!( + /// Err(ParseFormError { + /// error: ParseErrors::NoData + /// }), + /// wrong_request.get_get_form_keys(&["name", "age"]) + /// ); + /// + /// let bad_data = Request { + /// uri: "/form?age=", + /// ..request.clone() + /// }; + /// let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap(); + /// assert_eq!( + /// &Err(ParseFormError { + /// error: ParseErrors::NoData + /// }), + /// wrong.get("name").unwrap() + /// ); + /// assert_eq!(&Ok(""), wrong.get("age").unwrap()); + /// ``` + /// + /// # Panics + /// No Panics pub fn get_get_form_keys<'a>( &'a self, keys: &'a [&str], @@ -59,11 +108,11 @@ impl Request<'_> { error: ParseErrors::NoData, })); } - let Some(mime_type) = self.mime_type else { + let Some(ref mime_type) = self.mime_type else { return Err(ParseFormError { error: ParseErrors::BadData }); }; - match mime_type { + match mime_type.0 { Mime::ApplicationXWwwFormUrlencoded => { let Ok(data) = String::from_utf8(data.to_vec()) else { return Err(ParseFormError { error: ParseErrors::BadData }); @@ -82,15 +131,7 @@ impl Request<'_> { } } Mime::MultipartFormData => { - let Some(post_type) = self - .headers - .iter() - .find(|header| header.contains("Content-Type: ")) else { - return Err(ParseFormError { error: ParseErrors::BadData }); - }; - - let content_type = post_type.trim().strip_prefix("Content-Type: ").unwrap(); - let Some(mut boundary) = content_type.split(';').find(|element| element.trim().starts_with("boundary=")) else { + let Some(mut boundary) = mime_type.1.split(';').find(|element| element.trim().starts_with("boundary=")) else { return Err(ParseFormError { error: ParseErrors::BadData }); }; boundary = boundary @@ -187,20 +228,61 @@ impl Request<'_> { #[cfg(test)] mod test { use crate::{ - handling::routes::Data, + handling::{ + methods::Method, + request::{datatypes::ParseErrors, ParseFormError}, + routes::Data, + }, utils::mime::mime_enum::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, }; use super::Request; + #[test] + fn try_get_test() { + let request = Request { + uri: "/form?name=Name&age=Age", + headers: vec![], + method: Method::Get, + cookies: None, + mime_type: None, + }; + let right = request.get_get_form_keys(&["name", "age"]).unwrap(); + assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap()); + assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap()); + + let wrong_request = Request { + uri: "/form", + ..request.clone() + }; + assert_eq!( + Err(ParseFormError { + error: ParseErrors::NoData + }), + wrong_request.get_get_form_keys(&["name", "age"]) + ); + + let bad_data = Request { + uri: "/form?age=", + ..request.clone() + }; + let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap(); + assert_eq!( + &Err(ParseFormError { + error: ParseErrors::NoData + }), + wrong.get("name").unwrap() + ); + assert_eq!(&Ok(""), wrong.get("age").unwrap()); + } #[test] fn try_post_text() { let req = Request { uri: "", headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], - method: crate::handling::methods::Method::Post, + method: Method::Post, cookies: None, - mime_type: Some(ApplicationXWwwFormUrlencoded), + mime_type: Some((ApplicationXWwwFormUrlencoded, "".into())), }; let data = Data { buffer: b"message=23&message1=24".to_vec(), @@ -218,9 +300,12 @@ mod test { let req = Request { uri: "", headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], - method: crate::handling::methods::Method::Post, + method: Method::Post, cookies: None, - mime_type: Some(MultipartFormData), + mime_type: Some(( + MultipartFormData, + "charset=UTF-8; boundary=\"boundary\"".into(), + )), }; let data = Data { buffer: b"--boundary\r diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 03ac55a..7551bb7 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -1,6 +1,6 @@ use super::datatypes::Request; -impl Request<'_> { +impl<'a> Request<'a> { /// Sets the `mime_type` of this [`Request`] from the headers. /// /// The mime_type can remain none if there isn't a `Content-Type: ` header @@ -17,7 +17,7 @@ impl Request<'_> { /// headers: vec![ /// "GET / 23".to_string(), /// "SDF:LKJSD:F".to_string(), - /// "Content-Type: text/plain".to_string(), + /// "Content-Type: text/plain; charset=UTF-8".to_string(), /// "SDF".to_string(), /// ], /// method: Method::Get, @@ -38,7 +38,7 @@ impl Request<'_> { /// request.mime_from_headers(); /// wrong.mime_from_headers(); /// assert_eq!(None, wrong.mime_type); - /// assert_eq!(Mime::TextPlain, request.mime_type.unwrap()); + /// assert_eq!((Mime::TextPlain, " charset=UTF-8".to_string()), request.mime_type.unwrap()); /// ``` /// # Panics /// No Panics @@ -48,14 +48,21 @@ impl Request<'_> { .find(|header| header.starts_with("Content-Type: ")) else { return; }; - let content_type_string = content_type_header + let content_type_string: &str = content_type_header .strip_prefix("Content-Type: ") .unwrap() - .to_string(); + .trim(); - self.mime_type = match content_type_string.split_once(';') { - Some(sub) => sub.0.trim().parse().ok(), - None => content_type_string.trim().parse().ok(), + self.mime_type = if let Some(content_type) = content_type_string.split_once(';') { + let Ok(mime) = content_type.0.trim().parse() else { + return; + }; + Some((mime, content_type.1.to_owned())) + } else { + let Ok(mime) = content_type_string.parse() else { + return; + }; + Some((mime, "".to_owned())) }; } } @@ -96,6 +103,6 @@ mod test { request.mime_from_headers(); wrong.mime_from_headers(); assert_eq!(None, wrong.mime_type); - assert_eq!(Mime::TextPlain, request.mime_type.unwrap()); + assert_eq!((Mime::TextPlain, "".to_owned()), request.mime_type.unwrap()); } } -- GitLab From 94752493d1a03d7c7c3172a53279855e9c99203d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 30 Jun 2023 19:23:38 +0200 Subject: [PATCH 45/65] Add write and build function to result, add basis for cookie setting possibility, divide the response module; --- core/http/src/handlers/handler.rs | 69 +++++++++---------- core/http/src/handling/request/form_utils.rs | 6 ++ core/http/src/handling/request/mod.rs | 2 +- core/http/src/handling/response/cookie.rs | 0 core/http/src/handling/response/datatypes.rs | 45 ++++++++++++ core/http/src/handling/response/mod.rs | 12 ++++ core/http/src/handling/response/response.rs | 32 +++++++++ .../{response.rs => response/status.rs} | 66 ------------------ core/http/src/handling/response/traits.rs | 48 +++++++++++++ site/src/main.rs | 20 ++---- 10 files changed, 185 insertions(+), 115 deletions(-) create mode 100644 core/http/src/handling/response/cookie.rs create mode 100644 core/http/src/handling/response/datatypes.rs create mode 100644 core/http/src/handling/response/mod.rs create mode 100644 core/http/src/handling/response/response.rs rename core/http/src/handling/{response.rs => response/status.rs} (84%) create mode 100644 core/http/src/handling/response/traits.rs diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 9596f52..3c46159 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,12 +1,12 @@ -use std::path::PathBuf; +use std::{path::PathBuf, io}; -use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt, AsyncWriteExt}, net::TcpStream}; +use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt}, net::TcpStream}; use crate::handling::{ file_handlers::NamedFile, request::Request, response::{Outcome, Response, ResponseBody, Status}, - routes::Data, methods::Method, + routes::{Data, Body}, methods::Method, }; use crate::setup::MountPoint; @@ -78,13 +78,17 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin size } else { eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } } else { if request.mandatory_body() { eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } 0 @@ -94,7 +98,9 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()]; let read = buf_reader.read(&mut buffer).await.unwrap(); if read != length { - len_not_defined(stream, Status::LengthRequired).await; + if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { + error_occured_when_writing(e) + }; return; } data.is_complete = true; @@ -133,47 +139,40 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let response = match handled_response { Some(val) => match val { - Outcome::Success(success) => ( - format!("HTTP/1.1 {}\r\n", success.status.unwrap_or(Status::Ok)) - + &success.headers.join("\r\n") - + "\r\n\r\n", - if request.method == Method::Head { - vec![] - } else { - success.body.get_data() - } - ), + Outcome::Success(success) => success + , Outcome::Failure(error) => failure_handler(error), Outcome::Forward(_) => failure_handler(Status::NotFound), }, None => failure_handler(Status::NotFound), }; - let mut resp = response.0.as_bytes().to_vec(); - resp.extend_from_slice(&response.1); - if let Err(e) = stream.write_all(&resp).await { + if let Err(e) = response.write(stream, Some(request)).await { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } } -fn failure_handler(status: Status) -> (String, Vec<u8>) { +fn failure_handler<'a>(status: Status) -> Response<'a> { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); - ( - format!( - "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", - status, - page_404.get_len(), - page_404.get_mime() - ), - page_404.get_data(), - ) + Response { + cookies: None, + headers: vec![], + status: Some(status), + body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())), + } } -async fn len_not_defined(mut stream: TcpStream, status: Status) { +async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()>{ let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); - let mut response = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n\r\n", status, page_411.get_len(), page_411.get_mime()).as_bytes().to_vec(); - response.extend_from_slice(&page_411.get_data()); - if let Err(e) = stream.write_all(&response).await { - eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); - } + Response { + cookies: None, + headers: vec![], + status: Some(status), + body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())), + }.write(stream, None).await?; + Ok(()) +} + +fn error_occured_when_writing(e: io::Error) { + eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index d70b2bc..cab16eb 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -17,6 +17,12 @@ impl Request<'_> { /// /// # Examples /// ``` + /// use http::handling::request::Request; + /// use http::handling::request::ParseFormError; + /// use http::handling::request::ParseErrors; + /// use http::handling::methods::Method; + /// + /// /// let request = Request { /// uri: "/form?name=Name&age=Age", /// headers: vec![], diff --git a/core/http/src/handling/request/mod.rs b/core/http/src/handling/request/mod.rs index 54da4ad..f04c57a 100644 --- a/core/http/src/handling/request/mod.rs +++ b/core/http/src/handling/request/mod.rs @@ -3,4 +3,4 @@ mod datatypes; mod form_utils; mod request_impl; mod request_mime; -pub use datatypes::{MediaType, ParseFormError, Request}; +pub use datatypes::{MediaType, ParseErrors, ParseFormError, Request}; diff --git a/core/http/src/handling/response/cookie.rs b/core/http/src/handling/response/cookie.rs new file mode 100644 index 0000000..e69de29 diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs new file mode 100644 index 0000000..503929a --- /dev/null +++ b/core/http/src/handling/response/datatypes.rs @@ -0,0 +1,45 @@ +use std::time::Duration; + +use super::{ResponseBody, Status}; + +type HeaderMap = Vec<String>; + +#[derive(Debug)] +pub enum Outcome<S, E, F> { + Success(S), + Failure(E), + Forward(F), +} + +pub enum SameSite { + None, + Lax, + Strict, +} + +pub struct Cookie<'a> { + /// Storage for the cookie string. Only used if this structure was derived + /// from a string that was subsequently parsed. + cookie_string: &'a str, + name: &'a str, + value: &'a str, + // expires: Option<Tm>, + max_age: Option<Duration>, + /// The cookie's domain, if any. + domain: Option<&'a str>, + /// The cookie's path domain, if any. + path: Option<&'a str>, + /// Whether this cookie was marked Secure. + secure: Option<bool>, + /// Whether this cookie was marked HttpOnly. + http_only: Option<bool>, + /// The draft `SameSite` attribute. + same_site: Option<SameSite>, +} + +pub struct Response<'a> { + pub headers: HeaderMap, + pub cookies: Option<Cookie<'a>>, + pub status: Option<Status>, + pub body: Box<dyn ResponseBody>, +} diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs new file mode 100644 index 0000000..017bc15 --- /dev/null +++ b/core/http/src/handling/response/mod.rs @@ -0,0 +1,12 @@ +mod cookie; +mod datatypes; +mod response; +mod status; +mod traits; + +pub use datatypes::Cookie; +pub use datatypes::Outcome; +pub use datatypes::Response; +pub use datatypes::SameSite; +pub use status::Status; +pub use traits::ResponseBody; diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs new file mode 100644 index 0000000..dfdb909 --- /dev/null +++ b/core/http/src/handling/response/response.rs @@ -0,0 +1,32 @@ +use std::io::Result; + +use tokio::{net::TcpStream, io::AsyncWriteExt}; + +use crate::handling::{methods::Method, request::Request, response::Status}; + +use super::Response; + +impl Response<'_> { + pub fn build(self, request: Option<Request>) -> Vec<u8> { + let mut compiled_headers = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", self.status.unwrap_or(Status::Ok), self.body.get_len(), self.body.get_mime()) + + &self.headers.join("\r\n") + + "\r\n"; + let is_head = if let Some(req) = request { + req.method == Method::Head + } else { + false + }; + let compiled_body = if is_head { + vec![] + } else { + self.body.get_data() + }; + let compiled_out = unsafe { compiled_headers.as_mut_vec() }; + compiled_out.extend_from_slice(&compiled_body); + compiled_out.to_vec() + } + pub async fn write(self, mut stream: TcpStream, request: Option<Request<'_>>) -> Result<()> { + let resp = self.build(request); + stream.write_all(&resp).await?; + Ok(()) +}} diff --git a/core/http/src/handling/response.rs b/core/http/src/handling/response/status.rs similarity index 84% rename from core/http/src/handling/response.rs rename to core/http/src/handling/response/status.rs index 81a54af..9f825af 100644 --- a/core/http/src/handling/response.rs +++ b/core/http/src/handling/response/status.rs @@ -1,71 +1,5 @@ use std::fmt::Display; -use crate::utils::mime::mime_enum::Mime; - -use super::routes::Body; - -type HeaderMap = Vec<String>; - -#[derive(Debug)] -pub enum Outcome<S, E, F> { - Success(S), - Failure(E), - Forward(F), -} - -pub trait ResponseBody: Send { - fn get_data(&self) -> Vec<u8>; - fn get_mime(&self) -> Mime; - fn get_len(&self) -> usize; -} - -impl ResponseBody for Body { - fn get_data(&self) -> Vec<u8> { - self.body() - } - fn get_mime(&self) -> Mime { - self.mime_type() - } - - fn get_len(&self) -> usize { - self.get_data().len() - } -} - -impl ResponseBody for &str { - fn get_data(&self) -> Vec<u8> { - self.as_bytes().to_vec() - } - - fn get_mime(&self) -> Mime { - Mime::TextPlain - } - - fn get_len(&self) -> usize { - self.len() - } -} - -impl ResponseBody for String { - fn get_data(&self) -> Vec<u8> { - self.as_bytes().to_vec() - } - - fn get_mime(&self) -> Mime { - Mime::TextPlain - } - - fn get_len(&self) -> usize { - self.len() - } -} - -pub struct Response { - pub headers: HeaderMap, - pub status: Option<Status>, - pub body: Box<dyn ResponseBody>, -} - #[derive(Debug)] pub enum Status { Continue, diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs new file mode 100644 index 0000000..96ac87c --- /dev/null +++ b/core/http/src/handling/response/traits.rs @@ -0,0 +1,48 @@ +use crate::{handling::routes::Body, utils::mime::mime_enum::Mime}; + +pub trait ResponseBody: Send { + fn get_data(&self) -> Vec<u8>; + fn get_mime(&self) -> Mime; + fn get_len(&self) -> usize; +} + +impl ResponseBody for Body { + fn get_data(&self) -> Vec<u8> { + self.body() + } + fn get_mime(&self) -> Mime { + self.mime_type() + } + + fn get_len(&self) -> usize { + self.get_data().len() + } +} + +impl ResponseBody for &str { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + Mime::TextPlain + } + + fn get_len(&self) -> usize { + self.len() + } +} + +impl ResponseBody for String { + fn get_data(&self) -> Vec<u8> { + self.as_bytes().to_vec() + } + + fn get_mime(&self) -> Mime { + Mime::TextPlain + } + + fn get_len(&self) -> usize { + self.len() + } +} diff --git a/site/src/main.rs b/site/src/main.rs index f1fda07..f677a34 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -4,7 +4,7 @@ use http::handling::{ file_handlers::NamedFile, methods::Method, request::{Request, ParseFormError}, - response::{Outcome, Response, ResponseBody, Status}, + response::{Outcome, Response, Status}, routes::{Data, Route}, }; @@ -28,10 +28,8 @@ fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Statu }; let response = hashmap_to_string(&keys); Outcome::Success(Response { - headers: vec![ - format!("Content-Length: {}", response.len()), - format!("Content-Type: text/plain"), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(response), }) @@ -42,10 +40,8 @@ fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> let response = fileserver(request.uri.strip_prefix("static/").unwrap()); let response = match response { Ok(dat) => Response { - headers: vec![ - format!("Content-Length: {}", dat.get_len()), - format!("Content-Type: {}", dat.get_mime()), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(dat), }, @@ -69,10 +65,8 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da return Outcome::Failure(Status::BadRequest); }; Outcome::Success(Response { - headers: vec![ - format!("Content-Length: {}", dat.len()), - format!("Content-Type: text/plain"), - ], + headers: vec![], + cookies: None, status: Some(Status::Ok), body: Box::new(dat), }) -- GitLab From 40488db2e4c370bff605a48dcacda94155044855 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 1 Jul 2023 22:09:24 +0200 Subject: [PATCH 46/65] Add basic cookie utility prototypes --- core/http/src/handling/response/cookie.rs | 0 .../cookie_management/cookie_builder.rs | 1 + .../handling/response/cookie_management/mod.rs | 1 + core/http/src/handling/response/datatypes.rs | 18 +++++++++--------- core/http/src/handling/response/mod.rs | 2 +- 5 files changed, 12 insertions(+), 10 deletions(-) delete mode 100644 core/http/src/handling/response/cookie.rs create mode 100644 core/http/src/handling/response/cookie_management/cookie_builder.rs create mode 100644 core/http/src/handling/response/cookie_management/mod.rs diff --git a/core/http/src/handling/response/cookie.rs b/core/http/src/handling/response/cookie.rs deleted file mode 100644 index e69de29..0000000 diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs new file mode 100644 index 0000000..0a800f1 --- /dev/null +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -0,0 +1 @@ +struct CookieBuilder {} diff --git a/core/http/src/handling/response/cookie_management/mod.rs b/core/http/src/handling/response/cookie_management/mod.rs new file mode 100644 index 0000000..ee6df23 --- /dev/null +++ b/core/http/src/handling/response/cookie_management/mod.rs @@ -0,0 +1 @@ +mod cookie_builder; diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs index 503929a..669005e 100644 --- a/core/http/src/handling/response/datatypes.rs +++ b/core/http/src/handling/response/datatypes.rs @@ -20,21 +20,21 @@ pub enum SameSite { pub struct Cookie<'a> { /// Storage for the cookie string. Only used if this structure was derived /// from a string that was subsequently parsed. - cookie_string: &'a str, - name: &'a str, - value: &'a str, + pub(crate) cookie_string: Option<&'a str>, + pub(crate) name: &'a str, + pub(crate) value: &'a str, // expires: Option<Tm>, - max_age: Option<Duration>, + pub(crate) max_age: Option<Duration>, /// The cookie's domain, if any. - domain: Option<&'a str>, + pub(crate) domain: Option<&'a str>, /// The cookie's path domain, if any. - path: Option<&'a str>, + pub(crate) path: Option<&'a str>, /// Whether this cookie was marked Secure. - secure: Option<bool>, + pub(crate) secure: Option<bool>, /// Whether this cookie was marked HttpOnly. - http_only: Option<bool>, + pub(crate) http_only: Option<bool>, /// The draft `SameSite` attribute. - same_site: Option<SameSite>, + pub(crate) same_site: Option<SameSite>, } pub struct Response<'a> { diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs index 017bc15..1fdc875 100644 --- a/core/http/src/handling/response/mod.rs +++ b/core/http/src/handling/response/mod.rs @@ -1,4 +1,4 @@ -mod cookie; +mod cookie_management; mod datatypes; mod response; mod status; -- GitLab From 36d979d71e7dce8649028eefa2b175fd07a93cb2 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sun, 2 Jul 2023 13:02:41 +0200 Subject: [PATCH 47/65] introducing cookie builder, performance adjustment with requets.rs -> build --- core/http/src/handlers/handler.rs | 40 ++++++----- core/http/src/handling/request/datatypes.rs | 2 +- .../response/cookie_management/cookie.rs | 28 ++++++++ .../cookie_management/cookie_builder.rs | 66 ++++++++++++++++++- .../response/cookie_management/mod.rs | 4 ++ core/http/src/handling/response/datatypes.rs | 30 +-------- core/http/src/handling/response/mod.rs | 4 +- core/http/src/handling/response/response.rs | 17 +++-- core/http/src/setup.rs | 15 +++-- 9 files changed, 148 insertions(+), 58 deletions(-) create mode 100644 core/http/src/handling/response/cookie_management/cookie.rs diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 3c46159..67897c4 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,12 +1,16 @@ -use std::{path::PathBuf, io}; +use std::{io, path::PathBuf}; -use tokio::{io::{AsyncReadExt, BufReader, AsyncBufReadExt}, net::TcpStream}; +use tokio::{ + io::{AsyncBufReadExt, AsyncReadExt, BufReader}, + net::TcpStream, +}; use crate::handling::{ file_handlers::NamedFile, + methods::Method, request::Request, response::{Outcome, Response, ResponseBody, Status}, - routes::{Data, Body}, methods::Method, + routes::{Body, Data}, }; use crate::setup::MountPoint; @@ -44,9 +48,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin cookies: Request::extract_cookies_from_vec(&mut http_request), headers: http_request, mime_type: None, - method: if let Some(method) = request_status_line - .split(' ') - .next() { + method: if let Some(method) = request_status_line.split(' ').next() { if let Ok(ok) = method.parse() { ok } else { @@ -77,7 +79,10 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin if let Ok(size) = len { size } else { - eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); + eprintln!( + "\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", + request.method + ); if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { error_occured_when_writing(e) }; @@ -85,7 +90,10 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } } else { if request.mandatory_body() { - eprintln!("\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", request.method); + eprintln!( + "\x1b[31m`{}` must have a `Content-Length` header\x1b[0m", + request.method + ); if let Err(e) = len_not_defined(stream, Status::LengthRequired).await { error_occured_when_writing(e) }; @@ -115,8 +123,9 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); for route in mountpoint.routes { - if (route.method != request.method) && - ((route.method != Method::Get) && (request.method == Method::Head)) { + if (route.method != request.method) + && ((route.method != Method::Get) && (request.method == Method::Head)) + { continue; } if !route.compare_uri(mounted_request_uri) { @@ -139,8 +148,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let response = match handled_response { Some(val) => match val { - Outcome::Success(success) => success - , + Outcome::Success(success) => success, Outcome::Failure(error) => failure_handler(error), Outcome::Forward(_) => failure_handler(Status::NotFound), }, @@ -149,7 +157,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin if let Err(e) = response.write(stream, Some(request)).await { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); - } + } } fn failure_handler<'a>(status: Status) -> Response<'a> { @@ -162,14 +170,16 @@ fn failure_handler<'a>(status: Status) -> Response<'a> { } } -async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()>{ +async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { cookies: None, headers: vec![], status: Some(status), body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())), - }.write(stream, None).await?; + } + .write(stream, None) + .await?; Ok(()) } diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index f490137..acde6d8 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, error::Error, fmt::Display}; +use std::{borrow::Cow, collections::HashMap, error::Error, fmt::Display}; use crate::{ handling::{methods::Method, routes::Uri}, diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs new file mode 100644 index 0000000..cfe3501 --- /dev/null +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -0,0 +1,28 @@ +use std::time::Duration; + +pub struct Cookie<'a> { + /// Storage for the cookie string. Only used if this structure was derived + /// from a string that was subsequently parsed. + pub(crate) cookie_string: Option<&'a str>, + pub(crate) name: &'a str, + pub(crate) value: &'a str, + // expires: Option<Tm>, + pub(crate) max_age: Option<Duration>, + /// The cookie's domain, if any. + pub(crate) domain: Option<&'a str>, + /// The cookie's path domain, if any. + pub(crate) path: Option<&'a str>, + /// Whether this cookie was marked Secure. + pub(crate) secure: Option<bool>, + /// Whether this cookie was marked HttpOnly. + pub(crate) http_only: Option<bool>, + /// The draft `SameSite` attribute. + pub(crate) same_site: Option<SameSite>, + pub(crate) expires: Option<&'a str>, +} + +pub enum SameSite { + None, + Lax, + Strict, +} diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index 0a800f1..ead2df0 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -1 +1,65 @@ -struct CookieBuilder {} +use std::time::Duration; + +use super::{Cookie, SameSite}; + +pub struct CookieBuilder<'a> { + inner: Cookie<'a>, +} + +impl<'a> CookieBuilder<'a> { + pub fn build(name: &'a str, value: &'a str) -> Self { + CookieBuilder { + inner: Cookie { + cookie_string: None, + name, + value, + max_age: None, + domain: None, + path: None, + secure: None, + http_only: None, + same_site: None, + expires: None, + }, + } + } + pub fn finish(self) -> Cookie<'a> { + self.inner + } + pub fn max_age(mut self, duration: Duration) -> Self { + self.inner.max_age = Some(duration); + self + } + pub fn domain(mut self, domain: &'a str) -> Self { + self.inner.domain = Some(domain); + self + } + pub fn path(mut self, path: &'a str) -> Self { + self.inner.path = Some(path); + self + } + pub fn secure(mut self, secure: bool) -> Self { + self.inner.secure = Some(secure); + self + } + pub fn http_only(mut self, http_only: bool) -> Self { + self.inner.http_only = Some(http_only); + self + } + pub fn same_site(mut self, same_site: SameSite) -> Self { + self.inner.same_site = Some(same_site); + self + } + pub fn expires(mut self, expire: &'a str) -> Self { + self.inner.expires = Some(expire); + self + } + pub fn name(mut self, name: &'a str) -> Self { + self.inner.name = name; + self + } + pub fn value(mut self, value: &'a str) -> Self { + self.inner.value = value; + self + } +} diff --git a/core/http/src/handling/response/cookie_management/mod.rs b/core/http/src/handling/response/cookie_management/mod.rs index ee6df23..06d6b85 100644 --- a/core/http/src/handling/response/cookie_management/mod.rs +++ b/core/http/src/handling/response/cookie_management/mod.rs @@ -1 +1,5 @@ +mod cookie; mod cookie_builder; + +pub use cookie::Cookie; +pub use cookie::SameSite; diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs index 669005e..c174a5d 100644 --- a/core/http/src/handling/response/datatypes.rs +++ b/core/http/src/handling/response/datatypes.rs @@ -1,6 +1,4 @@ -use std::time::Duration; - -use super::{ResponseBody, Status}; +use super::{Cookie, ResponseBody, Status}; type HeaderMap = Vec<String>; @@ -11,32 +9,6 @@ pub enum Outcome<S, E, F> { Forward(F), } -pub enum SameSite { - None, - Lax, - Strict, -} - -pub struct Cookie<'a> { - /// Storage for the cookie string. Only used if this structure was derived - /// from a string that was subsequently parsed. - pub(crate) cookie_string: Option<&'a str>, - pub(crate) name: &'a str, - pub(crate) value: &'a str, - // expires: Option<Tm>, - pub(crate) max_age: Option<Duration>, - /// The cookie's domain, if any. - pub(crate) domain: Option<&'a str>, - /// The cookie's path domain, if any. - pub(crate) path: Option<&'a str>, - /// Whether this cookie was marked Secure. - pub(crate) secure: Option<bool>, - /// Whether this cookie was marked HttpOnly. - pub(crate) http_only: Option<bool>, - /// The draft `SameSite` attribute. - pub(crate) same_site: Option<SameSite>, -} - pub struct Response<'a> { pub headers: HeaderMap, pub cookies: Option<Cookie<'a>>, diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs index 1fdc875..ede6a42 100644 --- a/core/http/src/handling/response/mod.rs +++ b/core/http/src/handling/response/mod.rs @@ -4,9 +4,9 @@ mod response; mod status; mod traits; -pub use datatypes::Cookie; +pub use cookie_management::Cookie; +pub use cookie_management::SameSite; pub use datatypes::Outcome; pub use datatypes::Response; -pub use datatypes::SameSite; pub use status::Status; pub use traits::ResponseBody; diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs index dfdb909..b986d5c 100644 --- a/core/http/src/handling/response/response.rs +++ b/core/http/src/handling/response/response.rs @@ -1,6 +1,6 @@ use std::io::Result; -use tokio::{net::TcpStream, io::AsyncWriteExt}; +use tokio::{io::AsyncWriteExt, net::TcpStream}; use crate::handling::{methods::Method, request::Request, response::Status}; @@ -8,8 +8,12 @@ use super::Response; impl Response<'_> { pub fn build(self, request: Option<Request>) -> Vec<u8> { - let mut compiled_headers = format!("HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", self.status.unwrap_or(Status::Ok), self.body.get_len(), self.body.get_mime()) - + &self.headers.join("\r\n") + let compiled_headers = format!( + "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", + self.status.unwrap_or(Status::Ok), + self.body.get_len(), + self.body.get_mime() + ) + &self.headers.join("\r\n") + "\r\n"; let is_head = if let Some(req) = request { req.method == Method::Head @@ -21,12 +25,13 @@ impl Response<'_> { } else { self.body.get_data() }; - let compiled_out = unsafe { compiled_headers.as_mut_vec() }; + let mut compiled_out: Vec<u8> = compiled_headers.into(); compiled_out.extend_from_slice(&compiled_body); - compiled_out.to_vec() + compiled_out } pub async fn write(self, mut stream: TcpStream, request: Option<Request<'_>>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; Ok(()) -}} + } +} diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 417cf3d..1f061c6 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -1,6 +1,10 @@ use std::thread::available_parallelism; -use tokio::{net::TcpListener, signal::unix::{SignalKind, signal}, select}; +use tokio::{ + net::TcpListener, + select, + signal::unix::{signal, SignalKind}, +}; use crate::{ handlers::handler::handle_connection, @@ -65,7 +69,7 @@ impl<'a> Config { } /// # Launches/Starts the webserver /// Launches a Webserver Configuration - /// + /// /// Is Async /// /// Is Blocking -> Can be interrupted with ^C @@ -74,7 +78,10 @@ impl<'a> Config { /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there /// is no interrupt signal pub async fn launch(self) { - println!("Server launched from http://{}", self.address.local_addr().unwrap()); + println!( + "Server launched from http://{}", + self.address.local_addr().unwrap() + ); let mut sigint = signal(SignalKind::interrupt()).unwrap(); loop { select! { @@ -120,7 +127,7 @@ pub async fn build(ip: &str) -> Config { let ip = ip[0]; let workers = available_parallelism().unwrap().get(); println!( -"\x1b[34mâš™ Configuration\x1b[0m + "\x1b[34mâš™ Configuration\x1b[0m >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} -- GitLab From 6c90900d4933b4f84463dbfcfb80d973b502425b Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 3 Jul 2023 14:57:00 +0200 Subject: [PATCH 48/65] prototyping for cookies further, working on UrlEncoded text --- core/http/src/handling/file_handlers.rs | 2 +- core/http/src/handling/request/cookies.rs | 1 + core/http/src/handling/request/datatypes.rs | 4 +- core/http/src/handling/request/form_utils.rs | 4 +- .../http/src/handling/request/request_mime.rs | 4 +- .../response/cookie_management/cookie.rs | 18 +++ .../cookie_management/cookie_builder.rs | 5 + core/http/src/handling/response/traits.rs | 2 +- core/http/src/handling/routes.rs | 2 +- core/http/src/utils/mime/mod.rs | 5 +- core/http/src/utils/mod.rs | 1 + core/http/src/utils/urlencoded/datatypes.rs | 26 ++++ core/http/src/utils/urlencoded/endecode.rs | 140 ++++++++++++++++++ core/http/src/utils/urlencoded/mod.rs | 2 + 14 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 core/http/src/utils/urlencoded/datatypes.rs create mode 100644 core/http/src/utils/urlencoded/endecode.rs create mode 100644 core/http/src/utils/urlencoded/mod.rs diff --git a/core/http/src/handling/file_handlers.rs b/core/http/src/handling/file_handlers.rs index 8ca0019..7a0b080 100644 --- a/core/http/src/handling/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -2,7 +2,7 @@ use std::{fs, path::PathBuf}; use crate::{ handling::response::{ResponseBody, Status}, - utils::mime::mime_enum::Mime, + utils::mime::Mime, }; #[derive(Debug)] diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs index b386086..cd3c4ca 100644 --- a/core/http/src/handling/request/cookies.rs +++ b/core/http/src/handling/request/cookies.rs @@ -52,6 +52,7 @@ impl Request<'_> { .strip_prefix("Cookie: ") .unwrap() .trim() + .trim_matches('"') .to_string(); for cookie in cookies_string.split(';') { let Some((name, cookie)) = cookie.split_once('=') else { diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index acde6d8..df96177 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -1,8 +1,8 @@ -use std::{borrow::Cow, collections::HashMap, error::Error, fmt::Display}; +use std::{collections::HashMap, error::Error, fmt::Display}; use crate::{ handling::{methods::Method, routes::Uri}, - utils::mime::mime_enum::Mime, + utils::mime::Mime, }; pub trait FromRequest: Send { diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index cab16eb..9aa300c 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{handling::routes::Data, utils::mime::mime_enum::Mime}; +use crate::{handling::routes::Data, utils::mime::Mime}; use super::{datatypes::ParseErrors, ParseFormError, Request}; static TWO_NEWLINES: u8 = 3; @@ -239,7 +239,7 @@ mod test { request::{datatypes::ParseErrors, ParseFormError}, routes::Data, }, - utils::mime::mime_enum::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, + utils::mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, }; use super::Request; diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 7551bb7..56188d1 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -9,7 +9,7 @@ impl<'a> Request<'a> { /// ``` /// use http::{ /// handling::{methods::Method, request::Request}, - /// utils::mime::mime_enum::Mime, + /// utils::mime::Mime, /// }; /// /// let mut request = Request { @@ -71,7 +71,7 @@ impl<'a> Request<'a> { mod test { use crate::{ handling::{methods::Method, request::Request}, - utils::mime::mime_enum::Mime, + utils::mime::Mime, }; #[test] diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index cfe3501..4e072eb 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -19,10 +19,28 @@ pub struct Cookie<'a> { /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, pub(crate) expires: Option<&'a str>, + pub(crate) partitioned: Option<bool>, } +#[derive(Debug)] pub enum SameSite { None, Lax, Strict, } + +impl std::fmt::Display for SameSite { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "SameSite=None; Secure"), + Self::Lax => write!(f, "SameSite=Lax"), + Self::Strict => write!(f, "SameSite=Strict"), + } + } +} + +impl std::fmt::Display for Cookie<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() + } +} diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index ead2df0..431160a 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -20,6 +20,7 @@ impl<'a> CookieBuilder<'a> { http_only: None, same_site: None, expires: None, + partitioned: None, }, } } @@ -54,6 +55,10 @@ impl<'a> CookieBuilder<'a> { self.inner.expires = Some(expire); self } + pub fn partitioned(mut self, partitioned: bool) -> Self { + self.inner.partitioned = Some(partitioned); + self + } pub fn name(mut self, name: &'a str) -> Self { self.inner.name = name; self diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs index 96ac87c..fc7eb09 100644 --- a/core/http/src/handling/response/traits.rs +++ b/core/http/src/handling/response/traits.rs @@ -1,4 +1,4 @@ -use crate::{handling::routes::Body, utils::mime::mime_enum::Mime}; +use crate::{handling::routes::Body, utils::mime::Mime}; pub trait ResponseBody: Send { fn get_data(&self) -> Vec<u8>; diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index a85261a..e4246b8 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -4,7 +4,7 @@ use crate::{ request::{MediaType, Request}, response::{Outcome, Response, Status}, }, - utils::mime::mime_enum::Mime, + utils::mime::Mime, }; pub struct RoutInfo { diff --git a/core/http/src/utils/mime/mod.rs b/core/http/src/utils/mime/mod.rs index bbf3a87..4421654 100644 --- a/core/http/src/utils/mime/mod.rs +++ b/core/http/src/utils/mime/mod.rs @@ -1,2 +1,5 @@ mod map; -pub mod mime_enum; +mod mime_enum; +pub use map::MIME_MAP; +pub use mime_enum::Mime; +pub use mime_enum::ParseMimeError; diff --git a/core/http/src/utils/mod.rs b/core/http/src/utils/mod.rs index 909a531..98498d0 100644 --- a/core/http/src/utils/mod.rs +++ b/core/http/src/utils/mod.rs @@ -1 +1,2 @@ pub mod mime; +pub mod urlencoded; diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs new file mode 100644 index 0000000..c370ddf --- /dev/null +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -0,0 +1,26 @@ +use std::borrow::Cow; + +use super::endecode::EnDecodable; + +pub struct UrlEncodeData<'a> { + encoded: Cow<'a, str>, + raw: Cow<'a, str>, +} + +impl UrlEncodeData<'_> { + pub fn from_raw(raw: &str) -> Self { + todo!() + } + pub fn from_encoded(encoded: &str) -> Self { + todo!() + } +} + +impl EnDecodable for UrlEncodeData<'_> { + fn encode(&self) -> Cow<'_, str> { + self.raw.encode() + } + fn decode(&self) -> Result<Cow<'_, str>, ()> { + self.encoded.decode() + } +} diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs new file mode 100644 index 0000000..0e7fe2c --- /dev/null +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -0,0 +1,140 @@ +use std::borrow::Cow; + +static BASE16_HEXA_DECIMAL: u8 = 16; +pub trait EnDecodable { + fn encode(&self) -> Cow<'_, str>; + fn decode(&self) -> Result<Cow<'_, str>, ()>; +} + +impl EnDecodable for Cow<'_, str> { + fn encode(&self) -> Cow<'_, str> { + self.bytes() + .map(|byte| { + if !byte.is_ascii_alphanumeric() { + format!("%{:02X}", byte) + } else { + String::from_utf8([byte].to_vec()).unwrap() + } + }) + .collect() + } + fn decode(&self) -> Result<Cow<'_, str>, ()> { + let mut first = true; + let mut result = String::with_capacity(self.len()); + for i in self.split('%') { + if first { + first = false; + result += i; + continue; + } + let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { + return Err(()); + }; + unsafe { + result.as_mut_vec().push(char); + } + result = result + &i[2..]; + } + Ok(result.into()) + } +} + +impl EnDecodable for &str { + fn encode(&self) -> Cow<'_, str> { + self.bytes() + .map(|byte| { + if !byte.is_ascii_alphanumeric() { + format!("%{:02X}", byte) + } else { + String::from_utf8([byte].to_vec()).unwrap() + } + }) + .collect() + } + + fn decode(&self) -> Result<Cow<'_, str>, ()> { + let mut first = true; + let mut result = String::with_capacity(self.len()); + for i in self.split('%') { + if first { + first = false; + result += i; + continue; + } + let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { + return Err(()); + }; + unsafe { + result.as_mut_vec().push(char); + } + result = result + &i[2..]; + } + Ok(result.into()) + } +} + +impl EnDecodable for String { + fn encode(&self) -> Cow<'_, str> { + self.bytes() + .map(|byte| { + if !byte.is_ascii_alphanumeric() { + format!("%{:02X}", byte) + } else { + String::from_utf8([byte].to_vec()).unwrap() + } + }) + .collect() + } + fn decode(&self) -> Result<Cow<'_, str>, ()> { + let mut first = true; + let mut result = String::with_capacity(self.len()); + for i in self.split('%') { + if first { + first = false; + result += i; + continue; + } + let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { + return Err(()); + }; + unsafe { + result.as_mut_vec().push(char); + } + result = result + &i[2..]; + } + Ok(result.into()) + } +} + +#[cfg(test)] +mod test { + use std::borrow::Cow; + + use crate::utils::urlencoded::endecode::EnDecodable; + + #[test] + fn urlencoded_test() { + assert_eq!( + "Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire", + Cow::Borrowed("Darius is the biggest genius/Genie/Human extraordinäire").encode() + ) + } + #[test] + fn urldecoded_test() { + assert_eq!( + "Darius is the biggest genius/Genie/Human extraordinäire", + Cow::Borrowed( + "Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire" + ) + .decode() + .unwrap() + ); + assert_eq!( + Err(()), + Cow::Borrowed( + "Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire" + ) + .decode() + ); + } +} diff --git a/core/http/src/utils/urlencoded/mod.rs b/core/http/src/utils/urlencoded/mod.rs new file mode 100644 index 0000000..43ea58f --- /dev/null +++ b/core/http/src/utils/urlencoded/mod.rs @@ -0,0 +1,2 @@ +mod datatypes; +mod endecode; -- GitLab From 929f96ebca64977d492a63f2118da0871967a153 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 4 Jul 2023 22:03:01 +0200 Subject: [PATCH 49/65] converting the request uri to a urlencoded datatype many errors WIP --- core/http/src/handlers/handler.rs | 13 +- core/http/src/handling/request/cookies.rs | 2 +- core/http/src/handling/request/datatypes.rs | 6 +- core/http/src/handling/request/form_utils.rs | 22 ++- .../http/src/handling/request/request_impl.rs | 2 +- .../http/src/handling/request/request_mime.rs | 8 +- core/http/src/handling/response/response.rs | 2 +- core/http/src/utils/urlencoded/datatypes.rs | 51 +++++-- core/http/src/utils/urlencoded/endecode.rs | 142 ++++++------------ core/http/src/utils/urlencoded/mod.rs | 3 + 10 files changed, 116 insertions(+), 135 deletions(-) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 67897c4..9aded69 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -5,13 +5,13 @@ use tokio::{ net::TcpStream, }; -use crate::handling::{ +use crate::{handling::{ file_handlers::NamedFile, methods::Method, request::Request, response::{Outcome, Response, ResponseBody, Status}, routes::{Body, Data}, -}; +}, utils::urlencoded::UrlEncodeData}; use crate::setup::MountPoint; static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; @@ -40,7 +40,12 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut request = Request { uri: if let Some(uri) = &request_status_line.split(' ').nth(1) { - uri + if let Ok(uri) = UrlEncodeData::from_encoded(uri) { + uri + } else { + eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m"); + return; + } } else { eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m"); return; @@ -118,7 +123,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { - if !request.uri.starts_with(mountpoint.mountpoint) { + if !request.uri.raw().starts_with(mountpoint.mountpoint) { continue; } let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); diff --git a/core/http/src/handling/request/cookies.rs b/core/http/src/handling/request/cookies.rs index cd3c4ca..7f75fc1 100644 --- a/core/http/src/handling/request/cookies.rs +++ b/core/http/src/handling/request/cookies.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use super::Request; -impl Request<'_> { +impl Request { /// Extracts the cookies from a Vector and gives back an optional HashMap of Strings /// /// Returns none if there are no cookies or there is a problem with the cookies, for example diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index df96177..6d37b1f 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, error::Error, fmt::Display}; use crate::{ handling::{methods::Method, routes::Uri}, - utils::mime::Mime, + utils::{mime::Mime, urlencoded::UrlEncodeData}, }; pub trait FromRequest: Send { @@ -29,9 +29,9 @@ type HeaderMap = Vec<String>; /// A struct to handle Requests /// #[derive(Clone)] -pub struct Request<'a> { +pub struct Request { /// The requested Uri - pub uri: Uri<'a>, + pub uri: UrlEncodeData, /// All headers of the request that haven't been parsed pub headers: HeaderMap, /// The methods Request represented with the [Method] diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index 9aa300c..1579c11 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -1,11 +1,14 @@ use std::collections::HashMap; -use crate::{handling::routes::Data, utils::mime::Mime}; +use crate::{ + handling::routes::Data, + utils::{mime::Mime, urlencoded::DeCodable}, +}; use super::{datatypes::ParseErrors, ParseFormError, Request}; static TWO_NEWLINES: u8 = 3; -impl Request<'_> { +impl Request { /// # Gets data from a get_form as a HashMap /// /// # Errors @@ -124,16 +127,19 @@ impl Request<'_> { return Err(ParseFormError { error: ParseErrors::BadData }); }; for kvp in data.split('&') { - let Some(mut kvp) = kvp.split_once('=') else { + let Some(kvp) = kvp.split_once('=') else { return Err(ParseFormError { error: ParseErrors::BadData }); }; - - let Some(thing) = keymap.get_mut(kvp.0) else { + let Ok(key) = kvp.0.decode() else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + let Ok(value) = kvp.1.trim_end_matches('\0').decode() else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + let Some(thing) = keymap.get_mut(&key) else { continue; }; - kvp.1 = kvp.1.trim_end_matches('\0'); - - *thing = Ok(kvp.1.as_bytes().to_vec()); + *thing = Ok(value.into()); } } Mime::MultipartFormData => { diff --git a/core/http/src/handling/request/request_impl.rs b/core/http/src/handling/request/request_impl.rs index 2d2f7cb..2eb6fd0 100644 --- a/core/http/src/handling/request/request_impl.rs +++ b/core/http/src/handling/request/request_impl.rs @@ -2,7 +2,7 @@ use crate::handling::methods::Method; use super::Request; -impl Request<'_> { +impl Request { pub fn can_have_body(&self) -> bool { matches!( self.method, diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index 56188d1..c37045e 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -1,6 +1,6 @@ use super::datatypes::Request; -impl<'a> Request<'a> { +impl Request { /// Sets the `mime_type` of this [`Request`] from the headers. /// /// The mime_type can remain none if there isn't a `Content-Type: ` header @@ -71,13 +71,13 @@ impl<'a> Request<'a> { mod test { use crate::{ handling::{methods::Method, request::Request}, - utils::mime::Mime, + utils::{mime::Mime, urlencoded::UrlEncodeData}, }; #[test] pub fn test_mime_parse_from_header_vec() { let mut request = Request { - uri: "thing", + uri: UrlEncodeData::from_raw("thing"), headers: vec![ "GET / 23".to_string(), "SDF:LKJSD:F".to_string(), @@ -90,7 +90,7 @@ mod test { }; let mut wrong = Request { - uri: "thing", + uri: UrlEncodeData::from_raw("thing"), headers: vec![ "GET / 23".to_string(), "SDF:LKJSD:F".to_string(), diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs index b986d5c..3284d02 100644 --- a/core/http/src/handling/response/response.rs +++ b/core/http/src/handling/response/response.rs @@ -29,7 +29,7 @@ impl Response<'_> { compiled_out.extend_from_slice(&compiled_body); compiled_out } - pub async fn write(self, mut stream: TcpStream, request: Option<Request<'_>>) -> Result<()> { + pub async fn write(self, mut stream: TcpStream, request: Option<Request>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; Ok(()) diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index c370ddf..97dc0a9 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -1,26 +1,45 @@ -use std::borrow::Cow; +use std::{borrow::Cow, string::FromUtf8Error}; -use super::endecode::EnDecodable; +use crate::utils::urlencoded::endecode::EnCodable; -pub struct UrlEncodeData<'a> { - encoded: Cow<'a, str>, - raw: Cow<'a, str>, +use super::endecode::DeCodable; + +#[derive(Clone)] +pub struct UrlEncodeData { + encoded: String, + raw: Vec<u8>, + raw_string: Option<String>, } -impl UrlEncodeData<'_> { - pub fn from_raw(raw: &str) -> Self { - todo!() +impl UrlEncodeData { + pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Result<Self, FromUtf8Error> { + Ok(Self { + raw: raw.as_ref().to_owned(), + encoded: raw.as_ref().encode(), + raw_string: String::from_utf8(raw.as_ref().into()).ok(), + }) } - pub fn from_encoded(encoded: &str) -> Self { - todo!() + pub fn from_encoded(encoded: &str) -> Result<Self, ()> { + Ok(Self { + encoded: encoded.to_owned(), + raw: encoded.decode()?, + raw_string: String::from_utf8(encoded.decode()?).ok(), + }) } -} -impl EnDecodable for UrlEncodeData<'_> { - fn encode(&self) -> Cow<'_, str> { - self.raw.encode() + pub fn encoded(&self) -> &str { + self.encoded.as_ref() + } + pub fn raw(&self) -> &[u8] { + self.raw.as_ref() } - fn decode(&self) -> Result<Cow<'_, str>, ()> { - self.encoded.decode() + pub fn raw_string(&self) -> Option<&str> { + self.raw_string.as_ref().map(|x| x.as_str()) + } +} + +impl std::fmt::Display for UrlEncodeData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.encoded) } } diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 0e7fe2c..83e2261 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -1,140 +1,88 @@ -use std::borrow::Cow; - static BASE16_HEXA_DECIMAL: u8 = 16; -pub trait EnDecodable { - fn encode(&self) -> Cow<'_, str>; - fn decode(&self) -> Result<Cow<'_, str>, ()>; +static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 15; +static BASE16_HEXA_DECIMAL_DIGIT_BITS: u8 = 4; + +pub trait EnCodable { + fn encode(&self) -> String; } -impl EnDecodable for Cow<'_, str> { - fn encode(&self) -> Cow<'_, str> { - self.bytes() - .map(|byte| { - if !byte.is_ascii_alphanumeric() { - format!("%{:02X}", byte) - } else { - String::from_utf8([byte].to_vec()).unwrap() - } - }) - .collect() - } - fn decode(&self) -> Result<Cow<'_, str>, ()> { - let mut first = true; +pub trait DeCodable { + fn decode(&self) -> Result<Vec<u8>, ()>; +} + +impl EnCodable for [u8] { + fn encode(self: &[u8]) -> String { let mut result = String::with_capacity(self.len()); - for i in self.split('%') { - if first { - first = false; - result += i; - continue; + let result_vec = unsafe { result.as_mut_vec() }; + self.iter().for_each(|byte| { + if !matches!(byte, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'_' | b'.' | b'~') + { + result_vec.push(b'%'); + result_vec.push(hex_to_digit(byte >> BASE16_HEXA_DECIMAL_DIGIT_BITS)); + result_vec.push(hex_to_digit( + byte & BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT, + )); + } else { + result_vec.push(*byte) } - let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { - return Err(()); - }; - unsafe { - result.as_mut_vec().push(char); - } - result = result + &i[2..]; - } - Ok(result.into()) + }); + result } } - -impl EnDecodable for &str { - fn encode(&self) -> Cow<'_, str> { - self.bytes() - .map(|byte| { - if !byte.is_ascii_alphanumeric() { - format!("%{:02X}", byte) - } else { - String::from_utf8([byte].to_vec()).unwrap() - } - }) - .collect() - } - - fn decode(&self) -> Result<Cow<'_, str>, ()> { +impl DeCodable for &str { + fn decode(&self) -> Result<Vec<u8>, ()> { let mut first = true; - let mut result = String::with_capacity(self.len()); + let mut result = Vec::with_capacity(self.len()); + for i in self.split('%') { if first { first = false; - result += i; + result.extend_from_slice(i.as_bytes()); continue; } let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { - return Err(()); + return Err(()); }; - unsafe { - result.as_mut_vec().push(char); - } - result = result + &i[2..]; + result.push(char); + result.extend_from_slice(i[2..].as_bytes()); } - Ok(result.into()) + Ok(result) } } -impl EnDecodable for String { - fn encode(&self) -> Cow<'_, str> { - self.bytes() - .map(|byte| { - if !byte.is_ascii_alphanumeric() { - format!("%{:02X}", byte) - } else { - String::from_utf8([byte].to_vec()).unwrap() - } - }) - .collect() - } - fn decode(&self) -> Result<Cow<'_, str>, ()> { - let mut first = true; - let mut result = String::with_capacity(self.len()); - for i in self.split('%') { - if first { - first = false; - result += i; - continue; - } - let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { - return Err(()); - }; - unsafe { - result.as_mut_vec().push(char); - } - result = result + &i[2..]; - } - Ok(result.into()) +fn hex_to_digit(digit: u8) -> u8 { + match digit { + 0..=9 => b'0' + digit, + 10..=255 => b'A' + digit - 10, } } #[cfg(test)] mod test { - use std::borrow::Cow; - - use crate::utils::urlencoded::endecode::EnDecodable; + use crate::utils::urlencoded::endecode::DeCodable; + use crate::utils::urlencoded::endecode::EnCodable; #[test] fn urlencoded_test() { assert_eq!( "Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire", - Cow::Borrowed("Darius is the biggest genius/Genie/Human extraordinäire").encode() - ) + b"Darius is the biggest genius/Genie/Human extraordin\xC3\xA4ire".encode() + ); } #[test] fn urldecoded_test() { assert_eq!( "Darius is the biggest genius/Genie/Human extraordinäire", - Cow::Borrowed( + String::from_utf8( "Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire" + .decode() + .unwrap() ) - .decode() .unwrap() ); assert_eq!( Err(()), - Cow::Borrowed( - "Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire" - ) - .decode() + "Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire".decode() ); } } diff --git a/core/http/src/utils/urlencoded/mod.rs b/core/http/src/utils/urlencoded/mod.rs index 43ea58f..6511cfc 100644 --- a/core/http/src/utils/urlencoded/mod.rs +++ b/core/http/src/utils/urlencoded/mod.rs @@ -1,2 +1,5 @@ mod datatypes; mod endecode; +pub use datatypes::UrlEncodeData; +pub use endecode::DeCodable; +pub use endecode::EnCodable; -- GitLab From 35c2138d0352275fc9ac47e782846574f0adc09f Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 8 Jul 2023 14:36:14 +0200 Subject: [PATCH 50/65] FIX finish conversion to URLEncoded for URIs and UTF-8 support. --- core/http/src/handlers/handler.rs | 11 +++--- core/http/src/handling/request/form_utils.rs | 23 +++++++++---- .../response/cookie_management/cookie.rs | 16 ++++----- .../cookie_management/cookie_builder.rs | 34 +++++++++---------- core/http/src/handling/response/datatypes.rs | 4 +-- core/http/src/handling/response/response.rs | 2 +- core/http/src/utils/urlencoded/datatypes.rs | 8 ++--- core/http/src/utils/urlencoded/endecode.rs | 6 +++- site/src/main.rs | 6 ++-- 9 files changed, 62 insertions(+), 48 deletions(-) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 9aded69..8b02188 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -123,10 +123,13 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin let mut handled_response: Option<Outcome<Response, Status, Data>> = None; for mountpoint in mountpoints { - if !request.uri.raw().starts_with(mountpoint.mountpoint) { + if request.uri.raw_string().is_none() { + return; + } + if !request.uri.raw_string().unwrap().starts_with(mountpoint.mountpoint) { continue; } - let mounted_request_uri = request.uri.strip_prefix(mountpoint.mountpoint).unwrap(); + let mounted_request_uri = request.uri.raw_string().unwrap().strip_prefix(mountpoint.mountpoint).unwrap(); for route in mountpoint.routes { if (route.method != request.method) && ((route.method != Method::Get) && (request.method == Method::Head)) @@ -138,7 +141,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } handled_response = Some((route.handler)( Request { - uri: mounted_request_uri, + uri: UrlEncodeData::from_raw(mounted_request_uri), ..request.clone() }, data.clone(), @@ -165,7 +168,7 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } } -fn failure_handler<'a>(status: Status) -> Response<'a> { +fn failure_handler<'a>(status: Status) -> Response { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); Response { cookies: None, diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index 1579c11..b1e3a76 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -68,7 +68,10 @@ impl Request { &'a self, keys: &'a [&str], ) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> { - let data = if let Some(val) = self.uri.split_once('?') { + let Some(uri) = self.uri.raw_string() else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; + let data = if let Some(val) = uri.split_once('?') { val } else { return Err(ParseFormError { @@ -133,6 +136,9 @@ impl Request { let Ok(key) = kvp.0.decode() else { return Err(ParseFormError { error: ParseErrors::BadData }); }; + let Ok(key) = String::from_utf8(key) else { + return Err(ParseFormError { error: ParseErrors::BadData }); + }; let Ok(value) = kvp.1.trim_end_matches('\0').decode() else { return Err(ParseFormError { error: ParseErrors::BadData }); }; @@ -245,14 +251,17 @@ mod test { request::{datatypes::ParseErrors, ParseFormError}, routes::Data, }, - utils::mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, + utils::{ + mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData}, + urlencoded::UrlEncodeData, + }, }; use super::Request; #[test] fn try_get_test() { let request = Request { - uri: "/form?name=Name&age=Age", + uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(), headers: vec![], method: Method::Get, cookies: None, @@ -263,7 +272,7 @@ mod test { assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap()); let wrong_request = Request { - uri: "/form", + uri: UrlEncodeData::from_encoded("/form").unwrap(), ..request.clone() }; assert_eq!( @@ -274,7 +283,7 @@ mod test { ); let bad_data = Request { - uri: "/form?age=", + uri: UrlEncodeData::from_encoded("/form?age=").unwrap(), ..request.clone() }; let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap(); @@ -290,7 +299,7 @@ mod test { #[test] fn try_post_text() { let req = Request { - uri: "", + uri: UrlEncodeData::from_encoded("").unwrap(), headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()], method: Method::Post, cookies: None, @@ -310,7 +319,7 @@ mod test { map.get("message1").unwrap().as_ref().unwrap() ); let req = Request { - uri: "", + uri: UrlEncodeData::from_encoded("").unwrap(), headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()], method: Method::Post, cookies: None, diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 4e072eb..e96f825 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,24 +1,24 @@ use std::time::Duration; -pub struct Cookie<'a> { +pub struct Cookie { /// Storage for the cookie string. Only used if this structure was derived /// from a string that was subsequently parsed. - pub(crate) cookie_string: Option<&'a str>, - pub(crate) name: &'a str, - pub(crate) value: &'a str, + pub(crate) cookie_string: Option<String>, + pub(crate) name: String, + pub(crate) value: String, // expires: Option<Tm>, pub(crate) max_age: Option<Duration>, /// The cookie's domain, if any. - pub(crate) domain: Option<&'a str>, + pub(crate) domain: Option<String>, /// The cookie's path domain, if any. - pub(crate) path: Option<&'a str>, + pub(crate) path: Option<String>, /// Whether this cookie was marked Secure. pub(crate) secure: Option<bool>, /// Whether this cookie was marked HttpOnly. pub(crate) http_only: Option<bool>, /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, - pub(crate) expires: Option<&'a str>, + pub(crate) expires: Option<String>, pub(crate) partitioned: Option<bool>, } @@ -39,7 +39,7 @@ impl std::fmt::Display for SameSite { } } -impl std::fmt::Display for Cookie<'_> { +impl std::fmt::Display for Cookie { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!() } diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index 431160a..e71eb44 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -2,17 +2,17 @@ use std::time::Duration; use super::{Cookie, SameSite}; -pub struct CookieBuilder<'a> { - inner: Cookie<'a>, +pub struct CookieBuilder { + inner: Cookie, } -impl<'a> CookieBuilder<'a> { - pub fn build(name: &'a str, value: &'a str) -> Self { +impl CookieBuilder { + pub fn build(name: &str, value: &str) -> Self { CookieBuilder { inner: Cookie { cookie_string: None, - name, - value, + name: name.to_owned(), + value: value.to_owned(), max_age: None, domain: None, path: None, @@ -24,19 +24,19 @@ impl<'a> CookieBuilder<'a> { }, } } - pub fn finish(self) -> Cookie<'a> { + pub fn finish(self) -> Cookie { self.inner } pub fn max_age(mut self, duration: Duration) -> Self { self.inner.max_age = Some(duration); self } - pub fn domain(mut self, domain: &'a str) -> Self { - self.inner.domain = Some(domain); + pub fn domain(mut self, domain: &str) -> Self { + self.inner.domain = Some(domain.to_owned()); self } - pub fn path(mut self, path: &'a str) -> Self { - self.inner.path = Some(path); + pub fn path(mut self, path: &str) -> Self { + self.inner.path = Some(path.to_owned()); self } pub fn secure(mut self, secure: bool) -> Self { @@ -51,20 +51,20 @@ impl<'a> CookieBuilder<'a> { self.inner.same_site = Some(same_site); self } - pub fn expires(mut self, expire: &'a str) -> Self { - self.inner.expires = Some(expire); + pub fn expires(mut self, expire: &str) -> Self { + self.inner.expires = Some(expire.to_owned()); self } pub fn partitioned(mut self, partitioned: bool) -> Self { self.inner.partitioned = Some(partitioned); self } - pub fn name(mut self, name: &'a str) -> Self { - self.inner.name = name; + pub fn name(mut self, name: &str) -> Self { + self.inner.name = name.to_owned(); self } - pub fn value(mut self, value: &'a str) -> Self { - self.inner.value = value; + pub fn value(mut self, value: &str) -> Self { + self.inner.value = value.to_owned(); self } } diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs index c174a5d..7800deb 100644 --- a/core/http/src/handling/response/datatypes.rs +++ b/core/http/src/handling/response/datatypes.rs @@ -9,9 +9,9 @@ pub enum Outcome<S, E, F> { Forward(F), } -pub struct Response<'a> { +pub struct Response { pub headers: HeaderMap, - pub cookies: Option<Cookie<'a>>, + pub cookies: Option<Cookie>, pub status: Option<Status>, pub body: Box<dyn ResponseBody>, } diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs index 3284d02..23d2b09 100644 --- a/core/http/src/handling/response/response.rs +++ b/core/http/src/handling/response/response.rs @@ -6,7 +6,7 @@ use crate::handling::{methods::Method, request::Request, response::Status}; use super::Response; -impl Response<'_> { +impl Response<> { pub fn build(self, request: Option<Request>) -> Vec<u8> { let compiled_headers = format!( "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index 97dc0a9..6829e6b 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -1,5 +1,3 @@ -use std::{borrow::Cow, string::FromUtf8Error}; - use crate::utils::urlencoded::endecode::EnCodable; use super::endecode::DeCodable; @@ -12,12 +10,12 @@ pub struct UrlEncodeData { } impl UrlEncodeData { - pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Result<Self, FromUtf8Error> { - Ok(Self { + pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self { + Self { raw: raw.as_ref().to_owned(), encoded: raw.as_ref().encode(), raw_string: String::from_utf8(raw.as_ref().into()).ok(), - }) + } } pub fn from_encoded(encoded: &str) -> Result<Self, ()> { Ok(Self { diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 83e2261..389eb28 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -40,7 +40,7 @@ impl DeCodable for &str { result.extend_from_slice(i.as_bytes()); continue; } - let Ok(char) = u8::from_str_radix(i[0..2].as_ref(), BASE16_HEXA_DECIMAL.into()) else { + let Ok(char) = u8::from_str_radix(i[0..=1].as_ref(), BASE16_HEXA_DECIMAL.into()) else { return Err(()); }; result.push(char); @@ -84,5 +84,9 @@ mod test { Err(()), "Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire".decode() ); + assert_eq!( + "hi?asdf=sadf%%&jkl=s", + String::from_utf8("hi?asdf=sadf%25%25&jkl=s".decode().unwrap()).unwrap() + ) } } diff --git a/site/src/main.rs b/site/src/main.rs index f677a34..22e7900 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -20,7 +20,7 @@ fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> Strin result } -fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Status, Data> { +fn handle_static_hi(request: Request<>, data: Data) -> Outcome<Response, Status, Data> { let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { keys } else { @@ -36,8 +36,8 @@ fn handle_static_hi(request: Request<'_>, data: Data) -> Outcome<Response, Statu // Outcome::Forward(data) } -fn handler(request: Request<'_>, _data: Data) -> Outcome<Response, Status, Data> { - let response = fileserver(request.uri.strip_prefix("static/").unwrap()); +fn handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { + let response = fileserver(request.uri.raw_string().unwrap().strip_prefix("static/").unwrap()); let response = match response { Ok(dat) => Response { headers: vec![], -- GitLab From 02b280583dcbe74fb07d8ae0c844260d814d9bc5 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 10 Jul 2023 12:27:11 +0200 Subject: [PATCH 51/65] Add Doc for ResponseBody trait end correct Doc for request_cookies and request_form_utils --- core/http/src/handling/request/datatypes.rs | 2 +- core/http/src/handling/request/form_utils.rs | 12 +++++----- .../http/src/handling/request/request_mime.rs | 6 ++--- core/http/src/handling/response/traits.rs | 22 +++++++++++++++++++ 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index 6d37b1f..e8b0303 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, error::Error, fmt::Display}; use crate::{ - handling::{methods::Method, routes::Uri}, + handling::methods::Method, utils::{mime::Mime, urlencoded::UrlEncodeData}, }; diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index b1e3a76..7991f4d 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -20,14 +20,12 @@ impl Request { /// /// # Examples /// ``` - /// use http::handling::request::Request; - /// use http::handling::request::ParseFormError; - /// use http::handling::request::ParseErrors; - /// use http::handling::methods::Method; + /// use http::handling::{request::{Request, ParseFormError, ParseErrors}, methods::Method}; + /// use http::utils::urlencoded::UrlEncodeData; /// /// /// let request = Request { - /// uri: "/form?name=Name&age=Age", + /// uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(), /// headers: vec![], /// method: Method::Get, /// cookies: None, @@ -38,7 +36,7 @@ impl Request { /// assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap()); /// /// let wrong_request = Request { - /// uri: "/form", + /// uri: UrlEncodeData::from_encoded("/form").unwrap(), /// ..request.clone() /// }; /// assert_eq!( @@ -49,7 +47,7 @@ impl Request { /// ); /// /// let bad_data = Request { - /// uri: "/form?age=", + /// uri: UrlEncodeData::from_encoded("/form?age=").unwrap(), /// ..request.clone() /// }; /// let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap(); diff --git a/core/http/src/handling/request/request_mime.rs b/core/http/src/handling/request/request_mime.rs index c37045e..75ad1e5 100644 --- a/core/http/src/handling/request/request_mime.rs +++ b/core/http/src/handling/request/request_mime.rs @@ -9,11 +9,11 @@ impl Request { /// ``` /// use http::{ /// handling::{methods::Method, request::Request}, - /// utils::mime::Mime, + /// utils::{mime::Mime, urlencoded::UrlEncodeData}, /// }; /// /// let mut request = Request { - /// uri: "thing", + /// uri: UrlEncodeData::from_encoded("thing").unwrap(), /// headers: vec![ /// "GET / 23".to_string(), /// "SDF:LKJSD:F".to_string(), @@ -25,7 +25,7 @@ impl Request { /// mime_type: None, /// }; /// let mut wrong = Request { - /// uri: "thing", + /// uri: UrlEncodeData::from_encoded("thing").unwrap(), /// headers: vec![ /// "GET / 23".to_string(), /// "SDF:LKJSD:F".to_string(), diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs index fc7eb09..700c392 100644 --- a/core/http/src/handling/response/traits.rs +++ b/core/http/src/handling/response/traits.rs @@ -1,8 +1,30 @@ use crate::{handling::routes::Body, utils::mime::Mime}; pub trait ResponseBody: Send { + /// Get a cloned version of the data as a [Vec<u8>] + /// # Ecamples + /// ``` + /// use http::handling::response::ResponseBody; + /// let data = "DATA"; + /// assert_eq!(b"DATA".to_vec(), data.get_data()); + /// ``` fn get_data(&self) -> Vec<u8>; + /// get the miem type of the data as a [Mime] + /// # Examples + /// ``` + /// use http::handling::response::ResponseBody; + /// use http::utils::mime::Mime; + /// let data = "DATA"; + /// assert_eq!(Mime::TextPlain, data.get_mime()); + /// ``` fn get_mime(&self) -> Mime; + /// get the length in bytes of the data as a [usize] + /// # Examples + /// ``` + /// use http::handling::response::ResponseBody; + /// let data = "DATA"; + /// assert_eq!(4, data.get_len()); + /// ``` fn get_len(&self) -> usize; } -- GitLab From 47cf59bdcc14bf8258287a255934b09594d46f70 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 10 Jul 2023 12:46:37 +0200 Subject: [PATCH 52/65] Add some documentation --- core/http/src/handling/response/traits.rs | 2 +- core/http/src/handling/routes.rs | 28 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs index 700c392..bc0aed5 100644 --- a/core/http/src/handling/response/traits.rs +++ b/core/http/src/handling/response/traits.rs @@ -1,7 +1,7 @@ use crate::{handling::routes::Body, utils::mime::Mime}; pub trait ResponseBody: Send { - /// Get a cloned version of the data as a [Vec<u8>] + /// Get a cloned version of the data as a [`Vec<u8>`] /// # Ecamples /// ``` /// use http::handling::response::ResponseBody; diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index e4246b8..a31dc80 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -16,13 +16,34 @@ pub struct RoutInfo { rank: Option<isize>, } +/// A struct to define Routes on the Server #[derive(Clone, Copy)] pub struct Route<'a> { + /// An optional name of the route pub name: Option<&'static str>, + /// The [Method] via which the route is accesable pub method: Method, + /// The Uri of the route, allows special cases: + /// # Examples + /// ``` + /// "/home"; // Only /home + /// "/<home>/something"; + /// // Variable content the users provides this acts for /<anything>/something + /// "/<home..>"; + /// // All Information after this sequence is irrelvent + /// // Matches: /a, /a/b/c ... + /// ``` pub uri: Uri<'a>, + /// The Handler function for this route, which gets called when the request need the route. + /// Inputs to the function are an [Request] and the [Data] which represents the body of the + /// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if + /// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests + /// [Data] for the next [Route] to take care of. pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + /// The Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be + /// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest pub rank: isize, + /// The Specific answer format of the [Route] as a [MediaType]. Optional pub format: Option<MediaType>, } @@ -38,6 +59,7 @@ impl Route<'_> { format: routeinfo.format, } } + /// Matches a [Request] Uri with a [Route] Uri. Respecting special cases like `?` and `<a..>` pub fn compare_uri(&self, uri: Uri) -> bool { let mut iter_comp_str = uri.split('/'); for true_str in self.uri.split('/') { @@ -65,8 +87,11 @@ impl Route<'_> { pub type Uri<'a> = &'a str; #[derive(Debug, Clone)] +/// A basic Body type for respones pub struct Body { + /// The Response body body: Vec<u8>, + /// The Mime Type mime_type: Mime, } @@ -89,8 +114,11 @@ impl Body { } #[derive(Debug, Clone)] +/// Data of the Body of a [Request] pub struct Data { + /// The Data pub buffer: Vec<u8>, + /// For Split Data if it is complete pub is_complete: bool, } -- GitLab From 74b8a2fc3415f11b5f8a625b4650ab2cb9541056 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Tue, 11 Jul 2023 14:17:59 +0200 Subject: [PATCH 53/65] Add documentation --- core/http/src/handlers/handler.rs | 17 ++++- core/http/src/handling/file_handlers.rs | 21 ++++++ core/http/src/handling/methods.rs | 1 + core/http/src/handling/request/datatypes.rs | 24 ++----- .../http/src/handling/request/request_impl.rs | 2 + .../response/cookie_management/cookie.rs | 10 +++ .../cookie_management/cookie_builder.rs | 11 ++++ .../response/cookie_management/mod.rs | 1 + core/http/src/handling/response/datatypes.rs | 22 +++++++ core/http/src/handling/response/mod.rs | 1 + core/http/src/handling/response/response.rs | 5 +- core/http/src/handling/response/status.rs | 66 +++++++++++-------- core/http/src/handling/response/traits.rs | 1 + core/http/src/handling/routes.rs | 35 +++++++++- core/http/src/setup.rs | 12 ++++ core/http/src/utils/mime/map.rs | 1 + core/http/src/utils/mime/mime_enum.rs | 2 + core/http/src/utils/urlencoded/datatypes.rs | 13 ++++ core/http/src/utils/urlencoded/endecode.rs | 14 +++- 19 files changed, 208 insertions(+), 51 deletions(-) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 8b02188..7ffa9e9 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -14,8 +14,20 @@ use crate::{handling::{ }, utils::urlencoded::UrlEncodeData}; use crate::setup::MountPoint; +/// The Maximal size of the Body of an HTTP-Message in bytes static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; +/// Function which handles a TCP Connection according to http-Standards by taking in a +/// [tokio::net::TcpStream] and a [`Vec<MountPoint<'_>>`]. +/// +/// Firstly validates the headers and body, Aborts with error messages if it finds faults. +/// +/// Secondly matches the request with a route in the mountpoint Vector and executes its handler +/// function. If it fails, checks for another, if nothing is found writes back a +/// [Status::NotFound]. If the handler function could respond it uses the [Response] of the handler +/// +/// # Panics +/// No Panics pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); @@ -168,7 +180,8 @@ pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoin } } -fn failure_handler<'a>(status: Status) -> Response { +/// Dumb function that renders a 404 page from any status given. but still writes that status code. +fn failure_handler(status: Status) -> Response { let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap(); Response { cookies: None, @@ -178,6 +191,7 @@ fn failure_handler<'a>(status: Status) -> Response { } } +/// Handler for len_not_defined errors. writes back directly async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { @@ -191,6 +205,7 @@ async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> { Ok(()) } +/// takes in an io error and writes it back in the server console to the user if writing failed fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } diff --git a/core/http/src/handling/file_handlers.rs b/core/http/src/handling/file_handlers.rs index 7a0b080..2d4d39b 100644 --- a/core/http/src/handling/file_handlers.rs +++ b/core/http/src/handling/file_handlers.rs @@ -6,9 +6,14 @@ use crate::{ }; #[derive(Debug)] +/// Struct to handle files on the server side. +/// Validates paths ignores actions like `..` pub struct NamedFile { + /// The length of the file in bytes, format: [usize] pub content_len: usize, + /// The Mime Type of the file as a [Mime] pub content_type: Mime, + /// The content of the file as a [`Vec<u8>`] pub content: Vec<u8>, } @@ -27,6 +32,17 @@ impl ResponseBody for NamedFile { } impl NamedFile { + /// Reads in a file as a [NamedFile]. Ignores seqences like `..` + /// + /// # Panics + /// + /// Panics if a [PathBuf] can't be convertet to a [str] + /// + /// # Errors + /// + /// Can give a [Status::NotFound] if it can't find the file + /// + /// This function will return an error if . pub fn open(path: PathBuf) -> Result<NamedFile, Status> { let path = proove_path(path); let data = fs::read(&path); @@ -44,6 +60,11 @@ impl NamedFile { } } +/// Validates a path so that seqences like `//` and `..` are omitted +/// +/// # Panics +/// +/// Panics if it can't convert a [PathBuf] to a [str] fn proove_path(path: PathBuf) -> PathBuf { PathBuf::from( path.to_str() diff --git a/core/http/src/handling/methods.rs b/core/http/src/handling/methods.rs index 5ca6211..a13e498 100644 --- a/core/http/src/handling/methods.rs +++ b/core/http/src/handling/methods.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, str::FromStr}; #[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)] +/// All HTTP Methods pub enum Method { Get, Head, diff --git a/core/http/src/handling/request/datatypes.rs b/core/http/src/handling/request/datatypes.rs index e8b0303..44f1cf3 100644 --- a/core/http/src/handling/request/datatypes.rs +++ b/core/http/src/handling/request/datatypes.rs @@ -5,25 +5,6 @@ use crate::{ utils::{mime::Mime, urlencoded::UrlEncodeData}, }; -pub trait FromRequest: Send { - fn get_data(&self) -> &Self; - fn set_data(&mut self, data: &Self); - fn append(&mut self, data: &Self); -} - -impl FromRequest for Vec<u8> { - fn get_data(&self) -> &Self { - self - } - - fn set_data(&mut self, data: &Self) { - *self = data.to_vec(); - } - fn append(&mut self, data: &Self) { - self.extend_from_slice(data); - } -} - type HeaderMap = Vec<String>; /// A struct to handle Requests @@ -49,9 +30,13 @@ pub struct Request { // } #[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// Media Types in which a Route can be requested to ansewr, optional for routes pub enum MediaType { + /// Json Data Json, + /// Plain Text Plain, + /// HTML Text Html, } @@ -62,6 +47,7 @@ pub enum ParseErrors { } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +/// Errors that Occur when a Form can't be parsed pub struct ParseFormError { pub error: ParseErrors, } diff --git a/core/http/src/handling/request/request_impl.rs b/core/http/src/handling/request/request_impl.rs index 2eb6fd0..01d6e44 100644 --- a/core/http/src/handling/request/request_impl.rs +++ b/core/http/src/handling/request/request_impl.rs @@ -3,12 +3,14 @@ use crate::handling::methods::Method; use super::Request; impl Request { + /// Checks if the request can have a body pub fn can_have_body(&self) -> bool { matches!( self.method, Method::Post | Method::Put | Method::Patch | Method::Delete ) } + /// Checks if a body is mandatory for the Request pub fn mandatory_body(&self) -> bool { matches!(self.method, Method::Post | Method::Put | Method::Patch) } diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index e96f825..58d2a0d 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,5 +1,13 @@ use std::time::Duration; +/// Structure representing a Cookie +/// # Creating a Cookie: +/// ``` +/// use http::handling::response::Cookie; +/// use http::handling::response::CookieBuilder; +/// +/// let cookie = CookieBuilder::build("name", "value").finish(); +/// ``` pub struct Cookie { /// Storage for the cookie string. Only used if this structure was derived /// from a string that was subsequently parsed. @@ -23,7 +31,9 @@ pub struct Cookie { } #[derive(Debug)] +/// SameSite Paremeters pub enum SameSite { + /// Requires Secure None, Lax, Strict, diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index e71eb44..a38f92c 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -2,11 +2,22 @@ use std::time::Duration; use super::{Cookie, SameSite}; +/// Builder wrapper for a Cookie +/// +/// # Example +/// ``` +/// use http::handling::response::Cookie; +/// use http::handling::response::CookieBuilder; +/// +/// let cookie = CookieBuilder::build("name", "value").path("/").finish(); +/// ``` pub struct CookieBuilder { + /// Cookie under the hood inner: Cookie, } impl CookieBuilder { + /// Builds a basic CookieBuilder from a name and a value pub fn build(name: &str, value: &str) -> Self { CookieBuilder { inner: Cookie { diff --git a/core/http/src/handling/response/cookie_management/mod.rs b/core/http/src/handling/response/cookie_management/mod.rs index 06d6b85..50f34ea 100644 --- a/core/http/src/handling/response/cookie_management/mod.rs +++ b/core/http/src/handling/response/cookie_management/mod.rs @@ -3,3 +3,4 @@ mod cookie_builder; pub use cookie::Cookie; pub use cookie::SameSite; +pub use cookie_builder::CookieBuilder; diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs index 7800deb..9225ced 100644 --- a/core/http/src/handling/response/datatypes.rs +++ b/core/http/src/handling/response/datatypes.rs @@ -3,15 +3,37 @@ use super::{Cookie, ResponseBody, Status}; type HeaderMap = Vec<String>; #[derive(Debug)] +/// Enum for the result of a Handling Function, where... +/// +/// [Outcome::Success] represents that the route +/// was successful and the Answer is contained in \[S\]. +/// [Outcome::Failure] represents that it was unsuccessful and nobody else is going to be +/// successful. \[E\] represnts the Error Code. +/// [Outcome::Forward] represents that some requirements weren't met for a route to be working with +/// the request so the next one that matches should cover that \[F\] represents the maybe processed +/// data of the request. +/// +/// # Example +/// ``` +/// use http::handling::{response::{Outcome, Response, Status}, routes::Data, request::Request}; +/// fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> { +/// todo!() +/// } +/// ``` pub enum Outcome<S, E, F> { Success(S), Failure(E), Forward(F), } +/// Response is a wrapper for http responses. pub struct Response { + /// the [`Vec<String>`] of headers unrelated to `Content-Type` and `Content-Length` pub headers: HeaderMap, + /// Optional Cookie in the response pub cookies: Option<Cookie>, + /// Status code of the response pub status: Option<Status>, + /// Response body and `Content-Type` and `Content-Length` headers. pub body: Box<dyn ResponseBody>, } diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs index ede6a42..535eaa8 100644 --- a/core/http/src/handling/response/mod.rs +++ b/core/http/src/handling/response/mod.rs @@ -5,6 +5,7 @@ mod status; mod traits; pub use cookie_management::Cookie; +pub use cookie_management::CookieBuilder; pub use cookie_management::SameSite; pub use datatypes::Outcome; pub use datatypes::Response; diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs index 23d2b09..62de6e9 100644 --- a/core/http/src/handling/response/response.rs +++ b/core/http/src/handling/response/response.rs @@ -6,7 +6,9 @@ use crate::handling::{methods::Method, request::Request, response::Status}; use super::Response; -impl Response<> { +impl Response { + /// Builds a [`Vec<u8>`] valid http response from a [Response] and consumes it. Optionally + /// takes in a request for things like [Method::Head] pub fn build(self, request: Option<Request>) -> Vec<u8> { let compiled_headers = format!( "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n", @@ -29,6 +31,7 @@ impl Response<> { compiled_out.extend_from_slice(&compiled_body); compiled_out } + /// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response] pub async fn write(self, mut stream: TcpStream, request: Option<Request>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; diff --git a/core/http/src/handling/response/status.rs b/core/http/src/handling/response/status.rs index 9f825af..dfc56a5 100644 --- a/core/http/src/handling/response/status.rs +++ b/core/http/src/handling/response/status.rs @@ -1,11 +1,14 @@ use std::fmt::Display; #[derive(Debug)] +/// Enum With every http status for complete documentation [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) pub enum Status { Continue, SwitchingProtocols, - WebDavProcessing, - ExperimentalEarlyHints, + /// WebDAV + Processing, + /// Experimental + EarlyHints, Ok, Created, Accepted, @@ -13,21 +16,26 @@ pub enum Status { NoContent, ResetContent, PartialContent, - WebDavMultiStatus, - WebDavAlreadyReported, + /// WebDAV + MultiStatus, + /// WebDAV + AlreadyReported, HttpDataEncodingImUsed, MultipleChoices, MovedPermanently, Found, SeeOther, NotModfiied, - DeprecatedUseProxy, - UnusedUnused, + /// Deprecated + UseProxy, + /// Deprecated + Unused, TemporaryRedirect, PermanentRedirect, BadRequest, Unauthorized, - ExperimentalPaymentRequired, + /// Experimental + PaymentRequired, Forbidden, NotFound, MethodNotAllowed, @@ -45,10 +53,14 @@ pub enum Status { ExpectationFailed, ImATeapot, MisdirectedRequest, - WebDavUnprocessableContent, - WebDavLocked, - WebDavFailedDependency, - ExperimenalTooEarly, + /// WebDAV + UnprocessableContent, + /// WebDAV + Locked, + /// WebDAV + FailedDependency, + /// Experimental + TooEarly, UpgradeRequred, PreconditionRequired, TooManyRequests, @@ -61,8 +73,10 @@ pub enum Status { GetawayTimeout, HttpVersionNotSupported, VariantAlsoNegotiates, - WebDavInsufficientStorage, - WebDavLoopDetected, + /// WebDAV + InsufficientStorage, + /// WebDAV + LoopDetected, NotExtended, NetworkAuthenticationRequired, } @@ -72,8 +86,8 @@ impl Display for Status { match self { Status::Continue => write!(f, "100 Continue"), Status::SwitchingProtocols => write!(f, "101 Switching Protocols"), - Status::WebDavProcessing => write!(f, "102 Processing"), - Status::ExperimentalEarlyHints => write!(f, "103 Early Hints"), + Status::Processing => write!(f, "102 Processing"), + Status::EarlyHints => write!(f, "103 Early Hints"), Status::Ok => write!(f, "200 OK"), Status::Created => write!(f, "201 Created"), Status::Accepted => write!(f, "202 Accepted"), @@ -81,8 +95,8 @@ impl Display for Status { Status::NoContent => write!(f, "204 No Content"), Status::ResetContent => write!(f, "205 Reset Content"), Status::PartialContent => write!(f, "206 Partial Content"), - Status::WebDavMultiStatus => write!(f, "207 Mutli-Status"), - Status::WebDavAlreadyReported => write!(f, "208 Already Reported"), + Status::MultiStatus => write!(f, "207 Mutli-Status"), + Status::AlreadyReported => write!(f, "208 Already Reported"), Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"), Status::MultipleChoices => write!(f, "300 Multiple Choices"), Status::MovedPermanently => write!(f, "301 Moved Permanently"), @@ -91,11 +105,11 @@ impl Display for Status { Status::NotModfiied => write!(f, "304 Not Modified"), Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"), Status::PermanentRedirect => write!(f, "308 Permanent Redirect"), - Status::DeprecatedUseProxy => write!(f, "305 Use Proxy"), - Status::UnusedUnused => write!(f, "306 unused"), + Status::UseProxy => write!(f, "305 Use Proxy"), + Status::Unused => write!(f, "306 unused"), Status::BadRequest => write!(f, "400 Bad Request"), Status::Unauthorized => write!(f, "401 Unauthorized"), - Status::ExperimentalPaymentRequired => write!(f, "402 Payment Required"), + Status::PaymentRequired => write!(f, "402 Payment Required"), Status::Forbidden => write!(f, "403 Forbidden"), Status::NotFound => write!(f, "404 Not Found"), Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"), @@ -115,10 +129,10 @@ impl Display for Status { Status::ExpectationFailed => write!(f, "417 Expectation Failed"), Status::ImATeapot => write!(f, "418 I'm a Teapot"), Status::MisdirectedRequest => write!(f, "421 Misdirected Request"), - Status::WebDavUnprocessableContent => write!(f, "422 Unprocessable Content"), - Status::WebDavLocked => write!(f, "423 Locked"), - Status::WebDavFailedDependency => write!(f, "424 Failed Dependency"), - Status::ExperimenalTooEarly => write!(f, "425 Too Early"), + Status::UnprocessableContent => write!(f, "422 Unprocessable Content"), + Status::Locked => write!(f, "423 Locked"), + Status::FailedDependency => write!(f, "424 Failed Dependency"), + Status::TooEarly => write!(f, "425 Too Early"), Status::UpgradeRequred => write!(f, "426 Upgrade Required"), Status::PreconditionRequired => write!(f, "428 Precondition Required"), Status::TooManyRequests => write!(f, "429 Too Many Requests"), @@ -135,8 +149,8 @@ impl Display for Status { Status::GetawayTimeout => write!(f, "504 Getaway Timeout"), Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"), Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"), - Status::WebDavInsufficientStorage => write!(f, "507 Insufficient Storage"), - Status::WebDavLoopDetected => write!(f, "508 Loop Detected"), + Status::InsufficientStorage => write!(f, "507 Insufficient Storage"), + Status::LoopDetected => write!(f, "508 Loop Detected"), Status::NotExtended => write!(f, "510 Not Extendend"), Status::NetworkAuthenticationRequired => { write!(f, "511 Network Authentication Required") diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs index bc0aed5..6e96ddf 100644 --- a/core/http/src/handling/response/traits.rs +++ b/core/http/src/handling/response/traits.rs @@ -1,5 +1,6 @@ use crate::{handling::routes::Body, utils::mime::Mime}; +/// Trait for using datatypes as response bodies pub trait ResponseBody: Send { /// Get a cloned version of the data as a [`Vec<u8>`] /// # Ecamples diff --git a/core/http/src/handling/routes.rs b/core/http/src/handling/routes.rs index a31dc80..02a81a3 100644 --- a/core/http/src/handling/routes.rs +++ b/core/http/src/handling/routes.rs @@ -7,12 +7,34 @@ use crate::{ utils::mime::Mime, }; -pub struct RoutInfo { +/// A RouteBuilder struct +pub struct RoutBuilder { + /// An optional name of the route name: Option<&'static str>, + /// The [Method] via which the route is accesable method: Method, + /// The path of the route, allows special cases: + /// # Examples + /// ``` + /// "/home"; // Only /home + /// "/<home>/something"; + /// // Variable content the users provides this acts for /<anything>/something + /// "/<home..>"; + /// // All Information after this sequence is irrelvent + /// // Matches: /a, /a/b/c ... + /// ``` path: &'static str, + /// The Handler function for this route, which gets called when the request need the route. + /// Inputs to the function are an [Request] and the [Data] which represents the body of the + /// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if + /// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests + /// [Data] for the next [Route] to take care of. handler: fn(Request, Data) -> Outcome<Response, Status, Data>, + /// The Specific answer format of the [Route] as a [MediaType]. Optional format: Option<MediaType>, + /// The Optional Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be + /// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest + /// If not given generated based on parematers. rank: Option<isize>, } @@ -48,7 +70,9 @@ pub struct Route<'a> { } impl Route<'_> { - pub fn from(routeinfo: RoutInfo) -> Self { + /// generates a Route from a Routebuilder + //TODO: ranking + pub fn from(routeinfo: RoutBuilder) -> Self { let rank = routeinfo.rank.unwrap_or(0); Route { name: routeinfo.name, @@ -84,6 +108,7 @@ impl Route<'_> { } } +/// Alias for using a &'a str for Uri pub type Uri<'a> = &'a str; #[derive(Debug, Clone)] @@ -96,18 +121,23 @@ pub struct Body { } impl Body { + /// New body of a Response pub fn new(body: Vec<u8>, mime_type: Mime) -> Self { Self { body, mime_type } } + /// Sets the `mime_type` of the Body pub fn set_mime_type(&mut self, mime_type: Mime) { self.mime_type = mime_type; } + /// Reassigns the body pub fn set_body(&mut self, body: Vec<u8>) { self.body = body; } + /// mime_type of the body pub fn mime_type(&self) -> Mime { self.mime_type } + /// cloned body as [`Vec<u8>`] pub fn body(&self) -> Vec<u8> { self.body.clone() } @@ -123,6 +153,7 @@ pub struct Data { } impl Data { + /// Checks if the buffer.oen() is -0 pub fn is_empty(&self) -> bool { self.buffer.len() == 0 } diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 1f061c6..31669bb 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -12,17 +12,27 @@ use crate::{ }; #[derive(Clone)] +/// Represnts a [MountPoint] that can be mounted in the config pub struct MountPoint<'a> { + /// The prefix of the [MountPoint] pub mountpoint: Uri<'a>, + /// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints + /// mountpoint pub routes: Vec<Route<'a>>, } +/// A server configuration that is run pub struct Config { + /// Contains an Optional [`Vec<MountPoint>`]. which contains all [MountPoint]s on the Server. + /// Which contain all the [Route]s mountpoints: Option<Vec<MountPoint<'static>>>, + /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, } impl<'a> Config { + /// Utility that checks if the given mointpoint is already taken. takes in the uri of the to be + /// mounted mountpoint fn check_mountpoint_taken(&self, to_insert: Uri) -> bool { if let Some(to_check) = &self.mountpoints { for i in to_check.iter() { @@ -33,6 +43,8 @@ impl<'a> Config { }; false } + /// mounts a [MountPoint] on the [Config] takes in a blank [MountPoint] and the [Route]s to be + /// mounted, mounts them and inserts the new [MountPoint]. Also sorts by rank. pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self { if self.check_mountpoint_taken(mountpoint) { eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m"); diff --git a/core/http/src/utils/mime/map.rs b/core/http/src/utils/mime/map.rs index 7117314..7cb4db2 100644 --- a/core/http/src/utils/mime/map.rs +++ b/core/http/src/utils/mime/map.rs @@ -1,5 +1,6 @@ use super::mime_enum::Mime; +/// Map with the string version of the Mime types and the values being corresponding [Mime]s pub static MIME_MAP: phf::Map<&'static str, Mime> = phf::phf_map! { "application/1d-interleaved-parityfec" => Mime::Application1dInterleavedParityfec, "application/3gpdash-qoe-report+xml" => Mime::Application3gpdashQoeReportXml, diff --git a/core/http/src/utils/mime/mime_enum.rs b/core/http/src/utils/mime/mime_enum.rs index 0a6a068..12c63d6 100644 --- a/core/http/src/utils/mime/mime_enum.rs +++ b/core/http/src/utils/mime/mime_enum.rs @@ -1,6 +1,7 @@ use super::map::MIME_MAP; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +/// An Enum of all Mime types pub enum Mime { Application1dInterleavedParityfec, Application3gpdashQoeReportXml, @@ -4074,6 +4075,7 @@ impl std::str::FromStr for Mime { } impl Mime { + /// Gets the mime type from filename, defaults to [Mime::TextPlain] pub fn from_filename(filename: &str) -> Self { match filename.split('.').last() { Some(v) => match v { diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index 6829e6b..cbb24c2 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -3,13 +3,18 @@ use crate::utils::urlencoded::endecode::EnCodable; use super::endecode::DeCodable; #[derive(Clone)] +/// A way to store UrlEncoded data pub struct UrlEncodeData { + /// Encoded string encoded: String, + /// raw data, unencoded raw: Vec<u8>, + /// raw string if it exists raw_string: Option<String>, } impl UrlEncodeData { + /// Generates a [UrlEncodeData] from any raw data that can be a slice of [u8] pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self { Self { raw: raw.as_ref().to_owned(), @@ -17,6 +22,11 @@ impl UrlEncodeData { raw_string: String::from_utf8(raw.as_ref().into()).ok(), } } + /// Generates a [UrlEncodeData] from a correctly encoded string + /// + /// # Errors + /// + /// errors if the encoded data is wrongly encoded -> %<invalid_character> pub fn from_encoded(encoded: &str) -> Result<Self, ()> { Ok(Self { encoded: encoded.to_owned(), @@ -25,12 +35,15 @@ impl UrlEncodeData { }) } + /// Gets a reference to the encoded data pub fn encoded(&self) -> &str { self.encoded.as_ref() } + /// Get a reference to the raw [u8] slice pub fn raw(&self) -> &[u8] { self.raw.as_ref() } + /// Gets an Optional string slice to the raw data pub fn raw_string(&self) -> Option<&str> { self.raw_string.as_ref().map(|x| x.as_str()) } diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 389eb28..9fba4cb 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -1,12 +1,20 @@ -static BASE16_HEXA_DECIMAL: u8 = 16; -static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 15; +/// Base of the HexaDecimal Number system +static BASE16_HEXA_DECIMAL: u8 = 0x10; +/// Highest possible Value per digit +static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 0xF; +/// Bits of a hexa decimal number static BASE16_HEXA_DECIMAL_DIGIT_BITS: u8 = 4; pub trait EnCodable { + /// Encodes the give data into a Percent Encoded [String] fn encode(&self) -> String; } pub trait DeCodable { + /// Decodes the given data into a [`Vec<u8>`] + /// + /// # Errors + /// Errors if the encoding isn't right fn decode(&self) -> Result<Vec<u8>, ()>; } @@ -50,6 +58,8 @@ impl DeCodable for &str { } } +/// converts a [u8] digit into the ascii code of its counterpart. The digit shouldn't be bigger +/// than [BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT] fn hex_to_digit(digit: u8) -> u8 { match digit { 0..=9 => b'0' + digit, -- GitLab From e43effce7c049a2de71a224d20948deeb9acb56d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 12 Jul 2023 19:20:27 +0200 Subject: [PATCH 54/65] Add Display for cookie. Implement Encode trait for T: AsRef<[u8]> --- .../response/cookie_management/cookie.rs | 103 +++++++++++++++++- .../cookie_management/cookie_builder.rs | 28 ++--- core/http/src/utils/urlencoded/endecode.rs | 6 + 3 files changed, 118 insertions(+), 19 deletions(-) diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 58d2a0d..3005eb3 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,4 +1,6 @@ -use std::time::Duration; +use std::{error::Error, str::FromStr, time::Duration}; + +use crate::handling::response::CookieBuilder; /// Structure representing a Cookie /// # Creating a Cookie: @@ -21,16 +23,16 @@ pub struct Cookie { /// The cookie's path domain, if any. pub(crate) path: Option<String>, /// Whether this cookie was marked Secure. - pub(crate) secure: Option<bool>, + pub(crate) secure: bool, /// Whether this cookie was marked HttpOnly. - pub(crate) http_only: Option<bool>, + pub(crate) http_only: bool, /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, pub(crate) expires: Option<String>, - pub(crate) partitioned: Option<bool>, + pub(crate) partitioned: bool, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// SameSite Paremeters pub enum SameSite { /// Requires Secure @@ -42,7 +44,7 @@ pub enum SameSite { impl std::fmt::Display for SameSite { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::None => write!(f, "SameSite=None; Secure"), + Self::None => write!(f, "SameSite=None"), Self::Lax => write!(f, "SameSite=Lax"), Self::Strict => write!(f, "SameSite=Strict"), } @@ -51,6 +53,95 @@ impl std::fmt::Display for SameSite { impl std::fmt::Display for Cookie { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut appendix = String::from(""); + if self.secure { + appendix += "; Secure"; + } + if self.http_only { + appendix += "; HttpOnly"; + } + if self.partitioned { + appendix += "; Partitioned"; + } + if let Some(max_age) = &self.max_age { + appendix += &format!("; Max-Age={}", max_age.as_secs()); + } + if let Some(domain) = &self.domain { + appendix += &format!("; Domain={}", domain); + } + if let Some(path) = &self.path { + appendix += &format!("; Path={}", path); + } + if let Some(same_site) = &self.same_site { + appendix += &format!("; {}", same_site); + if !self.secure && *same_site == SameSite::None { + appendix += &format!("; Secure"); + } + } + if let Some(expires) = &self.expires { + appendix += &format!("; Expires={}", expires) + } + write!(f, "Set-Cookie: {}={}{}", self.name, self.value, appendix) + } +} + +impl Error for ParseCookieError {} + +#[derive(Debug)] +pub struct ParseCookieError { + inner: CookieError, +} + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum CookieError { + MissingEqual, +} + +impl std::fmt::Display for ParseCookieError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ParseCookieError {{ error: {:?} }}", self.inner) + } +} + +impl FromStr for Cookie { + type Err = ParseCookieError; + fn from_str(s: &str) -> Result<Self, Self::Err> { todo!() } } + +#[cfg(test)] +mod test { + use std::time::Duration; + + use crate::handling::response::CookieBuilder; + + use super::SameSite; + + #[test] + fn test_cookie_to_string() { + let test_cookie1 = CookieBuilder::build("a", "cookie").finish().to_string(); + let test_cookie1_res = "Set-Cookie: a=cookie"; + let test_cookie2 = CookieBuilder::build("a", "secure_cookie") + .secure(true) + .finish() + .to_string(); + let test_cookie2_res = "Set-Cookie: a=secure_cookie; Secure"; + let test_cookie3 = CookieBuilder::build("ab", "ss") + .max_age(Duration::from_secs(24)) + .domain("codecraft.com") + .path("/") + .same_site(SameSite::None) + .http_only(true) + .partitioned(true) + .expires("Monday") + .finish() + .to_string(); + let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + + assert_eq!(test_cookie1_res, test_cookie1); + assert_eq!(test_cookie2_res, test_cookie2); + assert_eq!(test_cookie3_res, test_cookie3); + } +} diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index a38f92c..41bdc97 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -1,5 +1,7 @@ use std::time::Duration; +use crate::utils::urlencoded::EnCodable; + use super::{Cookie, SameSite}; /// Builder wrapper for a Cookie @@ -22,16 +24,16 @@ impl CookieBuilder { CookieBuilder { inner: Cookie { cookie_string: None, - name: name.to_owned(), - value: value.to_owned(), + name: name.encode(), + value: value.encode(), max_age: None, domain: None, path: None, - secure: None, - http_only: None, + secure: false, + http_only: false, same_site: None, expires: None, - partitioned: None, + partitioned: false, }, } } @@ -43,19 +45,19 @@ impl CookieBuilder { self } pub fn domain(mut self, domain: &str) -> Self { - self.inner.domain = Some(domain.to_owned()); + self.inner.domain = Some(domain.encode()); self } pub fn path(mut self, path: &str) -> Self { - self.inner.path = Some(path.to_owned()); + self.inner.path = Some(path.encode()); self } pub fn secure(mut self, secure: bool) -> Self { - self.inner.secure = Some(secure); + self.inner.secure = secure; self } pub fn http_only(mut self, http_only: bool) -> Self { - self.inner.http_only = Some(http_only); + self.inner.http_only = http_only; self } pub fn same_site(mut self, same_site: SameSite) -> Self { @@ -63,19 +65,19 @@ impl CookieBuilder { self } pub fn expires(mut self, expire: &str) -> Self { - self.inner.expires = Some(expire.to_owned()); + self.inner.expires = Some(expire.encode()); self } pub fn partitioned(mut self, partitioned: bool) -> Self { - self.inner.partitioned = Some(partitioned); + self.inner.partitioned = partitioned; self } pub fn name(mut self, name: &str) -> Self { - self.inner.name = name.to_owned(); + self.inner.name = name.encode(); self } pub fn value(mut self, value: &str) -> Self { - self.inner.value = value.to_owned(); + self.inner.value = value.encode(); self } } diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 9fba4cb..8dd45d6 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -18,6 +18,12 @@ pub trait DeCodable { fn decode(&self) -> Result<Vec<u8>, ()>; } +impl<T: AsRef<[u8]>> EnCodable for T { + fn encode(&self) -> String { + self.as_ref().encode() + } +} + impl EnCodable for [u8] { fn encode(self: &[u8]) -> String { let mut result = String::with_capacity(self.len()); -- GitLab From b9320218a6da2a008c13fd8c24a28a349420717e Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 13 Jul 2023 23:12:32 +0200 Subject: [PATCH 55/65] small performance increase --- core/http/src/handling/request/form_utils.rs | 2 +- core/http/src/handling/response/cookie_management/cookie.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index 7991f4d..67ada47 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -157,7 +157,7 @@ impl Request { .trim_matches('"'); let mut temp_bound = "--".to_string(); temp_bound.push_str(boundary); - let end_boundary = format!("{temp_bound}--\r").as_bytes().to_owned(); + let end_boundary: Vec<u8> = format!("{temp_bound}--\r").into(); temp_bound.push('\r'); let boundary = temp_bound.as_bytes(); Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap); diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 3005eb3..bbb5ac3 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,7 +1,5 @@ use std::{error::Error, str::FromStr, time::Duration}; -use crate::handling::response::CookieBuilder; - /// Structure representing a Cookie /// # Creating a Cookie: /// ``` -- GitLab From f4d6db30e0bbde2a4ce20f2fc4f4b3fa40cabc6f Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 14 Jul 2023 23:35:50 +0200 Subject: [PATCH 56/65] Start the from_str implementation of Cookie --- .../response/cookie_management/cookie.rs | 56 +++++++++++++++++-- .../cookie_management/cookie_builder.rs | 1 + core/http/src/utils/urlencoded/datatypes.rs | 4 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index bbb5ac3..6de43e0 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,5 +1,7 @@ use std::{error::Error, str::FromStr, time::Duration}; +use crate::{handling::response::CookieBuilder, utils::urlencoded::DeCodable}; + /// Structure representing a Cookie /// # Creating a Cookie: /// ``` @@ -8,6 +10,7 @@ use std::{error::Error, str::FromStr, time::Duration}; /// /// let cookie = CookieBuilder::build("name", "value").finish(); /// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Cookie { /// Storage for the cookie string. Only used if this structure was derived /// from a string that was subsequently parsed. @@ -93,6 +96,7 @@ pub struct ParseCookieError { #[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] pub enum CookieError { MissingEqual, + InvalidAttribute, } impl std::fmt::Display for ParseCookieError { @@ -104,7 +108,44 @@ impl std::fmt::Display for ParseCookieError { impl FromStr for Cookie { type Err = ParseCookieError; fn from_str(s: &str) -> Result<Self, Self::Err> { - todo!() + let mut final_result = CookieBuilder::build("", ""); + let mut first = true; + for part in s.split(';') { + let trimmed_part = part.trim(); + if first { + let Some(name_val) = part.split_once('=') else { + return Err(Self::Err { inner: CookieError::MissingEqual }); + }; + unsafe { + final_result = CookieBuilder::build( + &String::from_utf8_unchecked(if let Ok(name) = name_val.0.decode() { + name + } else { + name_val.0.into() + }), + &String::from_utf8_unchecked(if let Ok(value) = name_val.1.decode() { + value + } else { + name_val.1.into() + }), + ); + } + first = false; + break; + } + final_result = match trimmed_part { + "Secure" => final_result.secure(true), + "HttpOnly" => final_result.http_only(true), + "Partitioned" => final_result.partitioned(true), + _ => { + return Err(Self::Err { + inner: CookieError::InvalidAttribute, + }); + } + } + } + println!("{:?}", final_result); + Ok(final_result.finish()) } } @@ -112,7 +153,7 @@ impl FromStr for Cookie { mod test { use std::time::Duration; - use crate::handling::response::CookieBuilder; + use crate::handling::response::{Cookie, CookieBuilder}; use super::SameSite; @@ -133,13 +174,16 @@ mod test { .http_only(true) .partitioned(true) .expires("Monday") - .finish() - .to_string(); + .finish(); let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ - Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + Max-Age=24; Domain=codecraft.com; Path=%2F; SameSite=None; Secure; Expires=Monday"; + + let test_cookie4_res = "ab=ss; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=%2F; SameSite=None; Secure; Expires=Monday"; assert_eq!(test_cookie1_res, test_cookie1); assert_eq!(test_cookie2_res, test_cookie2); - assert_eq!(test_cookie3_res, test_cookie3); + assert_eq!(test_cookie3_res, test_cookie3.to_string()); + assert_eq!(test_cookie4_res.parse::<Cookie>().unwrap(), test_cookie3); } } diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index 41bdc97..5a3dac3 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -13,6 +13,7 @@ use super::{Cookie, SameSite}; /// /// let cookie = CookieBuilder::build("name", "value").path("/").finish(); /// ``` +#[derive(Debug)] pub struct CookieBuilder { /// Cookie under the hood inner: Cookie, diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index cbb24c2..2db1e18 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -18,7 +18,7 @@ impl UrlEncodeData { pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self { Self { raw: raw.as_ref().to_owned(), - encoded: raw.as_ref().encode(), + encoded: raw.encode(), raw_string: String::from_utf8(raw.as_ref().into()).ok(), } } @@ -45,7 +45,7 @@ impl UrlEncodeData { } /// Gets an Optional string slice to the raw data pub fn raw_string(&self) -> Option<&str> { - self.raw_string.as_ref().map(|x| x.as_str()) + self.raw_string.as_deref() } } -- GitLab From 15a864bbfdd2d437315b0808da51fb9c6b941fb0 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 15 Jul 2023 22:09:50 +0200 Subject: [PATCH 57/65] Add uri , work on FromStr of cookie --- TODO.md | 7 + .../response/cookie_management/cookie.rs | 10 +- core/http/src/utils/mod.rs | 1 + core/http/src/utils/url_utils/datatypes.rs | 38 ++++ core/http/src/utils/url_utils/mod.rs | 7 + core/http/src/utils/url_utils/uri.rs | 183 ++++++++++++++++++ core/http/src/utils/urlencoded/datatypes.rs | 8 +- core/http/src/utils/urlencoded/mod.rs | 3 +- 8 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 core/http/src/utils/url_utils/datatypes.rs create mode 100644 core/http/src/utils/url_utils/mod.rs create mode 100644 core/http/src/utils/url_utils/uri.rs diff --git a/TODO.md b/TODO.md index c15fe15..7d14bdd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,3 +1,5 @@ +GOOD HABITS + 1. If you collect an iterator which you don't index in, you don't need to collect it 2. avoid temporary hashmaps 3. rewrite POST request stuff TICK @@ -6,4 +8,9 @@ 6. Remove unwraps 7. Reusable allocations +TODO: + +1. Uri structs everywhere +2. Cookie From String fix + API design 3. No decisions for the caller diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 6de43e0..4bd4d2e 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -153,7 +153,7 @@ impl FromStr for Cookie { mod test { use std::time::Duration; - use crate::handling::response::{Cookie, CookieBuilder}; + use crate::handling::response::CookieBuilder; use super::SameSite; @@ -176,14 +176,12 @@ mod test { .expires("Monday") .finish(); let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ - Max-Age=24; Domain=codecraft.com; Path=%2F; SameSite=None; Secure; Expires=Monday"; - - let test_cookie4_res = "ab=ss; HttpOnly; Partitioned; \ - Max-Age=24; Domain=codecraft.com; Path=%2F; SameSite=None; Secure; Expires=Monday"; + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; assert_eq!(test_cookie1_res, test_cookie1); assert_eq!(test_cookie2_res, test_cookie2); assert_eq!(test_cookie3_res, test_cookie3.to_string()); - assert_eq!(test_cookie4_res.parse::<Cookie>().unwrap(), test_cookie3); } + #[test] + fn cooki_from_string() {} } diff --git a/core/http/src/utils/mod.rs b/core/http/src/utils/mod.rs index 98498d0..6f54242 100644 --- a/core/http/src/utils/mod.rs +++ b/core/http/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod mime; +pub mod url_utils; pub mod urlencoded; diff --git a/core/http/src/utils/url_utils/datatypes.rs b/core/http/src/utils/url_utils/datatypes.rs new file mode 100644 index 0000000..3c40037 --- /dev/null +++ b/core/http/src/utils/url_utils/datatypes.rs @@ -0,0 +1,38 @@ +use std::error; + +use crate::utils::urlencoded::UrlEncodeData; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct Uri { + pub(super) parts: Vec<UrlEncodeData>, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct RawUri { + pub(super) raw_string: String, + pub(super) infinte_end: bool, + pub(super) parts: Vec<RawUriElement>, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum RawUriElement { + Variable, + Name(UrlEncodeData), +} + +#[derive(Debug)] +pub enum UriError { + InvalidUriEncoding, +} + +#[derive(Debug)] +pub struct ParseUriError { + error: UriError, +} +impl std::fmt::Display for ParseUriError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl error::Error for ParseUriError {} diff --git a/core/http/src/utils/url_utils/mod.rs b/core/http/src/utils/url_utils/mod.rs new file mode 100644 index 0000000..7a6061d --- /dev/null +++ b/core/http/src/utils/url_utils/mod.rs @@ -0,0 +1,7 @@ +mod datatypes; +mod uri; +pub use datatypes::ParseUriError; +pub use datatypes::RawUri; +pub use datatypes::RawUriElement; +pub use datatypes::Uri; +pub use datatypes::UriError; diff --git a/core/http/src/utils/url_utils/uri.rs b/core/http/src/utils/url_utils/uri.rs new file mode 100644 index 0000000..b9e6939 --- /dev/null +++ b/core/http/src/utils/url_utils/uri.rs @@ -0,0 +1,183 @@ +use std::str::FromStr; + +use crate::utils::{url_utils::datatypes::RawUriElement, urlencoded::UrlEncodeData}; + +use super::datatypes::{ParseUriError, RawUri, Uri, UriError}; + +impl Uri { + pub fn new(parts: Vec<&str>) -> Self { + Self { + parts: parts + .into_iter() + .map(|part| UrlEncodeData::from_raw(part)) + .collect(), + } + } +} + +impl RawUri { + pub fn new(parts: Vec<&str>) -> Self { + let mut result = Self { + infinte_end: false, + parts: Vec::with_capacity(parts.len()), + raw_string: "/".to_owned() + &parts.join("/"), + }; + for part in parts { + if part.starts_with("<") && part.ends_with("..>") { + result.infinte_end = true; + break; + } + if part.starts_with("<") && part.ends_with(">") { + result.parts.push(RawUriElement::Variable); + continue; + } + result + .parts + .push(RawUriElement::Name(UrlEncodeData::from_raw(part))) + } + result + } + pub fn compare_uri(self, uri: Uri) -> bool { + let mut iter_comp = uri.parts.iter(); + let mut counter = 0; + for element in self.parts.iter() { + counter += 1; + let Some(compare_element) = iter_comp.next() else { + return false; + }; + + if *element == RawUriElement::Variable { + continue; + } + if let RawUriElement::Name(name) = element { + if name.encoded() != compare_element.encoded() { + return false; + } + } else { + return false; + } + } + if counter > self.parts.len() && !self.infinte_end { + return false; + } + true + } +} + +impl std::fmt::Display for RawUri { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.raw_string) + } +} + +impl std::fmt::Display for Uri { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let url = self + .parts + .iter() + .map(|part| part.encoded()) + .collect::<Vec<_>>(); + write!(f, "/{}", url.join("/")) + } +} + +impl FromStr for Uri { + type Err = ParseUriError; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let split = s.split('/'); + let mut result = Vec::new(); + for sub in split { + if sub.is_empty() { + continue; + } + result.push(if let Ok(coded) = UrlEncodeData::from_encoded(sub) { + coded + } else { + UrlEncodeData::from_raw(sub) + }); + } + Ok(Self { parts: result }) + } +} + +impl FromStr for RawUri { + type Err = UriError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let parts = s.split('/').collect::<Vec<&str>>(); + let mut result = Self { + infinte_end: false, + parts: Vec::new(), + raw_string: parts.join("/"), + }; + for part in parts { + if part.is_empty() { + continue; + } + if part.starts_with("<") && part.ends_with("..>") { + result.infinte_end = true; + break; + } + if part.starts_with("<") && part.ends_with(">") { + result.parts.push(RawUriElement::Variable); + continue; + } + result + .parts + .push(RawUriElement::Name(UrlEncodeData::from_raw(part))) + } + Ok(result) + } +} + +#[cfg(test)] +mod test { + use crate::utils::{ + url_utils::{ + datatypes::{RawUri, RawUriElement}, + Uri, + }, + urlencoded::UrlEncodeData, + }; + + #[test] + fn uri_to_string() { + assert_eq!("/a/%20", Uri::new(vec!["a", " "]).to_string()); + } + + #[test] + fn uri_from_string() { + assert_eq!(Uri::new(vec!["a", " "]), "/a/%20".parse().unwrap()) + } + + #[test] + fn raw_uri_from_string() { + assert_eq!( + RawUri { + raw_string: "/<name>/a/<f..>".to_owned(), + infinte_end: true, + parts: vec![ + RawUriElement::Variable, + RawUriElement::Name(UrlEncodeData::from_raw("a")), + ] + }, + "/<name>/a/<f..>".parse().unwrap() + ); + } + + #[test] + fn raw_uri_to_string() { + assert_eq!( + "/<name>/a/<f..>", + RawUri { + raw_string: "/<name>/a/<f..>".to_owned(), + infinte_end: true, + parts: vec![ + RawUriElement::Variable, + RawUriElement::Name(UrlEncodeData::from_raw("a")), + ] + } + .to_string() + ) + } +} diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index 2db1e18..d48e02b 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -2,15 +2,15 @@ use crate::utils::urlencoded::endecode::EnCodable; use super::endecode::DeCodable; -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] /// A way to store UrlEncoded data pub struct UrlEncodeData { /// Encoded string - encoded: String, + pub(crate) encoded: String, /// raw data, unencoded - raw: Vec<u8>, + pub(crate) raw: Vec<u8>, /// raw string if it exists - raw_string: Option<String>, + pub(crate) raw_string: Option<String>, } impl UrlEncodeData { diff --git a/core/http/src/utils/urlencoded/mod.rs b/core/http/src/utils/urlencoded/mod.rs index 6511cfc..3145200 100644 --- a/core/http/src/utils/urlencoded/mod.rs +++ b/core/http/src/utils/urlencoded/mod.rs @@ -1,5 +1,4 @@ mod datatypes; mod endecode; pub use datatypes::UrlEncodeData; -pub use endecode::DeCodable; -pub use endecode::EnCodable; +pub use endecode::{DeCodable, EnCodable}; -- GitLab From e748f9785a2566621ea520bb3fcc89214b22bd31 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Mon, 17 Jul 2023 23:13:23 +0200 Subject: [PATCH 58/65] FromStr for Cookie, Change Cookie Path to be Uri - Finish implementing FromStr trait for Cookie - Change the path variable of the Cookie struct to be a Uri - Update docs of CookieBuilder to compile --- .../response/cookie_management/cookie.rs | 176 +++++++++++++----- .../cookie_management/cookie_builder.rs | 11 +- .../response/cookie_management/error_types.rs | 43 +++++ .../response/cookie_management/mod.rs | 5 + core/http/src/utils/url_utils/datatypes.rs | 4 +- 5 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 core/http/src/handling/response/cookie_management/error_types.rs diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 4bd4d2e..8234ab4 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -1,6 +1,17 @@ -use std::{error::Error, str::FromStr, time::Duration}; +use std::{collections::HashMap, str::FromStr, time::Duration}; -use crate::{handling::response::CookieBuilder, utils::urlencoded::DeCodable}; +use crate::{ + handling::response::{cookie_management::error_types::CookieError, CookieBuilder}, + utils::{url_utils::Uri, urlencoded::DeCodable}, +}; + +macro_rules! update_map { + ($map:expr, $key:expr) => { + *$map.get_mut($key).unwrap() = true; + }; +} + +use super::error_types::{ParseCookieError, ParseSameSiteError, SameSiteError}; /// Structure representing a Cookie /// # Creating a Cookie: @@ -17,20 +28,20 @@ pub struct Cookie { pub(crate) cookie_string: Option<String>, pub(crate) name: String, pub(crate) value: String, - // expires: Option<Tm>, - pub(crate) max_age: Option<Duration>, - /// The cookie's domain, if any. - pub(crate) domain: Option<String>, - /// The cookie's path domain, if any. - pub(crate) path: Option<String>, /// Whether this cookie was marked Secure. pub(crate) secure: bool, /// Whether this cookie was marked HttpOnly. pub(crate) http_only: bool, + pub(crate) partitioned: bool, + // expires: Option<Tm>, + pub(crate) max_age: Option<Duration>, /// The draft `SameSite` attribute. pub(crate) same_site: Option<SameSite>, + /// The cookie's domain, if any. + pub(crate) domain: Option<String>, + /// The cookie's path domain, if any. + pub(crate) path: Option<Uri>, pub(crate) expires: Option<String>, - pub(crate) partitioned: bool, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -52,6 +63,21 @@ impl std::fmt::Display for SameSite { } } +impl FromStr for SameSite { + type Err = ParseSameSiteError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "None" => Ok(SameSite::None), + "Lax" => Ok(SameSite::Lax), + "Strict" => Ok(SameSite::Strict), + _ => Err(Self::Err { + inner: SameSiteError::NotAValidVariant, + }), + } + } +} + impl std::fmt::Display for Cookie { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut appendix = String::from(""); @@ -86,31 +112,24 @@ impl std::fmt::Display for Cookie { } } -impl Error for ParseCookieError {} - -#[derive(Debug)] -pub struct ParseCookieError { - inner: CookieError, -} - -#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] -pub enum CookieError { - MissingEqual, - InvalidAttribute, -} - -impl std::fmt::Display for ParseCookieError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ParseCookieError {{ error: {:?} }}", self.inner) - } -} - impl FromStr for Cookie { type Err = ParseCookieError; fn from_str(s: &str) -> Result<Self, Self::Err> { + let mut map = HashMap::with_capacity(8); + map.insert("Partitioned", false); + map.insert("HttpOnly", false); + map.insert("Secure", false); + map.insert("SameSite", false); + map.insert("Max-Age", false); + map.insert("Domain", false); + map.insert("Path", false); + map.insert("Expires", false); let mut final_result = CookieBuilder::build("", ""); let mut first = true; - for part in s.split(';') { + let stripped = s.strip_prefix("Set-Cookie: ").ok_or(Self::Err { + inner: CookieError::NoSetCookieHeader, + })?; + for part in stripped.split(';') { let trimmed_part = part.trim(); if first { let Some(name_val) = part.split_once('=') else { @@ -131,20 +150,74 @@ impl FromStr for Cookie { ); } first = false; - break; + continue; } - final_result = match trimmed_part { - "Secure" => final_result.secure(true), - "HttpOnly" => final_result.http_only(true), - "Partitioned" => final_result.partitioned(true), - _ => { - return Err(Self::Err { - inner: CookieError::InvalidAttribute, - }); - } + if !map.get("Max-Age").unwrap() && trimmed_part.starts_with("Max-Age=") { + final_result = final_result.max_age(Duration::from_secs( + trimmed_part + .strip_prefix("Max-Age=") + .unwrap() + .parse() + .map_err(|_| Self::Err { + inner: CookieError::InvalidMaxAge, + })?, + )); + update_map!(map, "Max-Age"); + continue; + } + if !map.get("Expires").unwrap() && trimmed_part.starts_with("Expires=") { + final_result = final_result.expires(trimmed_part.strip_prefix("Expires=").unwrap()); + update_map!(map, "Expires"); + continue; + } + if !map.get("HttpOnly").unwrap() && trimmed_part == "HttpOnly" { + final_result = final_result.http_only(true); + update_map!(map, "HttpOnly"); + continue; + } + if !map.get("SameSite").unwrap() && trimmed_part.starts_with("SameSite=") { + final_result = final_result.same_site( + trimmed_part + .strip_prefix("SameSite=") + .unwrap() + .parse::<SameSite>() + .map_err(|err| Self::Err { + inner: CookieError::InvilidSameSite(err.inner), + })?, + ); + update_map!(map, "SameSite"); + continue; + } + if !map.get("Path").unwrap() && trimmed_part.starts_with("Path=") { + final_result = final_result.path( + trimmed_part + .strip_prefix("Path=") + .unwrap() + .parse::<Uri>() + .map_err(|err| Self::Err { + inner: CookieError::PathError(err.error), + })?, + ); + update_map!(map, "Path"); + continue; + } + if !map.get("Domain").unwrap() && trimmed_part.starts_with("Domain=") { + final_result = final_result.domain(trimmed_part.strip_prefix("Domain=").unwrap()); + update_map!(map, "Domain"); + continue; + } + if !map.get("Secure").unwrap() && trimmed_part == "Secure" { + final_result = final_result.secure(true); + update_map!(map, "Secure"); + continue; + } + + if !map.get("Partitioned").unwrap() && trimmed_part == "Partitioned" { + final_result = final_result.partitioned(true); + update_map!(map, "Partitioned"); + continue; } } - println!("{:?}", final_result); Ok(final_result.finish()) } } @@ -153,7 +226,7 @@ impl FromStr for Cookie { mod test { use std::time::Duration; - use crate::handling::response::CookieBuilder; + use crate::handling::response::{Cookie, CookieBuilder}; use super::SameSite; @@ -169,19 +242,32 @@ mod test { let test_cookie3 = CookieBuilder::build("ab", "ss") .max_age(Duration::from_secs(24)) .domain("codecraft.com") - .path("/") + .path("/".parse().unwrap()) .same_site(SameSite::None) .http_only(true) .partitioned(true) .expires("Monday") .finish(); - let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ - Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + let test_cookie3_res = "Set-Cookie: ab=ss; Secure; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Expires=Monday"; assert_eq!(test_cookie1_res, test_cookie1); assert_eq!(test_cookie2_res, test_cookie2); assert_eq!(test_cookie3_res, test_cookie3.to_string()); } #[test] - fn cooki_from_string() {} + fn cookie_from_string() { + let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \ + Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday"; + let test_cookie3 = CookieBuilder::build("ab", "ss") + .max_age(Duration::from_secs(24)) + .domain("codecraft.com") + .path("/".parse().unwrap()) + .same_site(SameSite::None) + .http_only(true) + .partitioned(true) + .expires("Monday") + .finish(); + assert_eq!(test_cookie3, test_cookie3_res.parse::<Cookie>().unwrap()); + } } diff --git a/core/http/src/handling/response/cookie_management/cookie_builder.rs b/core/http/src/handling/response/cookie_management/cookie_builder.rs index 5a3dac3..2699257 100644 --- a/core/http/src/handling/response/cookie_management/cookie_builder.rs +++ b/core/http/src/handling/response/cookie_management/cookie_builder.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::utils::urlencoded::EnCodable; +use crate::utils::{url_utils::Uri, urlencoded::EnCodable}; use super::{Cookie, SameSite}; @@ -11,7 +11,7 @@ use super::{Cookie, SameSite}; /// use http::handling::response::Cookie; /// use http::handling::response::CookieBuilder; /// -/// let cookie = CookieBuilder::build("name", "value").path("/").finish(); +/// let cookie = CookieBuilder::build("name", "value").path("/".parse().unwrap()).finish(); /// ``` #[derive(Debug)] pub struct CookieBuilder { @@ -49,8 +49,8 @@ impl CookieBuilder { self.inner.domain = Some(domain.encode()); self } - pub fn path(mut self, path: &str) -> Self { - self.inner.path = Some(path.encode()); + pub fn path(mut self, path: Uri) -> Self { + self.inner.path = Some(path); self } pub fn secure(mut self, secure: bool) -> Self { @@ -62,6 +62,9 @@ impl CookieBuilder { self } pub fn same_site(mut self, same_site: SameSite) -> Self { + if same_site == SameSite::None { + self.inner.secure = true; + } self.inner.same_site = Some(same_site); self } diff --git a/core/http/src/handling/response/cookie_management/error_types.rs b/core/http/src/handling/response/cookie_management/error_types.rs new file mode 100644 index 0000000..12277c0 --- /dev/null +++ b/core/http/src/handling/response/cookie_management/error_types.rs @@ -0,0 +1,43 @@ +use std::error::Error; + +use crate::utils::url_utils::UriError; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum SameSiteError { + NotAValidVariant, +} + +#[derive(Debug)] +pub struct ParseSameSiteError { + pub inner: SameSiteError, +} + +impl Error for ParseSameSiteError {} + +impl std::fmt::Display for ParseSameSiteError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} +impl Error for ParseCookieError {} + +#[derive(Debug)] +pub struct ParseCookieError { + pub inner: CookieError, +} + +#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)] +pub enum CookieError { + MissingEqual, + InvalidAttribute, + InvalidMaxAge, + NoSetCookieHeader, + InvilidSameSite(SameSiteError), + PathError(UriError), +} + +impl std::fmt::Display for ParseCookieError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ParseCookieError {{ error: {:?} }}", self.inner) + } +} diff --git a/core/http/src/handling/response/cookie_management/mod.rs b/core/http/src/handling/response/cookie_management/mod.rs index 50f34ea..5681658 100644 --- a/core/http/src/handling/response/cookie_management/mod.rs +++ b/core/http/src/handling/response/cookie_management/mod.rs @@ -1,6 +1,11 @@ mod cookie; mod cookie_builder; +mod error_types; pub use cookie::Cookie; pub use cookie::SameSite; pub use cookie_builder::CookieBuilder; +pub use error_types::CookieError; +pub use error_types::ParseCookieError; +pub use error_types::ParseSameSiteError; +pub use error_types::SameSiteError; diff --git a/core/http/src/utils/url_utils/datatypes.rs b/core/http/src/utils/url_utils/datatypes.rs index 3c40037..6e36080 100644 --- a/core/http/src/utils/url_utils/datatypes.rs +++ b/core/http/src/utils/url_utils/datatypes.rs @@ -20,14 +20,14 @@ pub enum RawUriElement { Name(UrlEncodeData), } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum UriError { InvalidUriEncoding, } #[derive(Debug)] pub struct ParseUriError { - error: UriError, + pub error: UriError, } impl std::fmt::Display for ParseUriError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -- GitLab From 0d46a64806ca273261d40f2685110953d34c2a7e Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 19 Jul 2023 14:42:48 +0200 Subject: [PATCH 59/65] Implement HTTPS --- core/http/Cargo.lock | 269 ++++++++++++++++++- core/http/Cargo.toml | 1 + core/http/src/certificates/certificate.crt | 29 +++ core/http/src/certificates/identity.pfx | Bin 0 -> 4179 bytes core/http/src/certificates/private.key | 54 ++++ core/http/src/handlers/handler.rs | 25 +- core/http/src/handling/response/response.rs | 3 +- core/http/src/setup.rs | 10 +- site/Cargo.lock | 275 +++++++++++++++++++- 9 files changed, 655 insertions(+), 11 deletions(-) create mode 100644 core/http/src/certificates/certificate.crt create mode 100644 core/http/src/certificates/identity.pfx create mode 100644 core/http/src/certificates/private.key diff --git a/core/http/Cargo.lock b/core/http/Cargo.lock index 8496ac6..9ac579c 100644 --- a/core/http/Cargo.lock +++ b/core/http/Cargo.lock @@ -20,12 +20,79 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -35,20 +102,59 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "http" version = "0.1.0" dependencies = [ "phf", "tokio", + "tokio-native-tls", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" version = "0.4.9" @@ -80,16 +186,84 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -108,7 +282,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -161,6 +335,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "proc-macro2" version = "1.0.56" @@ -203,12 +383,67 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -262,6 +497,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "tokio" version = "1.28.2" @@ -292,12 +541,28 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index 45da492..d2fc133 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] tokio = { version = "1.28.2", features = ["full"] } phf = { version = "0.11", features = ["macros"] } +tokio-native-tls = "0.3.0" diff --git a/core/http/src/certificates/certificate.crt b/core/http/src/certificates/certificate.crt new file mode 100644 index 0000000..fa80d20 --- /dev/null +++ b/core/http/src/certificates/certificate.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE+zCCAuOgAwIBAgIUduzhfNiMYBDh6wuv3MjdxK2C6bYwDQYJKoZIhvcNAQEL +BQAwDTELMAkGA1UEBhMCREUwHhcNMjMwNzE5MTEyNzE3WhcNMjQwNzE4MTEyNzE3 +WjANMQswCQYDVQQGEwJERTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AKKTQ3sMq9yYNST56n9NbNkycJ1QQORl6DtqqFB3iVRrNOzs0o1RAqXtVCWIiyJ2 +cvC/nPz9V+QRM9ZwovYGt7I2IGm3Wu15wWJH71QakPTElsoM+twVHUU+E8yNaDpY +vibP+6MIz7O1DD/uSqA5779n0/eZuC6Li1LYiaJNLHst9sqo1f5UccG66HdanT6+ +oCbvUyS8tiVnepM0rTku7k/7XVkKVa71VAojoVfNhSpF/tWQJC5PD8Sp9X5E31QK +BMjMckdS/ev4qruPGBOF10eilt+nJAxUpbJ87UamG/VxI3DDNchZ+ssckgDRcd44 +Td3Z2SsOAcHebCtrgOL4hFZQ+DsBMIDCWuj/BcKmN+RsnE40X7I01qBjbUABU/kp +cfJxd3YtkBysQ8VuMz00/jLzpUcTvbVjXq9ktCGBFbAUd+qqxNNA0Tqxw8TlE5pY +is2DzVpZvtuDscZAevMFRmi0aYN/RPGhcggeAUDAE4pHDgUdyZx8DKPZeDrPHRg+ +q+ESDB1UI23D4xkg97dhMFCClUute5YItVv21uGlK8HVa9BpXNgEmqOWEbrAyaFk +AKIsdibdo7G7DZxbC0NSP4MFrBxsdRl4MrCWKcipoWJ78LDQ5MXWtPEPotBQ+wsB +zK56WUFkMKwyRXA8/tcS3DZ+GNugavMaSewJ2zgjgkLjAgMBAAGjUzBRMB0GA1Ud +DgQWBBQpS4RUpA6uFkfMDlfH1AG8e8JyejAfBgNVHSMEGDAWgBQpS4RUpA6uFkfM +DlfH1AG8e8JyejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAW +9EW7neVsyntMqUWYHfg7hALxd800gF8t3jpBXOkg1j4Dg+XewsJsix8k96+zA0RF +wXR2iyQGSaGE5wENdArbg0KJsTcm5R754jyPmOdUUxoC8e96+vJAcHRHPCZLb+7q +meD/hqT22221bM517PUBYHzLvTEtUzRDOZkoG90QKBxyi934TWmr3hXVqp5YqLvu +V50AOTy0p38q4oFCdocyq2LpLst8IrLZphAV5uAdNzk94hjxfACRSWdQjWefUEJl +Qb/W5EdOIAZhXK+mMPskLxQcsqET70jWlVneuCocF11JzE5ouzefzUK9X/ofcaHI +C0Vfien5Haq6xHEw1Lyq47dVcd0ztqVDaolA7TzTnVzTwLT7REyvOOlb1eaq6ZoC +QpIiyLiJjytK1bof/H0HoJqHFEJtaPhL6no7HjhDMusKTXTOrWjG68Kwa6NgMzAa +XjUluyXPlIGs/M4qFZYlwWEo9lK5txNFiAfvu5DnrqMK4jvlpDpktCcIBAxBaN+2 +c5X8prRGN+u6w0CgYrwV5+tzHaTho6FWQvA+XnuWlmFR2UJjkYXD5IQNDkbejWL4 +6XWC6X2fEGXfTKR4f6mkXOyg2YtaBLI6cxgxqYVhjMpiRu00ncRKohZjqDXVtOll +u1HYrp5q7wYq/m9NVuZv8XYviTGSO/1oUR4n1Dng6g== +-----END CERTIFICATE----- diff --git a/core/http/src/certificates/identity.pfx b/core/http/src/certificates/identity.pfx new file mode 100644 index 0000000000000000000000000000000000000000..23327800204e7b757f67586e636994a03c8fc84e GIT binary patch literal 4179 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 N;o^^QT>5X-`wu`L;u-(| literal 0 HcmV?d00001 diff --git a/core/http/src/certificates/private.key b/core/http/src/certificates/private.key new file mode 100644 index 0000000..2bcad34 --- /dev/null +++ b/core/http/src/certificates/private.key @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIMWz28Wi/jXECAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECE+qsELeDfrPBIIJSHsoycMsEN+R +sTC7Si5KPfZWkYCaQTAmTPYnV6StHLmj8XNzCIUBxZAvhuWYUnCKHjT9lkJM8jEj +t22Ga3zSj/wdrGCPsJJuqc32grHMUXkZafAFXAQ8z3oVi3D4Kzed/ReUJJsDNnmP +dVi6vD1tDT/9mKI6Ootj370xDjXxPL6AEMvkjC7C/u0fwqSNUGiDn1X3h1G5z1+e +KCgRmmZnSydWbdpb6zBI6WKfvwqJafOpD2WJLuM/cmVPJir4zX2rruisyA2GHsMM +oAKIxii+mGETIr+Z+fyT2j7pZUKONdu6o34h2yRr//LIWV0fhQGPgtkVvx/ka6TH +8iZjKoCg5C0bNpPxILkflFcueAru9eo0IBUPku5mmaowxoTnv7JSSrdaFy98W/e/ +fHrtHh6Xg3sW/0Ny5AQpQX62WlyumbSLllKLMDtSUXryFBUh1Ftv5zQ5ixQURyP7 +TMhDh7Jg3SSgz8BDg7LTh+KcW+F3UGtnqSFZxu99ExbHMfQZL5OJgINaJzHrOM29 +YASzjHMf/nXHXA24nSCOHVhE3cXaLW3UZFhk4KUyFworF5uv+bP1bB10Fgvbl4gH +/KsEdPSf2AqFlEOhIZ6fACuykqch68VF2KWRl81Jgof95xaXnSJmoh3msobqds5C +sgV/PXsuHaSpOkFLThl6akv0P+HqVesXSEyfXxzYkoW/YGUp7mLQ32rsHuKwor6S +EDnftpirO+HIx+oUXlpeObAgoxN4L4JDorOMiw2+kCljhkS/G9+0vOg9wk/T9BXD +yYRKMao7Y8xA6nfcd6LMGUk1DcCR3NU+o+O+7uO9onaw0hGszQM9PFy5qeaNvLh3 +J4bOU94eK978kqRFHXmnSZIss5zCoDgmuhejxxcDQwo1tTLLMI+my4q2ccBv4dvQ +Zrf9B7A+q0ou0T81WjA8mrGuYg42GvalFrjuC54uuAmCfI64hF4eu3x8Cccnjbe2 +V6RdbFSQfENuF547Rvee1VTQ9bCTSkENIuDHLWcEhcBeGSrunJtHJB3yHoaBjApw +k/B9XPmpHVyvq/7DCFkqu+tmEvLhXRQOLBdi7BpXVhf1KF2/qFR4jbvc/JD52uPK +xpVpxFyXtd52VpOWyoWsImlWNAkA6H9LDnDlBM3Y8j0KCYz14y5SXX0FmOIXn92F +VTw+4BPC/nCa3LMPV/8eZvokZuxaBIInunNUXXRXOtjaTgW7UTBgcfvpZB1xXd8j +iokwSswD5i6FI2ggIU8thG2UR9dMxkAvDbKTLgh73l83DizFOlNA38AR8TdC/v8x +lxUaZAiA5pR2YLnU0gh0kbJQfOjFwN2X0fwHJNmCpigKrFHqqvkdprUC5FpYGQ1u +N+SIicJ8ry4gPqnDsm85mq7oNMrLp2p6RcC8YkmpM5DSbFFKvJ9KXaYRPlrMmUTb +rC8yWz6+mvpN5/YILwmxxpc8dw6plFI+mqBDTuY5ccxDYQkpFLFpZ+v30Qbo2ZN1 +dqZpsE+Ngxc0vGZhEJPNU+c+/KGG+V9cQUFJO9RvxfvY8cJkFO51zaKUdqVPHhcx +oUtB0q+oNGxFW7O59k0bGsaMT9ULgoZeLsZFp7kytbX06ueMUfsLJXFifWfbFHvD +iWivm4qOQM4H6h9nBPIvRRpOCKNtJVW9KCYVT9tm8w5e0WJyGrUQK44gM+HfWS6p +lB7+6f1XdrPh7s0SgVXTUT8M5urD76P/pYT1ra+zovLIAHVe7cm8k0UfCFt2jRM2 +RSv8yRcQnUfu+Jlu0QAe6SjNT6Wosst1+Cqato25FADlhnUvyyBjklUlS8Dh1owL +Mlb+Jj34SEdEbHhtGZ0C1m2PUubXGHaBKofLKFgSEFDpQYMj0+AI2PK5ZjKBmdYK +XnBE+DvebW2f55aZeL2e9kPRn7TqDwc8B8CNfNACcfR/91JTFYyLx15nYZWAZzv1 +pWfB5vJppKGqdLxbQz8CDbH7sgR60zjOIBVPnxs5qI1iM/MlPVirUEUg2xalF8/B +VhduuuTM2nDCB7V6jAMKIJsCTLaZLqwQOQpW4aLmKUGFvajF2P0FCTeDDedmOBwV +Rbzf8fhKdG0aAW16WOMFAT2ItJZg5aNvKJe15C4JjVxpcgTlCzTb0Q74a2znVKTt +e3xpe5fyUu+4bM/t1HKgJZNCHeEYotcvZbrHWLrmlLz0v+zvt8/ENSckuMIObqgo +CLidWOzhlTL462qPtj2kqTtzINnKtXNlGHoSNTdZxo4ASJBD8rQiTBTMZBbVBclW +xL7rKvL8C2+tYCkHwW2iJwl32WTiIwyRsfdyxG0ot+tozCOLvf+pPPy/7hG57RA0 +Yfw5j9jbbT/vxcUBrjN91wSzsEbYWlodZMuS8sXbmtWCzG+wXdF8X6nmc2g/J/1b +gA5h6oI9zFdwkGrH48oNtmXLlP3MXgRyugV5saAT7mQn+EEXmbKO0qysRsAd4Cqs +13YWDFYoTanm3+aDz2gXbHIzLBIInT1iEBp5K6IAS56gMglyQN7vqbEd1+88A/C5 +i7WzV6XeghRIpW7H1L5Da2/4liESrHmsKEsu3M60ST0Hub3PdjHd4sAjuFjYW8J3 +yQkheTkbYbe/hc+0TAdVN7nhYFGbEyrTpo2RvcRi9QUJ3M8IjfaWpPNuuj3oysJ7 +78I41KEix1eETDGdmNzA57SR9OctxGCUrtaqPsFP7GK0oXQGnpJSV74E09MM2a8k +CUZXu4SbsmRxmaXUf1MbGXqS7ShBrDBci/cIxOWOoUl+UiOIht8yGNYwyeArOCWJ +vb+LNyNK7CxHaeyYVdZy58szWzH1H3Np/fa0qEHQrTseHAbwjC+beI5hlXxx/AgR +cl8Y/Em8LdmODC5RL9JuMUghxzmgTR8Y9YakkwoRV46Hjp41Ayi2zBz6qKbwlR3p +o39WoAT2GguxIQvCCYK7G7x3AS8vo4Q6zi9C20oWAxTN1ovn1gOknR54Y0ZqZ5EG +DO5admR943ieM6p4byLqkfCy8A/k4k4RWcjVovMs4dAkl+U/KCKAZDnuvosCL4tm +bhop6nLzxBmxaevSbWjWZ/bCBMLitfaic4y5TiyWscoZP0uxfS25wy19ATPaBi9j +EerRGtfjcCkvghGFylCLZb8MQrVTAEugjNtrViqC4c7UUC+/j9IijmuUEE4IIVsN ++C+hItN7Lj2S0FM6dEa+1g== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index 7ffa9e9..f1847b2 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,9 +1,10 @@ use std::{io, path::PathBuf}; use tokio::{ - io::{AsyncBufReadExt, AsyncReadExt, BufReader}, - net::TcpStream, + io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWriteExt}, + net::TcpStream, stream, }; +use tokio_native_tls::{TlsStream, TlsAcceptor}; use crate::{handling::{ file_handlers::NamedFile, @@ -28,7 +29,11 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; /// /// # Panics /// No Panics -pub async fn handle_connection(mut stream: TcpStream, mountpoints: Vec<MountPoint<'_>>) { +pub async fn handle_connection(stream: TcpStream, mountpoints: Vec<MountPoint<'_>>, acceptor: TlsAcceptor) { + let Ok(mut stream) = acceptor.accept(stream).await else { + eprintln!("\x1b[31mClient used http, not https\x1b[0m"); + return; + }; let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); loop { @@ -192,7 +197,7 @@ fn failure_handler(status: Status) -> Response { } /// Handler for len_not_defined errors. writes back directly -async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> { +async fn len_not_defined(stream: TlsStream<TcpStream>, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { cookies: None, @@ -209,3 +214,15 @@ async fn len_not_defined(stream: TcpStream, status: Status) -> io::Result<()> { fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } + +async fn redirect_to_secure(mut stream: TcpStream) { + let resp = Response { + cookies: None, + status: Some(Status::MovedPermanently), + headers: vec!["Location: https://127.0.0.1".to_string()], + body: Box::new("") + }.build(None); + if let Err(e) = stream.write_all(&resp).await { + error_occured_when_writing(e); + } +} diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/response.rs index 62de6e9..67f87bb 100644 --- a/core/http/src/handling/response/response.rs +++ b/core/http/src/handling/response/response.rs @@ -1,6 +1,7 @@ use std::io::Result; use tokio::{io::AsyncWriteExt, net::TcpStream}; +use tokio_native_tls::TlsStream; use crate::handling::{methods::Method, request::Request, response::Status}; @@ -32,7 +33,7 @@ impl Response { compiled_out } /// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response] - pub async fn write(self, mut stream: TcpStream, request: Option<Request>) -> Result<()> { + pub async fn write(self, mut stream: TlsStream<TcpStream>, request: Option<Request>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; Ok(()) diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 31669bb..c1b3ced 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -1,10 +1,11 @@ -use std::thread::available_parallelism; +use std::{thread::available_parallelism}; use tokio::{ net::TcpListener, select, signal::unix::{signal, SignalKind}, }; +use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ handlers::handler::handle_connection, @@ -28,6 +29,7 @@ pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, + tls_acceptor: TlsAcceptor, } impl<'a> Config { @@ -103,7 +105,8 @@ impl<'a> Config { } Ok((socket, _)) = self.address.accept() => { let mountpoints = self.mountpoints.clone().unwrap(); - tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + let tls_acceptor = self.tls_acceptor.clone(); + tokio::spawn(async move { handle_connection(socket, mountpoints, tls_acceptor).await; }); } } } @@ -135,6 +138,8 @@ pub async fn build(ip: &str) -> Config { if ip.len() != 2 { panic!("Invalid IP Address"); } + let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap(); + let port = ip[1]; let ip = ip[0]; let workers = available_parallelism().unwrap().get(); @@ -148,5 +153,6 @@ pub async fn build(ip: &str) -> Config { Config { mountpoints: None, address: listener, + tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into() } } diff --git a/site/Cargo.lock b/site/Cargo.lock index 84f675c..8373d43 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -20,12 +20,79 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "hermit-abi" version = "0.2.6" @@ -35,20 +102,59 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "http" version = "0.1.0" dependencies = [ "phf", "tokio", + "tokio-native-tls", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.2", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "lock_api" version = "0.4.9" @@ -59,6 +165,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + [[package]] name = "mio" version = "0.8.7" @@ -70,16 +182,84 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "openssl" +version = "0.10.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -98,7 +278,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] @@ -151,6 +331,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "proc-macro2" version = "1.0.59" @@ -193,12 +379,67 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -260,6 +501,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +dependencies = [ + "autocfg", + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "tokio" version = "1.28.2" @@ -290,12 +545,28 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" -- GitLab From 13596bfd4ec97638220a6fe5aee354d0ff1eec61 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 19 Jul 2023 16:14:23 +0200 Subject: [PATCH 60/65] fix cargo clippy warnings --- core/http/src/handlers/handler.rs | 26 +++++++++---------- core/http/src/handling/request/form_utils.rs | 2 +- .../{response.rs => build_and_write.rs} | 0 .../response/cookie_management/cookie.rs | 2 +- core/http/src/handling/response/mod.rs | 2 +- core/http/src/utils/url_utils/uri.rs | 13 ++++------ core/http/src/utils/urlencoded/datatypes.rs | 20 +++++++++++++- core/http/src/utils/urlencoded/endecode.rs | 12 ++++++--- core/http/src/utils/urlencoded/mod.rs | 3 +++ site/src/main.rs | 2 +- 10 files changed, 52 insertions(+), 30 deletions(-) rename core/http/src/handling/response/{response.rs => build_and_write.rs} (100%) diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index f1847b2..fa5f75a 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,8 +1,8 @@ use std::{io, path::PathBuf}; use tokio::{ - io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWriteExt}, - net::TcpStream, stream, + io::{AsyncBufReadExt, AsyncReadExt, BufReader}, + net::TcpStream, }; use tokio_native_tls::{TlsStream, TlsAcceptor}; @@ -215,14 +215,14 @@ fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } -async fn redirect_to_secure(mut stream: TcpStream) { - let resp = Response { - cookies: None, - status: Some(Status::MovedPermanently), - headers: vec!["Location: https://127.0.0.1".to_string()], - body: Box::new("") - }.build(None); - if let Err(e) = stream.write_all(&resp).await { - error_occured_when_writing(e); - } -} +// async fn redirect_to_secure(mut stream: TcpStream) { +// let resp = Response { +// cookies: None, +// status: Some(Status::MovedPermanently), +// headers: vec!["Location: https://127.0.0.1".to_string()], +// body: Box::new("") +// }.build(None); +// if let Err(e) = stream.write_all(&resp).await { +// error_occured_when_writing(e); +// } +// } diff --git a/core/http/src/handling/request/form_utils.rs b/core/http/src/handling/request/form_utils.rs index 67ada47..cc8999d 100644 --- a/core/http/src/handling/request/form_utils.rs +++ b/core/http/src/handling/request/form_utils.rs @@ -143,7 +143,7 @@ impl Request { let Some(thing) = keymap.get_mut(&key) else { continue; }; - *thing = Ok(value.into()); + *thing = Ok(value); } } Mime::MultipartFormData => { diff --git a/core/http/src/handling/response/response.rs b/core/http/src/handling/response/build_and_write.rs similarity index 100% rename from core/http/src/handling/response/response.rs rename to core/http/src/handling/response/build_and_write.rs diff --git a/core/http/src/handling/response/cookie_management/cookie.rs b/core/http/src/handling/response/cookie_management/cookie.rs index 8234ab4..6175266 100644 --- a/core/http/src/handling/response/cookie_management/cookie.rs +++ b/core/http/src/handling/response/cookie_management/cookie.rs @@ -102,7 +102,7 @@ impl std::fmt::Display for Cookie { if let Some(same_site) = &self.same_site { appendix += &format!("; {}", same_site); if !self.secure && *same_site == SameSite::None { - appendix += &format!("; Secure"); + appendix += "; Secure"; } } if let Some(expires) = &self.expires { diff --git a/core/http/src/handling/response/mod.rs b/core/http/src/handling/response/mod.rs index 535eaa8..9233b38 100644 --- a/core/http/src/handling/response/mod.rs +++ b/core/http/src/handling/response/mod.rs @@ -1,6 +1,6 @@ +mod build_and_write; mod cookie_management; mod datatypes; -mod response; mod status; mod traits; diff --git a/core/http/src/utils/url_utils/uri.rs b/core/http/src/utils/url_utils/uri.rs index b9e6939..6b8146b 100644 --- a/core/http/src/utils/url_utils/uri.rs +++ b/core/http/src/utils/url_utils/uri.rs @@ -7,10 +7,7 @@ use super::datatypes::{ParseUriError, RawUri, Uri, UriError}; impl Uri { pub fn new(parts: Vec<&str>) -> Self { Self { - parts: parts - .into_iter() - .map(|part| UrlEncodeData::from_raw(part)) - .collect(), + parts: parts.into_iter().map(UrlEncodeData::from_raw).collect(), } } } @@ -23,11 +20,11 @@ impl RawUri { raw_string: "/".to_owned() + &parts.join("/"), }; for part in parts { - if part.starts_with("<") && part.ends_with("..>") { + if part.starts_with('<') && part.ends_with("..>") { result.infinte_end = true; break; } - if part.starts_with("<") && part.ends_with(">") { + if part.starts_with('<') && part.ends_with('>') { result.parts.push(RawUriElement::Variable); continue; } @@ -114,11 +111,11 @@ impl FromStr for RawUri { if part.is_empty() { continue; } - if part.starts_with("<") && part.ends_with("..>") { + if part.starts_with('<') && part.ends_with("..>") { result.infinte_end = true; break; } - if part.starts_with("<") && part.ends_with(">") { + if part.starts_with('<') && part.ends_with('>') { result.parts.push(RawUriElement::Variable); continue; } diff --git a/core/http/src/utils/urlencoded/datatypes.rs b/core/http/src/utils/urlencoded/datatypes.rs index d48e02b..3207955 100644 --- a/core/http/src/utils/urlencoded/datatypes.rs +++ b/core/http/src/utils/urlencoded/datatypes.rs @@ -13,6 +13,24 @@ pub struct UrlEncodeData { pub(crate) raw_string: Option<String>, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum UrlEncodeError { + NonHexAfterPercent, +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct ParseUrlEncodeError { + pub inner: UrlEncodeError, +} + +impl std::fmt::Display for ParseUrlEncodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for ParseUrlEncodeError {} + impl UrlEncodeData { /// Generates a [UrlEncodeData] from any raw data that can be a slice of [u8] pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self { @@ -27,7 +45,7 @@ impl UrlEncodeData { /// # Errors /// /// errors if the encoded data is wrongly encoded -> %<invalid_character> - pub fn from_encoded(encoded: &str) -> Result<Self, ()> { + pub fn from_encoded(encoded: &str) -> super::UriParseResult<Self> { Ok(Self { encoded: encoded.to_owned(), raw: encoded.decode()?, diff --git a/core/http/src/utils/urlencoded/endecode.rs b/core/http/src/utils/urlencoded/endecode.rs index 8dd45d6..8a77253 100644 --- a/core/http/src/utils/urlencoded/endecode.rs +++ b/core/http/src/utils/urlencoded/endecode.rs @@ -1,3 +1,5 @@ +use super::{datatypes::ParseUrlEncodeError, UriParseResult}; + /// Base of the HexaDecimal Number system static BASE16_HEXA_DECIMAL: u8 = 0x10; /// Highest possible Value per digit @@ -15,7 +17,7 @@ pub trait DeCodable { /// /// # Errors /// Errors if the encoding isn't right - fn decode(&self) -> Result<Vec<u8>, ()>; + fn decode(&self) -> UriParseResult<Vec<u8>>; } impl<T: AsRef<[u8]>> EnCodable for T { @@ -44,7 +46,7 @@ impl EnCodable for [u8] { } } impl DeCodable for &str { - fn decode(&self) -> Result<Vec<u8>, ()> { + fn decode(&self) -> UriParseResult<Vec<u8>> { let mut first = true; let mut result = Vec::with_capacity(self.len()); @@ -55,7 +57,7 @@ impl DeCodable for &str { continue; } let Ok(char) = u8::from_str_radix(i[0..=1].as_ref(), BASE16_HEXA_DECIMAL.into()) else { - return Err(()); + return Err(ParseUrlEncodeError { inner: super::datatypes::UrlEncodeError::NonHexAfterPercent }); }; result.push(char); result.extend_from_slice(i[2..].as_bytes()); @@ -97,7 +99,9 @@ mod test { .unwrap() ); assert_eq!( - Err(()), + Err(crate::utils::urlencoded::datatypes::ParseUrlEncodeError { + inner: crate::utils::urlencoded::datatypes::UrlEncodeError::NonHexAfterPercent + }), "Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire".decode() ); assert_eq!( diff --git a/core/http/src/utils/urlencoded/mod.rs b/core/http/src/utils/urlencoded/mod.rs index 3145200..bf5f292 100644 --- a/core/http/src/utils/urlencoded/mod.rs +++ b/core/http/src/utils/urlencoded/mod.rs @@ -1,4 +1,7 @@ mod datatypes; mod endecode; +use std::result; + +type UriParseResult<T> = result::Result<T, datatypes::ParseUrlEncodeError>; pub use datatypes::UrlEncodeData; pub use endecode::{DeCodable, EnCodable}; diff --git a/site/src/main.rs b/site/src/main.rs index 22e7900..014d33a 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -105,7 +105,7 @@ async fn main() { rank: 0, }; - http::build("127.0.0.1:8000") + http::build("127.0.0.1:8080") .await .mount("/", vec![fileserver, post_test, static_hi]) .mount("/post/", vec![post_test]) -- GitLab From 56a7d1fdde3ed6efb689d61423247dfd02ab582b Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Wed, 19 Jul 2023 17:37:45 +0200 Subject: [PATCH 61/65] Remove the println! statement that annoyed me half to death --- site/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/site/src/main.rs b/site/src/main.rs index 014d33a..6938a3b 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -58,7 +58,6 @@ fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Da if data.is_empty() { return Outcome::Forward(data); } - println!("{:?}", request.get_post_data(&["message"], &data)); let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) } else { -- GitLab From 9191168be199f4175a27d43502b46aa8fa32e247 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Thu, 20 Jul 2023 17:19:46 +0200 Subject: [PATCH 62/65] Add HTTPS-REdirect from Port 8080 to 8443 --- .../src/handling/response/build_and_write.rs | 6 ++++ core/http/src/setup.rs | 36 +++++++++++++------ site/src/main.rs | 2 +- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/core/http/src/handling/response/build_and_write.rs b/core/http/src/handling/response/build_and_write.rs index 67f87bb..3b6bd1b 100644 --- a/core/http/src/handling/response/build_and_write.rs +++ b/core/http/src/handling/response/build_and_write.rs @@ -38,4 +38,10 @@ impl Response { stream.write_all(&resp).await?; Ok(()) } + + pub async fn write_unencrypted(self, mut stream: TcpStream) -> Result<()> { + let resp = self.build(None); + stream.write_all(&resp).await?; + Ok(()) + } } diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index c1b3ced..4730fdb 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -9,7 +9,7 @@ use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ handlers::handler::handle_connection, - handling::routes::{Route, Uri}, + handling::{routes::{Route, Uri}, response::{Response, Status}}, }; #[derive(Clone)] @@ -29,6 +29,7 @@ pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, + to_secure_redirect: TcpListener, tls_acceptor: TlsAcceptor, } @@ -93,10 +94,11 @@ impl<'a> Config { /// is no interrupt signal pub async fn launch(self) { println!( - "Server launched from http://{}", - self.address.local_addr().unwrap() + "Server launched from https://{} and http://{}", + self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() ); let mut sigint = signal(SignalKind::interrupt()).unwrap(); + let location_string = format!("Location: https://{}", self.address.local_addr().unwrap()); loop { select! { _ = sigint.recv() => { @@ -108,6 +110,15 @@ impl<'a> Config { let tls_acceptor = self.tls_acceptor.clone(); tokio::spawn(async move { handle_connection(socket, mountpoints, tls_acceptor).await; }); } + Ok((socket, _)) = self.to_secure_redirect.accept() => { + let redirect_response = Response { + headers: vec![location_string.clone()], + cookies: None, + status: Some(Status::MovedPermanently), + body: Box::new(""), + }; + tokio::spawn(async move { let _ = redirect_response.write_unencrypted(socket).await; }); + } } } } @@ -122,22 +133,24 @@ impl<'a> Config { /// # Example /// ``` /// async fn example() { -/// let _ = http::build("127.0.0.1:8000"); +/// let _ = http::build("127.0.0.1:8080", "127.0.0.1:8443"); /// } /// ``` /// # Panics /// Panics if the IP is not bindable, or other forms of system errors or it's not a valid /// IP-Address -pub async fn build(ip: &str) -> Config { - let listener = if let Ok(listener) = TcpListener::bind(ip).await { - listener - } else { +pub async fn build(ip_http: &str, ip_secure: &str) -> Config { + let Ok(listener_secure) = TcpListener::bind(ip_secure).await else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; - let ip = ip.splitn(2, ':').collect::<Vec<&str>>(); + let ip = ip_secure.splitn(2, ':').collect::<Vec<&str>>(); if ip.len() != 2 { panic!("Invalid IP Address"); } + + let Ok(listener_http) = TcpListener::bind(ip_http).await else { + panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); + }; let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap(); let port = ip[1]; @@ -148,11 +161,14 @@ pub async fn build(ip: &str) -> Config { >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} +\x1b[32m Security\x1b[0m + >> \x1b[32mHttp to Https Redirect: {ip_http} -> {ip_secure}\x1b[0m \x1b[35m🛪 Mountpoints\x1b[0m" ); Config { mountpoints: None, - address: listener, + address: listener_secure, + to_secure_redirect: listener_http, tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into() } } diff --git a/site/src/main.rs b/site/src/main.rs index 6938a3b..ffa61b1 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -104,7 +104,7 @@ async fn main() { rank: 0, }; - http::build("127.0.0.1:8080") + http::build("127.0.0.1:8080", "127.0.0.1:8443") .await .mount("/", vec![fileserver, post_test, static_hi]) .mount("/post/", vec![post_test]) -- GitLab From 54277faade3c2f9b7610539714a1a0722ef0ee7d Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 21 Jul 2023 22:18:17 +0200 Subject: [PATCH 63/65] make https conditionally compiled with the secure feature Writing some examples Index page for lms --- core/http/Cargo.toml | 4 + core/http/src/handlers/handler.rs | 25 +--- .../src/handling/response/build_and_write.rs | 5 +- core/http/src/setup.rs | 128 +++++++++++++----- examples/1.rs | 113 ++++++++++++++++ site/Cargo.toml | 3 +- site/src/main.rs | 109 ++------------- site/static/hello.css | 4 - site/static/hello.html | 4 +- site/static/style.css | 16 +++ site/templates/index.html | 11 ++ 11 files changed, 259 insertions(+), 163 deletions(-) create mode 100644 examples/1.rs delete mode 100644 site/static/hello.css create mode 100644 site/static/style.css create mode 100644 site/templates/index.html diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index d2fc133..5d24dbd 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -3,8 +3,12 @@ name = "http" version = "0.1.0" edition = "2021" +[features] +secure = [] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] tokio = { version = "1.28.2", features = ["full"] } phf = { version = "0.11", features = ["macros"] } diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index fa5f75a..38315a1 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,10 +1,6 @@ use std::{io, path::PathBuf}; -use tokio::{ - io::{AsyncBufReadExt, AsyncReadExt, BufReader}, - net::TcpStream, -}; -use tokio_native_tls::{TlsStream, TlsAcceptor}; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead}; use crate::{handling::{ file_handlers::NamedFile, @@ -29,11 +25,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; /// /// # Panics /// No Panics -pub async fn handle_connection(stream: TcpStream, mountpoints: Vec<MountPoint<'_>>, acceptor: TlsAcceptor) { - let Ok(mut stream) = acceptor.accept(stream).await else { - eprintln!("\x1b[31mClient used http, not https\x1b[0m"); - return; - }; +pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); loop { @@ -197,7 +189,7 @@ fn failure_handler(status: Status) -> Response { } /// Handler for len_not_defined errors. writes back directly -async fn len_not_defined(stream: TlsStream<TcpStream>, status: Status) -> io::Result<()> { +async fn len_not_defined<T: AsyncRead + AsyncWrite + std::marker::Unpin>(stream: T, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { cookies: None, @@ -215,14 +207,3 @@ fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } -// async fn redirect_to_secure(mut stream: TcpStream) { -// let resp = Response { -// cookies: None, -// status: Some(Status::MovedPermanently), -// headers: vec!["Location: https://127.0.0.1".to_string()], -// body: Box::new("") -// }.build(None); -// if let Err(e) = stream.write_all(&resp).await { -// error_occured_when_writing(e); -// } -// } diff --git a/core/http/src/handling/response/build_and_write.rs b/core/http/src/handling/response/build_and_write.rs index 3b6bd1b..1d9e162 100644 --- a/core/http/src/handling/response/build_and_write.rs +++ b/core/http/src/handling/response/build_and_write.rs @@ -1,7 +1,6 @@ use std::io::Result; -use tokio::{io::AsyncWriteExt, net::TcpStream}; -use tokio_native_tls::TlsStream; +use tokio::{io::{AsyncWriteExt, AsyncRead, AsyncWrite}, net::TcpStream}; use crate::handling::{methods::Method, request::Request, response::Status}; @@ -33,7 +32,7 @@ impl Response { compiled_out } /// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response] - pub async fn write(self, mut stream: TlsStream<TcpStream>, request: Option<Request>) -> Result<()> { + pub async fn write<T: AsyncRead + AsyncWrite + std::marker::Unpin>(self, mut stream: T, request: Option<Request>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; Ok(()) diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 4730fdb..f451ed4 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -3,8 +3,9 @@ use std::{thread::available_parallelism}; use tokio::{ net::TcpListener, select, - signal::unix::{signal, SignalKind}, + signal::unix::{signal, SignalKind, Signal}, }; + use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ @@ -29,7 +30,9 @@ pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, + #[cfg(feature = "secure")] to_secure_redirect: TcpListener, + #[cfg(feature = "secure")] tls_acceptor: TlsAcceptor, } @@ -93,36 +96,69 @@ impl<'a> Config { /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there /// is no interrupt signal pub async fn launch(self) { - println!( - "Server launched from https://{} and http://{}", - self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() - ); + { + #[cfg(feature = "secure")] { + println!( + "Server launched from https://{} and http://{}", + self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() + ); + } + #[cfg(not(feature = "secure"))] { + println!("Server launched from http://{}", self.address.local_addr().unwrap()) + } + } let mut sigint = signal(SignalKind::interrupt()).unwrap(); let location_string = format!("Location: https://{}", self.address.local_addr().unwrap()); loop { - select! { - _ = sigint.recv() => { - println!("Shutting down..."); - break; - } - Ok((socket, _)) = self.address.accept() => { - let mountpoints = self.mountpoints.clone().unwrap(); - let tls_acceptor = self.tls_acceptor.clone(); - tokio::spawn(async move { handle_connection(socket, mountpoints, tls_acceptor).await; }); - } - Ok((socket, _)) = self.to_secure_redirect.accept() => { - let redirect_response = Response { - headers: vec![location_string.clone()], - cookies: None, - status: Some(Status::MovedPermanently), - body: Box::new(""), - }; - tokio::spawn(async move { let _ = redirect_response.write_unencrypted(socket).await; }); - } + if !self.selector(&mut sigint, &location_string).await { + break; } } } + #[cfg(feature = "secure")] + async fn selector(&self, sigint: &mut Signal, location_string: &str) -> bool{ + select! { + _ = sigint.recv() => { + println!("Shutting down..."); + return false; + } + Ok((socket, _)) = self.address.accept() => { + let mountpoints = self.mountpoints.clone().unwrap(); + let Ok(socket) = self.tls_acceptor.accept(socket).await else { + eprintln!("\x1b[31mClient used http, not https\x1b[0m"); + return true; + }; + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + } + Ok((socket, _)) = self.to_secure_redirect.accept() => { + let redirect_response = Response { + headers: vec![location_string.to_string()], + cookies: None, + status: Some(Status::MovedPermanently), + body: Box::new(""), + }; + tokio::spawn(async move { let _ = redirect_response.write(socket, None).await; }); + } + } + true + } + #[cfg(not(feature = "secure"))] + async fn selector(&self, sigint: &mut Signal, _location_string: &str) -> bool { + select! { + _ = sigint.recv() => { + println!("Shutting down..."); + return false; + } + Ok((socket, _)) = self.address.accept() => { + let mountpoints = self.mountpoints.clone().unwrap(); + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + } + } + true + } + } + /// # Creates a Webserver Config which can be launched with the launch function /// Takes the IP and Port as an argument /// @@ -139,36 +175,60 @@ impl<'a> Config { /// # Panics /// Panics if the IP is not bindable, or other forms of system errors or it's not a valid /// IP-Address -pub async fn build(ip_http: &str, ip_secure: &str) -> Config { - let Ok(listener_secure) = TcpListener::bind(ip_secure).await else { +#[cfg(feature = "secure")] +pub async fn build(ip: &str, ip_http: &str) -> Config { + let Ok(listener) = TcpListener::bind(ip).await else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; - let ip = ip_secure.splitn(2, ':').collect::<Vec<&str>>(); - if ip.len() != 2 { + let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>(); + if ip_vec.len() != 2 { panic!("Invalid IP Address"); } - let Ok(listener_http) = TcpListener::bind(ip_http).await else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap(); - let port = ip[1]; - let ip = ip[0]; + let port = ip_vec[1]; + let ip = ip_vec[0]; let workers = available_parallelism().unwrap().get(); println!( - "\x1b[34m⚙ Configuration\x1b[0m +"\x1b[34m⚙ Configuration\x1b[0m >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} \x1b[32m Security\x1b[0m - >> \x1b[32mHttp to Https Redirect: {ip_http} -> {ip_secure}\x1b[0m + >> \x1b[32mHttp to Https Redirect: http://{ip_http} -> https://{ip}:{port}\x1b[0m \x1b[35m🛪 Mountpoints\x1b[0m" ); Config { mountpoints: None, - address: listener_secure, + address: listener, to_secure_redirect: listener_http, tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into() } } +#[cfg(not(feature = "secure"))] +pub async fn build(ip: &str) -> Config { + let Ok(listener) = TcpListener::bind(ip).await else { + panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); + }; + let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>(); + if ip_vec.len() != 2 { + panic!("Invalid IP Address"); + } + let port = ip_vec[1]; + let ip = ip_vec[0]; + let workers = available_parallelism().unwrap().get(); + println!( +"\x1b[34m⚙ Configuration\x1b[0m + >> \x1b[34mIp\x1b[0m: {ip} + >> \x1b[34mPort\x1b[0m: {port} + >> \x1b[34mWorkers\x1b[0m: {workers} +\x1b[35m🛪 Mountpoints\x1b[0m" + ); + Config { + mountpoints: None, + address: listener, + } +} diff --git a/examples/1.rs b/examples/1.rs new file mode 100644 index 0000000..8d64834 --- /dev/null +++ b/examples/1.rs @@ -0,0 +1,113 @@ + +use std::{collections::HashMap, path::PathBuf}; + +use http::handling::{ + file_handlers::NamedFile, + methods::Method, + request::{Request, ParseFormError}, + response::{Outcome, Response, Status}, + routes::{Data, Route}, +}; + +fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> String { + let mut result = String::new(); + for (key, value) in map { + result.push_str(key); + result.push('='); + result.push_str(value.as_ref().unwrap()); + result.push(';'); + } + result.pop(); // Remove the trailing semicolon if desired + result +} + +fn handle_static_hi(request: Request<>, data: Data) -> Outcome<Response, Status, Data> { + let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { + keys + } else { + return Outcome::Forward(data); + }; + let response = hashmap_to_string(&keys); + Outcome::Success(Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(response), + }) + // Outcome::Forward(data) +} + +fn handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { + let response = fileserver(request.uri.raw_string().unwrap().strip_prefix("static/").unwrap()); + let response = match response { + Ok(dat) => Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status::NotFound), + }; + Outcome::Success(response) +} + +fn fileserver(path: &str) -> Result<NamedFile, Status> { + NamedFile::open(PathBuf::from("static/".to_string() + path)) +} + +fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> { + if data.is_empty() { + return Outcome::Forward(data); + } + let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { + post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) + } else { + return Outcome::Failure(Status::BadRequest); + }; + Outcome::Success(Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(dat), + }) +} + +fn post_hi(msg: String) -> String { + msg +} + +#[tokio::main] +async fn main() { + let fileserver = Route { + format: None, + handler, + name: Some("file_server"), + uri: "static/<path..>", + method: Method::Get, + rank: 1, + }; + + let post_test = Route { + format: None, + handler: post_hi_handler, + name: Some("post_test"), + uri: "post", + method: Method::Post, + rank: 0, + }; + + let static_hi = Route { + format: None, + handler: handle_static_hi, + name: Some("Handle_Static_hi"), + uri: "static/hi", + method: Method::Get, + rank: 0, + }; + http::build("127.0.0.1:8080", "127.0.0.1:8443") + .await + .mount("/", vec![fileserver, post_test, static_hi]) + .mount("/post/", vec![post_test]) + .launch() + .await; +} diff --git a/site/Cargo.toml b/site/Cargo.toml index 55ff9b1..1d8b400 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] -http = { path = "../core/http" } +http = { path = "../core/http", features = ["secure"]} tokio = { version = "1.28.2", features = ["full"] } diff --git a/site/src/main.rs b/site/src/main.rs index ffa61b1..c4b515a 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,113 +1,28 @@ -use std::{collections::HashMap, path::PathBuf}; +use http::handling::{methods::Method, routes::{Route, Data}, request::Request, response::{Response, Outcome, Status}, file_handlers::NamedFile}; -use http::handling::{ - file_handlers::NamedFile, - methods::Method, - request::{Request, ParseFormError}, - response::{Outcome, Response, Status}, - routes::{Data, Route}, -}; - -fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> String { - let mut result = String::new(); - for (key, value) in map { - result.push_str(key); - result.push('='); - result.push_str(value.as_ref().unwrap()); - result.push(';'); - } - result.pop(); // Remove the trailing semicolon if desired - result -} - -fn handle_static_hi(request: Request<>, data: Data) -> Outcome<Response, Status, Data> { - let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { - keys - } else { - return Outcome::Forward(data); - }; - let response = hashmap_to_string(&keys); - Outcome::Success(Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(response), - }) - // Outcome::Forward(data) -} - -fn handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { - let response = fileserver(request.uri.raw_string().unwrap().strip_prefix("static/").unwrap()); - let response = match response { - Ok(dat) => Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(dat), - }, - Err(_) => return Outcome::Failure(Status::NotFound), - }; - Outcome::Success(response) -} - -fn fileserver(path: &str) -> Result<NamedFile, Status> { - NamedFile::open(PathBuf::from("static/".to_string() + path)) -} - -fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> { - if data.is_empty() { - return Outcome::Forward(data); - } - let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { - post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) - } else { - return Outcome::Failure(Status::BadRequest); - }; - Outcome::Success(Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(dat), - }) +fn index_handler(_request: Request, _data: Data) -> Outcome<Response, Status, Data> { + Outcome::Success(Response { headers: vec![], cookies: None, status: None, body: Box::new(index()) }) } -fn post_hi(msg: String) -> String { - msg +fn index() -> NamedFile { + NamedFile::open("templates/index.html".into()).unwrap() } #[tokio::main] async fn main() { - let fileserver = Route { - format: None, - handler, - name: Some("file_server"), - uri: "static/<path..>", - method: Method::Get, - rank: 1, - }; - - let post_test = Route { - format: None, - handler: post_hi_handler, - name: Some("post_test"), - uri: "post", - method: Method::Post, - rank: 0, - }; - - let static_hi = Route { + let index_route = Route { format: None, - handler: handle_static_hi, - name: Some("Handle_Static_hi"), - uri: "static/hi", + handler: index_handler, + name: Some("Index"), + uri: "", method: Method::Get, rank: 0, }; - http::build("127.0.0.1:8080", "127.0.0.1:8443") + // http::build("127.0.0.1:8000") + http::build("127.0.0.1:8443", "127.0.0.1:8080") .await - .mount("/", vec![fileserver, post_test, static_hi]) - .mount("/post/", vec![post_test]) + .mount("/", vec![index_route]) .launch() .await; } diff --git a/site/static/hello.css b/site/static/hello.css deleted file mode 100644 index aba0df0..0000000 --- a/site/static/hello.css +++ /dev/null @@ -1,4 +0,0 @@ -body { - background-color: #212121; - color: #ffffff; -} diff --git a/site/static/hello.html b/site/static/hello.html index ff042af..bf12019 100644 --- a/site/static/hello.html +++ b/site/static/hello.html @@ -1,9 +1,9 @@ -<!DOCTYPE html> +<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Hello</title> - <link rel="stylesheet" href="/static/hello.css" /> + <link rel="stylesheet" href="/static/style.css" /> </head> <body> <h1>Managed</h1> diff --git a/site/static/style.css b/site/static/style.css new file mode 100644 index 0000000..27589ec --- /dev/null +++ b/site/static/style.css @@ -0,0 +1,16 @@ +body { + background-color: #212121; + color: #ffffff; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Oxygen, + Ubuntu, + Cantarell, + "Open Sans", + "Helvetica Neue", + sans-serif; +} diff --git a/site/templates/index.html b/site/templates/index.html new file mode 100644 index 0000000..8645f34 --- /dev/null +++ b/site/templates/index.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>LMS - The Library Management System</title> + <link rel="stylesheet" href="/static/style.css" /> + </head> + <body> + <h1>Hello and Welcome</h1> + </body> +</html> -- GitLab From 888a28d53b5c2c70e2956a9a8feada764cae4607 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Fri, 21 Jul 2023 22:22:20 +0200 Subject: [PATCH 64/65] Removing unnecessary function `write_unencrypted` due to replacement with generic write on response. Removing unnecessary imports --- core/http/src/handling/response/build_and_write.rs | 8 +------- core/http/src/setup.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/core/http/src/handling/response/build_and_write.rs b/core/http/src/handling/response/build_and_write.rs index 1d9e162..a8cdb09 100644 --- a/core/http/src/handling/response/build_and_write.rs +++ b/core/http/src/handling/response/build_and_write.rs @@ -1,6 +1,6 @@ use std::io::Result; -use tokio::{io::{AsyncWriteExt, AsyncRead, AsyncWrite}, net::TcpStream}; +use tokio::io::{AsyncWriteExt, AsyncRead, AsyncWrite}; use crate::handling::{methods::Method, request::Request, response::Status}; @@ -37,10 +37,4 @@ impl Response { stream.write_all(&resp).await?; Ok(()) } - - pub async fn write_unencrypted(self, mut stream: TcpStream) -> Result<()> { - let resp = self.build(None); - stream.write_all(&resp).await?; - Ok(()) - } } diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index f451ed4..9802f0e 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -1,4 +1,4 @@ -use std::{thread::available_parallelism}; +use std::thread::available_parallelism; use tokio::{ net::TcpListener, @@ -6,12 +6,16 @@ use tokio::{ signal::unix::{signal, SignalKind, Signal}, }; +#[cfg(feature = "secure")] use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ handlers::handler::handle_connection, - handling::{routes::{Route, Uri}, response::{Response, Status}}, + handling::routes::{Route, Uri}, }; +#[cfg(feature = "secure")] +use crate::handling::response::{Response, Status}; + #[derive(Clone)] /// Represnts a [MountPoint] that can be mounted in the config -- GitLab From 554dc92b91be13bb1e385b9c75d511b9d300a3d6 Mon Sep 17 00:00:00 2001 From: Darius Auding <Darius.auding@gmx.de> Date: Sat, 22 Jul 2023 20:02:31 +0200 Subject: [PATCH 65/65] Add favicon, clean up --- core/http/src/handling/response/datatypes.rs | 1 + core/http/src/handling/response/traits.rs | 4 +- core/http/src/utils/mime/mime_enum.rs | 1 + site/assets/favicon.svg | 125 +++++++++++++++++++ site/hello.css | 4 - site/img.jpg | Bin 51549 -> 0 bytes site/src/main.rs | 51 +++++++- site/static/hello.html | 12 -- site/static/hi | 1 - site/static/img.jpg | Bin 51549 -> 0 bytes site/templates/index.html | 12 +- 11 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 site/assets/favicon.svg delete mode 100644 site/hello.css delete mode 100644 site/img.jpg delete mode 100644 site/static/hello.html delete mode 100644 site/static/hi delete mode 100644 site/static/img.jpg diff --git a/core/http/src/handling/response/datatypes.rs b/core/http/src/handling/response/datatypes.rs index 9225ced..b8657ee 100644 --- a/core/http/src/handling/response/datatypes.rs +++ b/core/http/src/handling/response/datatypes.rs @@ -27,6 +27,7 @@ pub enum Outcome<S, E, F> { } /// Response is a wrapper for http responses. +#[derive(Debug)] pub struct Response { /// the [`Vec<String>`] of headers unrelated to `Content-Type` and `Content-Length` pub headers: HeaderMap, diff --git a/core/http/src/handling/response/traits.rs b/core/http/src/handling/response/traits.rs index 6e96ddf..d3179a2 100644 --- a/core/http/src/handling/response/traits.rs +++ b/core/http/src/handling/response/traits.rs @@ -1,7 +1,9 @@ +use std::fmt::Debug; + use crate::{handling::routes::Body, utils::mime::Mime}; /// Trait for using datatypes as response bodies -pub trait ResponseBody: Send { +pub trait ResponseBody: Send + Debug { /// Get a cloned version of the data as a [`Vec<u8>`] /// # Ecamples /// ``` diff --git a/core/http/src/utils/mime/mime_enum.rs b/core/http/src/utils/mime/mime_enum.rs index 12c63d6..1ad8181 100644 --- a/core/http/src/utils/mime/mime_enum.rs +++ b/core/http/src/utils/mime/mime_enum.rs @@ -4084,6 +4084,7 @@ impl Mime { "json" => Mime::ApplicationJson, "html" => Mime::TextHtml, "css" => Mime::TextCss, + "svg" => Mime::ImageSvgXml, &_ => Mime::TextPlain, }, None => Mime::TextPlain, diff --git a/site/assets/favicon.svg b/site/assets/favicon.svg new file mode 100644 index 0000000..b4d9147 --- /dev/null +++ b/site/assets/favicon.svg @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools --> + +<svg + fill="#000000" + height="800px" + width="800px" + version="1.1" + id="Capa_1" + viewBox="0 0 511 511" + xml:space="preserve" + sodipodi:docname="favicon.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"><defs + id="defs290" /><sodipodi:namedview + id="namedview288" + pagecolor="#505050" + bordercolor="#eeeeee" + borderopacity="1" + inkscape:showpageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#505050" + showgrid="false" + inkscape:zoom="1.51125" + inkscape:cx="390.73615" + inkscape:cy="400" + inkscape:window-width="5120" + inkscape:window-height="1387" + inkscape:window-x="0" + inkscape:window-y="28" + inkscape:window-maximized="1" + inkscape:current-layer="Capa_1" /> +<g + id="g285" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1"> + <path + d="M487.5,128.106H479v-24.5c0-2.905-1.678-5.549-4.307-6.786C405.088,64.066,325.408,63.6,255.5,95.371 C185.592,63.6,105.912,64.067,36.307,96.82C33.678,98.057,32,100.701,32,103.606v24.5h-8.5c-12.958,0-23.5,10.542-23.5,23.5v264 c0,12.958,10.542,23.5,23.5,23.5h464c12.958,0,23.5-10.542,23.5-23.5v-264C511,138.648,500.458,128.106,487.5,128.106z M263,239.583c0-0.009,0-0.019,0-0.028V108.416c64.137-28.707,136.861-28.707,201,0v27.161c0,0.01-0.001,0.02-0.001,0.029 s0.001,0.02,0.001,0.029v244.438c-32.237-13.461-66.371-20.193-100.5-20.193c-34.129,0-68.264,6.732-100.5,20.193V239.583z M215,96.391c11.187,3.204,22.217,7.198,33,12.025v117.177l-12.34-8.227c-2.52-1.68-5.801-1.68-8.32,0L215,225.593V96.391z M47,135.626c0-0.007,0.001-0.013,0.001-0.02S47,135.594,47,135.587v-27.171c48.563-21.736,102.046-26.999,153-15.82v32.856 c-26.767-5.505-54.078-6.777-81.328-3.75c-4.117,0.457-7.083,4.165-6.626,8.282c0.458,4.116,4.162,7.085,8.282,6.626 c26.708-2.967,53.479-1.562,79.671,4.165v48.686c-15.912-3.265-32.14-5.067-48.377-5.323c-4.145-0.078-7.552,3.239-7.618,7.38 c-0.065,4.142,3.239,7.552,7.38,7.618c16.331,0.258,32.654,2.164,48.614,5.647v16.66c-43.389-8.909-88.39-6.644-130.748,6.665 c-3.952,1.241-6.148,5.451-4.907,9.403c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347 c40.908-12.852,84.428-14.773,126.252-5.638v2.825c0,2.766,1.522,5.308,3.961,6.612c2.438,1.306,5.398,1.162,7.699-0.372 l19.84-13.227l16.5,11v136.454c-32.237-13.461-66.371-20.193-100.5-20.193c-34.129,0-68.264,6.732-100.5,20.193V135.626z M224,424.106H23.5c-4.687,0-8.5-3.813-8.5-8.5v-264c0-4.687,3.813-8.5,8.5-8.5H32v248.5v8c0,4.142,3.358,7.5,7.5,7.5H224V424.106z M57.29,392.106c58.099-22.934,122.32-22.935,180.42,0H57.29z M272,424.106h-33v-17h33V424.106z M453.71,392.106H273.29 C331.389,369.172,395.61,369.172,453.71,392.106z M496,415.606c0,4.687-3.813,8.5-8.5,8.5H287v-17h184.5c4.142,0,7.5-3.358,7.5-7.5 v-8v-248.5h8.5c4.687,0,8.5,3.813,8.5,8.5V415.606z" + id="path243" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M309.96,317.749c-8.302,1.74-16.615,3.911-24.708,6.454c-3.952,1.242-6.148,5.452-4.907,9.403 c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c7.628-2.396,15.464-4.443,23.288-6.083 c4.054-0.85,6.652-4.825,5.802-8.879C317.989,319.497,314.011,316.9,309.96,317.749z" + id="path245" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M439.502,338.859c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403 c-32.073-10.076-65.329-13.842-98.844-11.188c-4.129,0.326-7.211,3.938-6.885,8.068s3.935,7.213,8.068,6.885 c31.59-2.499,62.935,1.048,93.165,10.546C438,338.748,438.757,338.859,439.502,338.859z" + id="path247" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M287.498,306.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0 c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.306-16.083-156.496,0 c-3.952,1.241-6.149,5.451-4.907,9.403C281.352,304.716,284.309,306.767,287.498,306.767z" + id="path249" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M287.498,274.859c0.745,0,1.502-0.112,2.25-0.347c27.681-8.697,56.409-12.412,85.399-11.037 c4.147,0.192,7.651-2.999,7.847-7.137c0.196-4.138-2.999-7.65-7.137-7.847c-30.753-1.456-61.236,2.483-90.605,11.71 c-3.952,1.242-6.149,5.452-4.907,9.403C281.352,272.81,284.309,274.859,287.498,274.859z" + id="path251" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M441.748,260.202c-10.76-3.38-21.846-6.086-32.952-8.043c-4.08-0.719-7.968,2.006-8.688,6.085 c-0.719,4.079,2.005,7.969,6.085,8.688c10.467,1.844,20.917,4.395,31.058,7.581c0.749,0.235,1.505,0.347,2.25,0.347 c3.189,0,6.147-2.051,7.153-5.254C447.896,265.653,445.7,261.443,441.748,260.202z" + id="path253" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M287.498,242.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0 c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.306-16.083-156.496,0 c-3.952,1.241-6.149,5.451-4.907,9.403C281.352,240.716,284.309,242.767,287.498,242.767z" + id="path255" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M334.678,185.702c-16.732,1.858-33.362,5.36-49.426,10.407c-3.952,1.241-6.148,5.451-4.907,9.403 c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c15.141-4.757,30.815-8.057,46.585-9.809 c4.117-0.457,7.083-4.165,6.626-8.282S338.79,185.244,334.678,185.702z" + id="path257" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M367.386,199.137c23.725,0.375,47.231,4.17,69.866,11.283c0.748,0.234,1.505,0.347,2.25,0.347 c3.189,0,6.146-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-24.015-7.545-48.955-11.572-74.125-11.97 c-4.125-0.078-7.552,3.239-7.618,7.38S363.244,199.072,367.386,199.137z" + id="path259" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M390.671,168.704c4.116,0.46,7.825-2.509,8.282-6.626c0.458-4.117-2.509-7.825-6.626-8.282 c-36.252-4.027-72.278-0.526-107.075,10.406c-3.952,1.242-6.148,5.452-4.907,9.403c1.007,3.204,3.964,5.254,7.153,5.254 c0.745,0,1.502-0.112,2.25-0.347C322.545,168.208,356.5,164.909,390.671,168.704z" + id="path261" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M441.748,164.202c-5.418-1.702-10.96-3.246-16.472-4.588c-4.03-0.98-8.082,1.488-9.062,5.512 c-0.98,4.024,1.488,8.082,5.512,9.062c5.196,1.265,10.419,2.72,15.526,4.324c0.748,0.235,1.505,0.347,2.25,0.347 c3.189,0,6.147-2.051,7.153-5.254C447.896,169.653,445.7,165.443,441.748,164.202z" + id="path263" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M287.498,146.767c0.745,0,1.502-0.112,2.25-0.347c5.103-1.604,10.325-3.058,15.521-4.324 c4.024-0.98,6.492-5.037,5.512-9.062s-5.038-6.492-9.062-5.512c-5.513,1.342-11.053,2.886-16.468,4.587 c-3.951,1.242-6.148,5.452-4.907,9.403C281.352,144.716,284.309,146.767,287.498,146.767z" + id="path265" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M336.329,136.611c34.172-3.796,68.126-0.496,100.923,9.809c0.748,0.234,1.505,0.347,2.25,0.347 c3.189,0,6.146-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-34.797-10.933-70.824-14.435-107.076-10.406 c-4.117,0.457-7.083,4.165-6.626,8.282C328.504,134.102,332.21,137.07,336.329,136.611z" + id="path267" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M93.96,317.749c-8.302,1.74-16.615,3.911-24.708,6.454c-3.952,1.242-6.148,5.452-4.907,9.403 c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c7.628-2.396,15.464-4.443,23.288-6.083 c4.054-0.85,6.652-4.825,5.802-8.879S98.011,316.9,93.96,317.749z" + id="path269" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M223.502,338.859c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403 c-32.073-10.076-65.331-13.842-98.844-11.188c-4.129,0.326-7.211,3.938-6.885,8.068s3.934,7.213,8.068,6.885 c31.591-2.499,62.935,1.048,93.165,10.546C222,338.748,222.757,338.859,223.502,338.859z" + id="path271" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M71.498,306.767c0.745,0,1.502-0.112,2.25-0.347c48.249-15.159,99.256-15.159,147.504,0 c3.952,1.24,8.162-0.956,9.403-4.907c1.241-3.952-0.956-8.162-4.907-9.403c-51.191-16.083-105.307-16.083-156.496,0 c-3.952,1.241-6.149,5.451-4.907,9.403C65.352,304.716,68.309,306.767,71.498,306.767z" + id="path273" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M71.498,274.859c0.745,0,1.502-0.112,2.25-0.347c27.681-8.697,56.411-12.412,85.399-11.037 c4.158,0.192,7.65-2.999,7.847-7.137c0.196-4.138-2.999-7.65-7.137-7.847c-30.756-1.456-61.236,2.483-90.605,11.71 c-3.952,1.242-6.149,5.452-4.907,9.403C65.352,272.81,68.309,274.859,71.498,274.859z" + id="path275" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M190.194,266.932c10.467,1.844,20.917,4.395,31.058,7.581c0.749,0.235,1.505,0.347,2.25,0.347 c3.189,0,6.147-2.051,7.153-5.254c1.241-3.952-0.956-8.162-4.907-9.403c-10.76-3.38-21.846-6.086-32.952-8.043 c-4.079-0.719-7.969,2.006-8.688,6.085C183.39,262.323,186.114,266.213,190.194,266.932z" + id="path277" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M118.678,185.702c-16.732,1.858-33.362,5.36-49.426,10.407c-3.952,1.241-6.148,5.451-4.907,9.403 c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347c15.141-4.757,30.815-8.057,46.585-9.809 c4.117-0.457,7.083-4.165,6.626-8.282C126.503,188.212,122.788,185.244,118.678,185.702z" + id="path279" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M64.345,173.605c1.007,3.204,3.964,5.254,7.153,5.254c0.745,0,1.502-0.112,2.25-0.347 c32.797-10.305,66.752-13.604,100.923-9.809c4.116,0.46,7.825-2.509,8.282-6.626c0.458-4.117-2.509-7.825-6.626-8.282 c-36.253-4.027-72.278-0.526-107.075,10.406C65.3,165.444,63.104,169.654,64.345,173.605z" + id="path281" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> + <path + d="M71.498,146.767c0.745,0,1.502-0.112,2.25-0.347c5.103-1.604,10.325-3.058,15.521-4.324 c4.024-0.98,6.492-5.037,5.512-9.062s-5.038-6.492-9.062-5.512c-5.513,1.342-11.053,2.886-16.468,4.587 c-3.951,1.242-6.148,5.452-4.907,9.403C65.352,144.716,68.309,146.767,71.498,146.767z" + id="path283" + style="stroke:#ffffff;stroke-opacity:1;fill:#ffffff;fill-opacity:1" /> +</g> +</svg> diff --git a/site/hello.css b/site/hello.css deleted file mode 100644 index aba0df0..0000000 --- a/site/hello.css +++ /dev/null @@ -1,4 +0,0 @@ -body { - background-color: #212121; - color: #ffffff; -} diff --git a/site/img.jpg b/site/img.jpg deleted file mode 100644 index 07eb7f85ac26c70344d552f56a81c44a0bf1b3db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51549 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 B#&7@t diff --git a/site/src/main.rs b/site/src/main.rs index c4b515a..3520b3b 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,5 +1,23 @@ use http::handling::{methods::Method, routes::{Route, Data}, request::Request, response::{Response, Outcome, Status}, file_handlers::NamedFile}; +fn static_files_handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { + let response = static_files(request.uri.raw_string().unwrap()); + let response = match response { + Ok(dat) => Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status::NotFound), + }; + Outcome::Success(response) +} + +fn static_files(path: &str) -> Result<NamedFile, Status> { + NamedFile::open(("static/".to_string() + path).into()) +} + fn index_handler(_request: Request, _data: Data) -> Outcome<Response, Status, Data> { Outcome::Success(Response { headers: vec![], cookies: None, status: None, body: Box::new(index()) }) } @@ -8,21 +26,50 @@ fn index() -> NamedFile { NamedFile::open("templates/index.html".into()).unwrap() } +fn favicon_handler(_request: Request, _data: Data) -> Outcome<Response, Status, Data> { + let response = Response { + headers: vec![], + cookies: None, + status: None, + body: Box::new(NamedFile::open("/assets/favicon.svg".into()).unwrap()) + }; + Outcome::Success(response) +} + #[tokio::main] async fn main() { let index_route = Route { format: None, handler: index_handler, - name: Some("Index"), + name: Some("index"), uri: "", method: Method::Get, rank: 0, }; + let static_route = Route { + format: None, + handler: static_files_handler, + name: Some("static files"), + uri: "", + method: Method::Get, + rank: 0 + }; + + let favicon = Route { + format: None, + handler: favicon_handler, + name: Some("favicon"), + uri: "favicon.ico", + method: Method::Get, + rank: 0 + }; + // http::build("127.0.0.1:8000") http::build("127.0.0.1:8443", "127.0.0.1:8080") .await - .mount("/", vec![index_route]) + .mount("/", vec![index_route, favicon]) + .mount("/static", vec![static_route]) .launch() .await; } diff --git a/site/static/hello.html b/site/static/hello.html deleted file mode 100644 index bf12019..0000000 --- a/site/static/hello.html +++ /dev/null @@ -1,12 +0,0 @@ -<!doctype html> -<html> - <head> - <meta charset="UTF-8" /> - <title>Hello</title> - <link rel="stylesheet" href="/static/style.css" /> - </head> - <body> - <h1>Managed</h1> - <img src="/static/img.jpg" alt="" /> - </body> -</html> diff --git a/site/static/hi b/site/static/hi deleted file mode 100644 index 8bd6648..0000000 --- a/site/static/hi +++ /dev/null @@ -1 +0,0 @@ -asdf diff --git a/site/static/img.jpg b/site/static/img.jpg deleted file mode 100644 index 07eb7f85ac26c70344d552f56a81c44a0bf1b3db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51549 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 B#&7@t diff --git a/site/templates/index.html b/site/templates/index.html index 8645f34..7dc2842 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -6,6 +6,16 @@ <link rel="stylesheet" href="/static/style.css" /> </head> <body> - <h1>Hello and Welcome</h1> + <header> + <nav> + <div>LOGO</div> + <div> + <div>Dashboard</div> + <div>Books</div> + <div>Magazines</div> + <div>Newspapers</div> + </div> + </nav> + </header> </body> </html> -- GitLab