cageforge_command/
command.rs1use std::ffi::{OsStr, OsString};
11
12use crate::CommandError;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct CommandSpec {
22 program: OsString,
23 args: Vec<OsString>,
24}
25
26impl CommandSpec {
27 pub fn new(program: impl Into<OsString>) -> Result<Self, CommandError> {
29 let program = program.into();
30 validate_program(&program)?;
31 Ok(Self {
32 program,
33 args: Vec::new(),
34 })
35 }
36
37 pub fn with_arg(mut self, argument: impl Into<OsString>) -> Result<Self, CommandError> {
39 let argument = argument.into();
40 validate_argument(&argument)?;
41 self.args.push(argument);
42 Ok(self)
43 }
44
45 pub fn with_args<I, S>(mut self, arguments: I) -> Result<Self, CommandError>
47 where
48 I: IntoIterator<Item = S>,
49 S: Into<OsString>,
50 {
51 for argument in arguments {
52 let argument = argument.into();
53 validate_argument(&argument)?;
54 self.args.push(argument);
55 }
56 Ok(self)
57 }
58
59 pub fn program(&self) -> &OsStr {
61 &self.program
62 }
63
64 pub fn args(&self) -> &[OsString] {
66 &self.args
67 }
68
69 pub fn into_parts(self) -> (OsString, Vec<OsString>) {
71 (self.program, self.args)
72 }
73}
74
75fn validate_program(program: &OsStr) -> Result<(), CommandError> {
76 if program.is_empty() {
77 return Err(CommandError::EmptyProgram);
78 }
79 if contains_nul(program) {
80 return Err(CommandError::ProgramContainsNul);
81 }
82 Ok(())
83}
84
85fn validate_argument(argument: &OsStr) -> Result<(), CommandError> {
86 if contains_nul(argument) {
87 return Err(CommandError::ArgumentContainsNul);
88 }
89 Ok(())
90}
91
92pub(crate) fn contains_nul(value: &OsStr) -> bool {
93 #[cfg(unix)]
94 {
95 use std::os::unix::ffi::OsStrExt;
96 value.as_bytes().contains(&0)
97 }
98 #[cfg(windows)]
99 {
100 use std::os::windows::ffi::OsStrExt;
101 value.encode_wide().any(|unit| unit == 0)
102 }
103 #[cfg(not(any(unix, windows)))]
104 {
105 value.to_string_lossy().contains('\0')
106 }
107}