1 extern crate chrono;
2 #[macro_use]
3 extern crate clap;
4 #[macro_use]
5 extern crate lazy_static;
6 extern crate hyper;
7 extern crate mozprofile;
8 extern crate mozrunner;
9 extern crate mozversion;
10 extern crate regex;
11 extern crate rustc_serialize;
12 extern crate uuid;
13 extern crate zip;
14 extern crate webdriver;
15 
16 #[macro_use]
17 extern crate log;
18 
19 use std::fmt;
20 use std::io::Write;
21 use std::net::{IpAddr, SocketAddr};
22 use std::path::PathBuf;
23 use std::str::FromStr;
24 
25 use clap::{App, Arg};
26 
27 macro_rules! try_opt {
28     ($expr:expr, $err_type:expr, $err_msg:expr) => ({
29         match $expr {
30             Some(x) => x,
31             None => return Err(WebDriverError::new($err_type, $err_msg))
32         }
33     })
34 }
35 
36 mod logging;
37 mod prefs;
38 mod marionette;
39 mod capabilities;
40 
41 use marionette::{MarionetteHandler, MarionetteSettings, extension_routes};
42 
43 include!(concat!(env!("OUT_DIR"), "/build-info.rs"));
44 
45 type ProgramResult = std::result::Result<(), (ExitCode, String)>;
46 
47 enum ExitCode {
48     Ok = 0,
49     Usage = 64,
50     Unavailable = 69,
51 }
52 
53 struct BuildInfo;
54 impl BuildInfo {
version() -> &'static str55     pub fn version() -> &'static str {
56         crate_version!()
57     }
58 
hash() -> Option<&'static str>59     pub fn hash() -> Option<&'static str> {
60         COMMIT_HASH
61     }
62 
date() -> Option<&'static str>63     pub fn date() -> Option<&'static str> {
64         COMMIT_DATE
65     }
66 }
67 
68 impl fmt::Display for BuildInfo {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result69     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
70         write!(f, "{}", BuildInfo::version())?;
71         match (BuildInfo::hash(), BuildInfo::date()) {
72             (Some(hash), Some(date)) => write!(f, " ({} {})", hash, date)?,
73             (Some(hash), None) => write!(f, " ({})", hash)?,
74             _ => {}
75         }
76         Ok(())
77     }
78 }
79 
print_version()80 fn print_version() {
81     println!("geckodriver {}", BuildInfo);
82     println!("");
83     println!("The source code of this program is available from");
84     println!("testing/geckodriver in https://hg.mozilla.org/mozilla-central.");
85     println!("");
86     println!("This program is subject to the terms of the Mozilla Public License 2.0.");
87     println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
88 }
89 
app<'a, 'b>() -> App<'a, 'b>90 fn app<'a, 'b>() -> App<'a, 'b> {
91     App::new(format!("geckodriver {}", crate_version!()))
92         .about("WebDriver implementation for Firefox.")
93         .arg(Arg::with_name("webdriver_host")
94             .long("host")
95             .value_name("HOST")
96             .help("Host ip to use for WebDriver server (default: 127.0.0.1)")
97             .takes_value(true))
98         .arg(Arg::with_name("webdriver_port")
99             .short("p")
100             .long("port")
101             .value_name("PORT")
102             .help("Port to use for WebDriver server (default: 4444)")
103             .takes_value(true)
104             .alias("webdriver-port"))
105         .arg(Arg::with_name("binary")
106             .short("b")
107             .long("binary")
108             .value_name("BINARY")
109             .help("Path to the Firefox binary")
110             .takes_value(true))
111         .arg(Arg::with_name("marionette_port")
112             .long("marionette-port")
113             .value_name("PORT")
114             .help("Port to use to connect to Gecko (default: random free port)")
115             .takes_value(true))
116         .arg(Arg::with_name("connect_existing")
117             .long("connect-existing")
118             .requires("marionette_port")
119             .help("Connect to an existing Firefox instance"))
120         .arg(Arg::with_name("jsdebugger")
121             .long("jsdebugger")
122             .takes_value(false)
123             .help("Attach browser toolbox debugger for Firefox"))
124         .arg(Arg::with_name("verbosity")
125             .short("v")
126             .multiple(true)
127             .conflicts_with("log_level")
128             .help("Log level verbosity (-v for debug and -vv for trace level)"))
129         .arg(Arg::with_name("log_level")
130             .long("log")
131             .takes_value(true)
132             .value_name("LEVEL")
133             .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
134             .help("Set Gecko log level"))
135         .arg(Arg::with_name("version")
136             .short("V")
137             .long("version")
138             .help("Prints version and copying information"))
139 }
140 
run() -> ProgramResult141 fn run() -> ProgramResult {
142     let matches = app().get_matches();
143 
144     if matches.is_present("version") {
145         print_version();
146         return Ok(());
147     }
148 
149     let host = matches.value_of("webdriver_host").unwrap_or("127.0.0.1");
150     let port = match u16::from_str(
151         matches
152             .value_of("webdriver_port")
153             .or(matches.value_of("webdriver_port_alias"))
154             .unwrap_or("4444"),
155     ) {
156         Ok(x) => x,
157         Err(_) => return Err((ExitCode::Usage, "invalid WebDriver port".into())),
158     };
159     let addr = match IpAddr::from_str(host) {
160         Ok(addr) => SocketAddr::new(addr, port),
161         Err(_) => return Err((ExitCode::Usage, "invalid host address".into())),
162     };
163 
164     let binary = matches.value_of("binary").map(|x| PathBuf::from(x));
165 
166     let marionette_port = match matches.value_of("marionette_port") {
167         Some(x) => {
168             match u16::from_str(x) {
169                 Ok(x) => Some(x),
170                 Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
171             }
172         }
173         None => None,
174     };
175 
176     let log_level = if matches.is_present("log_level") {
177         logging::Level::from_str(matches.value_of("log_level").unwrap()).ok()
178     } else {
179         match matches.occurrences_of("verbosity") {
180             0 => Some(logging::Level::Info),
181             1 => Some(logging::Level::Debug),
182             _ => Some(logging::Level::Trace),
183         }
184     };
185     if let Some(ref level) = log_level {
186         logging::init_with_level(level.clone()).unwrap();
187     } else {
188         logging::init().unwrap();
189     }
190 
191     info!("geckodriver {}", BuildInfo);
192 
193     let settings = MarionetteSettings {
194         port: marionette_port,
195         binary,
196         connect_existing: matches.is_present("connect_existing"),
197         jsdebugger: matches.is_present("jsdebugger"),
198     };
199     let handler = MarionetteHandler::new(settings);
200     let listening = webdriver::server::start(addr, handler, &extension_routes()[..])
201         .map_err(|err| (ExitCode::Unavailable, err.to_string()))?;
202     info!("Listening on {}", listening.socket);
203 
204     Ok(())
205 }
206 
main()207 fn main() {
208     let exit_code = match run() {
209         Ok(_) => ExitCode::Ok,
210         Err((exit_code, reason)) => {
211             error!("{}", reason);
212             exit_code
213         }
214     };
215 
216     std::io::stdout().flush().unwrap();
217     std::process::exit(exit_code as i32);
218 }
219