Skip to content

Commit 724dbc1

Browse files
committed
feat(powershell): wrap potentially invalid expressions in Invoke-Expression
1 parent 5cccb33 commit 724dbc1

File tree

3 files changed

+105
-69
lines changed

3 files changed

+105
-69
lines changed

Diff for: crates/atuin-dotfiles/src/shell/powershell.rs

+103-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::path::PathBuf;
2-
1+
use crate::shell::{Alias, Var};
32
use crate::store::{var::VarStore, AliasStore};
3+
use std::path::PathBuf;
44

55
async fn cached_aliases(path: PathBuf, store: &AliasStore) -> String {
66
match tokio::fs::read_to_string(path).await {
@@ -66,3 +66,104 @@ pub async fn var_config(store: &VarStore) -> String {
6666

6767
cached_vars(vars, store).await
6868
}
69+
70+
pub fn format_alias(alias: &Alias) -> String {
71+
// Set-Alias doesn't support adding implicit arguments, so use a function.
72+
// See https://github.com/PowerShell/PowerShell/issues/12962
73+
74+
let mut result = secure_command(&format!(
75+
"function {} {{\n {}{} @args\n}}",
76+
alias.name,
77+
if alias.value.starts_with(['"', '\'']) {
78+
"& "
79+
} else {
80+
""
81+
},
82+
alias.value
83+
));
84+
85+
// This makes the file layout prettier
86+
result.insert(0, '\n');
87+
result
88+
}
89+
90+
pub fn format_var(var: &Var) -> String {
91+
secure_command(&format!(
92+
"${}{} = '{}'",
93+
if var.export { "env:" } else { "" },
94+
var.name,
95+
var.value.replace("'", "''")
96+
))
97+
}
98+
99+
/// Wraps the given command in an Invoke-Expression to ensure the outer script is not halted
100+
/// if the inner command contains a syntax error.
101+
fn secure_command(command: &str) -> String {
102+
format!(
103+
"Invoke-Expression -ErrorAction Continue -Command '{}'\n",
104+
command.replace("'", "''")
105+
)
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
112+
#[test]
113+
fn aliases() {
114+
assert_eq!(
115+
format_alias(&Alias {
116+
name: "gp".to_string(),
117+
value: "git push".to_string(),
118+
}),
119+
"\n".to_string()
120+
+ &secure_command(
121+
"function gp {
122+
git push @args
123+
}"
124+
)
125+
);
126+
127+
assert_eq!(
128+
format_alias(&Alias {
129+
name: "spc".to_string(),
130+
value: "\"path with spaces\" arg".to_string(),
131+
}),
132+
"\n".to_string()
133+
+ &secure_command(
134+
"function spc {
135+
& \"path with spaces\" arg @args
136+
}"
137+
)
138+
);
139+
}
140+
141+
#[test]
142+
fn vars() {
143+
assert_eq!(
144+
format_var(&Var {
145+
name: "FOO".to_owned(),
146+
value: "bar 'baz'".to_owned(),
147+
export: true,
148+
}),
149+
secure_command("$env:FOO = 'bar ''baz'''")
150+
);
151+
152+
assert_eq!(
153+
format_var(&Var {
154+
name: "TEST".to_owned(),
155+
value: "1".to_owned(),
156+
export: false,
157+
}),
158+
secure_command("$TEST = '1'")
159+
);
160+
}
161+
162+
#[test]
163+
fn invoke_expression() {
164+
assert_eq!(
165+
secure_command("echo 'foo'"),
166+
"Invoke-Expression -ErrorAction Continue -Command 'echo ''foo'''\n"
167+
)
168+
}
169+
}

Diff for: crates/atuin-dotfiles/src/store.rs

+1-41
Original file line numberDiff line numberDiff line change
@@ -183,18 +183,7 @@ impl AliasStore {
183183
let mut config = String::new();
184184

185185
for alias in aliases {
186-
// Set-Alias doesn't support adding implicit arguments, so use a function.
187-
// See https://github.com/PowerShell/PowerShell/issues/12962
188-
config.push_str(&format!(
189-
"\nfunction {} {{\n {}{} @args\n}}\n",
190-
alias.name,
191-
if alias.value.starts_with(['"', '\'']) {
192-
"& "
193-
} else {
194-
""
195-
},
196-
alias.value
197-
));
186+
config.push_str(&crate::shell::powershell::format_alias(alias));
198187
}
199188

200189
config
@@ -426,35 +415,6 @@ mod tests {
426415
"alias gp='git push'
427416
alias k='kubectl'
428417
alias kgap='kubectl get pods --all-namespaces'
429-
"
430-
)
431-
}
432-
433-
#[test]
434-
fn format_powershell() {
435-
let aliases = [
436-
Alias {
437-
name: "gp".to_string(),
438-
value: "git push".to_string(),
439-
},
440-
Alias {
441-
name: "spc".to_string(),
442-
value: "\"path with spaces\" arg".to_string(),
443-
},
444-
];
445-
446-
let result = AliasStore::format_powershell(&aliases);
447-
448-
assert_eq!(
449-
result,
450-
"
451-
function gp {
452-
git push @args
453-
}
454-
455-
function spc {
456-
& \"path with spaces\" arg @args
457-
}
458418
"
459419
)
460420
}

Diff for: crates/atuin-dotfiles/src/store/var.rs

+1-26
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,7 @@ impl VarStore {
173173
let mut config = String::new();
174174

175175
for env in env {
176-
config.push_str(&format!(
177-
"${}{} = '{}'\n",
178-
if env.export { "env:" } else { "" },
179-
env.name,
180-
env.value.replace("'", "''")
181-
));
176+
config.push_str(&crate::shell::powershell::format_var(env));
182177
}
183178

184179
config
@@ -388,24 +383,4 @@ mod tests {
388383
}
389384
);
390385
}
391-
392-
#[test]
393-
fn format_powershell() {
394-
let env = [
395-
Var {
396-
name: "FOO".to_owned(),
397-
value: "bar 'baz'".to_owned(),
398-
export: true,
399-
},
400-
Var {
401-
name: "TEST".to_owned(),
402-
value: "1".to_owned(),
403-
export: false,
404-
},
405-
];
406-
407-
let result = VarStore::format_powershell(&env);
408-
409-
assert_eq!(result, "$env:FOO = 'bar ''baz'''\n$TEST = '1'\n");
410-
}
411386
}

0 commit comments

Comments
 (0)