diff --git a/extension/bridge/src/commands.rs b/extension/bridge/src/commands.rs new file mode 100644 index 0000000000..a6d0260f8d --- /dev/null +++ b/extension/bridge/src/commands.rs @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + use std::error::Error; + use serde_json::{Value, json}; + + /** + * Handles commands that are sent from + * [Extension] === > [NativeMessagingBridge] + * + * Returns true if the command was handled, in which case it should + * *not* be forwarded to the VPN Client. + * + * Will attempt to print to STDOUT in case a command needs a response. + * + */ + pub fn handle(val:&Value)-> Result>{ + let obj = val.as_object().ok_or("Not an object")?; + // Type of command is in {t:'doThing'} + let cmd = obj.get_key_value("t").ok_or("Missing obj.t")?; + + match cmd.1.as_str().ok_or("T is not a string")? { + "bridge_ping" =>{ + crate::io::write_output(std::io::stdout(),&json!({"status": "bridge_pong"})) + .expect("Unable to Write to STDOUT?"); + Ok(true) + } + "start" =>{ + let out = launcher::start_vpn(); + crate::io::write_output(std::io::stdout(),&out) + .expect("Unable to Write to STDOUT?"); + Ok(true) + } + _ =>{ + // We did not handle this. + Ok(false) + } + } + } + + +#[cfg(target_os = "windows")] +mod launcher { + const CLIENT_PATH: &str = "C:\\Program Files\\Mozilla\\Mozilla VPN\\Mozilla VPN.exe"; + + use std::os::windows::process::CommandExt; + use std::process::Command; + + use serde_json::json; + + const CREATE_NEW_PROCESS_GROUP: u32 = 0x200; // CREATE_NEW_PROCESS_GROUP + const DETACHED_PROCESS: u32 = 0x00000008; // DETACHED_PROCESS + + pub fn start_vpn() -> serde_json::Value{ + let result = Command::new(CLIENT_PATH) + .args(["-foreground"]) + .creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS) + .spawn(); + + match result { + Ok(_) => json!("{status:'requested_start'}"), + Err(_) => json!("{error:'start_failed'}"), + } + } + + +} + +#[cfg(not(target_os = "windows"))] +mod launcher { + use serde_json::json; + pub fn start_vpn() -> serde_json::Value{ + json!("{error:'start_unsupported!'}") + } +} diff --git a/extension/bridge/src/io.rs b/extension/bridge/src/io.rs new file mode 100644 index 0000000000..c40ada709a --- /dev/null +++ b/extension/bridge/src/io.rs @@ -0,0 +1,127 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; + + + use serde_json::{json, Value}; + use std::io::{Cursor, Read, Write}; + use std::mem::size_of; + + + + + + + +#[derive(PartialEq)] +enum ReaderState { + ReadingLength, + ReadingBuffer, +} + +pub struct Reader { + state: ReaderState, + buffer: Vec, + length: usize, +} + +impl Reader { + pub fn new() -> Reader { + Reader { + state: ReaderState::ReadingLength, + buffer: Vec::new(), + length: 0, + } + } + + pub fn read_input(&mut self, mut input: R) -> Option { + // Until we are able to read things from the stream... + loop { + if self.state == ReaderState::ReadingLength { + assert!(self.buffer.len() < size_of::()); + + let mut buffer = vec![0; size_of::() - self.buffer.len()]; + match input.read(&mut buffer) { + Ok(size) => { + // Maybe we have read just part of the buffer. Let's append + // only what we have been read. + buffer.truncate(size); + self.buffer.append(&mut buffer); + + // Not enough data yet. + if self.buffer.len() < size_of::() { + continue; + } + + // Let's convert our buffer into a u32. + let mut rdr = Cursor::new(&self.buffer); + self.length = rdr.read_u32::().unwrap() as usize; + if self.length == 0 { + continue; + } + + self.state = ReaderState::ReadingBuffer; + self.buffer = Vec::with_capacity(self.length); + } + _ => return None, + } + } + + if self.state == ReaderState::ReadingBuffer { + assert!(self.length > 0); + assert!(self.buffer.len() < self.length); + + let mut buffer = vec![0; self.length - self.buffer.len()]; + match input.read(&mut buffer) { + Ok(size) => { + // Maybe we have read just part of the buffer. Let's append + // only what we have been read. + buffer.truncate(size); + self.buffer.append(&mut buffer); + + // Not enough data yet. + if self.buffer.len() < self.length { + continue; + } + + match serde_json::from_slice(&self.buffer) { + Ok(value) => { + self.buffer.clear(); + self.state = ReaderState::ReadingLength; + return Some(value); + } + _ => { + self.buffer.clear(); + self.state = ReaderState::ReadingLength; + continue; + } + } + } + _ => return None, + } + } + } + } +} + +pub fn write_output(mut output: W, value: &Value) -> Result<(), std::io::Error> { + let msg = serde_json::to_string(value)?; + let len = msg.len(); + output.write_u32::(len as u32)?; + output.write_all(msg.as_bytes())?; + output.flush()?; + Ok(()) +} + +pub fn write_vpn_down(error: bool) { + let field = if error { "error" } else { "status" }; + let value = json!({field: "vpn-client-down"}); + write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); +} + +pub fn write_vpn_up() { + let value = json!({"status": "vpn-client-up"}); + write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); +} diff --git a/extension/bridge/src/main.rs b/extension/bridge/src/main.rs index 9af3cfdb1a..da427358a3 100644 --- a/extension/bridge/src/main.rs +++ b/extension/bridge/src/main.rs @@ -2,17 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; use mio::net::TcpStream; use mio::{Events, Interest, Poll, Token, Waker}; -use serde_json::{json, Value}; -use std::io::{Cursor, Read, Write}; -use std::mem::size_of; use std::sync::mpsc::channel; use std::sync::Arc; use std::{thread, time}; use std::env; +mod commands; +mod io; + const SERVER_AND_PORT: &str = "127.0.0.1:8754"; const ALLOW_LISTED_WEBEXTENSIONS: [&str;2] = [ @@ -20,116 +19,6 @@ const ALLOW_LISTED_WEBEXTENSIONS: [&str;2] = [ "vpn@mozilla.com" ]; -#[derive(PartialEq)] -enum ReaderState { - ReadingLength, - ReadingBuffer, -} - -pub struct Reader { - state: ReaderState, - buffer: Vec, - length: usize, -} - -impl Reader { - pub fn new() -> Reader { - Reader { - state: ReaderState::ReadingLength, - buffer: Vec::new(), - length: 0, - } - } - - pub fn read_input(&mut self, mut input: R) -> Option { - // Until we are able to read things from the stream... - loop { - if self.state == ReaderState::ReadingLength { - assert!(self.buffer.len() < size_of::()); - - let mut buffer = vec![0; size_of::() - self.buffer.len()]; - match input.read(&mut buffer) { - Ok(size) => { - // Maybe we have read just part of the buffer. Let's append - // only what we have been read. - buffer.truncate(size); - self.buffer.append(&mut buffer); - - // Not enough data yet. - if self.buffer.len() < size_of::() { - continue; - } - - // Let's convert our buffer into a u32. - let mut rdr = Cursor::new(&self.buffer); - self.length = rdr.read_u32::().unwrap() as usize; - if self.length == 0 { - continue; - } - - self.state = ReaderState::ReadingBuffer; - self.buffer = Vec::with_capacity(self.length); - } - _ => return None, - } - } - - if self.state == ReaderState::ReadingBuffer { - assert!(self.length > 0); - assert!(self.buffer.len() < self.length); - - let mut buffer = vec![0; self.length - self.buffer.len()]; - match input.read(&mut buffer) { - Ok(size) => { - // Maybe we have read just part of the buffer. Let's append - // only what we have been read. - buffer.truncate(size); - self.buffer.append(&mut buffer); - - // Not enough data yet. - if self.buffer.len() < self.length { - continue; - } - - match serde_json::from_slice(&self.buffer) { - Ok(value) => { - self.buffer.clear(); - self.state = ReaderState::ReadingLength; - return Some(value); - } - _ => { - self.buffer.clear(); - self.state = ReaderState::ReadingLength; - continue; - } - } - } - _ => return None, - } - } - } - } -} - -fn write_output(mut output: W, value: &Value) -> Result<(), std::io::Error> { - let msg = serde_json::to_string(value)?; - let len = msg.len(); - output.write_u32::(len as u32)?; - output.write_all(msg.as_bytes())?; - output.flush()?; - Ok(()) -} - -fn write_vpn_down(error: bool) { - let field = if error { "error" } else { "status" }; - let value = json!({field: "vpn-client-down"}); - write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); -} - -fn write_vpn_up() { - let value = json!({"status": "vpn-client-up"}); - write_output(std::io::stdout(), &value).expect("Unable to write to STDOUT"); -} fn main() { /* @@ -168,24 +57,23 @@ fn main() { let waker = Arc::new(Waker::new(poll.registry(), STDIN_WAKER).unwrap()); let waker_cloned = waker.clone(); thread::spawn(move || { - let mut r = Reader::new(); + let mut r = io::Reader::new(); loop { match r.read_input(std::io::stdin()) { Some(value) => { - if value == "bridge_ping" { - // A simple ping/pong message. - let pong_value = json!("bridge_pong"); - write_output(std::io::stdout(), &pong_value) - .expect("Unable to write to STDOUT"); - } else { - // For a real message, we wake up the main thread. - sender - .send(value) - .expect("Unable to send data to the main thread"); - waker_cloned.wake().expect("Unable to wake the main thread"); + match commands::handle(&value){ + Ok(true) =>{ + // Command was handled successfully. + } + _ =>{ + // For a real message, we wake up the main thread. + sender + .send(value) + .expect("Unable to send data to the main thread"); + waker_cloned.wake().expect("Unable to wake the main thread"); + } } } - None => { thread::sleep(time::Duration::from_millis(500)); } @@ -204,7 +92,7 @@ fn main() { .register(&mut stream, VPN, Interest::READABLE | Interest::WRITABLE) .unwrap(); - let mut r = Reader::new(); + let mut r = io::Reader::new(); // This second loop processes messages coming from the tcp stream and from the // STDIN thread via the waker/sender. @@ -221,7 +109,7 @@ fn main() { { poll.registry().deregister(&mut stream).unwrap(); vpn_connected = false; - write_vpn_down(false); + io::write_vpn_down(false); thread::sleep(time::Duration::from_millis(500)); new_connection_needed = true; } @@ -233,7 +121,7 @@ fn main() { if !vpn_connected && (event.is_readable() || event.is_writable()) { vpn_connected = true; - write_vpn_up(); + io::write_vpn_up(); } if event.is_readable() { @@ -241,7 +129,7 @@ fn main() { loop { match r.read_input(&mut stream) { Some(value) => { - write_output(std::io::stdout(), &value) + io::write_output(std::io::stdout(), &value) .expect("Unable to write to STDOUT"); } _ => { @@ -253,8 +141,8 @@ fn main() { } STDIN_WAKER => { let value = receiver.recv().unwrap(); - if let Err(_) = write_output(&mut stream, &value) { - write_vpn_down(true); + if let Err(_) = io::write_output(&mut stream, &value) { + io::write_vpn_down(true); } } _ => unreachable!(), diff --git a/tests/nativemessaging/testbridge.cpp b/tests/nativemessaging/testbridge.cpp index d4e23bd188..b44f1f1973 100644 --- a/tests/nativemessaging/testbridge.cpp +++ b/tests/nativemessaging/testbridge.cpp @@ -4,14 +4,17 @@ #include "testbridge.h" +#include + #include "helperserver.h" void TestBridge::bridge_ping() { QVERIFY(s_nativeMessagingProcess); // A simple ping/pong. - QVERIFY(write("\"bridge_ping\"")); - QCOMPARE(readIgnoringStatus(), "\"bridge_pong\""); + QVERIFY(write(R"({"t": "bridge_ping"})")); + auto const json = QJsonDocument::fromJson(readIgnoringStatus()); + QCOMPARE(json["status"].toString(), "bridge_pong"); } void TestBridge::app_ping_failure() {