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