diff --git a/crates/ptth_file_server_bin/src/main.rs b/crates/ptth_file_server_bin/src/main.rs index d83b122..874dd88 100644 --- a/crates/ptth_file_server_bin/src/main.rs +++ b/crates/ptth_file_server_bin/src/main.rs @@ -51,14 +51,8 @@ async fn handle_all (req: Request , state: Arc ) let ptth_req = RequestParts::from_hyper (parts.method, path_and_query, parts.headers)?; - let default_root = PathBuf::from ("./"); - let file_server_root: &std::path::Path = state.config.file_server_root - .as_ref () - .unwrap_or (&default_root); - let ptth_resp = file_server::serve_all ( &state, - file_server_root, ptth_req.method, &ptth_req.uri, &ptth_req.headers @@ -94,12 +88,6 @@ async fn main () -> anyhow::Result <()> { let addr = SocketAddr::from(([0, 0, 0, 0], 4000)); - let handlebars = file_server::load_templates (&PathBuf::new ())?; - - let metrics_startup = metrics::Startup::new ( - config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()) - ); - let metrics_interval = Arc::new (ArcSwap::default ()); let interval_writer = Arc::clone (&metrics_interval); @@ -107,15 +95,13 @@ async fn main () -> anyhow::Result <()> { file_server::metrics::Interval::monitor (interval_writer).await; }); - let state = Arc::new (State { - config: file_server::Config { - file_server_root: config_file.file_server_root, - }, - handlebars, - metrics_startup, + let state = Arc::new (State::new ( + config_file.file_server_root, + &PathBuf::new (), + config_file.name.unwrap_or_else (|| "PTTH File Server".to_string ()), metrics_interval, - hidden_path: Some (path), - }); + Some (path), + )?); let make_svc = make_service_fn (|_conn| { let state = state.clone (); diff --git a/crates/ptth_server/src/errors.rs b/crates/ptth_server/src/errors.rs index 4ebdbe1..cfdb276 100644 --- a/crates/ptth_server/src/errors.rs +++ b/crates/ptth_server/src/errors.rs @@ -1,20 +1,30 @@ use thiserror::Error; +/// Errors thrown when the server loads its TOML config file + #[derive (Debug, Error)] pub enum LoadTomlError { + /// The server's config file is readable by other users, meaning + /// that the file server module could accidentally serve it to + /// clients, and expose the server's API key. #[error ("Config file has bad permissions mode, it should be octal 0600")] ConfigBadPermissions, + /// Wraps `std::io::Error` #[error ("I/O")] Io (#[from] std::io::Error), + /// Wraps `std::string::FromUtf8Error` #[error ("UTF-8")] Utf8 (#[from] std::string::FromUtf8Error), + /// Wraps `toml::de::Error` #[error ("TOML")] Toml (#[from] toml::de::Error), } +/// Errors thrown when the server is starting up or serving requests + #[derive (Debug, Error)] pub enum ServerError { #[error ("Loading TOML")] @@ -23,9 +33,6 @@ pub enum ServerError { #[error ("Loading Handlebars template file")] LoadHandlebars (#[from] handlebars::TemplateFileError), - #[error ("API key is too weak, server can't use it")] - WeakApiKey, - #[error ("File server error")] FileServer (#[from] super::file_server::errors::FileServerError), diff --git a/crates/ptth_server/src/file_server/errors.rs b/crates/ptth_server/src/file_server/errors.rs index 7be513c..cbae2d8 100644 --- a/crates/ptth_server/src/file_server/errors.rs +++ b/crates/ptth_server/src/file_server/errors.rs @@ -2,9 +2,6 @@ use thiserror::Error; #[derive (Debug, Error)] pub enum FileServerError { - #[error ("Handlebars render error")] - Handlebars (#[from] handlebars::RenderError), - #[error ("I/O error")] Io (#[from] std::io::Error), @@ -28,6 +25,9 @@ pub enum FileServerError { #[error ("Heim process error")] HeimProcess, + + #[error(transparent)] + Other (#[from] anyhow::Error), } impl From for FileServerError { diff --git a/crates/ptth_server/src/file_server/html.rs b/crates/ptth_server/src/file_server/html.rs index d729074..d221058 100644 --- a/crates/ptth_server/src/file_server/html.rs +++ b/crates/ptth_server/src/file_server/html.rs @@ -60,7 +60,7 @@ pub async fn serve_root ( metrics_interval: &**state.metrics_interval.load (), }; - let s = state.handlebars.render ("file_server_root", ¶ms)?; + let s = state.handlebars.render ("file_server_root", ¶ms).map_err (anyhow::Error::from)?; Ok (serve (s)) } @@ -85,7 +85,7 @@ pub async fn serve_dir ( path, entries, instance_metrics, - })?; + }).map_err (anyhow::Error::from)?; Ok (serve (s)) } diff --git a/crates/ptth_server/src/file_server/mod.rs b/crates/ptth_server/src/file_server/mod.rs index be21882..dec094a 100644 --- a/crates/ptth_server/src/file_server/mod.rs +++ b/crates/ptth_server/src/file_server/mod.rs @@ -67,6 +67,27 @@ pub struct State { pub hidden_path: Option , } +impl State { + pub fn new ( + file_server_root: Option , + asset_root: &Path, + name: String, + metrics_interval: Arc >>, + hidden_path: Option , + ) -> Result + { + Ok (Self { + config: Config { + file_server_root, + }, + handlebars: load_templates (asset_root)?, + metrics_startup: metrics::Startup::new (name), + metrics_interval, + hidden_path, + }) + } +} + #[derive (Serialize)] struct DirJson { entries: Vec , @@ -311,14 +332,14 @@ async fn stream_file ( } } -// Pass a request to the internal decision-making logic. -// When it returns, prettify it as HTML or JSON based on what the client -// asked for. +/// Top-level request handler for the file server module. +/// +/// Passes a request to the internal file server logic. +/// Returns an HTTP response as HTML or JSON, depending on the request. #[instrument (level = "debug", skip (state, headers))] pub async fn serve_all ( state: &State, - root: &Path, method: Method, uri: &str, headers: &HashMap > @@ -342,6 +363,11 @@ pub async fn serve_all ( resp } + let default_root = PathBuf::from ("./"); + let root: &std::path::Path = state.config.file_server_root + .as_ref () + .unwrap_or (&default_root); + Ok (match internal::serve_all (root, method, uri, headers, state.hidden_path.as_deref ()).await? { Favicon => serve_error (StatusCode::NotFound, "Not found\n"), Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden\n"), @@ -400,10 +426,10 @@ pub async fn serve_all ( }) } -pub fn load_templates ( +fn load_templates ( asset_root: &Path ) --> Result , handlebars::TemplateFileError> +-> Result , anyhow::Error> { let mut handlebars = Handlebars::new (); handlebars.set_strict_mode (true); diff --git a/crates/ptth_server/src/lib.rs b/crates/ptth_server/src/lib.rs index 89a49b0..9208a44 100644 --- a/crates/ptth_server/src/lib.rs +++ b/crates/ptth_server/src/lib.rs @@ -1,6 +1,6 @@ //! # PTTH Server //! -//! The PTTH server makes an outgoing HTTP connection to a +//! The PTTH server makes outgoing HTTP connections to a //! PTTH relay, and then serves incoming HTTP requests through //! the relay. @@ -35,7 +35,12 @@ use ptth_core::{ }; pub mod errors; + +/// In-process file server module with byte range and ETag support pub mod file_server; + +/// Load and de-serialize structures from TOML, with a size limit +/// and checking permissions (On Unix) pub mod load_toml; use errors::ServerError; @@ -58,14 +63,8 @@ async fn handle_one_req ( debug! ("Handling request {}", req_id); - let default_root = PathBuf::from ("./"); - let file_server_root: &std::path::Path = state.file_server.config.file_server_root - .as_ref () - .unwrap_or (&default_root); - let response = file_server::serve_all ( &state.file_server, - file_server_root, parts.method, &parts.uri, &parts.headers, @@ -161,16 +160,30 @@ async fn handle_req_resp ( Ok (()) } -// A complete config file. The bin frontend is allowed to use a different -// type to load an incomplete file, as long as the command line options -// complete it. +/// Config for ptth_server and the file server module +/// +/// This is a complete config. +/// The bin frontend is allowed to load an incomplete config from +/// the TOML file, fill it out with command-line options, and put +/// the completed config in this struct. #[derive (Clone)] pub struct ConfigFile { + /// A name that uniquely identifies this server on the relay. + /// May be human-readable. pub name: String, + + /// Secret API key used to authenticate the server with the relay pub api_key: String, + + /// URL of the PTTH relay server that ptth_server should connect to pub relay_url: String, + + /// Directory that the file server module will expose to clients + /// over the relay. If None, the current working dir is used. pub file_server_root: Option , + + /// For debugging. pub throttle_upload: bool, } @@ -192,12 +205,19 @@ impl ConfigFile { } } +/// Config for ptth_server itself + #[derive (Default)] pub struct Config { + /// URL of the PTTH relay server that ptth_server should connect to pub relay_url: String, + + /// For debugging. pub throttle_upload: bool, } +/// Runs a PTTH file server + pub async fn run_server ( config_file: ConfigFile, shutdown_oneshot: oneshot::Receiver <()>, @@ -224,9 +244,7 @@ pub async fn run_server ( .default_headers (headers) .connect_timeout (Duration::from_secs (30)) .build ().map_err (ServerError::CantBuildHttpClient)?; - let handlebars = file_server::load_templates (&asset_root)?; - let metrics_startup = file_server::metrics::Startup::new (config_file.name); let metrics_interval = Arc::new (ArcSwap::default ()); let interval_writer = Arc::clone (&metrics_interval); @@ -235,15 +253,13 @@ pub async fn run_server ( }); let state = Arc::new (State { - file_server: file_server::State { - config: file_server::Config { - file_server_root: config_file.file_server_root, - }, - handlebars, - metrics_startup, + file_server: file_server::State::new ( + config_file.file_server_root, + &asset_root, + config_file.name, metrics_interval, hidden_path, - }, + )?, config: Config { relay_url: config_file.relay_url, throttle_upload: config_file.throttle_upload, diff --git a/crates/ptth_server/src/load_toml.rs b/crates/ptth_server/src/load_toml.rs index 6851467..2637ca9 100644 --- a/crates/ptth_server/src/load_toml.rs +++ b/crates/ptth_server/src/load_toml.rs @@ -26,7 +26,7 @@ fn load_inner < /// For files that contain public-viewable information -pub fn load_public < +fn load_public < T: DeserializeOwned, P: AsRef + Debug > (