1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 //! Argument string parsing and matching functions for Firefox.
6 //!
7 //! Which arguments Firefox accepts and in what style depends on the platform.
8 //! On Windows only, arguments can be prefixed with `/` (slash), such as
9 //! `/foreground`. Elsewhere, including Windows, arguments may be prefixed
10 //! with both single (`-foreground`) and double (`--foreground`) dashes.
11 //!
12 //! An argument's name is determined by a space or an assignment operator (`=`)
13 //! so that for the string `-foo=bar`, `foo` is considered the argument's
14 //! basename.
15
16 use std::ffi::{OsStr, OsString};
17
18 use crate::runner::platform;
19
20 /// Parse an argument string into a name and value
21 ///
22 /// Given an argument like `"--arg=value"` this will split it into
23 /// `(Some("arg"), Some("value")). For a case like `"--arg"` it will
24 /// return `(Some("arg"), None)` and where the input doesn't look like
25 /// an argument e.g. `"value"` it will return `(None, Some("value"))`
parse_arg_name_value<T>(arg: T) -> (Option<String>, Option<String>) where T: AsRef<OsStr>,26 fn parse_arg_name_value<T>(arg: T) -> (Option<String>, Option<String>)
27 where
28 T: AsRef<OsStr>,
29 {
30 let arg_os_str: &OsStr = arg.as_ref();
31 let arg_str = arg_os_str.to_string_lossy();
32
33 let mut name_start = 0;
34 let mut name_end = 0;
35
36 // Look for an argument name at the start of the
37 // string
38 for (i, c) in arg_str.chars().enumerate() {
39 if i == 0 {
40 if !platform::arg_prefix_char(c) {
41 break;
42 }
43 } else if i == 1 {
44 if name_end_char(c) {
45 break;
46 } else if c != '-' {
47 name_start = i;
48 name_end = name_start + 1;
49 } else {
50 name_start = i + 1;
51 name_end = name_start;
52 }
53 } else {
54 name_end += 1;
55 if name_end_char(c) {
56 name_end -= 1;
57 break;
58 }
59 }
60 }
61
62 let name = if name_start > 0 && name_end > name_start {
63 Some(arg_str[name_start..name_end].into())
64 } else {
65 None
66 };
67
68 // If there are characters in the string after the argument, read
69 // them as the value, excluding the seperator (e.g. "=") if
70 // present.
71 let mut value_start = name_end;
72 let value_end = arg_str.len();
73 let value = if value_start < value_end {
74 if let Some(c) = arg_str[value_start..value_end].chars().next() {
75 if name_end_char(c) {
76 value_start += 1;
77 }
78 }
79 Some(arg_str[value_start..value_end].into())
80 } else {
81 None
82 };
83 (name, value)
84 }
85
name_end_char(c: char) -> bool86 fn name_end_char(c: char) -> bool {
87 c == ' ' || c == '='
88 }
89
90 /// Represents a Firefox command-line argument.
91 #[derive(Debug, PartialEq)]
92 pub enum Arg {
93 /// `-foreground` ensures application window gets focus, which is not the
94 /// default on macOS.
95 Foreground,
96
97 /// `-no-remote` prevents remote commands to this instance of Firefox, and
98 /// ensure we always start a new instance.
99 NoRemote,
100
101 /// `-P NAME` starts Firefox with a profile with a given name.
102 NamedProfile,
103
104 /// `-profile PATH` starts Firefox with the profile at the specified path.
105 Profile,
106
107 /// `-ProfileManager` starts Firefox with the profile chooser dialogue.
108 ProfileManager,
109
110 /// All other arguments.
111 Other(String),
112
113 /// Not an argument.
114 None,
115 }
116
117 impl Arg {
new(name: &str) -> Arg118 fn new(name: &str) -> Arg {
119 match &*name {
120 "profile" => Arg::Profile,
121 "P" => Arg::NamedProfile,
122 "ProfileManager" => Arg::ProfileManager,
123 "foreground" => Arg::Foreground,
124 "no-remote" => Arg::NoRemote,
125 _ => Arg::Other(name.into()),
126 }
127 }
128 }
129
130 impl<'a> From<&'a OsString> for Arg {
from(arg_str: &OsString) -> Arg131 fn from(arg_str: &OsString) -> Arg {
132 if let (Some(name), _) = parse_arg_name_value(arg_str) {
133 Arg::new(&name)
134 } else {
135 Arg::None
136 }
137 }
138 }
139
140 /// Parse an iterator over arguments into an vector of (name, value)
141 /// tuples
142 ///
143 /// Each entry in the input argument will produce a single item in the
144 /// output. Because we don't know anything about the specific
145 /// arguments, something that doesn't parse as a named argument may
146 /// either be the value of a previous named argument, or may be a
147 /// positional argument.
parse_args<'a>( args: impl Iterator<Item = &'a OsString>, ) -> Vec<(Option<Arg>, Option<String>)>148 pub fn parse_args<'a>(
149 args: impl Iterator<Item = &'a OsString>,
150 ) -> Vec<(Option<Arg>, Option<String>)> {
151 args.map(parse_arg_name_value)
152 .map(|(name, value)| {
153 if let Some(arg_name) = name {
154 (Some(Arg::new(&arg_name)), value)
155 } else {
156 (None, value)
157 }
158 })
159 .collect()
160 }
161
162 /// Given an iterator over all arguments, get the value of an argument
163 ///
164 /// This assumes that the argument takes a single value and that is
165 /// either provided as a single argument entry
166 /// (e.g. `["--name=value"]`) or as the following argument
167 /// (e.g. `["--name", "value"])
get_arg_value<'a>( mut parsed_args: impl Iterator<Item = &'a (Option<Arg>, Option<String>)>, arg: Arg, ) -> Option<String>168 pub fn get_arg_value<'a>(
169 mut parsed_args: impl Iterator<Item = &'a (Option<Arg>, Option<String>)>,
170 arg: Arg,
171 ) -> Option<String> {
172 let mut found_value = None;
173 for (arg_name, arg_value) in &mut parsed_args {
174 if let (Some(name), value) = (arg_name, arg_value) {
175 if *name == arg {
176 found_value = value.clone();
177 break;
178 }
179 }
180 }
181 if found_value.is_none() {
182 // If there wasn't a value, check if the following argument is a value
183 if let Some((None, value)) = parsed_args.next() {
184 found_value = value.clone();
185 }
186 }
187 found_value
188 }
189
190 #[cfg(test)]
191 mod tests {
192 use super::{get_arg_value, parse_arg_name_value, parse_args, Arg};
193 use std::ffi::OsString;
194
parse(arg: &str, name: Option<&str>)195 fn parse(arg: &str, name: Option<&str>) {
196 let (result, _) = parse_arg_name_value(arg);
197 assert_eq!(result, name.map(|x| x.to_string()));
198 }
199
200 #[test]
test_parse_arg_name_value()201 fn test_parse_arg_name_value() {
202 parse("-p", Some("p"));
203 parse("--p", Some("p"));
204 parse("--profile foo", Some("profile"));
205 parse("--profile", Some("profile"));
206 parse("--", None);
207 parse("", None);
208 parse("-=", None);
209 parse("--=", None);
210 parse("-- foo", None);
211 parse("foo", None);
212 parse("/ foo", None);
213 parse("/- foo", None);
214 parse("/=foo", None);
215 parse("foo", None);
216 parse("-profile", Some("profile"));
217 parse("-profile=foo", Some("profile"));
218 parse("-profile = foo", Some("profile"));
219 parse("-profile abc", Some("profile"));
220 parse("-profile /foo", Some("profile"));
221 }
222
223 #[cfg(target_os = "windows")]
224 #[test]
test_parse_arg_name_value_windows()225 fn test_parse_arg_name_value_windows() {
226 parse("/profile", Some("profile"));
227 }
228
229 #[cfg(not(target_os = "windows"))]
230 #[test]
test_parse_arg_name_value_non_windows()231 fn test_parse_arg_name_value_non_windows() {
232 parse("/profile", None);
233 }
234
235 #[test]
test_arg_from_osstring()236 fn test_arg_from_osstring() {
237 assert_eq!(Arg::from(&OsString::from("-- profile")), Arg::None);
238 assert_eq!(Arg::from(&OsString::from("profile")), Arg::None);
239 assert_eq!(Arg::from(&OsString::from("profile -P")), Arg::None);
240 assert_eq!(
241 Arg::from(&OsString::from("-profiled")),
242 Arg::Other("profiled".into())
243 );
244 assert_eq!(
245 Arg::from(&OsString::from("-PROFILEMANAGER")),
246 Arg::Other("PROFILEMANAGER".into())
247 );
248
249 assert_eq!(Arg::from(&OsString::from("--profile")), Arg::Profile);
250 assert_eq!(Arg::from(&OsString::from("-profile foo")), Arg::Profile);
251
252 assert_eq!(
253 Arg::from(&OsString::from("--ProfileManager")),
254 Arg::ProfileManager
255 );
256 assert_eq!(
257 Arg::from(&OsString::from("-ProfileManager")),
258 Arg::ProfileManager
259 );
260
261 // TODO: -Ptest is valid
262 //assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile);
263 assert_eq!(Arg::from(&OsString::from("-P")), Arg::NamedProfile);
264 assert_eq!(Arg::from(&OsString::from("-P test")), Arg::NamedProfile);
265
266 assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground);
267 assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground);
268
269 assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote);
270 assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote);
271 }
272
273 #[test]
test_get_arg_value()274 fn test_get_arg_value() {
275 let args = vec!["-P", "ProfileName", "--profile=/path/", "--no-remote"]
276 .iter()
277 .map(|x| OsString::from(x))
278 .collect::<Vec<OsString>>();
279 let parsed_args = parse_args(args.iter());
280 assert_eq!(
281 get_arg_value(parsed_args.iter(), Arg::NamedProfile),
282 Some("ProfileName".into())
283 );
284 assert_eq!(
285 get_arg_value(parsed_args.iter(), Arg::Profile),
286 Some("/path/".into())
287 );
288 assert_eq!(get_arg_value(parsed_args.iter(), Arg::NoRemote), None);
289
290 let args = vec!["--profile=", "-P test"]
291 .iter()
292 .map(|x| OsString::from(x))
293 .collect::<Vec<OsString>>();
294 let parsed_args = parse_args(args.iter());
295 assert_eq!(
296 get_arg_value(parsed_args.iter(), Arg::NamedProfile),
297 Some("test".into())
298 );
299 assert_eq!(
300 get_arg_value(parsed_args.iter(), Arg::Profile),
301 Some("".into())
302 );
303 }
304 }
305