diff --git a/crates/ptth_relay/src/errors.rs b/crates/ptth_relay/src/errors.rs index b0d52dc..14af9e0 100644 --- a/crates/ptth_relay/src/errors.rs +++ b/crates/ptth_relay/src/errors.rs @@ -1,6 +1,6 @@ use thiserror::Error; -#[derive (Error, Debug)] +#[derive (Debug, Error)] pub enum ConfigError { #[error ("I/O error")] Io (#[from] std::io::Error), @@ -23,13 +23,13 @@ pub enum ConfigError { // I'm not sure how important this is, but it was already in the code -#[derive (Error, Debug)] +#[derive (Debug, Error)] pub enum ShuttingDownError { #[error ("Relay is shutting down")] ShuttingDown, } -#[derive (Error, Debug)] +#[derive (Debug, Error)] pub enum HandleHttpResponseError { #[error ("HTTP error")] Http (#[from] http::Error), @@ -50,7 +50,7 @@ pub enum HandleHttpResponseError { RelayingTaskPanicked (#[from] tokio::task::JoinError), } -#[derive (Error, Debug)] +#[derive (Debug, Error)] pub enum RequestError { #[error ("HTTP error")] Http (#[from] http::Error), @@ -68,7 +68,7 @@ pub enum RequestError { Mysterious, } -#[derive (Error, Debug)] +#[derive (Debug, Error)] pub enum RelayError { #[error ("Handlebars template file error")] TemplateFile (#[from] handlebars::TemplateFileError), diff --git a/crates/ptth_server/Cargo.toml b/crates/ptth_server/Cargo.toml index a0a61af..77004a9 100644 --- a/crates/ptth_server/Cargo.toml +++ b/crates/ptth_server/Cargo.toml @@ -22,6 +22,7 @@ reqwest = { version = "0.10.8", features = ["stream"] } rmp-serde = "0.14.4" serde = {version = "1.0.117", features = ["derive"]} structopt = "0.3.20" +thiserror = "1.0.22" tokio = { version = "0.2.22", features = ["full"] } tracing = "0.1.21" tracing-futures = "0.2.4" diff --git a/crates/ptth_server/src/errors.rs b/crates/ptth_server/src/errors.rs new file mode 100644 index 0000000..25d8acc --- /dev/null +++ b/crates/ptth_server/src/errors.rs @@ -0,0 +1,6 @@ +use thiserror::Error; + +#[derive (Debug, Error)] +pub enum ServerError { + +} diff --git a/crates/ptth_server/src/file_server/errors.rs b/crates/ptth_server/src/file_server/errors.rs new file mode 100644 index 0000000..3cc5808 --- /dev/null +++ b/crates/ptth_server/src/file_server/errors.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive (Debug, Error)] +pub enum FileServerError { + +} + diff --git a/crates/ptth_server/src/file_server.rs b/crates/ptth_server/src/file_server/mod.rs similarity index 72% rename from crates/ptth_server/src/file_server.rs rename to crates/ptth_server/src/file_server/mod.rs index c111bbc..e5932a6 100644 --- a/crates/ptth_server/src/file_server.rs +++ b/crates/ptth_server/src/file_server/mod.rs @@ -46,6 +46,8 @@ use ptth_core::{ prefix_match, }; +mod errors; + #[derive (Debug, Serialize)] pub struct ServerInfo { pub server_name: String, @@ -680,221 +682,4 @@ fn pretty_print_bytes (b: u64) -> String { } #[cfg (test)] -mod tests { - use std::{ - ffi::OsStr, - path::{ - Component, - Path, - }, - }; - - use maplit::*; - use tokio::runtime::Runtime; - - #[test] - fn icons () { - let video = "🎞️"; - let picture = "📷"; - let file = "📄"; - - for (input, expected) in vec! [ - ("copying_is_not_theft.mp4", video), - ("copying_is_not_theft.avi", video), - ("copying_is_not_theft.mkv", video), - ("copying_is_not_theft.webm", video), - ("lolcats.jpg", picture), - ("lolcats.jpeg", picture), - ("lolcats.png", picture), - ("lolcats.bmp", picture), - ("ptth.log", file), - ("README.md", file), - ("todo.txt", file), - ].into_iter () { - assert_eq! (super::get_icon (input), expected); - } - } - - #[test] - fn parse_range_header () { - for (input, expected) in vec! [ - ("", (None, None)), - ("bytes=0-", (Some (0), None)), - ("bytes=0-999", (Some (0), Some (1000))), - ("bytes=111-999", (Some (111), Some (1000))), - ].into_iter () { - let actual = super::parse_range_header (input); - assert_eq! (actual, expected); - } - - use super::ParsedRange::*; - - for (header, file_len, expected) in vec! [ - (None, 0, Ok (0..0)), - (None, 1024, Ok (0..1024)), - - (Some (""), 0, RangeNotSatisfiable (0)), - (Some (""), 1024, PartialContent (0..1024)), - - (Some ("bytes=0-"), 1024, PartialContent (0..1024)), - (Some ("bytes=0-999"), 1024, PartialContent (0..1000)), - (Some ("bytes=0-1023"), 1024, PartialContent (0..1024)), - (Some ("bytes=111-999"), 1024, PartialContent (111..1000)), - (Some ("bytes=111-1023"), 1024, PartialContent (111..1024)), - (Some ("bytes=200-100"), 1024, RangeNotSatisfiable (1024)), - - (Some ("bytes=0-"), 512, PartialContent (0..512)), - (Some ("bytes=0-1023"), 512, RangeNotSatisfiable (512)), - (Some ("bytes=1000-1023"), 512, RangeNotSatisfiable (512)), - ].into_iter () { - let actual = super::check_range (header, file_len); - assert_eq! (actual, expected); - } - } - - #[test] - fn pretty_print_bytes () { - for (input_after, expected_before, expected_after) in vec! [ - (1, "0 B", "1 B"), - (1024, "1023 B", "1 KiB"), - (1024 + 512, "1 KiB", "2 KiB"), - (1023 * 1024 + 512, "1023 KiB", "1 MiB"), - ((1024 + 512) * 1024, "1 MiB", "2 MiB"), - (1023 * 1024 * 1024 + 512 * 1024, "1023 MiB", "1 GiB"), - ((1024 + 512) * 1024 * 1024, "1 GiB", "2 GiB"), - - ].into_iter () { - let actual = super::pretty_print_bytes (input_after - 1); - assert_eq! (&actual, expected_before); - - let actual = super::pretty_print_bytes (input_after); - assert_eq! (&actual, expected_after); - } - } - - #[test] - fn i_hate_paths () { - let mut components = Path::new ("/home/user").components (); - - assert_eq! (components.next (), Some (Component::RootDir)); - assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); - assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); - assert_eq! (components.next (), None); - - let mut components = Path::new ("./home/user").components (); - - assert_eq! (components.next (), Some (Component::CurDir)); - assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); - assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); - assert_eq! (components.next (), None); - - let mut components = Path::new (".").components (); - - assert_eq! (components.next (), Some (Component::CurDir)); - assert_eq! (components.next (), None); - } - - #[test] - fn file_server () { - use ptth_core::{ - http_serde::Method, - }; - use super::*; - - tracing_subscriber::fmt ().try_init ().ok (); - let mut rt = Runtime::new ().unwrap (); - - rt.block_on (async { - let file_server_root = PathBuf::from ("./"); - let headers = Default::default (); - - { - use InternalResponse::*; - - let bad_passwords_path = "/files/src/bad_passwords.txt"; - - for (uri_path, expected) in vec! [ - ("/", Root), - ("/files", Redirect ("files/".to_string ())), - ("/files/?", InvalidQuery), - ("/files/src", Redirect ("src/".to_string ())), - ("/files/src/?", InvalidQuery), - (bad_passwords_path, ServeFile (ServeFileParams { - send_body: true, - range: 0..1_048_576, - range_requested: false, - file: AlwaysEqual::testing_blank (), - })), - ("/files/test/test.md", ServeFile (ServeFileParams { - send_body: true, - range: 0..144, - range_requested: false, - file: AlwaysEqual::testing_blank (), - })), - ("/ ", InvalidUri), - ].into_iter () { - let resp = internal_serve_all ( - &file_server_root, - Method::Get, - uri_path, - &headers, - None - ).await; - - assert_eq! (resp, expected); - } - - let resp = internal_serve_all ( - &file_server_root, - Method::Get, - bad_passwords_path, - &hashmap! { - "range".into () => b"bytes=0-2000000".to_vec (), - }, - None - ).await; - - assert_eq! (resp, RangeNotSatisfiable (1_048_576)); - - let resp = internal_serve_all ( - &file_server_root, - Method::Head, - bad_passwords_path, - &headers, - None - ).await; - - assert_eq! (resp, ServeFile (ServeFileParams { - send_body: false, - range: 0..1_048_576, - range_requested: false, - file: AlwaysEqual::testing_blank (), - })); - } - }); - } - - #[test] - fn parse_uri () { - use hyper::Uri; - - assert! (Uri::from_maybe_shared ("/").is_ok ()); - } - - #[test] - fn markdown () { - use super::*; - - for (input, expected) in vec! [ - ("", ""), - ( - "Hello world, this is a ~~complicated~~ *very simple* example.", - "

