dolang_winterop/
process.rs1use std::{error::Error, fmt};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct NulError;
14
15impl fmt::Display for NulError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17 f.write_str("Windows process arguments cannot contain NUL characters")
18 }
19}
20
21impl Error for NulError {}
22
23pub fn quote_argument(argument: &str, output: &mut String) {
32 let quote = argument.is_empty() || argument.contains([' ', '\t', '"']);
33 if !quote {
34 output.push_str(argument);
35 return;
36 }
37
38 output.push('"');
39 let mut backslashes = 0;
40 for ch in argument.chars() {
41 if ch == '\\' {
42 backslashes += 1;
43 } else if ch == '"' {
44 output.extend(std::iter::repeat_n('\\', backslashes * 2 + 1));
45 output.push(ch);
46 backslashes = 0;
47 } else {
48 output.extend(std::iter::repeat_n('\\', backslashes));
49 output.push(ch);
50 backslashes = 0;
51 }
52 }
53 output.extend(std::iter::repeat_n('\\', backslashes * 2));
54 output.push('"');
55}
56
57pub fn join_arguments<I, S>(arguments: I) -> std::result::Result<String, NulError>
69where
70 I: IntoIterator<Item = S>,
71 S: AsRef<str>,
72{
73 let mut command_line = String::new();
74 for argument in arguments {
75 let argument = argument.as_ref();
76 if argument.contains('\0') {
77 return Err(NulError);
78 }
79 if !command_line.is_empty() {
80 command_line.push(' ');
81 }
82 quote_argument(argument, &mut command_line);
83 }
84 Ok(command_line)
85}
86
87pub fn split_arguments(command_line: &str) -> Vec<String> {
93 let mut chars = command_line.chars().peekable();
94 let mut arguments = Vec::new();
95
96 loop {
97 while matches!(chars.peek(), Some(' ' | '\t')) {
98 chars.next();
99 }
100 if chars.peek().is_none() {
101 break;
102 }
103
104 let mut argument = String::new();
105 let mut in_quotes = false;
106 loop {
107 match chars.peek() {
108 None => break,
109 Some(' ' | '\t') if !in_quotes => break,
110 Some('\\') => {
111 let mut backslashes = 0;
112 while chars.next_if_eq(&'\\').is_some() {
113 backslashes += 1;
114 }
115 if chars.next_if_eq(&'"').is_some() {
116 argument.extend(std::iter::repeat_n('\\', backslashes / 2));
117 let literal_quote =
118 backslashes % 2 == 1 || (in_quotes && chars.next_if_eq(&'"').is_some());
119 if literal_quote {
120 argument.push('"');
121 } else {
122 in_quotes = !in_quotes;
123 }
124 } else {
125 argument.extend(std::iter::repeat_n('\\', backslashes));
126 }
127 }
128 Some('"') => {
129 chars.next();
130 if in_quotes && chars.next_if_eq(&'"').is_some() {
131 argument.push('"');
132 } else {
133 in_quotes = !in_quotes;
134 }
135 }
136 Some(_) => argument.push(chars.next().unwrap()),
137 }
138 }
139 arguments.push(argument);
140 }
141
142 arguments
143}
144
145#[cfg(test)]
146mod tests {
147 use super::{NulError, join_arguments, quote_argument, split_arguments};
148
149 fn quote(argument: &str) -> String {
150 let mut result = String::new();
151 quote_argument(argument, &mut result);
152 result
153 }
154
155 #[test]
156 fn quotes_arguments() {
157 assert_eq!(quote(r"C:\plain"), r"C:\plain");
158 assert_eq!(quote(""), "\"\"");
159 assert_eq!(quote("two words"), r#""two words""#);
160 assert_eq!(quote("two\twords"), "\"two\twords\"");
161 assert_eq!(quote(r#"a\"b"#), r#""a\\\"b""#);
162 assert_eq!(
163 quote("C:\\path with space\\"),
164 "\"C:\\path with space\\\\\""
165 );
166 }
167
168 #[test]
169 fn splits_arguments() {
170 assert_eq!(split_arguments(""), Vec::<String>::new());
171 assert_eq!(split_arguments("a\tb"), ["a", "b"]);
172 assert_eq!(
173 split_arguments(r#""two words" a\\\"b"#),
174 ["two words", "a\\\"b"]
175 );
176 assert_eq!(
177 split_arguments(r#""C:\path with space\\""#),
178 ["C:\\path with space\\"]
179 );
180 assert_eq!(split_arguments(r#""a""b""#), ["a\"b"]);
181 }
182
183 #[test]
184 fn joins_and_splits_round_trip() {
185 let arguments = [
186 "",
187 "plain",
188 "two words",
189 "two\twords",
190 r#"a\"b"#,
191 r"trailing\\",
192 ];
193 let command_line = join_arguments(arguments).unwrap();
194 assert_eq!(split_arguments(&command_line), arguments);
195 }
196
197 #[test]
198 fn join_rejects_nul_arguments() {
199 assert_eq!(join_arguments(["a\0b"]), Err(NulError));
200 }
201}