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}