firefox_args.rs (12809B)
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 //! `/screenshot`. Elsewhere, including Windows, arguments may be prefixed 10 //! with both single (`-screenshot`) and double (`--screenshot`) 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 crate::runner::platform; 17 use std::ffi::{OsStr, OsString}; 18 use std::fmt; 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"))` 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 86 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. As such Firefox only supports it on MacOS. 95 Foreground, 96 97 /// --marionette enables Marionette in the application which is used 98 /// by WebDriver HTTP. 99 Marionette, 100 101 /// `-no-remote` prevents remote commands to this instance of Firefox, and 102 /// ensure we always start a new instance. 103 NoRemote, 104 105 /// `-P NAME` starts Firefox with a profile with a given name. 106 NamedProfile, 107 108 /// `-profile PATH` starts Firefox with the profile at the specified path. 109 Profile, 110 111 /// `-ProfileManager` starts Firefox with the profile chooser dialogue. 112 ProfileManager, 113 114 /// All other arguments. 115 Other(String), 116 117 /// --remote-allow-hosts contains comma-separated values of the Host header 118 /// to allow for incoming WebSocket requests of the Remote Agent. 119 RemoteAllowHosts, 120 121 /// --remote-allow-origins contains comma-separated values of the Origin header 122 /// to allow for incoming WebSocket requests of the Remote Agent. 123 RemoteAllowOrigins, 124 125 /// --remote-debugging-port enables the Remote Agent in the application 126 /// which is used for the WebDriver BiDi and CDP remote debugging protocols. 127 RemoteDebuggingPort, 128 129 /// Not an argument. 130 None, 131 } 132 133 impl Arg { 134 pub fn new(name: &str) -> Arg { 135 match name { 136 "foreground" => Arg::Foreground, 137 "marionette" => Arg::Marionette, 138 "no-remote" => Arg::NoRemote, 139 "profile" => Arg::Profile, 140 "P" => Arg::NamedProfile, 141 "ProfileManager" => Arg::ProfileManager, 142 "remote-allow-hosts" => Arg::RemoteAllowHosts, 143 "remote-allow-origins" => Arg::RemoteAllowOrigins, 144 "remote-debugging-port" => Arg::RemoteDebuggingPort, 145 _ => Arg::Other(name.into()), 146 } 147 } 148 } 149 150 impl From<&OsString> for Arg { 151 fn from(arg_str: &OsString) -> Arg { 152 if let (Some(name), _) = parse_arg_name_value(arg_str) { 153 Arg::new(&name) 154 } else { 155 Arg::None 156 } 157 } 158 } 159 160 impl fmt::Display for Arg { 161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 162 f.write_str(&match self { 163 Arg::Foreground => "--foreground".to_string(), 164 Arg::Marionette => "--marionette".to_string(), 165 Arg::NamedProfile => "-P".to_string(), 166 Arg::None => "".to_string(), 167 Arg::NoRemote => "--no-remote".to_string(), 168 Arg::Other(x) => format!("--{}", x), 169 Arg::Profile => "--profile".to_string(), 170 Arg::ProfileManager => "--ProfileManager".to_string(), 171 Arg::RemoteAllowHosts => "--remote-allow-hosts".to_string(), 172 Arg::RemoteAllowOrigins => "--remote-allow-origins".to_string(), 173 Arg::RemoteDebuggingPort => "--remote-debugging-port".to_string(), 174 }) 175 } 176 } 177 178 /// Parse an iterator over arguments into an vector of (name, value) 179 /// tuples 180 /// 181 /// Each entry in the input argument will produce a single item in the 182 /// output. Because we don't know anything about the specific 183 /// arguments, something that doesn't parse as a named argument may 184 /// either be the value of a previous named argument, or may be a 185 /// positional argument. 186 pub fn parse_args<'a>( 187 args: impl Iterator<Item = &'a OsString>, 188 ) -> Vec<(Option<Arg>, Option<String>)> { 189 args.map(parse_arg_name_value) 190 .map(|(name, value)| { 191 if let Some(arg_name) = name { 192 (Some(Arg::new(&arg_name)), value) 193 } else { 194 (None, value) 195 } 196 }) 197 .collect() 198 } 199 200 /// Given an iterator over all arguments, get the value of an argument 201 /// 202 /// This assumes that the argument takes a single value and that is 203 /// either provided as a single argument entry 204 /// (e.g. `["--name=value"]`) or as the following argument 205 /// (e.g. `["--name", "value"]) 206 pub fn get_arg_value<'a>( 207 mut parsed_args: impl Iterator<Item = &'a (Option<Arg>, Option<String>)>, 208 arg: Arg, 209 ) -> Option<String> { 210 let mut found_value = None; 211 for (arg_name, arg_value) in &mut parsed_args { 212 if let (Some(name), value) = (arg_name, arg_value) { 213 if *name == arg { 214 found_value = value.clone(); 215 break; 216 } 217 } 218 } 219 if found_value.is_none() { 220 // If there wasn't a value, check if the following argument is a value 221 if let Some((None, value)) = parsed_args.next() { 222 found_value = value.clone(); 223 } 224 } 225 found_value 226 } 227 228 #[cfg(test)] 229 mod tests { 230 use super::{get_arg_value, parse_arg_name_value, parse_args, Arg}; 231 use std::ffi::OsString; 232 233 fn parse(arg: &str, name: Option<&str>) { 234 let (result, _) = parse_arg_name_value(arg); 235 assert_eq!(result, name.map(|x| x.to_string())); 236 } 237 238 #[test] 239 fn test_parse_arg_name_value() { 240 parse("-p", Some("p")); 241 parse("--p", Some("p")); 242 parse("--profile foo", Some("profile")); 243 parse("--profile", Some("profile")); 244 parse("--", None); 245 parse("", None); 246 parse("-=", None); 247 parse("--=", None); 248 parse("-- foo", None); 249 parse("foo", None); 250 parse("/ foo", None); 251 parse("/- foo", None); 252 parse("/=foo", None); 253 parse("foo", None); 254 parse("-profile", Some("profile")); 255 parse("-profile=foo", Some("profile")); 256 parse("-profile = foo", Some("profile")); 257 parse("-profile abc", Some("profile")); 258 parse("-profile /foo", Some("profile")); 259 } 260 261 #[cfg(target_os = "windows")] 262 #[test] 263 fn test_parse_arg_name_value_windows() { 264 parse("/profile", Some("profile")); 265 } 266 267 #[cfg(not(target_os = "windows"))] 268 #[test] 269 fn test_parse_arg_name_value_non_windows() { 270 parse("/profile", None); 271 } 272 273 #[test] 274 fn test_arg_from_osstring() { 275 assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground); 276 assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground); 277 278 assert_eq!(Arg::from(&OsString::from("--marionette")), Arg::Marionette); 279 assert_eq!(Arg::from(&OsString::from("-marionette")), Arg::Marionette); 280 281 assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote); 282 assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote); 283 284 assert_eq!(Arg::from(&OsString::from("-- profile")), Arg::None); 285 assert_eq!(Arg::from(&OsString::from("profile")), Arg::None); 286 assert_eq!(Arg::from(&OsString::from("profile -P")), Arg::None); 287 assert_eq!( 288 Arg::from(&OsString::from("-profiled")), 289 Arg::Other("profiled".into()) 290 ); 291 assert_eq!( 292 Arg::from(&OsString::from("-PROFILEMANAGER")), 293 Arg::Other("PROFILEMANAGER".into()) 294 ); 295 296 assert_eq!(Arg::from(&OsString::from("--profile")), Arg::Profile); 297 assert_eq!(Arg::from(&OsString::from("-profile foo")), Arg::Profile); 298 299 assert_eq!( 300 Arg::from(&OsString::from("--ProfileManager")), 301 Arg::ProfileManager 302 ); 303 assert_eq!( 304 Arg::from(&OsString::from("-ProfileManager")), 305 Arg::ProfileManager 306 ); 307 308 // TODO: -Ptest is valid 309 //assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile); 310 assert_eq!(Arg::from(&OsString::from("-P")), Arg::NamedProfile); 311 assert_eq!(Arg::from(&OsString::from("-P test")), Arg::NamedProfile); 312 313 assert_eq!( 314 Arg::from(&OsString::from("--remote-debugging-port")), 315 Arg::RemoteDebuggingPort 316 ); 317 assert_eq!( 318 Arg::from(&OsString::from("-remote-debugging-port")), 319 Arg::RemoteDebuggingPort 320 ); 321 assert_eq!( 322 Arg::from(&OsString::from("--remote-debugging-port 9222")), 323 Arg::RemoteDebuggingPort 324 ); 325 326 assert_eq!( 327 Arg::from(&OsString::from("--remote-allow-hosts")), 328 Arg::RemoteAllowHosts 329 ); 330 assert_eq!( 331 Arg::from(&OsString::from("-remote-allow-hosts")), 332 Arg::RemoteAllowHosts 333 ); 334 assert_eq!( 335 Arg::from(&OsString::from("--remote-allow-hosts 9222")), 336 Arg::RemoteAllowHosts 337 ); 338 339 assert_eq!( 340 Arg::from(&OsString::from("--remote-allow-origins")), 341 Arg::RemoteAllowOrigins 342 ); 343 assert_eq!( 344 Arg::from(&OsString::from("-remote-allow-origins")), 345 Arg::RemoteAllowOrigins 346 ); 347 assert_eq!( 348 Arg::from(&OsString::from("--remote-allow-origins http://foo")), 349 Arg::RemoteAllowOrigins 350 ); 351 } 352 353 #[test] 354 fn test_get_arg_value() { 355 let args = vec!["-P", "ProfileName", "--profile=/path/", "--no-remote"] 356 .iter() 357 .map(|x| OsString::from(x)) 358 .collect::<Vec<OsString>>(); 359 let parsed_args = parse_args(args.iter()); 360 assert_eq!( 361 get_arg_value(parsed_args.iter(), Arg::NamedProfile), 362 Some("ProfileName".into()) 363 ); 364 assert_eq!( 365 get_arg_value(parsed_args.iter(), Arg::Profile), 366 Some("/path/".into()) 367 ); 368 assert_eq!(get_arg_value(parsed_args.iter(), Arg::NoRemote), None); 369 370 let args = vec!["--profile=", "-P test"] 371 .iter() 372 .map(|x| OsString::from(x)) 373 .collect::<Vec<OsString>>(); 374 let parsed_args = parse_args(args.iter()); 375 assert_eq!( 376 get_arg_value(parsed_args.iter(), Arg::NamedProfile), 377 Some("test".into()) 378 ); 379 assert_eq!( 380 get_arg_value(parsed_args.iter(), Arg::Profile), 381 Some("".into()) 382 ); 383 } 384 }