tor-browser

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

browser.rs (19950B)


      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 use crate::android::{AndroidError, AndroidHandler};
      6 use crate::capabilities::{FirefoxOptions, ProfileType};
      7 use crate::logging;
      8 use crate::prefs;
      9 use mozprofile::preferences::Pref;
     10 use mozprofile::profile::{PrefFile, Profile};
     11 use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
     12 use std::env;
     13 use std::fs;
     14 use std::io::prelude::*;
     15 use std::path::{Path, PathBuf};
     16 use std::time;
     17 use uuid::Uuid;
     18 use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
     19 
     20 /// A running Gecko instance.
     21 #[derive(Debug)]
     22 #[allow(clippy::large_enum_variant)]
     23 pub(crate) enum Browser {
     24    Local(LocalBrowser),
     25    Remote(RemoteBrowser),
     26 
     27    /// An existing browser instance not controlled by GeckoDriver
     28    Existing(u16),
     29 }
     30 
     31 impl Browser {
     32    pub(crate) fn close(self, wait_for_shutdown: bool) -> WebDriverResult<()> {
     33        match self {
     34            Browser::Local(x) => x.close(wait_for_shutdown),
     35            Browser::Remote(x) => x.close(),
     36            Browser::Existing(_) => Ok(()),
     37        }
     38    }
     39 
     40    pub(crate) fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
     41        match self {
     42            Browser::Local(x) => x.marionette_port(),
     43            Browser::Remote(x) => x.marionette_port(),
     44            Browser::Existing(x) => Ok(Some(*x)),
     45        }
     46    }
     47 
     48    pub(crate) fn update_marionette_port(&mut self, port: u16) {
     49        match self {
     50            Browser::Local(x) => x.update_marionette_port(port),
     51            Browser::Remote(x) => x.update_marionette_port(port),
     52            Browser::Existing(x) => {
     53                if port != *x {
     54                    error!(
     55                        "Cannot re-assign Marionette port when connected to an existing browser"
     56                    );
     57                }
     58            }
     59        }
     60    }
     61 
     62    pub(crate) fn create_file(&self, content: &[u8]) -> WebDriverResult<String> {
     63        let addon_file = format!("addon-{}.xpi", Uuid::new_v4());
     64        match self {
     65            Browser::Remote(x) => {
     66                let path = x.push_file(content, &addon_file).map_err(|e| {
     67                    WebDriverError::new(
     68                        ErrorStatus::UnknownError,
     69                        format!("Failed to create an addon file: {}", e),
     70                    )
     71                })?;
     72 
     73                Ok(path)
     74            }
     75            Browser::Local(_) | Browser::Existing(_) => {
     76                let path = env::temp_dir().as_path().join(addon_file);
     77                let mut xpi_file = fs::File::create(&path).map_err(|e| {
     78                    WebDriverError::new(
     79                        ErrorStatus::UnknownError,
     80                        format!("Failed to create an addon file: {}", e),
     81                    )
     82                })?;
     83                xpi_file.write_all(content).map_err(|e| {
     84                    WebDriverError::new(
     85                        ErrorStatus::UnknownError,
     86                        format!("Failed to write data to the addon file: {}", e),
     87                    )
     88                })?;
     89 
     90                Ok(path.display().to_string())
     91            }
     92        }
     93    }
     94 }
     95 
     96 #[derive(Debug)]
     97 /// A local Firefox process, running on this (host) device.
     98 pub(crate) struct LocalBrowser {
     99    marionette_port: u16,
    100    prefs_backup: Option<PrefsBackup>,
    101    process: FirefoxProcess,
    102    pub(crate) profile_path: Option<PathBuf>,
    103 }
    104 
    105 impl LocalBrowser {
    106    pub(crate) fn new(
    107        options: FirefoxOptions,
    108        marionette_port: u16,
    109        jsdebugger: bool,
    110        system_access: bool,
    111        profile_root: Option<&Path>,
    112    ) -> WebDriverResult<LocalBrowser> {
    113        let binary = options.binary.ok_or_else(|| {
    114            WebDriverError::new(
    115                ErrorStatus::SessionNotCreated,
    116                "Expected browser binary location, but unable to find \
    117             binary in default location, no \
    118             'moz:firefoxOptions.binary' capability provided, and \
    119             no binary flag set on the command line",
    120            )
    121        })?;
    122 
    123        let is_custom_profile = matches!(options.profile, ProfileType::Path(_));
    124 
    125        let mut profile = match options.profile {
    126            ProfileType::Named => None,
    127            ProfileType::Path(x) => Some(x),
    128            ProfileType::Temporary => Some(Profile::new(profile_root)?),
    129        };
    130 
    131        let (profile_path, prefs_backup) = if let Some(ref mut profile) = profile {
    132            let profile_path = profile.path.clone();
    133            let prefs_backup = set_prefs(
    134                marionette_port,
    135                profile,
    136                is_custom_profile,
    137                options.prefs,
    138                jsdebugger,
    139            )
    140            .map_err(|e| {
    141                WebDriverError::new(
    142                    ErrorStatus::SessionNotCreated,
    143                    format!("Failed to set preferences: {}", e),
    144                )
    145            })?;
    146            (Some(profile_path), prefs_backup)
    147        } else {
    148            warn!("Unable to set geckodriver prefs when using a named profile");
    149            (None, None)
    150        };
    151 
    152        let mut runner = FirefoxRunner::new(&binary, profile);
    153 
    154        runner.arg("--marionette");
    155        if jsdebugger {
    156            runner.arg("--jsdebugger");
    157        }
    158        if system_access {
    159            runner.arg("--remote-allow-system-access");
    160        }
    161        if let Some(args) = options.args.as_ref() {
    162            runner.args(args);
    163        }
    164 
    165        // https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
    166        runner
    167            .env("MOZ_CRASHREPORTER", "1")
    168            .env("MOZ_CRASHREPORTER_NO_REPORT", "1")
    169            .env("MOZ_CRASHREPORTER_SHUTDOWN", "1");
    170 
    171        let process = match runner.start() {
    172            Ok(process) => process,
    173            Err(e) => {
    174                if let Some(backup) = prefs_backup {
    175                    backup.restore();
    176                }
    177                return Err(WebDriverError::new(
    178                    ErrorStatus::SessionNotCreated,
    179                    format!("Failed to start browser {}: {}", binary.display(), e),
    180                ));
    181            }
    182        };
    183 
    184        Ok(LocalBrowser {
    185            marionette_port,
    186            prefs_backup,
    187            process,
    188            profile_path,
    189        })
    190    }
    191 
    192    fn close(mut self, wait_for_shutdown: bool) -> WebDriverResult<()> {
    193        if wait_for_shutdown {
    194            // TODO(https://bugzil.la/1443922):
    195            // Use toolkit.asyncshutdown.crash_timout pref
    196            let duration = time::Duration::from_secs(70);
    197            match self.process.wait(duration) {
    198                Ok(x) => debug!("Browser process stopped: {}", x),
    199                Err(e) => error!("Failed to stop browser process: {}", e),
    200            }
    201        }
    202        self.process.kill()?;
    203 
    204        // Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
    205        if let Some(prefs_backup) = self.prefs_backup {
    206            prefs_backup.restore();
    207        };
    208 
    209        Ok(())
    210    }
    211 
    212    fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
    213        if self.marionette_port != 0 {
    214            return Ok(Some(self.marionette_port));
    215        }
    216 
    217        if let Some(profile_path) = self.profile_path.as_ref() {
    218            return Ok(read_marionette_port(profile_path));
    219        }
    220 
    221        // This should be impossible, but it isn't enforced
    222        Err(WebDriverError::new(
    223            ErrorStatus::SessionNotCreated,
    224            "Port not known when using named profile",
    225        ))
    226    }
    227 
    228    fn update_marionette_port(&mut self, port: u16) {
    229        self.marionette_port = port;
    230    }
    231 
    232    pub(crate) fn check_status(&mut self) -> Option<String> {
    233        match self.process.try_wait() {
    234            Ok(Some(status)) => Some(
    235                status
    236                    .code()
    237                    .map(|c| c.to_string())
    238                    .unwrap_or_else(|| "signal".into()),
    239            ),
    240            Ok(None) => None,
    241            Err(_) => Some("{unknown}".into()),
    242        }
    243    }
    244 }
    245 
    246 fn read_marionette_port(profile_path: &Path) -> Option<u16> {
    247    let port_file = profile_path.join("MarionetteActivePort");
    248    let mut port_str = String::with_capacity(6);
    249    let mut file = match fs::File::open(&port_file) {
    250        Ok(file) => file,
    251        Err(_) => {
    252            trace!("Failed to open {}", &port_file.to_string_lossy());
    253            return None;
    254        }
    255    };
    256    if let Err(e) = file.read_to_string(&mut port_str) {
    257        trace!("Failed to read {}: {}", &port_file.to_string_lossy(), e);
    258        return None;
    259    };
    260    println!("Read port: {}", port_str);
    261    let port = port_str.parse::<u16>().ok();
    262    if port.is_none() {
    263        warn!("Failed fo convert {} to u16", &port_str);
    264    }
    265    port
    266 }
    267 
    268 #[derive(Debug)]
    269 /// A remote instance, running on a (target) Android device.
    270 pub(crate) struct RemoteBrowser {
    271    pub(crate) handler: AndroidHandler,
    272    marionette_port: u16,
    273    prefs_backup: Option<PrefsBackup>,
    274 }
    275 
    276 impl RemoteBrowser {
    277    pub(crate) fn new(
    278        options: FirefoxOptions,
    279        marionette_port: u16,
    280        websocket_port: Option<u16>,
    281        system_access: bool,
    282        profile_root: Option<&Path>,
    283    ) -> WebDriverResult<RemoteBrowser> {
    284        let android_options = options.android.unwrap();
    285 
    286        let handler = AndroidHandler::new(
    287            &android_options,
    288            marionette_port,
    289            system_access,
    290            websocket_port,
    291        )?;
    292 
    293        // Profile management.
    294        let (mut profile, is_custom_profile) = match options.profile {
    295            ProfileType::Named => {
    296                return Err(WebDriverError::new(
    297                    ErrorStatus::SessionNotCreated,
    298                    "Cannot use a named profile on Android",
    299                ));
    300            }
    301            ProfileType::Path(x) => (x, true),
    302            ProfileType::Temporary => (Profile::new(profile_root)?, false),
    303        };
    304 
    305        let prefs_backup = set_prefs(
    306            handler.marionette_target_port,
    307            &mut profile,
    308            is_custom_profile,
    309            options.prefs,
    310            false,
    311        )
    312        .map_err(|e| {
    313            WebDriverError::new(
    314                ErrorStatus::SessionNotCreated,
    315                format!("Failed to set preferences: {}", e),
    316            )
    317        })?;
    318 
    319        handler.prepare(&profile, options.args, options.env.unwrap_or_default())?;
    320 
    321        handler.launch()?;
    322 
    323        Ok(RemoteBrowser {
    324            handler,
    325            marionette_port,
    326            prefs_backup,
    327        })
    328    }
    329 
    330    fn close(self) -> WebDriverResult<()> {
    331        self.handler.force_stop()?;
    332 
    333        // Restoring the prefs if the browser fails to stop perhaps doesn't work anyway
    334        if let Some(prefs_backup) = self.prefs_backup {
    335            prefs_backup.restore();
    336        };
    337 
    338        Ok(())
    339    }
    340 
    341    fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
    342        Ok(Some(self.marionette_port))
    343    }
    344 
    345    fn update_marionette_port(&mut self, port: u16) {
    346        self.marionette_port = port;
    347    }
    348 
    349    fn push_file(&self, content: &[u8], path: &str) -> Result<String, AndroidError> {
    350        self.handler.push_as_file(content, path)
    351    }
    352 }
    353 
    354 fn set_prefs(
    355    port: u16,
    356    profile: &mut Profile,
    357    custom_profile: bool,
    358    extra_prefs: Vec<(String, Pref)>,
    359    js_debugger: bool,
    360 ) -> WebDriverResult<Option<PrefsBackup>> {
    361    let prefs = profile.user_prefs().map_err(|_| {
    362        WebDriverError::new(
    363            ErrorStatus::UnknownError,
    364            "Unable to read profile preferences file",
    365        )
    366    })?;
    367 
    368    let backup_prefs = if custom_profile && prefs.path.exists() {
    369        Some(PrefsBackup::new(prefs)?)
    370    } else {
    371        None
    372    };
    373 
    374    for &(name, ref value) in prefs::DEFAULT.iter() {
    375        if !custom_profile || !prefs.contains_key(name) {
    376            prefs.insert(name.to_string(), (*value).clone());
    377        }
    378    }
    379 
    380    prefs.insert_slice(&extra_prefs[..]);
    381 
    382    if js_debugger {
    383        prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger"));
    384        prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
    385        prefs.insert("devtools.chrome.enabled", Pref::new(true));
    386        prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
    387    }
    388 
    389    prefs.insert("marionette.port", Pref::new(port));
    390    prefs.insert("remote.log.level", logging::max_level().into());
    391 
    392    prefs.write().map_err(|e| {
    393        WebDriverError::new(
    394            ErrorStatus::UnknownError,
    395            format!("Unable to write Firefox profile: {}", e),
    396        )
    397    })?;
    398    Ok(backup_prefs)
    399 }
    400 
    401 #[derive(Debug)]
    402 struct PrefsBackup {
    403    orig_path: PathBuf,
    404    backup_path: PathBuf,
    405 }
    406 
    407 impl PrefsBackup {
    408    fn new(prefs: &PrefFile) -> WebDriverResult<PrefsBackup> {
    409        let mut prefs_backup_path = prefs.path.clone();
    410        let mut counter = 0;
    411        while {
    412            let ext = if counter > 0 {
    413                format!("geckodriver_backup_{}", counter)
    414            } else {
    415                "geckodriver_backup".to_string()
    416            };
    417            prefs_backup_path.set_extension(ext);
    418            prefs_backup_path.exists()
    419        } {
    420            counter += 1
    421        }
    422        debug!("Backing up prefs to {:?}", prefs_backup_path);
    423        fs::copy(&prefs.path, &prefs_backup_path)?;
    424 
    425        Ok(PrefsBackup {
    426            orig_path: prefs.path.clone(),
    427            backup_path: prefs_backup_path,
    428        })
    429    }
    430 
    431    fn restore(self) {
    432        if self.backup_path.exists() {
    433            let _ = fs::rename(self.backup_path, self.orig_path);
    434        }
    435    }
    436 }
    437 
    438 #[cfg(test)]
    439 mod tests {
    440    use super::set_prefs;
    441    use crate::browser::read_marionette_port;
    442    use crate::capabilities::{FirefoxOptions, ProfileType};
    443    use base64::prelude::BASE64_STANDARD;
    444    use base64::Engine;
    445    use mozprofile::preferences::{Pref, PrefValue};
    446    use mozprofile::profile::Profile;
    447    use serde_json::{Map, Value};
    448    use std::fs::File;
    449    use std::io::{Read, Write};
    450    use std::path::Path;
    451    use tempfile::tempdir;
    452 
    453    fn example_profile() -> Value {
    454        let mut profile_data = Vec::with_capacity(1024);
    455        let mut profile = File::open("src/tests/profile.zip").unwrap();
    456        profile.read_to_end(&mut profile_data).unwrap();
    457        Value::String(BASE64_STANDARD.encode(&profile_data))
    458    }
    459 
    460    // This is not a pretty test, mostly due to the nature of
    461    // mozprofile's and MarionetteHandler's APIs, but we have had
    462    // several regressions related to remote.log.level.
    463    #[test]
    464    fn test_remote_log_level() {
    465        let mut profile = Profile::new(None).unwrap();
    466        set_prefs(2828, &mut profile, false, vec![], false).ok();
    467        let user_prefs = profile.user_prefs().unwrap();
    468 
    469        let pref = user_prefs.get("remote.log.level").unwrap();
    470        let value = match pref.value {
    471            PrefValue::String(ref s) => s,
    472            _ => panic!(),
    473        };
    474        for (i, ch) in value.chars().enumerate() {
    475            if i == 0 {
    476                assert!(ch.is_uppercase());
    477            } else {
    478                assert!(ch.is_lowercase());
    479            }
    480        }
    481    }
    482 
    483    #[test]
    484    fn test_prefs() {
    485        let marionette_settings = Default::default();
    486 
    487        let encoded_profile = example_profile();
    488        let mut prefs: Map<String, Value> = Map::new();
    489        prefs.insert(
    490            "browser.display.background_color".into(),
    491            Value::String("#00ff00".into()),
    492        );
    493 
    494        let mut firefox_opts = Map::new();
    495        firefox_opts.insert("profile".into(), encoded_profile);
    496        firefox_opts.insert("prefs".into(), Value::Object(prefs));
    497 
    498        let mut caps = Map::new();
    499        caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts));
    500 
    501        let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
    502            .expect("Valid profile and prefs");
    503 
    504        let mut profile = match opts.profile {
    505            ProfileType::Path(profile) => profile,
    506            _ => panic!("Expected ProfileType::Path"),
    507        };
    508 
    509        set_prefs(2828, &mut profile, true, opts.prefs, false).expect("set preferences");
    510 
    511        let prefs_set = profile.user_prefs().expect("valid user preferences");
    512        println!("{:#?}", prefs_set.prefs);
    513 
    514        assert_eq!(
    515            prefs_set.get("startup.homepage_welcome_url"),
    516            Some(&Pref::new("data:text/html,PASS"))
    517        );
    518        assert_eq!(
    519            prefs_set.get("browser.display.background_color"),
    520            Some(&Pref::new("#00ff00"))
    521        );
    522        assert_eq!(prefs_set.get("marionette.port"), Some(&Pref::new(2828)));
    523    }
    524 
    525    #[test]
    526    fn test_pref_backup() {
    527        let mut profile = Profile::new(None).unwrap();
    528 
    529        // Create some prefs in the profile
    530        let initial_prefs = profile.user_prefs().unwrap();
    531        initial_prefs.insert("geckodriver.example", Pref::new("example"));
    532        initial_prefs.write().unwrap();
    533 
    534        let prefs_path = initial_prefs.path.clone();
    535 
    536        let mut conflicting_backup_path = initial_prefs.path.clone();
    537        conflicting_backup_path.set_extension("geckodriver_backup");
    538        println!("{:?}", conflicting_backup_path);
    539        let mut file = File::create(&conflicting_backup_path).unwrap();
    540        file.write_all(b"test").unwrap();
    541        assert!(conflicting_backup_path.exists());
    542 
    543        let mut initial_prefs_data = String::new();
    544        File::open(&prefs_path)
    545            .expect("Initial prefs exist")
    546            .read_to_string(&mut initial_prefs_data)
    547            .unwrap();
    548 
    549        let backup = set_prefs(2828, &mut profile, true, vec![], false)
    550            .unwrap()
    551            .unwrap();
    552        let user_prefs = profile.user_prefs().unwrap();
    553 
    554        assert!(user_prefs.path.exists());
    555        let mut backup_path = user_prefs.path.clone();
    556        backup_path.set_extension("geckodriver_backup_1");
    557 
    558        assert!(backup_path.exists());
    559 
    560        // Ensure the actual prefs contain both the existing ones and the ones we added
    561        let pref = user_prefs.get("marionette.port").unwrap();
    562        assert_eq!(pref.value, PrefValue::Int(2828));
    563 
    564        let pref = user_prefs.get("geckodriver.example").unwrap();
    565        assert_eq!(pref.value, PrefValue::String("example".into()));
    566 
    567        // Ensure the backup prefs don't contain the new settings
    568        let mut backup_data = String::new();
    569        File::open(&backup_path)
    570            .expect("Backup prefs exist")
    571            .read_to_string(&mut backup_data)
    572            .unwrap();
    573        assert_eq!(backup_data, initial_prefs_data);
    574 
    575        backup.restore();
    576 
    577        assert!(!backup_path.exists());
    578        let mut final_prefs_data = String::new();
    579        File::open(&prefs_path)
    580            .expect("Initial prefs exist")
    581            .read_to_string(&mut final_prefs_data)
    582            .unwrap();
    583        assert_eq!(final_prefs_data, initial_prefs_data);
    584    }
    585 
    586    #[test]
    587    fn test_local_read_marionette_port() {
    588        fn create_port_file(profile_path: &Path, data: &[u8]) {
    589            let port_path = profile_path.join("MarionetteActivePort");
    590            let mut file = File::create(&port_path).unwrap();
    591            file.write_all(data).unwrap();
    592        }
    593 
    594        let profile_dir = tempdir().unwrap();
    595        let profile_path = profile_dir.path();
    596        assert_eq!(read_marionette_port(profile_path), None);
    597        assert_eq!(read_marionette_port(profile_path), None);
    598        create_port_file(profile_path, b"");
    599        assert_eq!(read_marionette_port(profile_path), None);
    600        create_port_file(profile_path, b"1234");
    601        assert_eq!(read_marionette_port(profile_path), Some(1234));
    602        create_port_file(profile_path, b"1234abc");
    603        assert_eq!(read_marionette_port(profile_path), None);
    604    }
    605 }