mirror of
https://github.com/sharkdp/fd.git
synced 2026-09-01 15:12:46 +08:00
apply strict filter
This commit is contained in:
+3
-3
@@ -55,14 +55,14 @@ impl DirEntry {
|
||||
}
|
||||
|
||||
/// Returns the path as it should be presented to the user.
|
||||
/// When stripping `./` would leave the path starting with `-`, keep the `./` so
|
||||
/// downstream tools don't interpret the filename as an option.
|
||||
/// When stripping `./` would leave the path starting with `-`, keep the original
|
||||
/// (with `./`) so downstream tools don't interpret it as an option.
|
||||
pub fn stripped_path(&self, config: &Config) -> Cow<'_, Path> {
|
||||
let path = self.path();
|
||||
if config.strip_cwd_prefix {
|
||||
let stripped = strip_current_dir(path);
|
||||
if starts_with_dash(stripped) {
|
||||
Cow::Owned(Path::new(".").join(stripped))
|
||||
Cow::Borrowed(path)
|
||||
} else {
|
||||
Cow::Borrowed(stripped)
|
||||
}
|
||||
|
||||
@@ -242,6 +242,8 @@ impl CommandTemplate {
|
||||
bail!("No executable provided for --exec or --exec-batch");
|
||||
}
|
||||
|
||||
// Reject placeholder-as-executable for both --exec and --exec-batch
|
||||
// (was previously checked only for --exec-batch).
|
||||
if args[0].has_tokens() {
|
||||
bail!(
|
||||
"First argument of --exec/--exec-batch must be a fixed executable, not a placeholder"
|
||||
|
||||
+47
-8
@@ -3,22 +3,40 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
|
||||
/// True for any char that is neither printable nor permitted whitespace (only HT).
|
||||
/// Covers C0/C1/DEL, bidi overrides, zero-width and format chars, and tag chars.
|
||||
#[inline]
|
||||
fn is_dangerous_control(c: char) -> bool {
|
||||
// C0 (except HT), DEL, and C1 controls (U+0080..=U+009F can act as
|
||||
// single-byte CSI/OSC initiators on 8-bit-control terminals).
|
||||
matches!(c, '\x00'..='\x08' | '\x0A'..='\x1F' | '\x7F' | '\u{80}'..='\u{9F}')
|
||||
fn needs_escape(c: char) -> bool {
|
||||
if c == '\t' {
|
||||
return false;
|
||||
}
|
||||
c.is_control()
|
||||
|| matches!(c,
|
||||
'\u{00AD}' // soft hyphen (invisible)
|
||||
| '\u{180E}' // Mongolian vowel separator
|
||||
| '\u{200B}'..='\u{200F}' // zero-width + LRM/RLM
|
||||
| '\u{202A}'..='\u{202E}' // bidi embedding/override
|
||||
| '\u{2060}'..='\u{206F}' // word joiner, invisibles, deprecated formats
|
||||
| '\u{FEFF}' // BOM / zero-width no-break space
|
||||
| '\u{FFF9}'..='\u{FFFB}' // interlinear annotation
|
||||
| '\u{E0000}'..='\u{E007F}' // language tags
|
||||
)
|
||||
}
|
||||
|
||||
/// Replace control characters with `\xNN` so the original filename remains recoverable.
|
||||
/// Replace dangerous chars with `\xNN` / `\u{NNNN}` so the original is recoverable.
|
||||
pub fn sanitize_for_terminal(s: &str) -> Cow<'_, str> {
|
||||
if !s.chars().any(is_dangerous_control) {
|
||||
if !s.chars().any(needs_escape) {
|
||||
return Cow::Borrowed(s);
|
||||
}
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
if is_dangerous_control(c) {
|
||||
let _ = write!(out, "\\x{:02X}", c as u32);
|
||||
if needs_escape(c) {
|
||||
let v = c as u32;
|
||||
if v <= 0xFF {
|
||||
let _ = write!(out, "\\x{:02X}", v);
|
||||
} else {
|
||||
let _ = write!(out, "\\u{{{:04X}}}", v);
|
||||
}
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
@@ -98,4 +116,25 @@ mod tests {
|
||||
"\\x9D0;pwned\\x9C"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strips_bidi_overrides_and_zero_width() {
|
||||
// Trojan-Source style RLO/LRO that flip rendered order of filename text.
|
||||
assert_eq!(
|
||||
sanitize_for_terminal("safe\u{202E}fil\u{202D}gnp.exe"),
|
||||
"safe\\u{202E}fil\\u{202D}gnp.exe"
|
||||
);
|
||||
// Zero-width space and BOM are also format chars used to disguise filenames.
|
||||
assert_eq!(sanitize_for_terminal("a\u{200B}b"), "a\\u{200B}b");
|
||||
assert_eq!(sanitize_for_terminal("\u{FEFF}name"), "\\u{FEFF}name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_legitimate_unicode_features() {
|
||||
// Variation selectors (U+FE0F, U+E0100..) modify preceding glyphs in CJK/emoji
|
||||
// and are legitimate in filenames. Private-use chars are used by icon fonts.
|
||||
for s in ["heart\u{2764}\u{FE0F}.txt", "icon\u{E000}.cfg", "cjk\u{6F22}\u{E0101}.txt"] {
|
||||
assert!(matches!(sanitize_for_terminal(s), Cow::Borrowed(_)), "{s:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user