Hello world, this is a complicated very simple example.

\n" - ), - ].into_iter () { - let mut out = String::default (); - render_markdown (input.as_bytes (), &mut out).unwrap (); - assert_eq! (expected, &out); - } - } -} +mod tests; diff --git a/crates/ptth_server/src/file_server/tests.rs b/crates/ptth_server/src/file_server/tests.rs new file mode 100644 index 0000000..f85361a --- /dev/null +++ b/crates/ptth_server/src/file_server/tests.rs @@ -0,0 +1,216 @@ +use std::{ + ffi::OsStr, + path::{ + Component, + Path, + }, +}; + +use maplit::*; +use tokio::runtime::Runtime; + +#[test] +fn icons () { + let video = "🎞️"; + let picture = "📷"; + let file = "📄"; + + for (input, expected) in vec! [ + ("copying_is_not_theft.mp4", video), + ("copying_is_not_theft.avi", video), + ("copying_is_not_theft.mkv", video), + ("copying_is_not_theft.webm", video), + ("lolcats.jpg", picture), + ("lolcats.jpeg", picture), + ("lolcats.png", picture), + ("lolcats.bmp", picture), + ("ptth.log", file), + ("README.md", file), + ("todo.txt", file), + ].into_iter () { + assert_eq! (super::get_icon (input), expected); + } +} + +#[test] +fn parse_range_header () { + for (input, expected) in vec! [ + ("", (None, None)), + ("bytes=0-", (Some (0), None)), + ("bytes=0-999", (Some (0), Some (1000))), + ("bytes=111-999", (Some (111), Some (1000))), + ].into_iter () { + let actual = super::parse_range_header (input); + assert_eq! (actual, expected); + } + + use super::ParsedRange::*; + + for (header, file_len, expected) in vec! [ + (None, 0, Ok (0..0)), + (None, 1024, Ok (0..1024)), + + (Some (""), 0, RangeNotSatisfiable (0)), + (Some (""), 1024, PartialContent (0..1024)), + + (Some ("bytes=0-"), 1024, PartialContent (0..1024)), + (Some ("bytes=0-999"), 1024, PartialContent (0..1000)), + (Some ("bytes=0-1023"), 1024, PartialContent (0..1024)), + (Some ("bytes=111-999"), 1024, PartialContent (111..1000)), + (Some ("bytes=111-1023"), 1024, PartialContent (111..1024)), + (Some ("bytes=200-100"), 1024, RangeNotSatisfiable (1024)), + + (Some ("bytes=0-"), 512, PartialContent (0..512)), + (Some ("bytes=0-1023"), 512, RangeNotSatisfiable (512)), + (Some ("bytes=1000-1023"), 512, RangeNotSatisfiable (512)), + ].into_iter () { + let actual = super::check_range (header, file_len); + assert_eq! (actual, expected); + } +} + +#[test] +fn pretty_print_bytes () { + for (input_after, expected_before, expected_after) in vec! [ + (1, "0 B", "1 B"), + (1024, "1023 B", "1 KiB"), + (1024 + 512, "1 KiB", "2 KiB"), + (1023 * 1024 + 512, "1023 KiB", "1 MiB"), + ((1024 + 512) * 1024, "1 MiB", "2 MiB"), + (1023 * 1024 * 1024 + 512 * 1024, "1023 MiB", "1 GiB"), + ((1024 + 512) * 1024 * 1024, "1 GiB", "2 GiB"), + + ].into_iter () { + let actual = super::pretty_print_bytes (input_after - 1); + assert_eq! (&actual, expected_before); + + let actual = super::pretty_print_bytes (input_after); + assert_eq! (&actual, expected_after); + } +} + +#[test] +fn i_hate_paths () { + let mut components = Path::new ("/home/user").components (); + + assert_eq! (components.next (), Some (Component::RootDir)); + assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); + assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); + assert_eq! (components.next (), None); + + let mut components = Path::new ("./home/user").components (); + + assert_eq! (components.next (), Some (Component::CurDir)); + assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("home")))); + assert_eq! (components.next (), Some (Component::Normal (OsStr::new ("user")))); + assert_eq! (components.next (), None); + + let mut components = Path::new (".").components (); + + assert_eq! (components.next (), Some (Component::CurDir)); + assert_eq! (components.next (), None); +} + +#[test] +fn file_server () { + use ptth_core::{ + http_serde::Method, + }; + use super::*; + + tracing_subscriber::fmt ().try_init ().ok (); + let mut rt = Runtime::new ().unwrap (); + + rt.block_on (async { + let file_server_root = PathBuf::from ("./"); + let headers = Default::default (); + + { + use InternalResponse::*; + + let bad_passwords_path = "/files/src/bad_passwords.txt"; + + for (uri_path, expected) in vec! [ + ("/", Root), + ("/files", Redirect ("files/".to_string ())), + ("/files/?", InvalidQuery), + ("/files/src", Redirect ("src/".to_string ())), + ("/files/src/?", InvalidQuery), + (bad_passwords_path, ServeFile (ServeFileParams { + send_body: true, + range: 0..1_048_576, + range_requested: false, + file: AlwaysEqual::testing_blank (), + })), + ("/files/test/test.md", ServeFile (ServeFileParams { + send_body: true, + range: 0..144, + range_requested: false, + file: AlwaysEqual::testing_blank (), + })), + ("/ ", InvalidUri), + ].into_iter () { + let resp = internal_serve_all ( + &file_server_root, + Method::Get, + uri_path, + &headers, + None + ).await; + + assert_eq! (resp, expected); + } + + let resp = internal_serve_all ( + &file_server_root, + Method::Get, + bad_passwords_path, + &hashmap! { + "range".into () => b"bytes=0-2000000".to_vec (), + }, + None + ).await; + + assert_eq! (resp, RangeNotSatisfiable (1_048_576)); + + let resp = internal_serve_all ( + &file_server_root, + Method::Head, + bad_passwords_path, + &headers, + None + ).await; + + assert_eq! (resp, ServeFile (ServeFileParams { + send_body: false, + range: 0..1_048_576, + range_requested: false, + file: AlwaysEqual::testing_blank (), + })); + } + }); +} + +#[test] +fn parse_uri () { + use hyper::Uri; + + assert! (Uri::from_maybe_shared ("/").is_ok ()); +} + +#[test] +fn markdown () { + use super::*; + + for (input, expected) in vec! [ + ("", ""), + ( + "Hello world, this is a ~~complicated~~ *very simple* example.", + "

Hello world, this is a complicated very simple example.

\n" + ), + ].into_iter () { + let mut out = String::default (); + render_markdown (input.as_bytes (), &mut out).unwrap (); + assert_eq! (expected, &out); + } +} diff --git a/crates/ptth_server/src/lib.rs b/crates/ptth_server/src/lib.rs index a6b8d69..41dcaea 100644 --- a/crates/ptth_server/src/lib.rs +++ b/crates/ptth_server/src/lib.rs @@ -22,6 +22,7 @@ use ptth_core::{ prelude::*, }; +pub mod errors; pub mod file_server; pub mod load_toml;