tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }