diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c4d56010f..afd4696e8 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +* Optionally support hidden files/directories. [#1811] + +[#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index a99b4699e..d0cac6aa4 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -39,6 +39,7 @@ pub struct Files { mime_override: Option>, file_flags: named::Flags, guards: Option>, + hidden_files: bool, } impl fmt::Debug for Files { @@ -60,6 +61,7 @@ impl Clone for Files { path: self.path.clone(), mime_override: self.mime_override.clone(), guards: self.guards.clone(), + hidden_files: self.hidden_files, } } } @@ -103,6 +105,7 @@ impl Files { mime_override: None, file_flags: named::Flags::default(), guards: None, + hidden_files: false, } } @@ -213,6 +216,13 @@ impl Files { self } + + /// Enables serving hidden files and directories, allowing a leading dots in url fragments. + #[inline] + pub fn use_hidden_files(mut self) -> Self { + self.hidden_files = true; + self + } } impl HttpServiceFactory for Files { @@ -251,6 +261,7 @@ impl ServiceFactory for Files { mime_override: self.mime_override.clone(), file_flags: self.file_flags, guards: self.guards.clone(), + hidden_files: self.hidden_files, }; if let Some(ref default) = *self.default.borrow() { diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 2f3ae84d4..dd8e5b503 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -15,12 +15,19 @@ impl FromStr for PathBufWrap { type Err = UriSegmentError; fn from_str(path: &str) -> Result { + Self::parse_path(path, false) + } +} + +impl PathBufWrap { + /// Parse a path, giving the choice of allowing hidden files to be considered valid segments. + pub fn parse_path(path: &str, hidden_files: bool) -> Result { let mut buf = PathBuf::new(); for segment in path.split('/') { if segment == ".." { buf.pop(); - } else if segment.starts_with('.') { + } else if !hidden_files && segment.starts_with('.') { return Err(UriSegmentError::BadStart('.')); } else if segment.starts_with('*') { return Err(UriSegmentError::BadStart('*')); @@ -96,4 +103,17 @@ mod tests { PathBuf::from_iter(vec!["seg2"]) ); } + + #[test] + fn test_parse_path() { + assert_eq!( + PathBufWrap::parse_path("/test/.tt", false).map(|t| t.0), + Err(UriSegmentError::BadStart('.')) + ); + + assert_eq!( + PathBufWrap::parse_path("/test/.tt", true).unwrap().0, + PathBuf::from_iter(vec!["test", ".tt"]) + ); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index cbf4c2d3b..dc4f2bd2c 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -31,6 +31,7 @@ pub struct FilesService { pub(crate) mime_override: Option>, pub(crate) file_flags: named::Flags, pub(crate) guards: Option>, + pub(crate) hidden_files: bool, } type FilesServiceFuture = Either< @@ -83,10 +84,11 @@ impl Service for FilesService { ))); } - let real_path: PathBufWrap = match req.match_info().path().parse() { - Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), - }; + let real_path = + match PathBufWrap::parse_path(req.match_info().path(), self.hidden_files) { + Ok(item) => item, + Err(e) => return Either::Left(ok(req.error_response(e))), + }; // full file path let path = match self.directory.join(&real_path).canonicalize() {