diff --git a/Cargo.lock b/Cargo.lock index 1a0d829d387..a307e7ee0b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "msg", "serde", "serde_json", + "servo_rand", "servo_url", "time", "uuid", diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index 4cd41deec68..6a6532df54a 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -22,6 +22,7 @@ log = "0.4" msg = { path = "../msg" } serde = "1.0" serde_json = "1.0" +servo_rand = { path = "../rand" } servo_url = { path = "../url" } time = "0.1" uuid = { version = "0.8", features = ["v4"] } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 2227be98549..aafd8860590 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -39,9 +39,11 @@ use devtools_traits::{PageError, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{EmbedderMsg, EmbedderProxy, PromptDefinition, PromptOrigin, PromptResult}; use ipc_channel::ipc::{self, IpcSender}; use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use servo_rand::RngCore; use std::borrow::ToOwned; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; +use std::io::Read; use std::net::{Shutdown, TcpListener, TcpStream}; use std::sync::{Arc, Mutex}; use std::thread; @@ -137,8 +139,11 @@ fn run_server( .map(|port| (l, port)) }); + // A token shared with the embedder to bypass permission prompt. + let token = format!("{:X}", servo_rand::ServoRng::new().next_u32()); + let port = bound.as_ref().map(|(_, port)| *port).ok_or(()); - embedder.send((None, EmbedderMsg::OnDevtoolsStarted(port))); + embedder.send((None, EmbedderMsg::OnDevtoolsStarted(port, token.clone()))); let listener = match bound { Some((l, _)) => l, @@ -563,20 +568,14 @@ fn run_server( .spawn(move || { // accept connections and process them, spawning a new thread for each one for stream in listener.incoming() { - // Prompt user for permission - let (embedder_sender, receiver) = - ipc::channel().expect("Failed to create IPC channel!"); - let message = "Accept incoming devtools connection?".to_owned(); - let prompt = PromptDefinition::YesNo(message, embedder_sender); - let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Trusted); - embedder.send((None, msg)); - if receiver.recv().unwrap() != PromptResult::Primary { + let mut stream = stream.expect("Can't retrieve stream"); + if !allow_devtools_client(&mut stream, &embedder, &token) { continue; - } + }; // connection succeeded and accepted sender .send(DevtoolsControlMsg::FromChrome( - ChromeToDevtoolsControlMsg::AddClient(stream.unwrap()), + ChromeToDevtoolsControlMsg::AddClient(stream), )) .unwrap(); } @@ -704,3 +703,33 @@ fn run_server( let _ = connection.shutdown(Shutdown::Both); } } + +fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool { + // By-pass prompt if we receive a valid token. + let token = format!("25:{{\"auth_token\":\"{}\"}}", token); + let mut buf = [0; 28]; + let timeout = std::time::Duration::from_millis(500); + // This will read but not consume the bytes from the stream. + stream.set_read_timeout(Some(timeout)).unwrap(); + let peek = stream.peek(&mut buf); + stream.set_read_timeout(None).unwrap(); + if let Ok(len) = peek { + if len == buf.len() { + if let Ok(s) = std::str::from_utf8(&buf) { + if s == token { + // Consume the message as it was relevant to us. + let _ = stream.read_exact(&mut buf); + return true; + } + } + } + }; + + // No token found. Prompt user + let (embedder_sender, receiver) = ipc::channel().expect("Failed to create IPC channel!"); + let message = "Accept incoming devtools connection?".to_owned(); + let prompt = PromptDefinition::YesNo(message, embedder_sender); + let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Trusted); + embedder.send((None, msg)); + receiver.recv().unwrap() == PromptResult::Primary +} diff --git a/components/embedder_traits/lib.rs b/components/embedder_traits/lib.rs index 7e76a3f2446..1d16e3d13bc 100644 --- a/components/embedder_traits/lib.rs +++ b/components/embedder_traits/lib.rs @@ -207,8 +207,8 @@ pub enum EmbedderMsg { /// Notifies the embedder about media session events /// (i.e. when there is metadata for the active media session, playback state changes...). MediaSessionEvent(MediaSessionEvent), - /// Report the status of Devtools Server - OnDevtoolsStarted(Result), + /// Report the status of Devtools Server with a token that can be used to bypass the permission prompt. + OnDevtoolsStarted(Result, String), } impl Debug for EmbedderMsg { diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index 7dd1881243f..cc2d84f9814 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -146,7 +146,7 @@ pub trait HostTrait { /// Called when the media session position state is set. fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64); /// Called when devtools server is started - fn on_devtools_started(&self, port: Result); + fn on_devtools_started(&self, port: Result, token: String); } pub struct ServoGlue { @@ -751,8 +751,10 @@ impl ServoGlue { ), }; }, - EmbedderMsg::OnDevtoolsStarted(port) => { - self.callbacks.host_callbacks.on_devtools_started(port); + EmbedderMsg::OnDevtoolsStarted(port, token) => { + self.callbacks + .host_callbacks + .on_devtools_started(port, token); }, EmbedderMsg::Status(..) | EmbedderMsg::SelectFiles(..) | diff --git a/ports/libsimpleservo/capi/src/lib.rs b/ports/libsimpleservo/capi/src/lib.rs index e37ee89178e..1730f1c1cf2 100644 --- a/ports/libsimpleservo/capi/src/lib.rs +++ b/ports/libsimpleservo/capi/src/lib.rs @@ -227,7 +227,8 @@ pub struct CHostCallbacks { default: *const c_char, trusted: bool, ) -> *const c_char, - pub on_devtools_started: extern "C" fn(result: CDevtoolsServerState, port: c_uint), + pub on_devtools_started: + extern "C" fn(result: CDevtoolsServerState, port: c_uint, token: *const c_char), pub show_context_menu: extern "C" fn(title: *const c_char, items_list: *const *const c_char, items_size: u32), pub on_log_output: extern "C" fn(buffer: *const c_char, buffer_length: u32), @@ -883,15 +884,20 @@ impl HostTrait for HostCallbacks { Some(contents_str.to_owned()) } - fn on_devtools_started(&self, port: Result) { + fn on_devtools_started(&self, port: Result, token: String) { + let token = CString::new(token).expect("Can't create string"); match port { Ok(p) => { info!("Devtools Server running on port {}", p); - (self.0.on_devtools_started)(CDevtoolsServerState::Started, p.into()); + (self.0.on_devtools_started)( + CDevtoolsServerState::Started, + p.into(), + token.as_ptr(), + ); }, Err(()) => { error!("Error running devtools server"); - (self.0.on_devtools_started)(CDevtoolsServerState::Error, 0); + (self.0.on_devtools_started)(CDevtoolsServerState::Error, 0, token.as_ptr()); }, } } diff --git a/ports/winit/browser.rs b/ports/winit/browser.rs index 6371a39c056..08e2c501f48 100644 --- a/ports/winit/browser.rs +++ b/ports/winit/browser.rs @@ -515,7 +515,7 @@ where debug!("MediaSessionEvent received"); // TODO(ferjm): MediaSession support for winit based browsers. }, - EmbedderMsg::OnDevtoolsStarted(port) => { + EmbedderMsg::OnDevtoolsStarted(port, _token) => { match port { Ok(p) => info!("Devtools Server running on port {}", p), Err(()) => error!("Error running devtools server"), diff --git a/support/hololens/ServoApp/BrowserPage.cpp b/support/hololens/ServoApp/BrowserPage.cpp index e4f460d3497..cc4b811b4ba 100644 --- a/support/hololens/ServoApp/BrowserPage.cpp +++ b/support/hololens/ServoApp/BrowserPage.cpp @@ -80,9 +80,10 @@ void BrowserPage::BindServoEvents() { : Visibility::Visible); }); servoControl().OnDevtoolsStatusChanged( - [=](DevtoolsStatus status, unsigned int port) { + [=](DevtoolsStatus status, unsigned int port, hstring token) { mDevtoolsStatus = status; mDevtoolsPort = port; + mDevtoolsToken = token; }); Window::Current().VisibilityChanged( [=](const auto &, const VisibilityChangedEventArgs &args) { @@ -318,8 +319,8 @@ void BrowserPage::OnDevtoolsButtonClicked(IInspectable const &, hstring port = to_hstring(mDevtoolsPort); if (mDevtoolsClient == nullptr) { DevtoolsDelegate *dd = static_cast(this); - mDevtoolsClient = - std::make_unique(L"localhost", port, *dd); + mDevtoolsClient = std::make_unique(L"localhost", port, + mDevtoolsToken, *dd); } mDevtoolsClient->Run(); std::wstring message = diff --git a/support/hololens/ServoApp/BrowserPage.h b/support/hololens/ServoApp/BrowserPage.h index 454baee9076..74f2b57ee57 100644 --- a/support/hololens/ServoApp/BrowserPage.h +++ b/support/hololens/ServoApp/BrowserPage.h @@ -57,6 +57,7 @@ private: void BuildPrefList(); DevtoolsStatus mDevtoolsStatus = DevtoolsStatus::Stopped; unsigned int mDevtoolsPort = 0; + hstring mDevtoolsToken; std::unique_ptr mDevtoolsClient; Collections::IObservableVector mLogs; }; diff --git a/support/hololens/ServoApp/Devtools/Client.cpp b/support/hololens/ServoApp/Devtools/Client.cpp index 65fb51df892..b3bc3b9c7b6 100644 --- a/support/hololens/ServoApp/Devtools/Client.cpp +++ b/support/hololens/ServoApp/Devtools/Client.cpp @@ -31,6 +31,11 @@ void DevtoolsClient::Run() { connecting.Completed([=](const auto &, const auto &) { mDataReader = DataReader(socket.InputStream()); mDataWriter = DataWriter(socket.OutputStream()); + + JsonObject out; + out.Insert(L"auth_token", JsonValue::CreateStringValue(mToken)); + Send(out); + mReceiveOp = {Loop()}; mReceiveOp->Completed([=](const auto &, const auto &) { mReceiveOp = {}; diff --git a/support/hololens/ServoApp/Devtools/Client.h b/support/hololens/ServoApp/Devtools/Client.h index 67e11c3fe79..ad3a919577f 100644 --- a/support/hololens/ServoApp/Devtools/Client.h +++ b/support/hololens/ServoApp/Devtools/Client.h @@ -19,8 +19,9 @@ enum DevtoolsMessageLevel { Error, Warn, None }; class DevtoolsClient { public: - DevtoolsClient(hstring hostname, hstring port, DevtoolsDelegate &d) - : mDelegate(d), mHostname(hostname), mPort(port){}; + DevtoolsClient(hstring hostname, hstring port, hstring token, + DevtoolsDelegate &d) + : mDelegate(d), mHostname(hostname), mToken(token), mPort(port){}; ~DevtoolsClient() { Stop(); } void Run(); @@ -30,6 +31,7 @@ public: private: hstring mPort; + hstring mToken; hstring mHostname; DevtoolsDelegate &mDelegate; std::optional mDataReader; diff --git a/support/hololens/ServoApp/ServoControl/Servo.cpp b/support/hololens/ServoApp/ServoControl/Servo.cpp index b7569a244e5..57f9a753619 100644 --- a/support/hololens/ServoApp/ServoControl/Servo.cpp +++ b/support/hololens/ServoApp/ServoControl/Servo.cpp @@ -83,9 +83,9 @@ void show_context_menu(const char *title, const char *const *items_list, } void on_devtools_started(Servo::DevtoolsServerState result, - const unsigned int port) { - sServo->Delegate().OnServoDevtoolsStarted( - result == Servo::DevtoolsServerState::Started, port); + const unsigned int port, const char *token) { + auto state = result == Servo::DevtoolsServerState::Started; + sServo->Delegate().OnServoDevtoolsStarted(state, port, char2hstring(token)); } void on_log_output(const char *buffer, uint32_t buffer_length) { diff --git a/support/hololens/ServoApp/ServoControl/Servo.h b/support/hololens/ServoApp/ServoControl/Servo.h index 7484c778f88..d662942f1da 100644 --- a/support/hololens/ServoApp/ServoControl/Servo.h +++ b/support/hololens/ServoApp/ServoControl/Servo.h @@ -108,7 +108,7 @@ public: virtual bool OnServoAllowNavigation(hstring) = 0; virtual void OnServoAnimatingChanged(bool) = 0; virtual void OnServoIMEStateChanged(bool) = 0; - virtual void OnServoDevtoolsStarted(bool, const unsigned int) = 0; + virtual void OnServoDevtoolsStarted(bool, const unsigned int, hstring) = 0; virtual void OnServoMediaSessionMetadata(hstring, hstring, hstring) = 0; virtual void OnServoMediaSessionPlaybackStateChange(int) = 0; virtual void OnServoPromptAlert(hstring, bool) = 0; diff --git a/support/hololens/ServoApp/ServoControl/ServoControl.cpp b/support/hololens/ServoApp/ServoControl/ServoControl.cpp index 4f07ecd985e..1b9fb910d6e 100644 --- a/support/hololens/ServoApp/ServoControl/ServoControl.cpp +++ b/support/hololens/ServoApp/ServoControl/ServoControl.cpp @@ -571,11 +571,11 @@ std::optional ServoControl::OnServoPromptInput(winrt::hstring message, return string; } -void ServoControl::OnServoDevtoolsStarted(bool success, - const unsigned int port) { +void ServoControl::OnServoDevtoolsStarted(bool success, const unsigned int port, + hstring token) { RunOnUIThread([=] { auto status = success ? DevtoolsStatus::Running : DevtoolsStatus::Failed; - mOnDevtoolsStatusChangedEvent(status, port); + mOnDevtoolsStatusChangedEvent(status, port, token); }); } diff --git a/support/hololens/ServoApp/ServoControl/ServoControl.h b/support/hololens/ServoApp/ServoControl/ServoControl.h index 0804af7adca..56d32e2a12c 100644 --- a/support/hololens/ServoApp/ServoControl/ServoControl.h +++ b/support/hololens/ServoApp/ServoControl/ServoControl.h @@ -185,7 +185,7 @@ struct ServoControl : ServoControlT, public servo::ServoDelegate { virtual servo::Servo::PromptResult OnServoPromptYesNo(winrt::hstring, bool); virtual std::optional OnServoPromptInput(winrt::hstring, winrt::hstring, bool); - virtual void OnServoDevtoolsStarted(bool, const unsigned int); + virtual void OnServoDevtoolsStarted(bool, const unsigned int, winrt::hstring); DevtoolsStatus GetDevtoolsStatus(); diff --git a/support/hololens/ServoApp/ServoControl/ServoControl.idl b/support/hololens/ServoApp/ServoControl/ServoControl.idl index 99f411a9be4..72568edf3f8 100644 --- a/support/hololens/ServoApp/ServoControl/ServoControl.idl +++ b/support/hololens/ServoApp/ServoControl/ServoControl.idl @@ -3,7 +3,7 @@ namespace ServoApp { delegate void EventDelegate(); delegate void HistoryChangedDelegate(Boolean back, Boolean forward); delegate void MediaSessionMetadataDelegate(String title, String artist, String album); - delegate void DevtoolsStatusChangedDelegate(DevtoolsStatus status, UInt32 port); + delegate void DevtoolsStatusChangedDelegate(DevtoolsStatus status, UInt32 port, String token); enum DevtoolsStatus { Running = 0,