win_desktop_utils/
elevation.rs1use std::ffi::{OsStr, OsString};
4
5use windows::Win32::UI::Shell::IsUserAnAdmin;
6
7use crate::error::{Error, Result};
8use crate::win::{join_quoted_args, normalize_nonempty_str, os_str_contains_nul, shell_execute};
9
10fn join_args_for_shell_execute(args: &[OsString]) -> String {
11 join_quoted_args(args)
12}
13
14fn validate_shell_args(args: &[OsString], message: &'static str) -> Result<()> {
15 if args.iter().any(|arg| os_str_contains_nul(arg.as_os_str())) {
16 return Err(Error::InvalidInput(message));
17 }
18
19 Ok(())
20}
21
22fn validate_restart_args(args: &[OsString]) -> Result<()> {
23 validate_shell_args(args, "restart arguments cannot contain NUL bytes")
24}
25
26fn validate_command_args(args: &[OsString]) -> Result<()> {
27 validate_shell_args(args, "arguments cannot contain NUL bytes")
28}
29
30fn validate_executable(executable: &OsStr) -> Result<()> {
31 if executable.is_empty() {
32 return Err(Error::InvalidInput("executable cannot be empty"));
33 }
34
35 if os_str_contains_nul(executable) {
36 return Err(Error::InvalidInput("executable cannot contain NUL bytes"));
37 }
38
39 Ok(())
40}
41
42fn normalize_shell_verb(verb: &str) -> Result<&str> {
43 normalize_nonempty_str(
44 verb,
45 "verb cannot be empty",
46 "verb cannot contain NUL bytes",
47 )
48}
49
50fn shell_execute_command(
51 verb: &str,
52 executable: &OsStr,
53 args: &[OsString],
54 context: &'static str,
55) -> Result<()> {
56 let joined_args = join_args_for_shell_execute(args);
57 let parameters = if joined_args.is_empty() {
58 None
59 } else {
60 Some(joined_args.as_str())
61 };
62 shell_execute(verb, executable, parameters, context)
63}
64
65pub fn is_elevated() -> Result<bool> {
80 let is_admin = unsafe { IsUserAnAdmin() };
81 Ok(is_admin.as_bool())
82}
83
84pub fn restart_as_admin(args: &[OsString]) -> Result<()> {
109 validate_restart_args(args)?;
110
111 let exe = std::env::current_exe()?;
112 shell_execute_command("runas", exe.as_os_str(), args, "ShellExecuteW(runas)")
113}
114
115pub fn run_as_admin(executable: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
136 run_with_verb("runas", executable, args)
137}
138
139pub fn run_with_verb(verb: &str, executable: impl AsRef<OsStr>, args: &[OsString]) -> Result<()> {
161 let verb = normalize_shell_verb(verb)?;
162 let executable = executable.as_ref();
163
164 validate_executable(executable)?;
165 validate_command_args(args)?;
166
167 shell_execute_command(verb, executable, args, "ShellExecuteW")
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{
173 join_args_for_shell_execute, normalize_shell_verb, validate_command_args,
174 validate_executable, validate_restart_args,
175 };
176 use std::ffi::OsStr;
177 use std::ffi::OsString;
178
179 #[test]
180 fn join_args_empty_is_empty() {
181 let args: [OsString; 0] = [];
182 assert_eq!(join_args_for_shell_execute(&args), "");
183 }
184
185 #[test]
186 fn join_args_quotes_each_argument() {
187 let args = [OsString::from("alpha"), OsString::from("two words")];
188 assert_eq!(
189 join_args_for_shell_execute(&args),
190 "\"alpha\" \"two words\""
191 );
192 }
193
194 #[test]
195 fn join_args_escapes_inner_quotes() {
196 let args = [OsString::from("say \"hi\"")];
197 assert_eq!(join_args_for_shell_execute(&args), "\"say \\\"hi\\\"\"");
198 }
199
200 #[test]
201 fn join_args_doubles_trailing_backslashes_inside_quotes() {
202 let args = [OsString::from(r"C:\Program Files\demo\")];
203 assert_eq!(
204 join_args_for_shell_execute(&args),
205 r#""C:\Program Files\demo\\""#
206 );
207 }
208
209 #[test]
210 fn validate_restart_args_rejects_nul_bytes() {
211 let args = [OsString::from("hello\0world")];
212 let result = validate_restart_args(&args);
213 assert!(matches!(
214 result,
215 Err(crate::Error::InvalidInput(
216 "restart arguments cannot contain NUL bytes"
217 ))
218 ));
219 }
220
221 #[test]
222 fn validate_command_args_rejects_nul_bytes() {
223 let args = [OsString::from("hello\0world")];
224 let result = validate_command_args(&args);
225 assert!(matches!(
226 result,
227 Err(crate::Error::InvalidInput(
228 "arguments cannot contain NUL bytes"
229 ))
230 ));
231 }
232
233 #[test]
234 fn validate_executable_rejects_empty_string() {
235 let result = validate_executable(OsStr::new(""));
236 assert!(matches!(
237 result,
238 Err(crate::Error::InvalidInput("executable cannot be empty"))
239 ));
240 }
241
242 #[test]
243 fn validate_executable_rejects_nul_bytes() {
244 let result = validate_executable(OsStr::new("cmd\0.exe"));
245 assert!(matches!(
246 result,
247 Err(crate::Error::InvalidInput(
248 "executable cannot contain NUL bytes"
249 ))
250 ));
251 }
252
253 #[test]
254 fn normalize_shell_verb_rejects_empty_string() {
255 let result = normalize_shell_verb("");
256 assert!(matches!(
257 result,
258 Err(crate::Error::InvalidInput("verb cannot be empty"))
259 ));
260 }
261
262 #[test]
263 fn normalize_shell_verb_trims_surrounding_whitespace() {
264 assert_eq!(normalize_shell_verb(" runas ").unwrap(), "runas");
265 }
266}