/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! Argument string parsing and matching functions for Firefox.
//!
//! Which arguments Firefox accepts and in what style depends on the platform.
//! On Windows only, arguments can be prefixed with `/` (slash), such as
//! `/screenshot`. Elsewhere, including Windows, arguments may be prefixed
//! with both single (`-screenshot`) and double (`--screenshot`) dashes.
//!
//! An argument's name is determined by a space or an assignment operator (`=`)
//! so that for the string `-foo=bar`, `foo` is considered the argument's
//! basename.
use crate::runner::platform;
use std::ffi::{OsStr, OsString};
use std::fmt;
/// Parse an argument string into a name and value
///
/// Given an argument like `"--arg=value"` this will split it into
/// `(Some("arg"), Some("value")). For a case like `"--arg"` it will
/// return `(Some("arg"), None)` and where the input doesn't look like
/// an argument e.g. `"value"` it will return `(None, Some("value"))`
fn parse_arg_name_value<T>(arg: T) -> (Option<String>, Option<String>)
where
T: AsRef<OsStr>,
{
let arg_os_str: &OsStr = arg.as_ref();
let arg_str = arg_os_str.to_string_lossy();
let mut name_start =
0;
let mut name_end =
0;
// Look for an argument name at the start of the
// string
for (i, c)
in arg_str.chars().enumerate() {
if i ==
0 {
if !platform::arg_prefix_char(c) {
break;
}
}
else if i ==
1 {
if name_end_char(c) {
break;
}
else if c !=
'-' {
name_start = i;
name_end = name_start +
1;
}
else {
name_start = i +
1;
name_end = name_start;
}
}
else {
name_end +=
1;
if name_end_char(c) {
name_end -=
1;
break;
}
}
}
let name =
if name_start >
0 && name_end > name_start {
Some(arg_str[name_start..name_end].into())
}
else {
None
};
// If there are characters in the string after the argument, read
// them as the value, excluding the seperator (e.g. "=") if
// present.
let mut value_start = name_end;
let value_end = arg_str.len();
let value =
if value_start < value_end {
if let Some(c) = arg_str[value_start..value_end].chars().next() {
if name_end_char(c) {
value_start +=
1;
}
}
Some(arg_str[value_start..value_end].into())
}
else {
None
};
(name, value)
}
fn name_end_char(c: char) -> bool {
c ==
' ' || c ==
'='
}
/// Represents a Firefox command-line argument.
#[derive(Debug, PartialEq)]
pub enum Arg {
/// `-foreground` ensures application window gets focus, which is not the
/// default on macOS. As such Firefox only supports it on MacOS.
Foreground,
/// --marionette enables Marionette in the application which is used
/// by WebDriver HTTP.
Marionette,
/// `-no-remote` prevents remote commands to this instance of Firefox, and
/// ensure we always start a new instance.
NoRemote,
/// `-P NAME` starts Firefox with a profile with a given name.
NamedProfile,
/// `-profile PATH` starts Firefox with the profile at the specified path.
Profile,
/// `-ProfileManager` starts Firefox with the profile chooser dialogue.
ProfileManager,
/// All other arguments.
Other(String),
/// --remote-allow-hosts contains comma-separated values of the Host header
/// to allow for incoming WebSocket requests of the Remote Agent.
RemoteAllowHosts,
/// --remote-allow-origins contains comma-separated values of the Origin header
/// to allow for incoming WebSocket requests of the Remote Agent.
RemoteAllowOrigins,
/// --remote-debugging-port enables the Remote Agent in the application
/// which is used for the WebDriver BiDi and CDP remote debugging protocols.
RemoteDebuggingPort,
/// Not an argument.
None,
}
impl Arg {
pub fn new(name: &str) -> Arg {
match name {
"foreground" => Arg::Foreground,
"marionette" => Arg::Marionette,
"no-remote" => Arg::NoRemote,
"profile" => Arg::Profile,
"P" => Arg::NamedProfile,
"ProfileManager" => Arg::ProfileManager,
"remote-allow-hosts" => Arg::RemoteAllowHosts,
"remote-allow-origins" => Arg::RemoteAllowOrigins,
"remote-debugging-port" => Arg::RemoteDebuggingPort,
_ => Arg::Other(name.into()),
}
}
}
impl<
'a> From<&'a OsString>
for Arg {
fn from(arg_str: &OsString) -> Arg {
if let (Some(name), _) = parse_arg_name_value(arg_str) {
Arg::new(&name)
}
else {
Arg::None
}
}
}
impl fmt::Display
for Arg {
fn fmt(&
self, f: &
mut fmt::Formatter<
'_>) -> fmt::Result {
f.write_str(&
match self {
Arg::Foreground =>
"--foreground".to_string(),
Arg::Marionette =>
"--marionette".to_string(),
Arg::NamedProfile =>
"-P".to_string(),
Arg::None =>
"".to_string(),
Arg::NoRemote =>
"--no-remote".to_string(),
Arg::Other(x) => format!(
"--{}", x),
Arg::Profile =>
"--profile".to_string(),
Arg::ProfileManager =>
"--ProfileManager".to_string(),
Arg::RemoteAllowHosts =>
"--remote-allow-hosts".to_string(),
Arg::RemoteAllowOrigins =>
"--remote-allow-origins".to_string(),
Arg::RemoteDebuggingPort =>
"--remote-debugging-port".to_string(),
})
}
}
/// Parse an iterator over arguments into an vector of (name, value)
/// tuples
///
/// Each entry in the input argument will produce a single item in the
/// output. Because we don't know anything about the specific
/// arguments, something that doesn't parse as a named argument may
/// either be the value of a previous named argument, or may be a
/// positional argument.
pub fn parse_args<
'a>(
args:
impl Iterator<Item = &
'a OsString>,
) -> Vec<(Option<Arg>, Option<String>)> {
args.map(parse_arg_name_value)
.map(|(name, value)| {
if let Some(arg_name) = name {
(Some(Arg::new(&arg_name)), value)
}
else {
(None, value)
}
})
.collect()
}
/// Given an iterator over all arguments, get the value of an argument
///
/// This assumes that the argument takes a single value and that is
/// either provided as a single argument entry
/// (e.g. `["--name=value"]`) or as the following argument
/// (e.g. `["--name", "value"])
pub fn get_arg_value<
'a>(
mut parsed_args:
impl Iterator<Item = &
'a (Option<Arg>, Option<String>)>,
arg: Arg,
) -> Option<String> {
let mut found_value = None;
for (arg_name, arg_value)
in &
mut parsed_args {
if let (Some(name), value) = (arg_name, arg_value) {
if *name == arg {
found_value = value.clone();
break;
}
}
}
if found_value.is_none() {
// If there wasn't a value, check if the following argument is a value
if let Some((None, value)) = parsed_args.next() {
found_value = value.clone();
}
}
found_value
}
#[cfg(test)]
mod tests {
use super::{get_arg_value, parse_arg_name_value, parse_args, Arg};
use std::ffi::OsString;
fn parse(arg: &str, name: Option<&str>) {
let (result, _) = parse_arg_name_value(arg);
assert_eq!(result, name.map(|x| x.to_string()));
}
#[test]
fn test_parse_arg_name_value() {
parse(
"-p", Some(
"p"));
parse(
"--p", Some(
"p"));
parse(
"--profile foo", Some(
"profile"));
parse(
"--profile", Some(
"profile"));
parse(
"--", None);
parse(
"", None);
parse(
"-=", None);
parse(
"--=", None);
parse(
"-- foo", None);
parse(
"foo", None);
parse(
"/ foo", None);
parse(
"/- foo", None);
parse(
"/=foo", None);
parse(
"foo", None);
parse(
"-profile", Some(
"profile"));
parse(
"-profile=foo", Some(
"profile"));
parse(
"-profile = foo", Some(
"profile"));
parse(
"-profile abc", Some(
"profile"));
parse(
"-profile /foo", Some(
"profile"));
}
#[cfg(target_os =
"windows")]
#[test]
fn test_parse_arg_name_value_windows() {
parse(
"/profile", Some(
"profile"));
}
#[cfg(not(target_os =
"windows"))]
#[test]
fn test_parse_arg_name_value_non_windows() {
parse(
"/profile", None);
}
#[test]
fn test_arg_from_osstring() {
assert_eq!(Arg::from(&OsString::from(
"--foreground")), Arg::Foreground);
assert_eq!(Arg::from(&OsString::from(
"-foreground")), Arg::Foreground);
assert_eq!(Arg::from(&OsString::from(
"--marionette")), Arg::Marionette);
assert_eq!(Arg::from(&OsString::from(
"-marionette")), Arg::Marionette);
assert_eq!(Arg::from(&OsString::from(
"--no-remote")), Arg::NoRemote);
assert_eq!(Arg::from(&OsString::from(
"-no-remote")), Arg::NoRemote);
assert_eq!(Arg::from(&OsString::from(
"-- profile")), Arg::None);
assert_eq!(Arg::from(&OsString::from(
"profile")), Arg::None);
assert_eq!(Arg::from(&OsString::from(
"profile -P")), Arg::None);
assert_eq!(
Arg::from(&OsString::from(
"-profiled")),
Arg::Other(
"profiled".into())
);
assert_eq!(
Arg::from(&OsString::from(
"-PROFILEMANAGER")),
Arg::Other(
"PROFILEMANAGER".into())
);
assert_eq!(Arg::from(&OsString::from(
"--profile")), Arg::Profile);
assert_eq!(Arg::from(&OsString::from(
"-profile foo")), Arg::Profile);
assert_eq!(
Arg::from(&OsString::from(
"--ProfileManager")),
Arg::ProfileManager
);
assert_eq!(
Arg::from(&OsString::from(
"-ProfileManager")),
Arg::ProfileManager
);
// TODO: -Ptest is valid
//assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile);
assert_eq!(Arg::from(&OsString::from(
"-P")), Arg::NamedProfile);
assert_eq!(Arg::from(&OsString::from(
"-P test")), Arg::NamedProfile);
assert_eq!(
Arg::from(&OsString::from(
"--remote-debugging-port")),
Arg::RemoteDebuggingPort
);
assert_eq!(
Arg::from(&OsString::from(
"-remote-debugging-port")),
Arg::RemoteDebuggingPort
);
assert_eq!(
Arg::from(&OsString::from(
"--remote-debugging-port 9222")),
Arg::RemoteDebuggingPort
);
assert_eq!(
Arg::from(&OsString::from(
"--remote-allow-hosts")),
Arg::RemoteAllowHosts
);
assert_eq!(
Arg::from(&OsString::from(
"-remote-allow-hosts")),
Arg::RemoteAllowHosts
);
assert_eq!(
Arg::from(&OsString::from(
"--remote-allow-hosts 9222")),
Arg::RemoteAllowHosts
);
assert_eq!(
Arg::from(&OsString::from(
"--remote-allow-origins")),
Arg::RemoteAllowOrigins
);
assert_eq!(
Arg::from(&OsString::from(
"-remote-allow-origins")),
Arg::RemoteAllowOrigins
);
assert_eq!(
Arg::from(&OsString::from(
"--remote-allow-origins http://foo")),
Arg::RemoteAllowOrigins
);
}
#[test]
fn test_get_arg_value() {
let args = vec![
"-P",
"ProfileName",
"--profile=/path/",
"--no-remote"]
.iter()
.map(|x| OsString::from(x))
.collect::<Vec<OsString>>();
let parsed_args = parse_args(args.iter());
assert_eq!(
get_arg_value(parsed_args.iter(), Arg::NamedProfile),
Some(
"ProfileName".into())
);
assert_eq!(
get_arg_value(parsed_args.iter(), Arg::Profile),
Some(
"/path/".into())
);
assert_eq!(get_arg_value(parsed_args.iter(), Arg::NoRemote), None);
let args = vec![
"--profile=",
"-P test"]
.iter()
.map(|x| OsString::from(x))
.collect::<Vec<OsString>>();
let parsed_args = parse_args(args.iter());
assert_eq!(
get_arg_value(parsed_args.iter(), Arg::NamedProfile),
Some(
"test".into())
);
assert_eq!(
get_arg_value(parsed_args.iter(), Arg::Profile),
Some(
"".into())
);
}
}