diff --git a/Cargo.lock b/Cargo.lock index 8dd49b0..7a7f7a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,7 +74,7 @@ dependencies = [ name = "bukubrow" version = "4.0.1" dependencies = [ - "chrome_native_messaging 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "rusqlite 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -86,7 +86,7 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.2.6" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -99,16 +99,6 @@ name = "cfg-if" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "chrome_native_messaging" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "clap" version = "2.33.0" @@ -146,14 +136,6 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "error-chain" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "failure" version = "0.1.5" @@ -487,15 +469,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" -"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" +"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" -"checksum chrome_native_messaging 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d90ef0330a56caf0a77d10fe27a32cea70c15b94c675e027b07700030edf00df" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fallible-iterator 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eb7217124812dc5672b7476d0c2d20cfe9f7c0f1ba0904b674a9762a0212f72e" diff --git a/Cargo.toml b/Cargo.toml index acb7226..86a3c82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/src/buku/types.rs b/src/buku/types.rs index 62edd69..0ef050d 100644 --- a/src/buku/types.rs +++ b/src/buku/types.rs @@ -1,4 +1,4 @@ -pub type BookmarkId = i32; +pub type BookmarkId = u32; #[derive(Serialize, Deserialize)] pub struct SavedBookmark { diff --git a/src/main.rs b/src/main.rs index 722c3c3..a394840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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; @@ -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), + } } diff --git a/src/native_messaging.rs b/src/native_messaging.rs new file mode 100644 index 0000000..bae9001 --- /dev/null +++ b/src/native_messaging.rs @@ -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(mut input: R) -> Result { + match input.read_u32::() { + 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(mut output: W, val: &JSON) -> Result { + 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::(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) { + 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); + } +} diff --git a/src/server.rs b/src/server.rs index 73ddb1f..0d83703 100644 --- a/src/server.rs +++ b/src/server.rs @@ -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 @@ -73,12 +72,19 @@ impl Server { } // 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 {