diff --git a/Cargo.toml b/Cargo.toml index 7dc75c0..86a2720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ http-body-util = "0.1" bytes = "1.5" pin-project-lite = "0.2" sha2 = "0.10.8" +regex = "1" [features] default = ["tls"] diff --git a/src/args.rs b/src/args.rs index 032d383..92c275f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -75,7 +75,17 @@ pub fn build_cli() -> Command { .long("hidden") .action(ArgAction::Append) .value_delimiter(',') - .help("Hide paths from directory listings, e.g. tmp,*.log,*.lock") + .help("Hide files from directory listings, by glob e.g. tmp,*.log,*.lock") + .value_name("value"), + ) + .arg( + Arg::new("hidden-path") + .env("DUFS_HIDDEN_PATH") + .hide_env(true) + .long("hidden-path") + .action(ArgAction::Append) + .value_delimiter(',') + .help("Hide paths from directory listings, by regex e.g. ^tmp$,.+\\.log,\\\\hid(?:e|den)-subdir\\\\ unlike \"hidden\" processes full system paths") .value_name("value"), ) .arg( @@ -273,6 +283,8 @@ pub struct Args { pub uri_prefix: String, #[serde(deserialize_with = "deserialize_string_or_vec")] pub hidden: Vec, + #[serde(deserialize_with = "deserialize_string_or_vec")] + pub hidden_path: Vec, #[serde(deserialize_with = "deserialize_access_control")] pub auth: AccessControl, pub allow_all: bool, diff --git a/src/server.rs b/src/server.rs index 5bb195a..8583d10 100644 --- a/src/server.rs +++ b/src/server.rs @@ -4,7 +4,7 @@ use crate::auth::{www_authenticate, AccessPaths, AccessPerm}; use crate::http_utils::{body_full, IncomingStream, LengthLimitedStream}; use crate::utils::{ decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, parse_range, - try_get_file_name, + try_get_file_name, regx }; use crate::Args; @@ -592,6 +592,8 @@ impl Server { let path_buf = path.to_path_buf(); let hidden = Arc::new(self.args.hidden.to_vec()); let hidden = hidden.clone(); + let hidden_path = Arc::new(self.args.hidden_path.to_vec()); + let hidden_path = hidden_path.clone(); let running = self.running.clone(); let access_paths = access_paths.clone(); let search_paths = tokio::task::spawn_blocking(move || { @@ -606,7 +608,8 @@ impl Server { let entry_path = entry.path(); let base_name = get_file_name(entry_path); let is_dir = entry.file_type().is_dir(); - if is_hidden(&hidden, base_name, is_dir) { + if is_hidden(&hidden, base_name, is_dir) + || is_hidden_path(&hidden_path, entry_path, is_dir) { if is_dir { it.skip_current_dir(); } @@ -656,6 +659,7 @@ impl Server { } let path = path.to_owned(); let hidden = self.args.hidden.clone(); + let hidden_path = self.args.hidden_path.clone(); let running = self.running.clone(); let compression = self.args.compress.to_compression(); tokio::spawn(async move { @@ -664,6 +668,7 @@ impl Server { &path, access_paths, &hidden, + &hidden_path, compression, running, ) @@ -1339,7 +1344,8 @@ impl Server { async fn add_pathitem(&self, paths: &mut Vec, base_path: &Path, entry_path: &Path) { let base_name = get_file_name(entry_path); if let Ok(Some(item)) = self.to_pathitem(entry_path, base_path).await { - if is_hidden(&self.args.hidden, base_name, item.is_dir()) { + if is_hidden(&self.args.hidden, base_name, item.is_dir()) + || is_hidden_path(&self.args.hidden_path, entry_path, item.is_dir()) { return; } paths.push(item); @@ -1374,7 +1380,8 @@ impl Server { .await .map(|v| v.is_dir()) .unwrap_or_default(); - if is_hidden(&self.args.hidden, base_name, is_dir) { + if is_hidden(&self.args.hidden, base_name, is_dir) + || is_hidden_path(&self.args.hidden_path, &entry_path, is_dir) { continue; } count += 1; @@ -1599,11 +1606,13 @@ async fn zip_dir( dir: &Path, access_paths: AccessPaths, hidden: &[String], + hidden_path: &[String], compression: Compression, running: Arc, ) -> Result<()> { let mut writer = ZipFileWriter::with_tokio(writer); let hidden = Arc::new(hidden.to_vec()); + let hidden_path = Arc::new(hidden_path.to_vec()); let dir_clone = dir.to_path_buf(); let zip_paths = tokio::task::spawn_blocking(move || { let mut paths: Vec = vec![]; @@ -1617,7 +1626,8 @@ async fn zip_dir( let entry_path = entry.path(); let base_name = get_file_name(entry_path); let file_type = entry.file_type(); - if is_hidden(&hidden, base_name, file_type.is_dir()) { + if is_hidden(&hidden, base_name, file_type.is_dir()) + || is_hidden_path(&hidden_path, entry_path, file_type.is_dir()) { if file_type.is_dir() { it.skip_current_dir(); } @@ -1708,6 +1718,18 @@ fn set_content_disposition(res: &mut Response, inline: bool, filename: &str) -> Ok(()) } +pub fn is_hidden_path(hidden: &[String], entry_path: &std::path::Path, is_dir_type: bool) -> bool { + let file_name = entry_path.to_str().unwrap_or_default(); + hidden.iter().any(|v| { + if is_dir_type { + if let Some(x) = v.strip_suffix('/') { + return regx(x, file_name); + } + } + regx(v, file_name) + }) +} + fn is_hidden(hidden: &[String], file_name: &str, is_dir: bool) -> bool { hidden.iter().any(|v| { if is_dir { diff --git a/src/utils.rs b/src/utils.rs index edf8544..084fe0c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -52,6 +52,14 @@ pub fn try_get_file_name(path: &Path) -> Result<&str> { .ok_or_else(|| anyhow!("Failed to get file name of `{}`", path.display())) } +pub fn regx(pattern: &str, target: &str) -> bool { + let rex = match regex::Regex::new(pattern) { + Ok(rex) => rex, + Err(_) => return false, + }; + rex.is_match(target) +} + pub fn glob(pattern: &str, target: &str) -> bool { let pat = match ::glob::Pattern::new(pattern) { Ok(pat) => pat,