audb_core/tools/
shell_escape.rs

1/// Shell escaping utilities for safe command construction
2///
3/// This module provides functions to safely escape shell arguments for use in
4/// shell commands, preventing shell injection vulnerabilities.
5
6/// Escape a string for use in a single-quote context in shell commands.
7///
8/// In a single-quote context, the only character that needs escaping is the
9/// single quote itself. This is done by closing the quote, escaping the quote,
10/// and reopening the quote: '\''
11///
12/// # Example
13/// ```
14/// use audb::tools::shell_escape::escape_single_quote;
15///
16/// let password = "my'password";
17/// let escaped = escape_single_quote(password);
18/// assert_eq!(escaped, "my'\\''password");
19/// ```
20///
21/// # Security
22/// This function should be used whenever constructing shell commands that
23/// incorporate user-provided or external data within single quotes.
24pub fn escape_single_quote(s: &str) -> String {
25    s.replace('\'', r"'\''")
26}
27
28/// Wrapper type for shell-escaped strings
29///
30/// This newtype pattern ensures that strings used in shell contexts
31/// are properly escaped at compile time.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct ShellEscaped(String);
34
35impl ShellEscaped {
36    /// Create a new shell-escaped string for single-quote context
37    pub fn single_quote(s: &str) -> Self {
38        Self(escape_single_quote(s))
39    }
40
41    /// Get the escaped string
42    pub fn as_str(&self) -> &str {
43        &self.0
44    }
45
46    /// Consume and return the inner string
47    pub fn into_inner(self) -> String {
48        self.0
49    }
50}
51
52impl AsRef<str> for ShellEscaped {
53    fn as_ref(&self) -> &str {
54        &self.0
55    }
56}
57
58impl std::fmt::Display for ShellEscaped {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        write!(f, "{}", self.0)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_escape_single_quote_no_quotes() {
70        assert_eq!(escape_single_quote("password"), "password");
71    }
72
73    #[test]
74    fn test_escape_single_quote_with_quotes() {
75        assert_eq!(escape_single_quote("pass'word"), "pass'\\''word");
76    }
77
78    #[test]
79    fn test_escape_single_quote_multiple_quotes() {
80        assert_eq!(
81            escape_single_quote("'multiple'quotes'"),
82            "'\\''multiple'\\''quotes'\\''",
83        );
84    }
85
86    #[test]
87    fn test_shell_escaped_wrapper() {
88        let escaped = ShellEscaped::single_quote("test'value");
89        assert_eq!(escaped.as_str(), "test'\\''value");
90        assert_eq!(escaped.to_string(), "test'\\''value");
91    }
92}