Skip to content

Commit

Permalink
basti/nativeMessaging (#10196)
Browse files Browse the repository at this point in the history
* Fix the command handling of NM

* Fix tests

* Add the command to start

* Thanks, clippy!

* fix non windows

* formatting

* casing
  • Loading branch information
strseb authored Feb 4, 2025
1 parent 878f8e3 commit 8a7cffc
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 135 deletions.
78 changes: 78 additions & 0 deletions extension/bridge/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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<bool,Box<dyn Error>>{
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!'}")
}
}
127 changes: 127 additions & 0 deletions extension/bridge/src/io.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
length: usize,
}

impl Reader {
pub fn new() -> Reader {
Reader {
state: ReaderState::ReadingLength,
buffer: Vec::new(),
length: 0,
}
}

pub fn read_input<R: Read>(&mut self, mut input: R) -> Option<Value> {
// Until we are able to read things from the stream...
loop {
if self.state == ReaderState::ReadingLength {
assert!(self.buffer.len() < size_of::<u32>());

let mut buffer = vec![0; size_of::<u32>() - 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::<u32>() {
continue;
}

// Let's convert our buffer into a u32.
let mut rdr = Cursor::new(&self.buffer);
self.length = rdr.read_u32::<NativeEndian>().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<W: Write>(mut output: W, value: &Value) -> Result<(), std::io::Error> {
let msg = serde_json::to_string(value)?;
let len = msg.len();
output.write_u32::<NativeEndian>(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");
}
Loading

0 comments on commit 8a7cffc

Please sign in to comment.