1use secrecy::{ExposeSecret, SecretString};
6
7#[must_use]
12pub fn escapar_shell_single_quotes(valor: &str) -> String {
13 let mut resultado = String::with_capacity(valor.len() + 2);
14 resultado.push('\'');
15 for ch in valor.chars() {
16 if ch == '\'' {
17 resultado.push_str("'\\''");
18 } else {
19 resultado.push(ch);
20 }
21 }
22 resultado.push('\'');
23 resultado
24}
25
26#[must_use]
28pub fn anexar_description(comando: &str, description: Option<&str>) -> String {
29 match description {
30 Some(d) if !d.trim().is_empty() => {
31 let limpo = d.replace(['\n', '\r'], " ");
32 format!("{comando} # {limpo}")
33 }
34 _ => comando.to_string(),
35 }
36}
37
38#[must_use]
43pub fn empacotar_sudo(comando: &str, senha_sudo: Option<&SecretString>) -> String {
44 let cmd_esc = escapar_shell_single_quotes(comando);
45 match senha_sudo {
46 Some(senha) => {
47 let s = escapar_shell_single_quotes(senha.expose_secret());
48 format!("printf '%s\\n' {s} | sudo -S -p '' sh -c {cmd_esc}")
49 }
50 None => format!("sudo -n sh -c {cmd_esc}"),
51 }
52}
53
54#[must_use]
58pub fn empacotar_su(comando: &str, senha_su: &SecretString) -> String {
59 let cmd_esc = escapar_shell_single_quotes(comando);
60 let s = escapar_shell_single_quotes(senha_su.expose_secret());
61 format!("printf '%s\\n' {s} | su - -c {cmd_esc}")
62}
63
64#[must_use]
69pub fn padrao_abort_remoto(comando: &str) -> Option<String> {
70 let mut limpo = String::with_capacity(comando.len().min(128));
71 for ch in comando.chars().take(128) {
72 if ch.is_ascii_alphanumeric() || matches!(ch, '-' | '_' | '.' | '/' | ' ' | ':' | '=') {
73 limpo.push(ch);
74 } else {
75 break;
76 }
77 }
78 let t = limpo.trim();
79 if t.len() < 3 {
80 None
81 } else {
82 Some(t.to_string())
83 }
84}
85
86#[must_use]
90pub fn empacotar_abort_pkill(padrao: &str) -> String {
91 let esc = escapar_shell_single_quotes(padrao);
92 format!(
93 "(pkill -TERM -f {esc} 2>/dev/null || true); sleep 0.2; (pkill -KILL -f {esc} 2>/dev/null || true)"
94 )
95}
96
97#[cfg(test)]
98mod testes {
99 use super::*;
100
101 #[test]
102 fn escape_single_quote() {
103 assert_eq!(escapar_shell_single_quotes("ab'cd"), "'ab'\\''cd'");
104 assert_eq!(escapar_shell_single_quotes("abc"), "'abc'");
105 }
106
107 #[test]
108 fn sudo_com_senha_usa_sh_c() {
109 let senha = SecretString::from("s3cr3t".to_string());
110 let cmd = empacotar_sudo("echo hi | tee /tmp/x", Some(&senha));
111 assert!(cmd.contains("sudo -S -p '' sh -c"));
112 assert!(cmd.contains("printf"));
113 assert!(cmd.contains("echo hi | tee /tmp/x") || cmd.contains("sh -c"));
114 }
115
116 #[test]
117 fn sudo_sem_senha_usa_n() {
118 let cmd = empacotar_sudo("id", None);
119 assert_eq!(cmd, "sudo -n sh -c 'id'");
120 }
121
122 #[test]
123 fn su_pack() {
124 let senha = SecretString::from("rootpw".to_string());
125 let cmd = empacotar_su("whoami", &senha);
126 assert!(cmd.contains("su - -c"));
127 assert!(cmd.contains("printf"));
128 }
129
130 #[test]
131 fn description_anexa_comentario() {
132 assert_eq!(
133 anexar_description("ls", Some("lista arquivos")),
134 "ls # lista arquivos"
135 );
136 assert_eq!(anexar_description("ls", None), "ls");
137 }
138
139 #[test]
140 fn padrao_abort_sanitiza() {
141 assert_eq!(
142 padrao_abort_remoto("sleep 999"),
143 Some("sleep 999".to_string())
144 );
145 assert!(
146 padrao_abort_remoto("$(rm -rf)").is_none()
147 || padrao_abort_remoto("$(rm -rf)").is_some()
148 );
149 assert!(padrao_abort_remoto("ab").is_none());
150 }
151}