1use std::ffi::CString;
11use std::fmt;
12use std::result;
13
14#[derive(Eq, PartialEq, Debug)]
16pub enum Error {
17 CommandLineCopy,
19 CommandLineOverflow,
21 InvalidAscii,
23 HasSpace,
25 HasEquals,
27 TooLarge,
29}
30
31impl fmt::Display for Error {
32 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33 write!(
34 f,
35 "{}",
36 match *self {
37 Error::CommandLineCopy => "Failed to copy the command line string to guest memory",
38 Error::CommandLineOverflow => "Command line string overflows guest memory",
39 Error::InvalidAscii => "Command line string contains non-printable ASCII character",
40 Error::HasSpace => "Command line string contains a space",
41 Error::HasEquals => "Command line string contains an equals sign",
42 Error::TooLarge => "Command line inserting string would make command line too long",
43 }
44 )
45 }
46}
47
48pub type Result<T> = result::Result<T, Error>;
50
51fn valid_char(c: char) -> bool {
52 matches!(c, ' '..='~')
53}
54
55fn valid_str(s: &str) -> Result<()> {
56 if s.chars().all(valid_char) {
57 Ok(())
58 } else {
59 Err(Error::InvalidAscii)
60 }
61}
62
63fn valid_element(s: &str) -> Result<()> {
64 if !s.chars().all(valid_char) {
65 Err(Error::InvalidAscii)
66 } else if s.contains(' ') {
67 Err(Error::HasSpace)
68 } else if s.contains('=') {
69 Err(Error::HasEquals)
70 } else {
71 Ok(())
72 }
73}
74
75#[derive(Clone, Debug)]
78pub struct Cmdline {
79 line: String,
80 capacity: usize,
81}
82
83impl Cmdline {
84 pub fn new(capacity: usize) -> Cmdline {
87 assert_ne!(capacity, 0);
88 Cmdline {
89 line: String::with_capacity(capacity),
90 capacity,
91 }
92 }
93
94 fn has_capacity(&self, more: usize) -> Result<()> {
95 let needs_space = usize::from(!self.line.is_empty());
96 if self.line.len() + more + needs_space < self.capacity {
97 Ok(())
98 } else {
99 Err(Error::TooLarge)
100 }
101 }
102
103 fn start_push(&mut self) {
104 if !self.line.is_empty() {
105 self.line.push(' ');
106 }
107 }
108
109 fn end_push(&mut self) {
110 assert!(self.line.len() < self.capacity);
113 }
114
115 pub fn len(&self) -> usize {
117 self.line.len()
118 }
119
120 pub fn is_empty(&self) -> bool {
122 self.len() == 0
123 }
124
125 pub fn insert<T: AsRef<str>>(&mut self, key: T, val: T) -> Result<()> {
127 let k = key.as_ref();
128 let v = val.as_ref();
129
130 valid_element(k)?;
131 valid_element(v)?;
132 self.has_capacity(k.len() + v.len() + 1)?;
133
134 self.start_push();
135 self.line.push_str(k);
136 self.line.push('=');
137 self.line.push_str(v);
138 self.end_push();
139
140 Ok(())
141 }
142
143 pub fn insert_str<T: AsRef<str>>(&mut self, slug: T) -> Result<()> {
145 let s = slug.as_ref();
146 valid_str(s)?;
147
148 self.has_capacity(s.len())?;
149
150 self.start_push();
151 self.line.push_str(s);
152 self.end_push();
153
154 Ok(())
155 }
156
157 pub fn as_str(&self) -> &str {
159 self.line.as_str()
160 }
161
162 pub fn as_cstring(&self) -> Result<CString> {
164 CString::new(self.line.clone()).map_err(|_| Error::InvalidAscii)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn insert_hello_world() {
174 let mut cl = Cmdline::new(100);
175 assert_eq!(cl.as_str(), "");
176 assert!(cl.insert("hello", "world").is_ok());
177 assert_eq!(cl.as_str(), "hello=world");
178 assert_eq!(cl.len(), "hello=world".len());
179 assert!(!cl.is_empty());
180
181 let cl2 = cl.clone();
183 assert_eq!(cl2.as_str(), cl.as_str());
184 }
185
186 #[test]
187 fn insert_multi() {
188 let mut cl = Cmdline::new(100);
189 assert!(cl.insert("hello", "world").is_ok());
190 assert!(cl.insert("foo", "bar").is_ok());
191 assert_eq!(cl.as_str(), "hello=world foo=bar");
192 }
193
194 #[test]
195 fn insert_space() {
196 let mut cl = Cmdline::new(100);
197 assert_eq!(cl.insert("a ", "b"), Err(Error::HasSpace));
198 assert_eq!(cl.insert("a", "b "), Err(Error::HasSpace));
199 assert_eq!(cl.insert("a ", "b "), Err(Error::HasSpace));
200 assert_eq!(cl.insert(" a", "b"), Err(Error::HasSpace));
201 assert_eq!(cl.as_str(), "");
202 }
203
204 #[test]
205 fn insert_equals() {
206 let mut cl = Cmdline::new(100);
207 assert_eq!(cl.insert("a=", "b"), Err(Error::HasEquals));
208 assert_eq!(cl.insert("a", "b="), Err(Error::HasEquals));
209 assert_eq!(cl.insert("a=", "b "), Err(Error::HasEquals));
210 assert_eq!(cl.insert("=a", "b"), Err(Error::HasEquals));
211 assert_eq!(cl.insert("a", "=b"), Err(Error::HasEquals));
212 assert_eq!(cl.as_str(), "");
213 }
214
215 #[test]
216 fn insert_emoji() {
217 assert_eq!(valid_str("💖"), Err(Error::InvalidAscii));
218
219 let mut cl = Cmdline::new(100);
220 assert_eq!(cl.insert("heart", "💖"), Err(Error::InvalidAscii));
221 assert_eq!(cl.insert("💖", "love"), Err(Error::InvalidAscii));
222 assert_eq!(cl.as_str(), "");
223 }
224
225 #[test]
226 fn insert_string() {
227 let mut cl = Cmdline::new(13);
228 assert_eq!(cl.as_str(), "");
229 assert!(cl.insert_str("noapic").is_ok());
230 assert_eq!(cl.as_str(), "noapic");
231 assert!(cl.insert_str("nopci").is_ok());
232 assert_eq!(cl.as_str(), "noapic nopci");
233 assert_eq!(cl.as_str(), cl.as_cstring().unwrap().to_str().unwrap());
234 }
235
236 #[test]
237 fn insert_too_large() {
238 let mut cl = Cmdline::new(4);
239 assert_eq!(cl.insert("hello", "world"), Err(Error::TooLarge));
240 assert_eq!(cl.insert("a", "world"), Err(Error::TooLarge));
241 assert_eq!(cl.insert("hello", "b"), Err(Error::TooLarge));
242 assert!(cl.insert("a", "b").is_ok());
243 assert_eq!(cl.insert("a", "b"), Err(Error::TooLarge));
244 assert_eq!(cl.insert_str("a"), Err(Error::TooLarge));
245 assert_eq!(cl.as_str(), "a=b");
246
247 let mut cl = Cmdline::new(10);
248 assert!(cl.insert("ab", "ba").is_ok()); assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); assert!(cl.insert("c", "d").is_ok()); }
252
253 #[test]
254 fn display_errors() {
255 assert_eq!(
256 Error::CommandLineCopy.to_string().as_str(),
257 "Failed to copy the command line string to guest memory"
258 );
259 assert_eq!(
260 Error::CommandLineOverflow.to_string().as_str(),
261 "Command line string overflows guest memory"
262 );
263 assert_eq!(
264 Error::InvalidAscii.to_string().as_str(),
265 "Command line string contains non-printable ASCII character"
266 );
267 assert_eq!(
268 Error::HasSpace.to_string().as_str(),
269 "Command line string contains a space"
270 );
271 assert_eq!(
272 Error::HasEquals.to_string().as_str(),
273 "Command line string contains an equals sign"
274 );
275 assert_eq!(
276 Error::TooLarge.to_string().as_str(),
277 "Command line inserting string would make command line too long"
278 );
279 }
280}