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 serde_yaml;
20 extern crate uuid;
21 extern crate webdriver;
22 extern crate zip;
23 
24 #[macro_use]
25 extern crate log;
26 
27 use std::env;
28 use std::fmt;
29 use std::io;
30 use std::net::{IpAddr, SocketAddr};
31 use std::path::PathBuf;
32 use std::result;
33 use std::str::FromStr;
34 
35 use clap::{App, Arg};
36 
37 macro_rules! try_opt {
38     ($expr:expr, $err_type:expr, $err_msg:expr) => {{
39         match $expr {
40             Some(x) => x,
41             None => return Err(WebDriverError::new($err_type, $err_msg)),
42         }
43     }};
44 }
45 
46 mod android;
47 mod browser;
48 mod build;
49 mod capabilities;
50 mod command;
51 mod logging;
52 mod marionette;
53 mod prefs;
54 
55 #[cfg(test)]
56 pub mod test;
57 
58 use crate::command::extension_routes;
59 use crate::logging::Level;
60 use crate::marionette::{MarionetteHandler, MarionetteSettings};
61 use mozdevice::AndroidStorageInput;
62 
63 const EXIT_SUCCESS: i32 = 0;
64 const EXIT_USAGE: i32 = 64;
65 const EXIT_UNAVAILABLE: i32 = 69;
66 
67 enum FatalError {
68     Parsing(clap::Error),
69     Usage(String),
70     Server(io::Error),
71 }
72 
73 impl FatalError {
exit_code(&self) -> i3274     fn exit_code(&self) -> i32 {
75         use FatalError::*;
76         match *self {
77             Parsing(_) | Usage(_) => EXIT_USAGE,
78             Server(_) => EXIT_UNAVAILABLE,
79         }
80     }
81 
help_included(&self) -> bool82     fn help_included(&self) -> bool {
83         matches!(*self, FatalError::Parsing(_))
84     }
85 }
86 
87 impl From<clap::Error> for FatalError {
from(err: clap::Error) -> FatalError88     fn from(err: clap::Error) -> FatalError {
89         FatalError::Parsing(err)
90     }
91 }
92 
93 impl From<io::Error> for FatalError {
from(err: io::Error) -> FatalError94     fn from(err: io::Error) -> FatalError {
95         FatalError::Server(err)
96     }
97 }
98 
99 // harmonise error message from clap to avoid duplicate "error:" prefix
100 impl fmt::Display for FatalError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result101     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
102         use FatalError::*;
103         let s = match *self {
104             Parsing(ref err) => err.to_string(),
105             Usage(ref s) => format!("error: {}", s),
106             Server(ref err) => format!("error: {}", err.to_string()),
107         };
108         write!(f, "{}", s)
109     }
110 }
111 
112 macro_rules! usage {
113     ($msg:expr) => {
114         return Err(FatalError::Usage($msg.to_string()));
115     };
116 
117     ($fmt:expr, $($arg:tt)+) => {
118         return Err(FatalError::Usage(format!($fmt, $($arg)+)));
119     };
120 }
121 
122 type ProgramResult<T> = result::Result<T, FatalError>;
123 
124 enum Operation {
125     Help,
126     Version,
127     Server {
128         log_level: Option<Level>,
129         address: SocketAddr,
130         settings: MarionetteSettings,
131         deprecated_storage_arg: bool,
132     },
133 }
134 
parse_args(app: &mut App) -> ProgramResult<Operation>135 fn parse_args(app: &mut App) -> ProgramResult<Operation> {
136     let matches = app.get_matches_from_safe_borrow(env::args())?;
137 
138     let log_level = if matches.is_present("log_level") {
139         Level::from_str(matches.value_of("log_level").unwrap()).ok()
140     } else {
141         Some(match matches.occurrences_of("verbosity") {
142             0 => Level::Info,
143             1 => Level::Debug,
144             _ => Level::Trace,
145         })
146     };
147 
148     let host = matches.value_of("webdriver_host").unwrap();
149     let port = {
150         let s = matches.value_of("webdriver_port").unwrap();
151         match u16::from_str(s) {
152             Ok(n) => n,
153             Err(e) => usage!("invalid --port: {}: {}", e, s),
154         }
155     };
156     let address = match IpAddr::from_str(host) {
157         Ok(addr) => SocketAddr::new(addr, port),
158         Err(e) => usage!("{}: {}:{}", e, host, port),
159     };
160 
161     let android_storage = value_t!(matches, "android_storage", AndroidStorageInput)
162         .unwrap_or(AndroidStorageInput::Auto);
163 
164     let binary = matches.value_of("binary").map(PathBuf::from);
165 
166     let marionette_host = matches.value_of("marionette_host").unwrap();
167     let marionette_port = match matches.value_of("marionette_port") {
168         Some(s) => match u16::from_str(s) {
169             Ok(n) => Some(n),
170             Err(e) => usage!("invalid --marionette-port: {}", e),
171         },
172         None => None,
173     };
174 
175     let op = if matches.is_present("help") {
176         Operation::Help
177     } else if matches.is_present("version") {
178         Operation::Version
179     } else {
180         let settings = MarionetteSettings {
181             host: marionette_host.to_string(),
182             port: marionette_port,
183             binary,
184             connect_existing: matches.is_present("connect_existing"),
185             jsdebugger: matches.is_present("jsdebugger"),
186             android_storage,
187         };
188         Operation::Server {
189             log_level,
190             address,
191             settings,
192             deprecated_storage_arg: matches.is_present("android_storage"),
193         }
194     };
195 
196     Ok(op)
197 }
198 
inner_main(app: &mut App) -> ProgramResult<()>199 fn inner_main(app: &mut App) -> ProgramResult<()> {
200     match parse_args(app)? {
201         Operation::Help => print_help(app),
202         Operation::Version => print_version(),
203 
204         Operation::Server {
205             log_level,
206             address,
207             settings,
208             deprecated_storage_arg,
209         } => {
210             if let Some(ref level) = log_level {
211                 logging::init_with_level(*level).unwrap();
212             } else {
213                 logging::init().unwrap();
214             }
215 
216             if deprecated_storage_arg {
217                 warn!("--android-storage argument is deprecated and will be removed soon.");
218             };
219 
220             let handler = MarionetteHandler::new(settings);
221             let listening = webdriver::server::start(address, handler, extension_routes())?;
222             info!("Listening on {}", listening.socket);
223         }
224     }
225 
226     Ok(())
227 }
228 
main()229 fn main() {
230     use std::process::exit;
231 
232     let mut app = make_app();
233 
234     // use std::process:Termination when it graduates
235     exit(match inner_main(&mut app) {
236         Ok(_) => EXIT_SUCCESS,
237 
238         Err(e) => {
239             eprintln!("{}: {}", get_program_name(), e);
240             if !e.help_included() {
241                 print_help(&mut app);
242             }
243 
244             e.exit_code()
245         }
246     });
247 }
248 
make_app<'a, 'b>() -> App<'a, 'b>249 fn make_app<'a, 'b>() -> App<'a, 'b> {
250     App::new(format!("geckodriver {}", build::build_info()))
251         .about("WebDriver implementation for Firefox")
252         .arg(
253             Arg::with_name("webdriver_host")
254                 .long("host")
255                 .takes_value(true)
256                 .value_name("HOST")
257                 .default_value("127.0.0.1")
258                 .help("Host IP to use for WebDriver server"),
259         )
260         .arg(
261             Arg::with_name("webdriver_port")
262                 .short("p")
263                 .long("port")
264                 .takes_value(true)
265                 .value_name("PORT")
266                 .default_value("4444")
267                 .help("Port to use for WebDriver server"),
268         )
269         .arg(
270             Arg::with_name("binary")
271                 .short("b")
272                 .long("binary")
273                 .takes_value(true)
274                 .value_name("BINARY")
275                 .help("Path to the Firefox binary"),
276         )
277         .arg(
278             Arg::with_name("marionette_host")
279                 .long("marionette-host")
280                 .takes_value(true)
281                 .value_name("HOST")
282                 .default_value("127.0.0.1")
283                 .help("Host to use to connect to Gecko"),
284         )
285         .arg(
286             Arg::with_name("marionette_port")
287                 .long("marionette-port")
288                 .takes_value(true)
289                 .value_name("PORT")
290                 .help("Port to use to connect to Gecko [default: system-allocated port]"),
291         )
292         .arg(
293             Arg::with_name("connect_existing")
294                 .long("connect-existing")
295                 .requires("marionette_port")
296                 .help("Connect to an existing Firefox instance"),
297         )
298         .arg(
299             Arg::with_name("jsdebugger")
300                 .long("jsdebugger")
301                 .help("Attach browser toolbox debugger for Firefox"),
302         )
303         .arg(
304             Arg::with_name("verbosity")
305                 .multiple(true)
306                 .conflicts_with("log_level")
307                 .short("v")
308                 .help("Log level verbosity (-v for debug and -vv for trace level)"),
309         )
310         .arg(
311             Arg::with_name("log_level")
312                 .long("log")
313                 .takes_value(true)
314                 .value_name("LEVEL")
315                 .possible_values(&["fatal", "error", "warn", "info", "config", "debug", "trace"])
316                 .help("Set Gecko log level"),
317         )
318         .arg(
319             Arg::with_name("help")
320                 .short("h")
321                 .long("help")
322                 .help("Prints this message"),
323         )
324         .arg(
325             Arg::with_name("version")
326                 .short("V")
327                 .long("version")
328                 .help("Prints version and copying information"),
329         )
330         .arg(
331             Arg::with_name("android_storage")
332                 .long("android-storage")
333                 .possible_values(&["auto", "app", "internal", "sdcard"])
334                 .value_name("ANDROID_STORAGE")
335                 .help("Selects storage location to be used for test data (deprecated)."),
336         )
337 }
338 
get_program_name() -> String339 fn get_program_name() -> String {
340     env::args().next().unwrap()
341 }
342 
print_help(app: &mut App)343 fn print_help(app: &mut App) {
344     app.print_help().ok();
345     println!();
346 }
347 
print_version()348 fn print_version() {
349     println!("geckodriver {}", build::build_info());
350     println!();
351     println!("The source code of this program is available from");
352     println!("testing/geckodriver in https://hg.mozilla.org/mozilla-central.");
353     println!();
354     println!("This program is subject to the terms of the Mozilla Public License 2.0.");
355     println!("You can obtain a copy of the license at https://mozilla.org/MPL/2.0/.");
356 }
357