diff --git a/CHANGELOG.md b/CHANGELOG.md index daadfbf..644be94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Docments can be converted into responses with std::convert::Into [@Alch-Emi] ### Improved - build time and size by [@Alch-Emi] -### Changed -- `Response::success` now takes a request body [@Alch-Emi] +- Improved error handling in serve_dir [@Alch-Emi] ## [0.3.0] - 2020-11-14 ### Added diff --git a/Cargo.toml b/Cargo.toml index 9ad991d..3233546 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,3 +30,7 @@ mime_guess = { version = "2.0.3", optional = true } env_logger = "0.8.1" futures-util = "0.3.7" tokio = { version = "0.3.1", features = ["macros", "rt-multi-thread", "sync"] } + +[[example]] +name = "serve_dir" +required-features = ["serve_dir"] diff --git a/src/lib.rs b/src/lib.rs index b8e00d6..a014a8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ use tokio::{ }; use tokio::net::TcpListener; use rustls::ClientCertVerifier; +use rustls::internal::msgs::handshake::DigitallySignedStruct; use tokio_rustls::{rustls, TlsAcceptor}; use rustls::*; use anyhow::*; @@ -434,6 +435,8 @@ impl ClientCertVerifier for AllowAnonOrSelfsignedClient { Some(false) } + // the below methods are a hack until webpki doesn't break with certain certs + fn verify_client_cert( &self, _: &[Certificate], @@ -441,6 +444,24 @@ impl ClientCertVerifier for AllowAnonOrSelfsignedClient { ) -> Result { Ok(ClientCertVerified::assertion()) } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &Certificate, + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) + } } #[cfg(test)] diff --git a/src/types/document.rs b/src/types/document.rs index d71c851..06d12bf 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -147,8 +147,9 @@ impl Document { /// /// assert_eq!(document.to_string(), "hello\n * world!\n"); /// ``` - pub fn add_text(&mut self, text: &str) -> &mut Self { + pub fn add_text(&mut self, text: impl AsRef) -> &mut Self { let text = text + .as_ref() .lines() .map(Text::new_lossy) .map(Item::Text); @@ -236,8 +237,8 @@ impl Document { /// /// assert_eq!(document.to_string(), "```\na\n b\n c\n```\n"); /// ``` - pub fn add_preformatted(&mut self, preformatted_text: &str) -> &mut Self { - self.add_preformatted_with_alt("", preformatted_text) + pub fn add_preformatted(&mut self, preformatted_text: impl AsRef) -> &mut Self { + self.add_preformatted_with_alt("", preformatted_text.as_ref()) } /// Adds a block of preformatted text with an alt text. @@ -257,9 +258,10 @@ impl Document { /// /// assert_eq!(document.to_string(), "```rust\nfn main() {\n}\n```\n"); /// ``` - pub fn add_preformatted_with_alt(&mut self, alt: &str, preformatted_text: &str) -> &mut Self { - let alt = AltText::new_lossy(alt); + pub fn add_preformatted_with_alt(&mut self, alt: impl AsRef, preformatted_text: impl AsRef) -> &mut Self { + let alt = AltText::new_lossy(alt.as_ref()); let lines = preformatted_text + .as_ref() .lines() .map(PreformattedText::new_lossy) .collect(); @@ -318,8 +320,8 @@ impl Document { /// /// assert_eq!(document.to_string(), "* milk\n* eggs\n"); /// ``` - pub fn add_unordered_list_item(&mut self, text: &str) -> &mut Self { - let item = UnorderedListItem::new_lossy(text); + pub fn add_unordered_list_item(&mut self, text: impl AsRef) -> &mut Self { + let item = UnorderedListItem::new_lossy(text.as_ref()); let item = Item::UnorderedListItem(item); self.add_item(item); @@ -340,8 +342,9 @@ impl Document { /// /// assert_eq!(document.to_string(), "> I think,\n> therefore I am\n"); /// ``` - pub fn add_quote(&mut self, text: &str) -> &mut Self { + pub fn add_quote(&mut self, text: impl AsRef) -> &mut Self { let quote = text + .as_ref() .lines() .map(Quote::new_lossy) .map(Item::Quote); diff --git a/src/types/request.rs b/src/types/request.rs index 76eace2..7dcffde 100644 --- a/src/types/request.rs +++ b/src/types/request.rs @@ -60,7 +60,8 @@ impl Request { self.certificate = cert; } - pub const fn certificate(&self) -> Option<&Certificate> { + #[allow(clippy::missing_const_for_fn)] + pub fn certificate(&self) -> Option<&Certificate> { self.certificate.as_ref() } } diff --git a/src/types/status.rs b/src/types/status.rs index 18c58a1..ba9ee71 100644 --- a/src/types/status.rs +++ b/src/types/status.rs @@ -29,7 +29,8 @@ impl Status { self.category().is_success() } - pub const fn category(&self) -> StatusCategory { + #[allow(clippy::missing_const_for_fn)] + pub fn category(&self) -> StatusCategory { let class = self.0 / 10; match class { diff --git a/src/util.rs b/src/util.rs index 99cb65c..7af5a20 100644 --- a/src/util.rs +++ b/src/util.rs @@ -23,8 +23,11 @@ pub async fn serve_file>(path: P, mime: &Mime) -> Result file, Err(err) => match err.kind() { - io::ErrorKind::NotFound => return Ok(Response::not_found()), - _ => return Err(err.into()), + std::io::ErrorKind::PermissionDenied => { + warn!("Asked to serve {}, but permission denied by OS", path.display()); + return Ok(Response::not_found()); + }, + _ => return warn_unexpected(err, path, line!()), } }; @@ -34,16 +37,44 @@ pub async fn serve_file>(path: P, mime: &Mime) -> Result, P: AsRef>(dir: D, virtual_path: &[P]) -> Result { debug!("Dir: {}", dir.as_ref().display()); - let dir = dir.as_ref().canonicalize() - .context("Failed to canonicalize directory")?; + let dir = dir.as_ref(); + let dir = match dir.canonicalize() { + Ok(dir) => dir, + Err(e) => { + match e.kind() { + std::io::ErrorKind::NotFound => { + warn!("Path {} not found. Check your configuration.", dir.display()); + return Response::server_error("Server incorrectly configured") + }, + std::io::ErrorKind::PermissionDenied => { + warn!("Permission denied for {}. Check that the server has access.", dir.display()); + return Response::server_error("Server incorrectly configured") + }, + _ => return warn_unexpected(e, dir, line!()), + } + }, + }; let mut path = dir.to_path_buf(); for segment in virtual_path { path.push(segment); } - let path = path.canonicalize() - .context("Failed to canonicalize path")?; + let path = match path.canonicalize() { + Ok(dir) => dir, + Err(e) => { + match e.kind() { + std::io::ErrorKind::NotFound => return Ok(Response::not_found()), + std::io::ErrorKind::PermissionDenied => { + // Runs when asked to serve a file in a restricted dir + // i.e. not /noaccess, but /noaccess/file + warn!("Asked to serve {}, but permission denied by OS", path.display()); + return Ok(Response::not_found()); + }, + _ => return warn_unexpected(e, path.as_ref(), line!()), + } + }, + }; if !path.starts_with(&dir) { return Ok(Response::not_found()); @@ -59,11 +90,15 @@ pub async fn serve_dir, P: AsRef>(dir: D, virtual_path: &[P #[cfg(feature="serve_dir")] async fn serve_dir_listing, B: AsRef>(path: P, virtual_path: &[B]) -> Result { - let mut dir = match fs::read_dir(path).await { + let mut dir = match fs::read_dir(path.as_ref()).await { Ok(dir) => dir, Err(err) => match err.kind() { io::ErrorKind::NotFound => return Ok(Response::not_found()), - _ => return Err(err.into()), + std::io::ErrorKind::PermissionDenied => { + warn!("Asked to serve {}, but permission denied by OS", path.as_ref().display()); + return Ok(Response::not_found()); + }, + _ => return warn_unexpected(err, path.as_ref(), line!()), } }; @@ -112,6 +147,22 @@ pub fn guess_mime_from_path>(path: P) -> Mime { mime_guess::from_ext(extension).first_or_octet_stream() } +#[cfg(feature="serve_dir")] +/// Print a warning to the log asking to file an issue and respond with "Unexpected Error" +pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32) -> Result { + warn!( + concat!( + "Unexpected error serving path {} at util.rs:{}, please report to ", + env!("CARGO_PKG_REPOSITORY"), + "/issues: {:?}", + ), + path.display(), + line, + err + ); + Response::server_error("Unexpected error") +} + /// A convenience trait alias for `AsRef + Into`, /// most commonly used to accept `&str` or `String`: ///