1 //! Argument string parsing and matching functions for Firefox.
2 //!
3 //! Which arguments Firefox accepts and in what style depends on the platform.
4 //! On Windows only, arguments can be prefixed with `/` (slash), such as
5 //! `/foreground`.  Elsewhere, including Windows, arguments may be prefixed
6 //! with both single (`-foreground`) and double (`--foreground`) dashes.
7 //!
8 //! An argument's name is determined by a space or an assignment operator (`=`)
9 //! so that for the string `-foo=bar`, `foo` is considered the argument's
10 //! basename.
11 
12 use std::ffi::{OsStr, OsString};
13 
14 use crate::runner::platform;
15 
parse_arg_name<T>(arg: T) -> Option<String> where T: AsRef<OsStr>,16 fn parse_arg_name<T>(arg: T) -> Option<String>
17 where
18     T: AsRef<OsStr>,
19 {
20     let arg_os_str: &OsStr = arg.as_ref();
21     let arg_str = arg_os_str.to_string_lossy();
22 
23     let mut start = 0;
24     let mut end = 0;
25 
26     for (i, c) in arg_str.chars().enumerate() {
27         if i == 0 {
28             if !platform::arg_prefix_char(c) {
29                 break;
30             }
31         } else if i == 1 {
32             if name_end_char(c) {
33                 break;
34             } else if c != '-' {
35                 start = i;
36                 end = start + 1;
37             } else {
38                 start = i + 1;
39                 end = start;
40             }
41         } else {
42             end += 1;
43             if name_end_char(c) {
44                 end -= 1;
45                 break;
46             }
47         }
48     }
49 
50     if start > 0 && end > start {
51         Some(arg_str[start..end].into())
52     } else {
53         None
54     }
55 }
56 
name_end_char(c: char) -> bool57 fn name_end_char(c: char) -> bool {
58     c == ' ' || c == '='
59 }
60 
61 /// Represents a Firefox command-line argument.
62 #[derive(Debug, PartialEq)]
63 pub enum Arg {
64     /// `-foreground` ensures application window gets focus, which is not the
65     /// default on macOS.
66     Foreground,
67 
68     /// `-no-remote` prevents remote commands to this instance of Firefox, and
69     /// ensure we always start a new instance.
70     NoRemote,
71 
72     /// `-P NAME` starts Firefox with a profile with a given name.
73     NamedProfile,
74 
75     /// `-profile PATH` starts Firefox with the profile at the specified path.
76     Profile,
77 
78     /// `-ProfileManager` starts Firefox with the profile chooser dialogue.
79     ProfileManager,
80 
81     /// All other arguments.
82     Other(String),
83 
84     /// Not an argument.
85     None,
86 }
87 
88 impl<'a> From<&'a OsString> for Arg {
from(arg_str: &OsString) -> Arg89     fn from(arg_str: &OsString) -> Arg {
90         if let Some(basename) = parse_arg_name(arg_str) {
91             match &*basename {
92                 "profile" => Arg::Profile,
93                 "P" => Arg::NamedProfile,
94                 "ProfileManager" => Arg::ProfileManager,
95                 "foreground" => Arg::Foreground,
96                 "no-remote" => Arg::NoRemote,
97                 _ => Arg::Other(basename),
98             }
99         } else {
100             Arg::None
101         }
102     }
103 }
104 
105 #[cfg(test)]
106 mod tests {
107     use super::{parse_arg_name, Arg};
108     use std::ffi::OsString;
109 
parse(arg: &str, name: Option<&str>)110     fn parse(arg: &str, name: Option<&str>) {
111         let result = parse_arg_name(arg);
112         assert_eq!(result, name.map(|x| x.to_string()));
113     }
114 
115     #[test]
test_parse_arg_name()116     fn test_parse_arg_name() {
117         parse("-p", Some("p"));
118         parse("--p", Some("p"));
119         parse("--profile foo", Some("profile"));
120         parse("--profile", Some("profile"));
121         parse("--", None);
122         parse("", None);
123         parse("-=", None);
124         parse("--=", None);
125         parse("-- foo", None);
126         parse("foo", None);
127         parse("/ foo", None);
128         parse("/- foo", None);
129         parse("/=foo", None);
130         parse("foo", None);
131         parse("-profile", Some("profile"));
132         parse("-profile=foo", Some("profile"));
133         parse("-profile = foo", Some("profile"));
134         parse("-profile abc", Some("profile"));
135         parse("-profile /foo", Some("profile"));
136     }
137 
138     #[cfg(target_os = "windows")]
139     #[test]
test_parse_arg_name_windows()140     fn test_parse_arg_name_windows() {
141         parse("/profile", Some("profile"));
142     }
143 
144     #[cfg(not(target_os = "windows"))]
145     #[test]
test_parse_arg_name_non_windows()146     fn test_parse_arg_name_non_windows() {
147         parse("/profile", None);
148     }
149 
150     #[test]
test_arg_from_osstring()151     fn test_arg_from_osstring() {
152         assert_eq!(Arg::from(&OsString::from("-- profile")), Arg::None);
153         assert_eq!(Arg::from(&OsString::from("profile")), Arg::None);
154         assert_eq!(Arg::from(&OsString::from("profile -P")), Arg::None);
155         assert_eq!(
156             Arg::from(&OsString::from("-profiled")),
157             Arg::Other("profiled".into())
158         );
159         assert_eq!(
160             Arg::from(&OsString::from("-PROFILEMANAGER")),
161             Arg::Other("PROFILEMANAGER".into())
162         );
163 
164         assert_eq!(Arg::from(&OsString::from("--profile")), Arg::Profile);
165         assert_eq!(Arg::from(&OsString::from("-profile foo")), Arg::Profile);
166 
167         assert_eq!(
168             Arg::from(&OsString::from("--ProfileManager")),
169             Arg::ProfileManager
170         );
171         assert_eq!(
172             Arg::from(&OsString::from("-ProfileManager")),
173             Arg::ProfileManager
174         );
175 
176         // TODO: -Ptest is valid
177         //assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile);
178         assert_eq!(Arg::from(&OsString::from("-P")), Arg::NamedProfile);
179         assert_eq!(Arg::from(&OsString::from("-P test")), Arg::NamedProfile);
180 
181         assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground);
182         assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground);
183 
184         assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote);
185         assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote);
186     }
187 }
188