linux_cmdline/
lib.rs

1// cmdline.rs
2//
3// Copyright 2019 Alberto Ruiz <aruiz@gnome.org>
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8//
9// SPDX-License-Identifier: MPL-2.0
10
11#![cfg_attr(not(feature = "std"), no_std)]
12#![cfg(not(feature = "std"))]
13extern crate alloc;
14
15#[cfg(not(feature = "std"))]
16use alloc::string::String;
17#[cfg(not(feature = "std"))]
18use alloc::vec::Vec;
19
20/// Represents a set of values for a cmdline parameter
21pub type CmdlineValue = Vec<Option<String>>;
22/// Represents a parameter and all its values
23pub type CmdlineParam = (String, CmdlineValue);
24/// Represents a full or partial cmdline parameter set
25pub type Cmdline = Vec<CmdlineParam>;
26
27// Basic dictionary like methods for Cmdline
28trait VecOfParamsAsDict {
29    fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue>;
30    fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue>;
31    fn entry_or_insert<'a>(&'a mut self, key: String, insert: CmdlineValue)
32        -> &'a mut CmdlineValue;
33}
34
35impl VecOfParamsAsDict for Cmdline {
36    fn get<'a>(&'a mut self, key: &String) -> Option<&'a mut CmdlineValue> {
37        for item in self {
38            if &item.0 == key {
39                return Some(&mut item.1);
40            }
41        }
42        None
43    }
44
45    fn take_from_key(&mut self, key: &str) -> Option<CmdlineValue> {
46        match { self.iter().position(|x| &x.0 == key) } {
47            Some(pos) => Some(self.remove(pos).1),
48            None => None,
49        }
50    }
51
52    fn entry_or_insert<'a>(
53        &'a mut self,
54        key: String,
55        insert: CmdlineValue,
56    ) -> &'a mut CmdlineValue {
57        let pos = { self.iter().position(|(k, _)| k == &key) };
58
59        match pos {
60            Some(index) => &mut self[index].1,
61            None => {
62                self.push((key, insert));
63                let len = { self.len() };
64                &mut self[len - 1].1
65            }
66        }
67    }
68}
69
70/// Basic Cmdline content operations
71pub trait CmdlineContent {
72    /// Parses a UTF8 &str and returns a ```Cmdline``` object
73    ///
74    /// # Arguments
75    ///
76    /// - ```buffer: &str```: the cmdline string
77    ///
78    /// # Returns
79    ///
80    /// A ```Cmdline``` object that represents the contents of the input string
81    ///
82    /// # Errors
83    ///
84    /// Returns a (usize, &'static str) error pointing at the line position of the error as well as an error message
85
86    fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)>;
87    /// Renders a Cmdline object into a valid cmdline string
88    ///
89    /// # Returns
90    ///
91    /// A String that represents the ```Cmdline``` object
92    ///
93    /// # Errors
94    ///
95    /// Returns error if a parameter has bogus values
96    fn render(&self) -> Result<String, &'static str>;
97
98    /// Adds a parameter to a ```Cmdline```
99    ///
100    /// # Arguments
101    /// - ```key: String```: The parameter name
102    /// - ```value: Option<String>```: An optional value
103    fn add_param(&mut self, key: String, value: Option<String>);
104}
105
106impl CmdlineContent for Cmdline {
107    fn parse(buffer: &str) -> Result<Cmdline, (usize, &'static str)> {
108        #[derive(Debug)]
109        enum Scope {
110            InValueQuoted,
111            InValueUnquoted,
112            InKey,
113            InEqual,
114            InSpace,
115        }
116
117        let mut key = String::new();
118        let mut value = String::new();
119
120        let mut result = Cmdline::new();
121        let mut scope = Scope::InSpace;
122
123        let mut i: usize = 0;
124        for c in buffer.chars() {
125            match c {
126                ' ' => match scope {
127                    Scope::InValueQuoted => {
128                        value.push(c);
129                    }
130                    Scope::InValueUnquoted => {
131                        result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()));
132                        scope = Scope::InSpace;
133                    }
134                    Scope::InSpace => {}
135                    Scope::InEqual => {
136                        return Err((i, "empty parameter value"));
137                    }
138                    Scope::InKey => {
139                        result.add_param(key.drain(..).collect(), None);
140                    }
141                },
142                '"' => match scope {
143                    Scope::InValueQuoted => {
144                        scope = Scope::InValueUnquoted;
145                    }
146                    Scope::InEqual => {
147                        scope = Scope::InValueQuoted;
148                    }
149                    Scope::InKey => {
150                        return Err((i, "quote in parameter name"));
151                    }
152                    Scope::InValueUnquoted => {
153                        scope = Scope::InValueQuoted;
154                    }
155                    Scope::InSpace => {
156                        return Err((i, "quote after unquoted space"));
157                    }
158                },
159                '=' => match scope {
160                    Scope::InKey => {
161                        scope = Scope::InEqual;
162                    }
163                    Scope::InValueQuoted | Scope::InValueUnquoted => {
164                        value.push(c);
165                    }
166                    Scope::InEqual => {
167                        scope = Scope::InValueUnquoted;
168                        value.push(c)
169                    }
170                    Scope::InSpace => {
171                        return Err((i, "equals after space"));
172                    }
173                },
174                _ => match scope {
175                    Scope::InKey => {
176                        key.push(c);
177                    }
178                    Scope::InValueQuoted => {
179                        value.push(c);
180                    }
181                    Scope::InValueUnquoted => {
182                        value.push(c);
183                    }
184                    Scope::InSpace => {
185                        scope = Scope::InKey;
186                        key.push(c);
187                    }
188                    Scope::InEqual => {
189                        scope = Scope::InValueUnquoted;
190                        value.push(c);
191                    }
192                },
193            };
194            i += 1;
195        }
196
197        match scope {
198            Scope::InKey => {
199                result.add_param(key.drain(..).collect(), None);
200            }
201            Scope::InValueQuoted => {
202                return Err((i, "unclosed quote in parameter value"));
203            }
204            Scope::InValueUnquoted => {
205                result.add_param(key.drain(..).collect(), Some(value.drain(..).collect()))
206            }
207            Scope::InEqual => {
208                return Err((i, "empty parameter value"));
209            }
210            Scope::InSpace => {}
211        }
212
213        Ok(result)
214    }
215
216    fn add_param(&mut self, key: String, value: Option<String>) {
217        //FIXME: Prevent malformed
218        let vec = self.entry_or_insert(key, Vec::new());
219        vec.push(value);
220    }
221
222    fn render(&self) -> Result<String, &'static str> {
223        let mut render = String::new();
224        for (param, values) in self {
225            for value in values {
226                match value {
227                    Some(value) => {
228                        render.push_str(&param);
229                        render.push('=');
230                        if value.contains('"') {
231                            return Err("cannot escape quote character");
232                        }
233                        if value.contains(' ') {
234                            render.push('"');
235                            render.push_str(&value);
236                            render.push('"');
237                        } else {
238                            render.push_str(&value);
239                        }
240                    }
241                    _ => {
242                        render.push_str(&param);
243                    }
244                }
245                render.push(' ');
246            }
247        }
248        render.pop();
249
250        Ok(render)
251    }
252}
253
254#[cfg(test)]
255mod cmdline_tests {
256    use super::*;
257    #[cfg(not(feature = "std"))]
258    use alloc::vec;
259
260    #[test]
261    fn cmdline_parse_test() {
262        let test = "a=test";
263        assert_eq!(
264            Cmdline::parse(test),
265            Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
266        );
267
268        let test = "a=te\"s\"t";
269        assert_eq!(
270            Cmdline::parse(test),
271            Ok(vec![(String::from("a"), vec![Some(String::from("test"))])])
272        );
273
274        let test = "a b c";
275        assert_eq!(
276            Cmdline::parse(test),
277            Ok(vec![
278                (String::from("a"), vec![None]),
279                (String::from("b"), vec![None]),
280                (String::from("c"), vec![None])
281            ])
282        );
283
284        let test = "a=test a a=test2 c a=test3";
285        assert_eq!(
286            Cmdline::parse(test),
287            Ok(vec![
288                (
289                    String::from("a"),
290                    vec![
291                        Some(String::from("test")),
292                        None,
293                        Some(String::from("test2")),
294                        Some(String::from("test3"))
295                    ]
296                ),
297                (String::from("c"), vec![None])
298            ])
299        );
300
301        let test = "a=3 =asd";
302        assert!(Cmdline::parse(test).is_err());
303
304        let test = "a=3 b= ";
305        assert!(Cmdline::parse(test).is_err());
306
307        let test = "a=3 b=";
308        assert!(Cmdline::parse(test).is_err());
309
310        let test = "\"quoted param\"=should_error";
311        assert!(Cmdline::parse(test).is_err());
312
313        let test = "quot\"ed param\"=should_error";
314        assert!(Cmdline::parse(test).is_err());
315
316        let test = "arg1 \"quoted param\"=should_error";
317        assert!(Cmdline::parse(test).is_err());
318
319        let test = "param=\"unclosed quote";
320        assert!(Cmdline::parse(test).is_err());
321    }
322}