Skip to content
This repository has been archived by the owner on Sep 7, 2024. It is now read-only.

Commit

Permalink
Merge pull request #12 from SamHH/self-comms
Browse files Browse the repository at this point in the history
Handle native messaging without third-party crate
  • Loading branch information
Sam A. Horvath-Hunt authored May 10, 2019
2 parents b4c4807 + 83340b5 commit 2db1652
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 35 deletions.
26 changes: 3 additions & 23 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2018"
name = "bukubrow"

[dependencies]
chrome_native_messaging = "0.1"
byteorder = "1.3"
clap = "2.33"
dirs = "1.0"
rusqlite = { version = "0.17", features = ["bundled"] }
Expand Down
2 changes: 1 addition & 1 deletion src/buku/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub type BookmarkId = i32;
pub type BookmarkId = u32;

#[derive(Serialize, Deserialize)]
pub struct SavedBookmark {
Expand Down
11 changes: 9 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ extern crate serde_derive;
mod buku;
mod cli;
mod hosts;
mod native_messaging;
mod server;

use crate::buku::database::{BukuDatabase, SqliteDatabase};
use crate::buku::utils::get_db_path;
use crate::cli::{exit_with_stdout_err, Argument, CliError};
use crate::hosts::installer::install_host;
use crate::native_messaging::NativeMessagingError;
use crate::server::{map_init_err_friendly_msg, InitError, Server};
use clap::ErrorKind;

Expand Down Expand Up @@ -97,6 +99,11 @@ fn main() {

// No installation arguments supplied, proceed with native messaging. Do not
// exit if cannot find or access Buku database, instead allow server to
// communicate that
Server::new(db).listen();
// communicate that. This is an asynchronous call.
let res = Server::new(db).listen();

match res {
Ok(_) | Err(NativeMessagingError::NoMoreInput) => std::process::exit(0),
_ => std::process::exit(1),
}
}
105 changes: 105 additions & 0 deletions src/native_messaging.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
use std::io;
use std::io::{Read, Write};

type JSON = serde_json::Value;

/// Number of bytes in one megabyte. Stored as a usize as that's the type it
/// will be compared against later.
const ONE_MEGABYTE_BYTES: &'static usize = &1_048_576;

#[derive(Debug, PartialEq)]
pub enum NativeMessagingError {
/// Chrome restricts message sizes to a maximum of 1MB.
MessageTooLarge(usize),
NoMoreInput,
UnknownFailure,
}

/// Decodes data from a reader, where said data is decoded according to
/// Chrome's documentation on native messaging.
/// (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-protocol)
///
/// 1. A u32 integer specifies how long the following message is.
/// 2. The message is encoded in JSON.
pub fn read_input<R: Read>(mut input: R) -> Result<JSON, NativeMessagingError> {
match input.read_u32::<NativeEndian>() {
Ok(len) => {
// Due to read_exact looking at a vector's length rather than its
// capacity we need to preallocate here
let mut buffer = vec![0; len as usize];

input
.read_exact(&mut buffer)
.map_err(|_| NativeMessagingError::UnknownFailure)?;

let value = serde_json::from_slice(&buffer)
.map_err(|_| NativeMessagingError::UnknownFailure)?;

Ok(value)
}
Err(err) => match err.kind() {
io::ErrorKind::UnexpectedEof => Err(NativeMessagingError::NoMoreInput),
_ => Err(NativeMessagingError::UnknownFailure),
},
}
}

/// Outputs JSON data to a writer, where said data is encoded according to
/// Chrome's documentation on native messaging.
/// (https://developer.chrome.com/extensions/nativeMessaging#native-messaging-host-protocol)
pub fn write_output<W: Write>(mut output: W, val: &JSON) -> Result<W, NativeMessagingError> {
let msg = serde_json::to_string(val).map_err(|_| NativeMessagingError::UnknownFailure)?;
let len = msg.len();

// Web browsers won't accept a message larger than 1MB
if len > *ONE_MEGABYTE_BYTES {
return Err(NativeMessagingError::MessageTooLarge(len));
}

output
.write_u32::<NativeEndian>(len as u32)
.map_err(|_| NativeMessagingError::UnknownFailure)?;
output
.write_all(msg.as_bytes())
.map_err(|_| NativeMessagingError::UnknownFailure)?;
output
.flush()
.map_err(|_| NativeMessagingError::UnknownFailure)?;

Ok(output)
}

#[cfg(test)]
mod tests {
use super::*;

/// Returns a matching pair of a JSON value and its native messaging-encoded
/// representation.
fn encoded_pair() -> (JSON, Vec<u8>) {
let json = json!({ "property": { "subproperty": "value" } });
let message = vec![
36, 0, 0, 0, 123, 34, 112, 114, 111, 112, 101, 114, 116, 121, 34, 58, 123, 34, 115,
117, 98, 112, 114, 111, 112, 101, 114, 116, 121, 34, 58, 34, 118, 97, 108, 117, 101,
34, 125, 125,
];

(json, message)
}

#[test]
fn test_reader() {
let (json, msg) = encoded_pair();
let read = read_input(msg.as_slice()).unwrap();

assert_eq!(read, json);
}

#[test]
fn test_writer() {
let (json, msg) = encoded_pair();
let written = write_output(Vec::new(), &json).unwrap();

assert_eq!(written, msg);
}
}
22 changes: 14 additions & 8 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::buku::database::BukuDatabase;
use crate::buku::types::{BookmarkId, SavedBookmark, UnsavedBookmark};
use chrome_native_messaging::{errors, event_loop, write_output};
use crate::native_messaging::{read_input, write_output, NativeMessagingError};
use clap::crate_version;
use serde_json;
use std::io;

/// If the server is not provided with a valid database, it needs to know why
Expand Down Expand Up @@ -73,12 +72,19 @@ impl<T: BukuDatabase> Server<T> {
}

// Listen for native messages from WebExtension in a loop
pub fn listen(&self) {
event_loop(|payload: JSON| -> Result<(), errors::Error> {
let res = self.router(payload);

write_output(io::stdout(), &res)
});
pub fn listen(&self) -> Result<(), NativeMessagingError> {
loop {
match read_input(io::stdin()) {
Ok(payload) => {
let res = self.router(payload);
write_output(io::stdout(), &res)?;
}
Err(err) => match err {
NativeMessagingError::NoMoreInput => break Err(err),
_ => {}
},
}
}
}

fn method_deserializer(&self, payload: JSON) -> Method {
Expand Down

0 comments on commit 2db1652

Please sign in to comment.