1 use mozprofile::prefreader::PrefReaderError;
2 use mozprofile::profile::Profile;
3 use std::collections::HashMap;
4 use std::convert::From;
5 use std::error::Error;
6 use std::ffi::{OsStr, OsString};
7 use std::fmt;
8 use std::io;
9 use std::io::ErrorKind;
10 use std::path::{Path, PathBuf};
11 use std::process;
12 use std::process::{Child, Command, Stdio};
13 use std::thread;
14 use std::time;
15 
16 use crate::firefox_args::Arg;
17 
18 pub trait Runner {
19     type Process;
20 
arg<'a, S>(&'a mut self, arg: S) -> &'a mut Self where S: AsRef<OsStr>21     fn arg<'a, S>(&'a mut self, arg: S) -> &'a mut Self
22     where
23         S: AsRef<OsStr>;
24 
args<'a, I, S>(&'a mut self, args: I) -> &'a mut Self where I: IntoIterator<Item = S>, S: AsRef<OsStr>25     fn args<'a, I, S>(&'a mut self, args: I) -> &'a mut Self
26     where
27         I: IntoIterator<Item = S>,
28         S: AsRef<OsStr>;
29 
env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut Self where K: AsRef<OsStr>, V: AsRef<OsStr>30     fn env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut Self
31     where
32         K: AsRef<OsStr>,
33         V: AsRef<OsStr>;
34 
envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>35     fn envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut Self
36     where
37         I: IntoIterator<Item = (K, V)>,
38         K: AsRef<OsStr>,
39         V: AsRef<OsStr>;
40 
stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self where T: Into<Stdio>41     fn stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self
42     where
43         T: Into<Stdio>;
44 
stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self where T: Into<Stdio>45     fn stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self
46     where
47         T: Into<Stdio>;
48 
start(self) -> Result<Self::Process, RunnerError>49     fn start(self) -> Result<Self::Process, RunnerError>;
50 }
51 
52 pub trait RunnerProcess {
53     /// Attempts to collect the exit status of the process if it has already exited.
54     ///
55     /// This function will not block the calling thread and will only advisorily check to see if
56     /// the child process has exited or not.  If the process has exited then on Unix the process ID
57     /// is reaped.  This function is guaranteed to repeatedly return a successful exit status so
58     /// long as the child has already exited.
59     ///
60     /// If the process has exited, then `Ok(Some(status))` is returned.  If the exit status is not
61     /// available at this time then `Ok(None)` is returned.  If an error occurs, then that error is
62     /// returned.
try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>63     fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>;
64 
65     /// Waits for the process to exit completely, killing it if it does not stop within `timeout`,
66     /// and returns the status that it exited with.
67     ///
68     /// Firefox' integrated background monitor observes long running threads during shutdown and
69     /// kills these after 63 seconds.  If the process fails to exit within the duration of
70     /// `timeout`, it is forcefully killed.
71     ///
72     /// This function will continue to have the same return value after it has been called at least
73     /// once.
wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>74     fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>;
75 
76     /// Determine if the process is still running.
running(&mut self) -> bool77     fn running(&mut self) -> bool;
78 
79     /// Forces the process to exit and returns the exit status.  This is
80     /// equivalent to sending a SIGKILL on Unix platforms.
kill(&mut self) -> io::Result<process::ExitStatus>81     fn kill(&mut self) -> io::Result<process::ExitStatus>;
82 }
83 
84 #[derive(Debug)]
85 pub enum RunnerError {
86     Io(io::Error),
87     PrefReader(PrefReaderError),
88 }
89 
90 impl fmt::Display for RunnerError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result91     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92         self.description().fmt(f)
93     }
94 }
95 
96 impl Error for RunnerError {
description(&self) -> &str97     fn description(&self) -> &str {
98         match *self {
99             RunnerError::Io(ref err) => match err.kind() {
100                 ErrorKind::NotFound => "no such file or directory",
101                 _ => err.description(),
102             },
103             RunnerError::PrefReader(ref err) => err.description(),
104         }
105     }
106 
cause(&self) -> Option<&dyn Error>107     fn cause(&self) -> Option<&dyn Error> {
108         Some(match *self {
109             RunnerError::Io(ref err) => err as &dyn Error,
110             RunnerError::PrefReader(ref err) => err as &dyn Error,
111         })
112     }
113 }
114 
115 impl From<io::Error> for RunnerError {
from(value: io::Error) -> RunnerError116     fn from(value: io::Error) -> RunnerError {
117         RunnerError::Io(value)
118     }
119 }
120 
121 impl From<PrefReaderError> for RunnerError {
from(value: PrefReaderError) -> RunnerError122     fn from(value: PrefReaderError) -> RunnerError {
123         RunnerError::PrefReader(value)
124     }
125 }
126 
127 #[derive(Debug)]
128 pub struct FirefoxProcess {
129     process: Child,
130     profile: Profile,
131 }
132 
133 impl RunnerProcess for FirefoxProcess {
try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>134     fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>> {
135         self.process.try_wait()
136     }
137 
wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>138     fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus> {
139         let start = time::Instant::now();
140         loop {
141             match self.try_wait() {
142                 // child has already exited, reap its exit code
143                 Ok(Some(status)) => return Ok(status),
144 
145                 // child still running and timeout elapsed, kill it
146                 Ok(None) if start.elapsed() >= timeout => return self.kill(),
147 
148                 // child still running, let's give it more time
149                 Ok(None) => thread::sleep(time::Duration::from_millis(100)),
150 
151                 Err(e) => return Err(e),
152             }
153         }
154     }
155 
running(&mut self) -> bool156     fn running(&mut self) -> bool {
157         self.try_wait().unwrap().is_none()
158     }
159 
kill(&mut self) -> io::Result<process::ExitStatus>160     fn kill(&mut self) -> io::Result<process::ExitStatus> {
161         debug!("Killing process {}", self.process.id());
162         self.process.kill()?;
163         self.process.wait()
164     }
165 }
166 
167 #[derive(Debug)]
168 pub struct FirefoxRunner {
169     path: PathBuf,
170     profile: Profile,
171     args: Vec<OsString>,
172     envs: HashMap<OsString, OsString>,
173     stdout: Option<Stdio>,
174     stderr: Option<Stdio>,
175 }
176 
177 impl FirefoxRunner {
178     /// Initialise Firefox process runner.
179     ///
180     /// On macOS, `path` can optionally point to an application bundle,
181     /// i.e. _/Applications/Firefox.app_, as well as to an executable program
182     /// such as _/Applications/Firefox.app/Content/MacOS/firefox-bin_.
new(path: &Path, profile: Profile) -> FirefoxRunner183     pub fn new(path: &Path, profile: Profile) -> FirefoxRunner {
184         let mut envs: HashMap<OsString, OsString> = HashMap::new();
185         envs.insert("MOZ_NO_REMOTE".into(), "1".into());
186 
187         FirefoxRunner {
188             path: path.to_path_buf(),
189             envs: envs,
190             profile: profile,
191             args: vec![],
192             stdout: None,
193             stderr: None,
194         }
195     }
196 }
197 
198 impl Runner for FirefoxRunner {
199     type Process = FirefoxProcess;
200 
arg<'a, S>(&'a mut self, arg: S) -> &'a mut FirefoxRunner where S: AsRef<OsStr>,201     fn arg<'a, S>(&'a mut self, arg: S) -> &'a mut FirefoxRunner
202     where
203         S: AsRef<OsStr>,
204     {
205         self.args.push((&arg).into());
206         self
207     }
208 
args<'a, I, S>(&'a mut self, args: I) -> &'a mut FirefoxRunner where I: IntoIterator<Item = S>, S: AsRef<OsStr>,209     fn args<'a, I, S>(&'a mut self, args: I) -> &'a mut FirefoxRunner
210     where
211         I: IntoIterator<Item = S>,
212         S: AsRef<OsStr>,
213     {
214         for arg in args {
215             self.args.push((&arg).into());
216         }
217         self
218     }
219 
env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut FirefoxRunner where K: AsRef<OsStr>, V: AsRef<OsStr>,220     fn env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut FirefoxRunner
221     where
222         K: AsRef<OsStr>,
223         V: AsRef<OsStr>,
224     {
225         self.envs.insert((&key).into(), (&value).into());
226         self
227     }
228 
envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut FirefoxRunner where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>,229     fn envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut FirefoxRunner
230     where
231         I: IntoIterator<Item = (K, V)>,
232         K: AsRef<OsStr>,
233         V: AsRef<OsStr>,
234     {
235         for (key, value) in envs {
236             self.envs.insert((&key).into(), (&value).into());
237         }
238         self
239     }
240 
stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self where T: Into<Stdio>,241     fn stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self
242     where
243         T: Into<Stdio>,
244     {
245         self.stdout = Some(stdout.into());
246         self
247     }
248 
stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self where T: Into<Stdio>,249     fn stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self
250     where
251         T: Into<Stdio>,
252     {
253         self.stderr = Some(stderr.into());
254         self
255     }
256 
start(mut self) -> Result<FirefoxProcess, RunnerError>257     fn start(mut self) -> Result<FirefoxProcess, RunnerError> {
258         self.profile.user_prefs()?.write()?;
259 
260         let stdout = self.stdout.unwrap_or_else(|| Stdio::inherit());
261         let stderr = self.stderr.unwrap_or_else(|| Stdio::inherit());
262 
263         let binary_path = platform::resolve_binary_path(&mut self.path);
264         let mut cmd = Command::new(binary_path);
265         cmd.args(&self.args[..])
266             .envs(&self.envs)
267             .stdout(stdout)
268             .stderr(stderr);
269 
270         let mut seen_foreground = false;
271         let mut seen_no_remote = false;
272         let mut seen_profile = false;
273         for arg in self.args.iter() {
274             match arg.into() {
275                 Arg::Foreground => seen_foreground = true,
276                 Arg::NoRemote => seen_no_remote = true,
277                 Arg::Profile | Arg::NamedProfile | Arg::ProfileManager => seen_profile = true,
278                 Arg::Other(_) | Arg::None => {}
279             }
280         }
281         if !seen_foreground {
282             cmd.arg("-foreground");
283         }
284         if !seen_no_remote {
285             cmd.arg("-no-remote");
286         }
287         if !seen_profile {
288             cmd.arg("-profile").arg(&self.profile.path);
289         }
290 
291         info!("Running command: {:?}", cmd);
292         let process = cmd.spawn()?;
293         Ok(FirefoxProcess {
294             process,
295             profile: self.profile,
296         })
297     }
298 }
299 
300 #[cfg(all(not(target_os = "macos"), unix))]
301 pub mod platform {
302     use path::find_binary;
303     use std::path::PathBuf;
304 
resolve_binary_path(path: &mut PathBuf) -> &PathBuf305     pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf {
306         path
307     }
308 
309     /// Searches the system path for `firefox`.
firefox_default_path() -> Option<PathBuf>310     pub fn firefox_default_path() -> Option<PathBuf> {
311         find_binary("firefox")
312     }
313 
arg_prefix_char(c: char) -> bool314     pub fn arg_prefix_char(c: char) -> bool {
315         c == '-'
316     }
317 }
318 
319 #[cfg(target_os = "macos")]
320 pub mod platform {
321     use crate::path::{find_binary, is_binary};
322     use dirs;
323     use plist::Value;
324     use std::path::PathBuf;
325 
326     /// Searches for the binary file inside the path passed as parameter.
327     /// If the binary is not found, the path remains unaltered.
328     /// Else, it gets updated by the new binary path.
resolve_binary_path(path: &mut PathBuf) -> &PathBuf329     pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf {
330         if path.as_path().is_dir() {
331             let mut info_plist = path.clone();
332             info_plist.push("Contents");
333             info_plist.push("Info.plist");
334             if let Ok(plist) = Value::from_file(&info_plist) {
335                 if let Some(dict) = plist.as_dictionary() {
336                     if let Some(binary_file) = dict.get("CFBundleExecutable") {
337                         match binary_file {
338                             Value::String(s) => {
339                                 path.push("Contents");
340                                 path.push("MacOS");
341                                 path.push(s);
342                             }
343                             _ => {}
344                         }
345                     }
346                 }
347             }
348         }
349         path
350     }
351 
352     /// Searches the system path for `firefox-bin`, then looks for
353     /// `Applications/Firefox.app/Contents/MacOS/firefox-bin` as well
354     /// as `Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin`
355     /// under both `/` (system root) and the user home directory.
firefox_default_path() -> Option<PathBuf>356     pub fn firefox_default_path() -> Option<PathBuf> {
357         if let Some(path) = find_binary("firefox-bin") {
358             return Some(path);
359         }
360 
361         let home = dirs::home_dir();
362         for &(prefix_home, trial_path) in [
363             (
364                 false,
365                 "/Applications/Firefox.app/Contents/MacOS/firefox-bin",
366             ),
367             (true, "Applications/Firefox.app/Contents/MacOS/firefox-bin"),
368             (
369                 false,
370                 "/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin",
371             ),
372             (
373                 true,
374                 "Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin",
375             ),
376         ]
377         .iter()
378         {
379             let path = match (home.as_ref(), prefix_home) {
380                 (Some(ref home_dir), true) => home_dir.join(trial_path),
381                 (None, true) => continue,
382                 (_, false) => PathBuf::from(trial_path),
383             };
384             if is_binary(&path) {
385                 return Some(path);
386             }
387         }
388 
389         None
390     }
391 
arg_prefix_char(c: char) -> bool392     pub fn arg_prefix_char(c: char) -> bool {
393         c == '-'
394     }
395 }
396 
397 #[cfg(target_os = "windows")]
398 pub mod platform {
399     use path::{find_binary, is_binary};
400     use std::io::Error;
401     use std::path::PathBuf;
402     use winreg::enums::*;
403     use winreg::RegKey;
404 
resolve_binary_path(path: &mut PathBuf) -> &PathBuf405     pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf {
406         path
407     }
408 
409     /// Searches the Windows registry, then the system path for `firefox.exe`.
410     ///
411     /// It _does not_ currently check the `HKEY_CURRENT_USER` tree.
firefox_default_path() -> Option<PathBuf>412     pub fn firefox_default_path() -> Option<PathBuf> {
413         if let Ok(Some(path)) = firefox_registry_path() {
414             if is_binary(&path) {
415                 return Some(path);
416             }
417         };
418         find_binary("firefox.exe")
419     }
420 
firefox_registry_path() -> Result<Option<PathBuf>, Error>421     fn firefox_registry_path() -> Result<Option<PathBuf>, Error> {
422         let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
423         for subtree_key in ["SOFTWARE", "SOFTWARE\\WOW6432Node"].iter() {
424             let subtree = hklm.open_subkey_with_flags(subtree_key, KEY_READ)?;
425             let mozilla_org = match subtree.open_subkey_with_flags("mozilla.org\\Mozilla", KEY_READ)
426             {
427                 Ok(val) => val,
428                 Err(_) => continue,
429             };
430             let current_version: String = mozilla_org.get_value("CurrentVersion")?;
431             let mozilla = subtree.open_subkey_with_flags("Mozilla", KEY_READ)?;
432             for key_res in mozilla.enum_keys() {
433                 let key = key_res?;
434                 let section_data = mozilla.open_subkey_with_flags(&key, KEY_READ)?;
435                 let version: Result<String, _> = section_data.get_value("GeckoVer");
436                 if let Ok(ver) = version {
437                     if ver == current_version {
438                         let mut bin_key = key.to_owned();
439                         bin_key.push_str("\\bin");
440                         if let Ok(bin_subtree) = mozilla.open_subkey_with_flags(bin_key, KEY_READ) {
441                             let path_to_exe: Result<String, _> = bin_subtree.get_value("PathToExe");
442                             if let Ok(path_to_exe) = path_to_exe {
443                                 let path = PathBuf::from(path_to_exe);
444                                 if is_binary(&path) {
445                                     return Ok(Some(path));
446                                 }
447                             }
448                         }
449                     }
450                 }
451             }
452         }
453         Ok(None)
454     }
455 
arg_prefix_char(c: char) -> bool456     pub fn arg_prefix_char(c: char) -> bool {
457         c == '/' || c == '-'
458     }
459 }
460 
461 #[cfg(not(any(unix, target_os = "windows")))]
462 pub mod platform {
463     use std::path::PathBuf;
464 
465     /// Returns an unaltered path for all operating systems other than macOS.
resolve_binary_path(path: &mut PathBuf) -> &PathBuf466     pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf {
467         path
468     }
469 
470     /// Returns `None` for all other operating systems than Linux, macOS, and
471     /// Windows.
firefox_default_path() -> Option<PathBuf>472     pub fn firefox_default_path() -> Option<PathBuf> {
473         None
474     }
475 
arg_prefix_char(c: char) -> bool476     pub fn arg_prefix_char(c: char) -> bool {
477         c == '-'
478     }
479 }
480