Skip to main content

shell_sanitize_rules/
glob.rs

1use shell_sanitize::{Rule, RuleResult, RuleViolation};
2
3use crate::charset::CharSet;
4
5/// Rejects input containing shell glob characters.
6///
7/// Default denied: `*`, `?`, `[`, `]`, `{`, `}`.
8///
9/// # Rationale
10///
11/// Glob characters cause unintended filename expansion in shell contexts:
12/// ```text
13/// rm *.log          → deletes all .log files
14/// cat file[0-9]     → expands to matching filenames
15/// echo {a,b,c}      → brace expansion
16/// ```
17///
18/// Note: `{` and `}` overlap with [`ShellMetaRule`](crate::ShellMetaRule).
19/// When both rules are active, each reports its own violation independently.
20/// This is intentional — overlapping reports provide richer context about
21/// why an input was rejected.
22pub struct GlobRule {
23    denied: CharSet,
24}
25
26impl Default for GlobRule {
27    fn default() -> Self {
28        Self {
29            denied: CharSet::from_chars(&['*', '?', '[', ']', '{', '}']),
30        }
31    }
32}
33
34impl GlobRule {
35    /// Create a rule with a custom set of denied glob characters.
36    pub fn with_denied(denied: Vec<char>) -> Self {
37        Self {
38            denied: CharSet::from_chars(&denied),
39        }
40    }
41}
42
43impl Rule for GlobRule {
44    fn name(&self) -> &'static str {
45        "glob"
46    }
47
48    fn check(&self, input: &str) -> RuleResult {
49        let violations: Vec<_> = input
50            .char_indices()
51            .filter(|(_, c)| self.denied.contains(*c))
52            .map(|(i, c)| {
53                RuleViolation::new(self.name(), format!("glob character {:?} found", c))
54                    .at(i)
55                    .with_fragment(c.to_string())
56            })
57            .collect();
58
59        if violations.is_empty() {
60            Ok(())
61        } else {
62            Err(violations)
63        }
64    }
65}