tor-browser

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

main.rs (15863B)


      1 #![forbid(unsafe_code)]
      2 
      3 extern crate chrono;
      4 #[macro_use]
      5 extern crate clap;
      6 #[macro_use]
      7 extern crate lazy_static;
      8 extern crate hyper;
      9 extern crate marionette as marionette_rs;
     10 extern crate mozdevice;
     11 extern crate mozprofile;
     12 extern crate mozrunner;
     13 extern crate mozversion;
     14 extern crate regex;
     15 extern crate serde;
     16 #[macro_use]
     17 extern crate serde_derive;
     18 extern crate serde_json;
     19 extern crate tempfile;
     20 extern crate url;
     21 extern crate uuid;
     22 extern crate webdriver;
     23 extern crate yaml_rust;
     24 extern crate zip;
     25 
     26 #[macro_use]
     27 extern crate log;
     28 
     29 use std::env;
     30 use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
     31 use std::path::PathBuf;
     32 use std::process::ExitCode;
     33 
     34 use std::str::FromStr;
     35 
     36 use clap::{Arg, ArgAction, Command};
     37 
     38 macro_rules! try_opt {
     39    ($expr:expr, $err_type:expr, $err_msg:expr) => {{
     40        match $expr {
     41            Some(x) => x,
     42            None => return Err(WebDriverError::new($err_type, $err_msg)),
     43        }
     44    }};
     45 }
     46 
     47 mod android;
     48 mod browser;
     49 mod build;
     50 mod capabilities;
     51 mod command;
     52 mod logging;
     53 mod marionette;
     54 mod prefs;
     55 
     56 #[cfg(test)]
     57 pub mod test;
     58 
     59 use crate::command::extension_routes;
     60 use crate::logging::Level;
     61 use crate::marionette::{MarionetteHandler, MarionetteSettings};
     62 use anyhow::{bail, Result as ProgramResult};
     63 use clap::ArgMatches;
     64 use mozdevice::AndroidStorageInput;
     65 use url::{Host, Url};
     66 
     67 const EXIT_USAGE: u8 = 64;
     68 const EXIT_UNAVAILABLE: u8 = 69;
     69 
     70 #[allow(clippy::large_enum_variant)]
     71 enum Operation {
     72    Help,
     73    Version,
     74    Server {
     75        log_level: Option<Level>,
     76        log_truncate: bool,
     77        address: SocketAddr,
     78        allow_hosts: Vec<Host>,
     79        allow_origins: Vec<Url>,
     80        settings: MarionetteSettings,
     81        deprecated_storage_arg: bool,
     82    },
     83 }
     84 
     85 /// Get a socket address from the provided host and port
     86 ///
     87 /// # Arguments
     88 /// * `webdriver_host` - The hostname on which the server will listen
     89 /// * `webdriver_port` - The port on which the server will listen
     90 ///
     91 /// When the host and port resolve to multiple addresses, prefer
     92 /// IPv4 addresses vs IPv6.
     93 fn server_address(webdriver_host: &str, webdriver_port: u16) -> ProgramResult<SocketAddr> {
     94    let mut socket_addrs = match format!("{}:{}", webdriver_host, webdriver_port).to_socket_addrs()
     95    {
     96        Ok(addrs) => addrs.collect::<Vec<_>>(),
     97        Err(e) => bail!("{}: {}:{}", e, webdriver_host, webdriver_port),
     98    };
     99    if socket_addrs.is_empty() {
    100        bail!(
    101            "Unable to resolve host: {}:{}",
    102            webdriver_host,
    103            webdriver_port
    104        )
    105    }
    106    // Prefer ipv4 address
    107    socket_addrs.sort_by(|a, b| {
    108        let a_val = i32::from(!a.ip().is_ipv4());
    109        let b_val = i32::from(!b.ip().is_ipv4());
    110        a_val.partial_cmp(&b_val).expect("Comparison failed")
    111    });
    112    Ok(socket_addrs.remove(0))
    113 }
    114 
    115 /// Parse a given string into a Host
    116 fn parse_hostname(webdriver_host: &str) -> Result<Host, url::ParseError> {
    117    let host_str = if let Ok(ip_addr) = IpAddr::from_str(webdriver_host) {
    118        // In this case we have an IP address as the host
    119        if ip_addr.is_ipv6() {
    120            // Convert to quoted form
    121            format!("[{}]", &webdriver_host)
    122        } else {
    123            webdriver_host.into()
    124        }
    125    } else {
    126        webdriver_host.into()
    127    };
    128 
    129    Host::parse(&host_str)
    130 }
    131 
    132 /// Get a list of default hostnames to allow
    133 ///
    134 /// This only covers domain names, not IP addresses, since IP adresses
    135 /// are always accepted.
    136 fn get_default_allowed_hosts(ip: IpAddr) -> Vec<Host> {
    137    let localhost_is_loopback = ("localhost".to_string(), 80)
    138        .to_socket_addrs()
    139        .map(|addr_iter| {
    140            addr_iter
    141                .map(|addr| addr.ip())
    142                .filter(|ip| ip.is_loopback())
    143        })
    144        .iter()
    145        .len()
    146        > 0;
    147    if ip.is_loopback() && localhost_is_loopback {
    148        vec![Host::parse("localhost").unwrap()]
    149    } else {
    150        vec![]
    151    }
    152 }
    153 
    154 fn get_allowed_hosts(host: Host, allow_hosts: Option<clap::parser::ValuesRef<Host>>) -> Vec<Host> {
    155    allow_hosts
    156        .map(|hosts| hosts.cloned().collect())
    157        .unwrap_or_else(|| match host {
    158            Host::Domain(_) => {
    159                vec![host.clone()]
    160            }
    161            Host::Ipv4(ip) => get_default_allowed_hosts(IpAddr::V4(ip)),
    162            Host::Ipv6(ip) => get_default_allowed_hosts(IpAddr::V6(ip)),
    163        })
    164 }
    165 
    166 fn get_allowed_origins(allow_origins: Option<clap::parser::ValuesRef<Url>>) -> Vec<Url> {
    167    allow_origins.into_iter().flatten().cloned().collect()
    168 }
    169 
    170 fn parse_args(args: &ArgMatches) -> ProgramResult<Operation> {
    171    if args.get_flag("help") {
    172        return Ok(Operation::Help);
    173    } else if args.get_flag("version") {
    174        return Ok(Operation::Version);
    175    }
    176 
    177    let log_level = if let Some(log_level) = args.get_one::<String>("log_level") {
    178        Level::from_str(log_level).ok()
    179    } else {
    180        Some(match args.get_count("verbosity") {
    181            0 => Level::Info,
    182            1 => Level::Debug,
    183            _ => Level::Trace,
    184        })
    185    };
    186 
    187    let webdriver_host = args.get_one::<String>("webdriver_host").unwrap();
    188    let webdriver_port = {
    189        let s = args.get_one::<String>("webdriver_port").unwrap();
    190        match u16::from_str(s) {
    191            Ok(n) => n,
    192            Err(e) => bail!("invalid --port: {}: {}", e, s),
    193        }
    194    };
    195 
    196    let android_storage = args
    197        .get_one::<String>("android_storage")
    198        .and_then(|arg| AndroidStorageInput::from_str(arg).ok())
    199        .unwrap_or(AndroidStorageInput::Auto);
    200 
    201    let binary = args.get_one::<String>("binary").map(PathBuf::from);
    202 
    203    let profile_root = args.get_one::<String>("profile_root").map(PathBuf::from);
    204 
    205    // Try to create a temporary directory on startup to check that the directory exists and is writable
    206    {
    207        let tmp_dir = if let Some(ref tmp_root) = profile_root {
    208            tempfile::tempdir_in(tmp_root)
    209        } else {
    210            tempfile::tempdir()
    211        };
    212        if tmp_dir.is_err() {
    213            bail!("Unable to write to temporary directory; consider --profile-root with a writeable directory")
    214        }
    215    }
    216 
    217    let marionette_host = args.get_one::<String>("marionette_host").unwrap();
    218    let marionette_port = match args.get_one::<String>("marionette_port") {
    219        Some(s) => match u16::from_str(s) {
    220            Ok(n) => Some(n),
    221            Err(e) => bail!("invalid --marionette-port: {}", e),
    222        },
    223        None => None,
    224    };
    225 
    226    // For Android the port on the device must be the same as the one on the
    227    // host. For now default to 9222, which is the default for --remote-debugging-port.
    228    let websocket_port = match args.get_one::<String>("websocket_port") {
    229        Some(s) => match u16::from_str(s) {
    230            Ok(n) => n,
    231            Err(e) => bail!("invalid --websocket-port: {}", e),
    232        },
    233        None => 9222,
    234    };
    235 
    236    let host = match parse_hostname(webdriver_host) {
    237        Ok(name) => name,
    238        Err(e) => bail!("invalid --host {}: {}", webdriver_host, e),
    239    };
    240 
    241    let allow_hosts = get_allowed_hosts(host, args.get_many("allow_hosts"));
    242 
    243    let allow_origins = get_allowed_origins(args.get_many("allow_origins"));
    244 
    245    let address = server_address(webdriver_host, webdriver_port)?;
    246 
    247    let settings = MarionetteSettings {
    248        binary,
    249        profile_root,
    250        connect_existing: args.get_flag("connect_existing"),
    251        host: marionette_host.into(),
    252        port: marionette_port,
    253        websocket_port,
    254        allow_hosts: allow_hosts.clone(),
    255        allow_origins: allow_origins.clone(),
    256        jsdebugger: args.get_flag("jsdebugger"),
    257        android_storage,
    258        system_access: args.get_flag("allow_system_access"),
    259    };
    260    Ok(Operation::Server {
    261        log_level,
    262        log_truncate: !args.get_flag("log_no_truncate"),
    263        allow_hosts,
    264        allow_origins,
    265        address,
    266        settings,
    267        deprecated_storage_arg: args.contains_id("android_storage"),
    268    })
    269 }
    270 
    271 fn inner_main(operation: Operation, cmd: &mut Command) -> ProgramResult<()> {
    272    match operation {
    273        Operation::Help => print_help(cmd),
    274        Operation::Version => print_version(),
    275 
    276        Operation::Server {
    277            log_level,
    278            log_truncate,
    279            address,
    280            allow_hosts,
    281            allow_origins,
    282            settings,
    283            deprecated_storage_arg,
    284        } => {
    285            if let Some(ref level) = log_level {
    286                logging::init_with_level(*level, log_truncate).unwrap();
    287            } else {
    288                logging::init(log_truncate).unwrap();
    289            }
    290 
    291            if deprecated_storage_arg {
    292                warn!("--android-storage argument is deprecated and will be removed soon.");
    293            };
    294 
    295            let handler = MarionetteHandler::new(settings);
    296            let listening = webdriver::server::start(
    297                address,
    298                allow_hosts,
    299                allow_origins,
    300                handler,
    301                extension_routes(),
    302            )?;
    303            info!("Listening on {}", listening.socket);
    304        }
    305    }
    306 
    307    Ok(())
    308 }
    309 
    310 fn main() -> ExitCode {
    311    let mut cmd = make_command();
    312 
    313    let args = match cmd.try_get_matches_from_mut(env::args()) {
    314        Ok(args) => args,
    315        Err(e) => {
    316            // Clap already says "error:" and don't repeat help.
    317            eprintln!("{}: {}", get_program_name(), e);
    318            return ExitCode::from(EXIT_USAGE);
    319        }
    320    };
    321 
    322    let operation = match parse_args(&args) {
    323        Ok(op) => op,
    324        Err(e) => {
    325            eprintln!("{}: error: {}", get_program_name(), e);
    326            print_help(&mut cmd);
    327            return ExitCode::from(EXIT_USAGE);
    328        }
    329    };
    330 
    331    if let Err(e) = inner_main(operation, &mut cmd) {
    332        eprintln!("{}: error: {}", get_program_name(), e);
    333        print_help(&mut cmd);
    334        return ExitCode::from(EXIT_UNAVAILABLE);
    335    }
    336 
    337    ExitCode::SUCCESS
    338 }
    339 
    340 fn make_command() -> Command {
    341    Command::new(format!("geckodriver {}", build::build_info()))
    342        .disable_help_flag(true)
    343        .disable_version_flag(true)
    344        .about("WebDriver implementation for Firefox")
    345        .arg(
    346            Arg::new("allow_hosts")
    347                .long("allow-hosts")
    348                .num_args(1..)
    349                .value_parser(clap::builder::ValueParser::new(Host::parse))
    350                .value_name("ALLOW_HOSTS")
    351                .help("List of hostnames to allow. By default the value of --host is allowed, and in addition if that's a well known local address, other variations on well known local addresses are allowed. If --allow-hosts is provided only exactly those hosts are allowed."),
    352        )
    353        .arg(
    354            Arg::new("allow_origins")
    355                .long("allow-origins")
    356                .num_args(1..)
    357                .value_parser(clap::builder::ValueParser::new(Url::parse))
    358                .value_name("ALLOW_ORIGINS")
    359                .help("List of request origins to allow. These must be formatted as scheme://host:port. By default any request with an origin header is rejected. If --allow-origins is provided then only exactly those origins are allowed."),
    360        )
    361        .arg(
    362            Arg::new("allow_system_access")
    363                .long("allow-system-access")
    364                .action(ArgAction::SetTrue)
    365                .help("Enable privileged access to the application's parent process"),
    366        )
    367        .arg(
    368            Arg::new("android_storage")
    369                .long("android-storage")
    370                .value_parser(["auto", "app", "internal", "sdcard"])
    371                .value_name("ANDROID_STORAGE")
    372                .help("Selects storage location to be used for test data (deprecated)."),
    373        )
    374        .arg(
    375            Arg::new("binary")
    376                .short('b')
    377                .long("binary")
    378                .num_args(1)
    379                .value_name("BINARY")
    380                .help("Path to the Firefox binary"),
    381        )
    382        .arg(
    383            Arg::new("connect_existing")
    384                .long("connect-existing")
    385                .requires("marionette_port")
    386                .action(ArgAction::SetTrue)
    387                .help("Connect to an existing Firefox instance"),
    388        )
    389        .arg(
    390            Arg::new("help")
    391                .short('h')
    392                .long("help")
    393                .action(ArgAction::SetTrue)
    394                .help("Prints this message"),
    395        )
    396        .arg(
    397            Arg::new("webdriver_host")
    398                .long("host")
    399                .num_args(1)
    400                .value_name("HOST")
    401                .default_value("127.0.0.1")
    402                .help("Host IP to use for WebDriver server"),
    403        )
    404        .arg(
    405            Arg::new("jsdebugger")
    406                .long("jsdebugger")
    407                .action(ArgAction::SetTrue)
    408                .help("Attach browser toolbox debugger for Firefox"),
    409        )
    410        .arg(
    411            Arg::new("log_level")
    412                .long("log")
    413                .num_args(1)
    414                .value_name("LEVEL")
    415                .value_parser(["fatal", "error", "warn", "info", "config", "debug", "trace"])
    416                .help("Set Gecko log level"),
    417        )
    418        .arg(
    419            Arg::new("log_no_truncate")
    420                .long("log-no-truncate")
    421                .action(ArgAction::SetTrue)
    422                .help("Disable truncation of long log lines"),
    423        )
    424        .arg(
    425            Arg::new("marionette_host")
    426                .long("marionette-host")
    427                .num_args(1)
    428                .value_name("HOST")
    429                .default_value("127.0.0.1")
    430                .help("Host to use to connect to Gecko"),
    431        )
    432        .arg(
    433            Arg::new("marionette_port")
    434                .long("marionette-port")
    435                .num_args(1)
    436                .value_name("PORT")
    437                .help("Port to use to connect to Gecko [default: system-allocated port]"),
    438        )
    439        .arg(
    440            Arg::new("webdriver_port")
    441                .short('p')
    442                .long("port")
    443                .num_args(1)
    444                .value_name("PORT")
    445                .default_value("4444")
    446                .help("Port to use for WebDriver server"),
    447        )
    448        .arg(
    449            Arg::new("profile_root")
    450                .long("profile-root")
    451                .num_args(1)
    452                .value_name("PROFILE_ROOT")
    453                .help("Directory in which to create profiles. Defaults to the system temporary directory."),
    454        )
    455        .arg(
    456            Arg::new("verbosity")
    457                .conflicts_with("log_level")
    458                .short('v')
    459                .action(ArgAction::Count)
    460                .help("Log level verbosity (-v for debug and -vv for trace level)"),
    461        )
    462        .arg(
    463            Arg::new("version")
    464                .short('V')
    465                .long("version")
    466                .action(ArgAction::SetTrue)
    467                .help("Prints version and copying information"),
    468        )
    469        .arg(
    470            Arg::new("websocket_port")
    471                .long("websocket-port")
    472                .num_args(1)
    473                .value_name("PORT")
    474                .conflicts_with("connect_existing")
    475                .help("Port to use to connect to WebDriver BiDi [default: 9222]"),
    476        )
    477 }
    478 
    479 fn get_program_name() -> String {
    480    env::args().next().unwrap()
    481 }
    482 
    483 fn print_help(cmd: &mut Command) {
    484    cmd.print_help().ok();
    485    println!();
    486 }
    487 
    488 fn print_version() {
    489    println!("geckodriver {}", build::build_info());
    490    println!();
    491    println!("The source code of this program is available from");
    492    println!("testing/geckodriver in https://hg.mozilla.org/mozilla-central.");
    493    println!();
    494    println!("This program is subject to the terms of the Mozilla Public License 2.0.");
    495    println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
    496 }