Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basti/nativeMessaging #10196

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any way it's ever installed to a different directory?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we don't offer other ways of installation, no :)


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"))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After doing a bit of Googling, I think the way to handle this on Linux would be to use a deep-link to try and launch the VPN client. This could then be invoked using the xdg-open tool. A similar approach could also be done on Windows too, but the invocation would be different.

This is not a request to change anything here, we can tackle that in a future PR.

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
Loading