From 4d0b0521d67e7e692d7757de2a712ebad540d289 Mon Sep 17 00:00:00 2001 From: Ben Aaron Goldberg Date: Sat, 21 Nov 2020 23:45:05 -0500 Subject: [PATCH 1/6] Include a workaround for a bug with rustls & webpki --- src/lib.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) 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)] From 48c0783e36f31776bb19585d5c84804c5d22e67e Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Mon, 23 Nov 2020 09:39:45 -0500 Subject: [PATCH 2/6] Improve error handling for serve_dir Specifically: - Serve NOT_FOUND when file or directory doesn't exist - Serve NOT_FOUND and warn in log when permission denied for file or directory - Serve SERVER_ERROR and warn in log when permission denied or not found for path to serve from - Serve SERVER_ERROR and warn in log with a link to the GH if an unexpected error happens --- src/util.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 8 deletions(-) diff --git a/src/util.rs b/src/util.rs index 5c623aa..983e67a 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`: /// From 4743419f1e7b6df9ae3b911f830796bd34878ca3 Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Mon, 23 Nov 2020 09:49:04 -0500 Subject: [PATCH 3/6] Update changelog with improved error handling for serve_dir --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472fd82..7b446a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +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] +- Improved error handling in serve_dir [@Alch-Emi] ## [0.3.0] - 2020-11-14 ### Added From 65f3bbf2a2587dd6fdd90128d36ef0a95f02ae4f Mon Sep 17 00:00:00 2001 From: Emi Tatsuo Date: Tue, 24 Nov 2020 16:50:28 -0500 Subject: [PATCH 4/6] Add "serve_dir" feature as a reaquired feature for the example of the same name --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) 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"] From 71a79ad6ff9b471b17efc2c4c897ca4529392d8b Mon Sep 17 00:00:00 2001 From: Emii Tatsuo Date: Fri, 27 Nov 2020 22:19:08 -0500 Subject: [PATCH 5/6] Fix stable build --- src/types/request.rs | 3 ++- src/types/status.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) 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 { From 9c999609ef97907369723f76d70199f1f1583b1f Mon Sep 17 00:00:00 2001 From: Emii Tatsuo Date: Sat, 28 Nov 2020 14:47:39 -0500 Subject: [PATCH 6/6] Accept strings in Document methods A lot of the time users are using the document, it's in order to dynamically generate a page. However, when dynamically generating a page, most of the time you're working with String's, not just &str's. Currently, many Document methods only take an &str, which means that each time you call a method with something like the output from `format!()`, you need to add a `.as_str()`, which adds just a little bit of clutter. This change makes it so that Document methods that previously took an `&str` can now take an `impl AsRef`, which allows users to pass either an `&str` or a `String`. --- src/types/document.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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);