1use crate::Parameters;
6use crate::command::{WebDriverCommand, WebDriverMessage};
7use crate::error::{ErrorStatus, WebDriverError, WebDriverResult};
8use crate::httpapi::{
9 Route, VoidWebDriverExtensionRoute, WebDriverExtensionRoute, standard_routes,
10};
11use crate::response::{CloseWindowResponse, WebDriverResponse};
12use bytes::Bytes;
13use http::{Method, StatusCode};
14use std::marker::PhantomData;
15use std::net::{SocketAddr, TcpListener as StdTcpListener};
16use std::sync::mpsc::{Receiver, Sender, channel};
17use std::sync::{Arc, Mutex};
18use std::thread;
19use bytes::Buf;
20use tokio::net::TcpListener;
21use url::{Host, Url};
22use warp::{Filter, Rejection};
23
24enum DispatchMessage<U: WebDriverExtensionRoute> {
25 HandleWebDriver(
26 WebDriverMessage<U>,
27 Sender<WebDriverResult<WebDriverResponse>>,
28 ),
29 Quit,
30}
31
32#[derive(Clone, Debug, PartialEq)]
33pub enum SessionTeardownKind {
36 Deleted,
38 NotDeleted,
40}
41
42#[derive(Clone, Debug, PartialEq)]
43pub struct Session {
44 pub id: String,
45}
46
47impl Session {
48 fn new(id: String) -> Session {
49 Session { id }
50 }
51}
52
53pub trait WebDriverHandler<U: WebDriverExtensionRoute = VoidWebDriverExtensionRoute>: Send {
54 fn handle_command(
55 &mut self,
56 session: &Option<Session>,
57 msg: WebDriverMessage<U>,
58 ) -> WebDriverResult<WebDriverResponse>;
59 fn teardown_session(&mut self, kind: SessionTeardownKind);
60}
61
62#[derive(Debug)]
63struct Dispatcher<T: WebDriverHandler<U>, U: WebDriverExtensionRoute> {
64 handler: T,
65 session: Option<Session>,
66 extension_type: PhantomData<U>,
67}
68
69impl<T: WebDriverHandler<U>, U: WebDriverExtensionRoute> Dispatcher<T, U> {
70 fn new(handler: T) -> Dispatcher<T, U> {
71 Dispatcher {
72 handler,
73 session: None,
74 extension_type: PhantomData,
75 }
76 }
77
78 fn run(&mut self, msg_chan: &Receiver<DispatchMessage<U>>) {
79 loop {
80 match msg_chan.recv() {
81 Ok(DispatchMessage::HandleWebDriver(msg, resp_chan)) => {
82 let resp = match self.check_session(&msg) {
83 Ok(_) => self.handler.handle_command(&self.session, msg),
84 Err(e) => Err(e),
85 };
86
87 match resp {
88 Ok(WebDriverResponse::NewSession(ref new_session)) => {
89 self.session = Some(Session::new(new_session.session_id.clone()));
90 }
91 Ok(WebDriverResponse::CloseWindow(CloseWindowResponse(ref handles))) => {
92 if handles.is_empty() {
93 debug!("Last window was closed, deleting session");
94 self.teardown_session(SessionTeardownKind::NotDeleted);
97 }
98 }
99 Ok(WebDriverResponse::DeleteSession) => {
100 self.teardown_session(SessionTeardownKind::Deleted);
101 }
102 Err(ref x) if x.delete_session => {
103 self.teardown_session(SessionTeardownKind::NotDeleted)
105 }
106 _ => {}
107 }
108
109 if resp_chan.send(resp).is_err() {
110 error!("Sending response to the main thread failed");
111 };
112 }
113 Ok(DispatchMessage::Quit) => {
114 debug!("Quit signal received, tearing down session");
115 self.teardown_session(SessionTeardownKind::NotDeleted);
116 break;
117 }
118 Err(e) => panic!("Error receiving message in handler: {:?}", e),
119 }
120 }
121 }
122
123 fn teardown_session(&mut self, kind: SessionTeardownKind) {
124 debug!("Teardown session");
125 let final_kind = match kind {
126 SessionTeardownKind::NotDeleted if self.session.is_some() => {
127 let delete_session = WebDriverMessage {
128 session_id: Some(
129 self.session
130 .as_ref()
131 .expect("Failed to get session")
132 .id
133 .clone(),
134 ),
135 command: WebDriverCommand::DeleteSession,
136 };
137 match self.handler.handle_command(&self.session, delete_session) {
138 Ok(_) => SessionTeardownKind::Deleted,
139 Err(_) => SessionTeardownKind::NotDeleted,
140 }
141 }
142 _ => kind,
143 };
144 self.handler.teardown_session(final_kind);
145 self.session = None;
146 }
147
148 fn check_session(&self, msg: &WebDriverMessage<U>) -> WebDriverResult<()> {
149 match msg.session_id {
150 Some(ref msg_session_id) => match self.session {
151 Some(ref existing_session) => {
152 if existing_session.id != *msg_session_id {
153 Err(WebDriverError::new(
154 ErrorStatus::InvalidSessionId,
155 format!("Got unexpected session id {}", msg_session_id),
156 ))
157 } else {
158 Ok(())
159 }
160 }
161 None => Ok(()),
162 },
163 None => {
164 match self.session {
165 Some(_) => {
166 match msg.command {
167 WebDriverCommand::Status => Ok(()),
168 WebDriverCommand::NewSession(_) => Err(WebDriverError::new(
169 ErrorStatus::SessionNotCreated,
170 "Session is already started",
171 )),
172 _ => {
173 error!("Got a message with no session id");
175 Err(WebDriverError::new(
176 ErrorStatus::UnknownError,
177 "Got a command with no session?!",
178 ))
179 }
180 }
181 }
182 None => match msg.command {
183 WebDriverCommand::NewSession(_) => Ok(()),
184 WebDriverCommand::Status => Ok(()),
185 _ => Err(WebDriverError::new(
186 ErrorStatus::InvalidSessionId,
187 "Tried to run a command before creating a session",
188 )),
189 },
190 }
191 }
192 }
193 }
194}
195
196pub struct Listener {
197 server: Option<thread::JoinHandle<()>>,
198 dispatcher: Option<thread::JoinHandle<()>>,
199 pub socket: SocketAddr,
200}
201
202impl Drop for Listener {
203 fn drop(&mut self) {
204 let _ = self.server.take().map(|j| j.join());
205 let _ = self.dispatcher.take().map(|j| j.join());
206 }
207}
208
209#[cfg(unix)]
210struct ShutdownSignal {
211 sigint: Option<tokio::signal::unix::Signal>,
212 sigterm: Option<tokio::signal::unix::Signal>,
213}
214
215#[cfg(unix)]
216impl ShutdownSignal {
217 fn new() -> Self {
218 use tokio::signal::unix::{SignalKind, signal};
219 ShutdownSignal {
220 sigint: signal(SignalKind::interrupt())
221 .map_err(|_| warn!("Failed to register SIGINT handler"))
222 .ok(),
223 sigterm: signal(SignalKind::terminate())
224 .map_err(|_| warn!("Failed to register SIGTERM handler"))
225 .ok(),
226 }
227 }
228
229 async fn recv(&mut self) {
230 tokio::select! {
231 _ = async { self.sigint.as_mut().unwrap().recv().await }, if self.sigint.is_some() => {},
232 _ = async { self.sigterm.as_mut().unwrap().recv().await }, if self.sigterm.is_some() => {},
233 _ = std::future::pending::<()>(), if self.sigint.is_none() && self.sigterm.is_none() => {},
234 }
235 }
236}
237
238#[cfg(windows)]
239struct ShutdownSignal {
240 ctrl_c: Option<tokio::signal::windows::CtrlC>,
241 ctrl_break: Option<tokio::signal::windows::CtrlBreak>,
242}
243
244#[cfg(windows)]
245impl ShutdownSignal {
246 fn new() -> Self {
247 use tokio::signal::windows;
248 ShutdownSignal {
249 ctrl_c: windows::ctrl_c()
250 .map_err(|_| warn!("Failed to register ctrl_c handler"))
251 .ok(),
252 ctrl_break: windows::ctrl_break()
253 .map_err(|_| warn!("Failed to register ctrl_break handler"))
254 .ok(),
255 }
256 }
257
258 async fn recv(&mut self) {
259 tokio::select! {
260 _ = async { self.ctrl_c.as_mut().unwrap().recv().await }, if self.ctrl_c.is_some() => {},
261 _ = async { self.ctrl_break.as_mut().unwrap().recv().await }, if self.ctrl_break.is_some() => {},
262 _ = std::future::pending::<()>(), if self.ctrl_c.is_none() && self.ctrl_break.is_none() => {},
263 }
264 }
265}
266
267pub fn start<T, U>(
268 mut address: SocketAddr,
269 allow_hosts: Vec<Host>,
270 allow_origins: Vec<Url>,
271 handler: T,
272 extension_routes: Vec<(Method, &'static str, U)>,
273) -> ::std::io::Result<Listener>
274where
275 T: 'static + WebDriverHandler<U>,
276 U: 'static + WebDriverExtensionRoute + Send + Sync,
277{
278 let listener = StdTcpListener::bind(address)?;
279 listener.set_nonblocking(true)?;
280 let addr = listener.local_addr()?;
281 if address.port() == 0 {
282 address.set_port(addr.port())
285 }
286 let (msg_send, msg_recv) = channel();
287 let (dispatcher_done_send, dispatcher_done_recv) = tokio::sync::oneshot::channel();
288
289 let builder = thread::Builder::new().name("webdriver server".to_string());
290 let msg_send_signal = msg_send.clone();
291 let server_handle = builder.spawn(move || {
292 let rt = tokio::runtime::Builder::new_current_thread()
293 .enable_io()
294 .build()
295 .unwrap();
296 let listener = rt.block_on(async { TcpListener::from_std(listener).unwrap() });
297 let wroutes = build_warp_routes(
298 address,
299 allow_hosts,
300 allow_origins,
301 &extension_routes,
302 msg_send.clone(),
303 );
304 let fut = warp::serve(wroutes).incoming(listener).run();
305 rt.block_on(async move {
306 let mut shutdown_signal = ShutdownSignal::new();
307 tokio::select! {
308 _ = fut => {}
309 _ = shutdown_signal.recv() => {
310 info!("Shutting down");
311 let _ = msg_send_signal.send(DispatchMessage::Quit);
312 tokio::select! {
313 _ = dispatcher_done_recv => {},
314 _ = shutdown_signal.recv() => {
315 std::process::exit(130);
316 }
317 }
318 }
319 }
320 });
321 })?;
322
323 let builder = thread::Builder::new().name("webdriver dispatcher".to_string());
324 let dispatcher_handle = builder.spawn(move || {
325 let mut dispatcher = Dispatcher::new(handler);
326 dispatcher.run(&msg_recv);
327 let _ = dispatcher_done_send.send(());
328 })?;
329
330 Ok(Listener {
331 server: Some(server_handle),
332 dispatcher: Some(dispatcher_handle),
333 socket: addr,
334 })
335}
336
337fn build_warp_routes<U: 'static + WebDriverExtensionRoute + Send + Sync>(
338 address: SocketAddr,
339 allow_hosts: Vec<Host>,
340 allow_origins: Vec<Url>,
341 ext_routes: &[(Method, &'static str, U)],
342 chan: Sender<DispatchMessage<U>>,
343) -> impl Filter<Extract = (impl warp::Reply,), Error = Rejection> + Clone + 'static {
344 let chan = Arc::new(Mutex::new(chan));
345 let mut std_routes = standard_routes::<U>();
346
347 let (method, path, res) = std_routes.pop().unwrap();
348 trace!("Build standard route for {path}");
349 let mut wroutes = build_route(
350 address,
351 allow_hosts.clone(),
352 allow_origins.clone(),
353 method,
354 path,
355 res,
356 chan.clone(),
357 );
358
359 for (method, path, res) in std_routes {
360 trace!("Build standard route for {path}");
361 wroutes = wroutes
362 .or(build_route(
363 address,
364 allow_hosts.clone(),
365 allow_origins.clone(),
366 method,
367 path,
368 res.clone(),
369 chan.clone(),
370 ))
371 .unify()
372 .boxed()
373 }
374
375 for (method, path, res) in ext_routes {
376 trace!("Build vendor route for {path}");
377 wroutes = wroutes
378 .or(build_route(
379 address,
380 allow_hosts.clone(),
381 allow_origins.clone(),
382 method.clone(),
383 path,
384 Route::Extension(res.clone()),
385 chan.clone(),
386 ))
387 .unify()
388 .boxed()
389 }
390
391 wroutes
392}
393
394fn is_host_allowed(server_address: &SocketAddr, allow_hosts: &[Host], host_header: &str) -> bool {
395 let header_host_url = match Url::parse(&format!("http://{}", &host_header)) {
398 Ok(x) => x,
399 Err(_) => {
400 return false;
401 }
402 };
403
404 let host = match header_host_url.host() {
405 Some(host) => host.to_owned(),
406 None => {
407 return false;
411 }
412 };
413 let port = match header_host_url.port_or_known_default() {
414 Some(port) => port,
415 None => {
416 return false;
420 }
421 };
422
423 let host_matches = match host {
424 Host::Domain(_) => allow_hosts.contains(&host),
425 Host::Ipv4(_) | Host::Ipv6(_) => true,
426 };
427 let port_matches = server_address.port() == port;
428 host_matches && port_matches
429}
430
431fn is_origin_allowed(allow_origins: &[Url], origin_url: Url) -> bool {
432 allow_origins.contains(&origin_url)
434}
435
436fn build_route<U: 'static + WebDriverExtensionRoute + Send + Sync>(
437 server_address: SocketAddr,
438 allow_hosts: Vec<Host>,
439 allow_origins: Vec<Url>,
440 method: Method,
441 path: &'static str,
442 route: Route<U>,
443 chan: Arc<Mutex<Sender<DispatchMessage<U>>>>,
444) -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
445 let mut subroute = match method {
448 Method::GET => warp::get().boxed(),
449 Method::POST => warp::post().boxed(),
450 Method::DELETE => warp::delete().boxed(),
451 Method::OPTIONS => warp::options().boxed(),
452 Method::PUT => warp::put().boxed(),
453 _ => panic!("Unsupported method"),
454 }
455 .or(warp::head())
456 .unify()
457 .map(Parameters::new)
458 .boxed();
459
460 for part in path.split('/') {
464 if part.is_empty() {
465 continue;
466 } else if part.starts_with('{') {
467 assert!(part.ends_with('}'));
468
469 subroute = subroute
470 .and(warp::path::param())
471 .map(move |mut params: Parameters, param: String| {
472 let name = &part[1..part.len() - 1];
473 params.insert(name.to_string(), param);
474 params
475 })
476 .boxed();
477 } else {
478 subroute = subroute.and(warp::path(part)).boxed();
479 }
480 }
481
482 subroute
484 .and(warp::path::end())
485 .and(warp::path::full())
486 .and(warp::method())
487 .and(warp::header::optional::<String>("origin"))
488 .and(warp::header::optional::<String>("host"))
489 .and(warp::header::optional::<String>("content-type"))
490 .and(warp::body::bytes())
491 .map(
492 move |params,
493 full_path: warp::path::FullPath,
494 method,
495 origin_header: Option<String>,
496 host_header: Option<String>,
497 content_type_header: Option<String>,
498 body: Bytes| {
499 if method == Method::HEAD {
500 return warp::reply::with_status("".into(), StatusCode::OK);
501 }
502 if let Some(host) = host_header {
503 if !is_host_allowed(&server_address, &allow_hosts, &host) {
504 warn!(
505 "Rejected request with Host header {}, allowed values are [{}]",
506 host,
507 allow_hosts
508 .iter()
509 .map(|x| format!("{}:{}", x, server_address.port()))
510 .collect::<Vec<_>>()
511 .join(",")
512 );
513 let err = WebDriverError::new(
514 ErrorStatus::UnknownError,
515 format!("Invalid Host header {}", host),
516 );
517 return warp::reply::with_status(
518 serde_json::to_string(&err).unwrap(),
519 StatusCode::INTERNAL_SERVER_ERROR,
520 );
521 };
522 } else {
523 warn!("Rejected request with missing Host header");
524 let err = WebDriverError::new(
525 ErrorStatus::UnknownError,
526 "Missing Host header".to_string(),
527 );
528 return warp::reply::with_status(
529 serde_json::to_string(&err).unwrap(),
530 StatusCode::INTERNAL_SERVER_ERROR,
531 );
532 }
533 if let Some(origin) = origin_header {
534 let make_err = || {
535 warn!(
536 "Rejected request with Origin header {}, allowed values are [{}]",
537 origin,
538 allow_origins
539 .iter()
540 .map(|x| x.to_string())
541 .collect::<Vec<_>>()
542 .join(",")
543 );
544 WebDriverError::new(
545 ErrorStatus::UnknownError,
546 format!("Invalid Origin header {}", origin),
547 )
548 };
549 let origin_url = match Url::parse(&origin) {
550 Ok(url) => url,
551 Err(_) => {
552 return warp::reply::with_status(
553 serde_json::to_string(&make_err()).unwrap(),
554 StatusCode::INTERNAL_SERVER_ERROR,
555 );
556 }
557 };
558 if !is_origin_allowed(&allow_origins, origin_url) {
559 return warp::reply::with_status(
560 serde_json::to_string(&make_err()).unwrap(),
561 StatusCode::INTERNAL_SERVER_ERROR,
562 );
563 }
564 }
565 if method == Method::POST {
566 let content_type = content_type_header
569 .as_ref()
570 .map(|x| x.find(';').and_then(|idx| x.get(0..idx)).unwrap_or(x))
571 .map(|x| x.trim())
572 .map(|x| x.to_lowercase());
573 match content_type.as_ref().map(|x| x.as_ref()) {
574 Some("application/x-www-form-urlencoded")
575 | Some("multipart/form-data")
576 | Some("text/plain") => {
577 warn!(
578 "Rejected POST request with disallowed content type {}",
579 content_type.unwrap_or_else(|| "".into())
580 );
581 let err = WebDriverError::new(
582 ErrorStatus::UnknownError,
583 "Invalid Content-Type",
584 );
585 return warp::reply::with_status(
586 serde_json::to_string(&err).unwrap(),
587 StatusCode::INTERNAL_SERVER_ERROR,
588 );
589 }
590 Some(_) | None => {}
591 }
592 }
593 let body = String::from_utf8(body.chunk().to_vec());
594 if body.is_err() {
595 let err = WebDriverError::new(
596 ErrorStatus::UnknownError,
597 "Request body wasn't valid UTF-8",
598 );
599 return warp::reply::with_status(
600 serde_json::to_string(&err).unwrap(),
601 StatusCode::INTERNAL_SERVER_ERROR,
602 );
603 }
604 let body = body.unwrap();
605
606 debug!("-> {} {} {}", method, full_path.as_str(), body);
607 let msg_result = WebDriverMessage::from_http(
608 route.clone(),
609 ¶ms,
610 &body,
611 method == Method::POST,
612 );
613
614 let (status, resp_body) = match msg_result {
615 Ok(message) => {
616 let (send_res, recv_res) = channel();
617 match chan.lock() {
618 Ok(ref c) => {
619 let res =
620 c.send(DispatchMessage::HandleWebDriver(message, send_res));
621 match res {
622 Ok(x) => x,
623 Err(e) => panic!("Error: {:?}", e),
624 }
625 }
626 Err(e) => panic!("Error reading response: {:?}", e),
627 }
628
629 match recv_res.recv() {
630 Ok(data) => match data {
631 Ok(response) => {
632 (StatusCode::OK, serde_json::to_string(&response).unwrap())
633 }
634 Err(e) => (e.http_status(), serde_json::to_string(&e).unwrap()),
635 },
636 Err(e) => panic!("Error reading response: {:?}", e),
637 }
638 }
639 Err(e) => (e.http_status(), serde_json::to_string(&e).unwrap()),
640 };
641
642 debug!("<- {} {}", status, resp_body);
643 warp::reply::with_status(resp_body, status)
644 },
645 )
646 .with(warp::reply::with::header(
647 http::header::CONTENT_TYPE,
648 "application/json; charset=utf-8",
649 ))
650 .with(warp::reply::with::header(
651 http::header::CACHE_CONTROL,
652 "no-cache",
653 ))
654 .boxed()
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660 use std::net::IpAddr;
661 use std::str::FromStr;
662
663 #[test]
664 fn test_host_allowed() {
665 let addr_80 = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), 80);
666 let addr_8000 = SocketAddr::new(IpAddr::from_str("127.0.0.1").unwrap(), 8000);
667 let addr_v6_80 = SocketAddr::new(IpAddr::from_str("::1").unwrap(), 80);
668 let addr_v6_8000 = SocketAddr::new(IpAddr::from_str("::1").unwrap(), 8000);
669
670 let localhost_host = Host::Domain("localhost".to_string());
672 let test_host = Host::Domain("example.test".to_string());
673 let subdomain_localhost_host = Host::Domain("subdomain.localhost".to_string());
674
675 assert!(is_host_allowed(
676 &addr_80,
677 &[localhost_host.clone()],
678 "localhost:80"
679 ));
680 assert!(is_host_allowed(
681 &addr_80,
682 &[test_host.clone()],
683 "example.test:80"
684 ));
685 assert!(is_host_allowed(
686 &addr_80,
687 &[test_host.clone(), localhost_host.clone()],
688 "example.test"
689 ));
690 assert!(is_host_allowed(
691 &addr_80,
692 &[subdomain_localhost_host.clone()],
693 "subdomain.localhost"
694 ));
695
696 assert!(is_host_allowed(&addr_80, &[], "127.0.0.1:80"));
698 assert!(is_host_allowed(&addr_v6_80, &[], "127.0.0.1"));
699 assert!(is_host_allowed(&addr_80, &[], "[::1]"));
700 assert!(is_host_allowed(&addr_8000, &[], "127.0.0.1:8000"));
701 assert!(is_host_allowed(
702 &addr_80,
703 &[subdomain_localhost_host.clone()],
704 "[::1]"
705 ));
706 assert!(is_host_allowed(
707 &addr_v6_8000,
708 &[subdomain_localhost_host.clone()],
709 "[::1]:8000"
710 ));
711
712 assert!(!is_host_allowed(&addr_80, &[test_host], "localhost"));
715
716 assert!(!is_host_allowed(&addr_80, &[], "localhost:80"));
717
718 assert!(!is_host_allowed(
721 &addr_80,
722 &[localhost_host.clone()],
723 "localhost:8000"
724 ));
725 assert!(!is_host_allowed(
726 &addr_8000,
727 &[localhost_host.clone()],
728 "localhost"
729 ));
730 assert!(!is_host_allowed(
731 &addr_v6_8000,
732 &[localhost_host.clone()],
733 "[::1]"
734 ));
735 }
736
737 #[test]
738 fn test_origin_allowed() {
739 assert!(is_origin_allowed(
740 &[Url::parse("http://localhost").unwrap()],
741 Url::parse("http://localhost").unwrap()
742 ));
743 assert!(is_origin_allowed(
744 &[Url::parse("http://localhost").unwrap()],
745 Url::parse("http://localhost:80").unwrap()
746 ));
747 assert!(is_origin_allowed(
748 &[
749 Url::parse("https://test.example").unwrap(),
750 Url::parse("http://localhost").unwrap()
751 ],
752 Url::parse("http://localhost").unwrap()
753 ));
754 assert!(is_origin_allowed(
755 &[
756 Url::parse("https://test.example").unwrap(),
757 Url::parse("http://localhost").unwrap()
758 ],
759 Url::parse("https://test.example:443").unwrap()
760 ));
761 assert!(!is_origin_allowed(
763 &[],
764 Url::parse("http://localhost").unwrap()
765 ));
766 assert!(!is_origin_allowed(
767 &[Url::parse("http://localhost").unwrap()],
768 Url::parse("http://localhost:8000").unwrap()
769 ));
770 assert!(!is_origin_allowed(
771 &[Url::parse("https://localhost").unwrap()],
772 Url::parse("http://localhost").unwrap()
773 ));
774 assert!(!is_origin_allowed(
775 &[Url::parse("https://example.test").unwrap()],
776 Url::parse("http://subdomain.example.test").unwrap()
777 ));
778 }
779}