From d63ceb730f532bc8360c8b11fd45cf210cfe99d9 Mon Sep 17 00:00:00 2001 From: John Doty Date: Sat, 17 Dec 2022 08:12:30 -0800 Subject: [PATCH] Initial browse support, server side --- Cargo.lock | 126 ++++++++++++++++++++++++++ Cargo.toml | 3 + src/message.rs | 9 ++ src/server/browser.rs | 15 ++++ src/server/browser/browser_unix.rs | 136 +++++++++++++++++++++++++++++ src/server/mod.rs | 13 +-- 6 files changed, 297 insertions(+), 5 deletions(-) create mode 100644 src/server/browser.rs create mode 100644 src/server/browser/browser_unix.rs diff --git a/Cargo.lock b/Cargo.lock index d71888e..8bd7733 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "errno" version = "0.2.8" @@ -164,6 +184,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures-core" version = "0.3.24" @@ -182,11 +208,25 @@ dependencies = [ "log", "open", "procfs", + "tempdir", "thiserror", "tokio", "tokio-stream", "toml", "tui", + "users", + "xdg", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -418,6 +458,43 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -427,6 +504,26 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rustix" version = "0.35.11" @@ -510,6 +607,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + [[package]] name = "thiserror" version = "1.0.37" @@ -623,6 +730,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -753,3 +870,12 @@ name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "xdg" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" +dependencies = [ + "dirs", +] diff --git a/Cargo.toml b/Cargo.toml index 42466f3..190ab02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,9 +20,12 @@ tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" toml = "0.5" tui = "0.19" +users = "0.11" +xdg = "2" [dev-dependencies] assert_matches = "1" +tempdir = "0.3" [target.'cfg(target_os="linux")'.dependencies] procfs = "0.14.1" diff --git a/src/message.rs b/src/message.rs index 66c81c4..eb5efd8 100644 --- a/src/message.rs +++ b/src/message.rs @@ -54,6 +54,9 @@ pub enum Message { // List of available ports from server to client. Ports(Vec), + + // Browse a thing + Browse(String), } impl Message { @@ -96,6 +99,10 @@ impl Message { put_string(result, sliced); } } + Browse(url) => { + result.put_u8(0x07); + put_string(result, url); + } }; } @@ -124,6 +131,7 @@ impl Message { } Ok(Ports(ports)) } + 0x07 => Ok(Browse(get_string(cursor)?)), b => Err(Error::Unknown(b).into()), } } @@ -274,6 +282,7 @@ mod message_tests { desc: "metadata-library".to_string(), }, ])); + assert_round_trip(Browse("https://google.com/".to_string())); } #[test] diff --git a/src/server/browser.rs b/src/server/browser.rs new file mode 100644 index 0000000..b2c54ac --- /dev/null +++ b/src/server/browser.rs @@ -0,0 +1,15 @@ +use crate::message::Message; +use anyhow::Result; +use tokio::sync::mpsc; + +#[cfg(target_family = "unix")] +use browser_unix::handle_browser_open_impl; + +mod browser_unix; + +#[inline] +pub async fn handle_browser_open( + messages: mpsc::Sender, +) -> Result<()> { + handle_browser_open_impl(messages).await +} diff --git a/src/server/browser/browser_unix.rs b/src/server/browser/browser_unix.rs new file mode 100644 index 0000000..cafda7e --- /dev/null +++ b/src/server/browser/browser_unix.rs @@ -0,0 +1,136 @@ +use crate::message::{Message, MessageReader}; +use anyhow::{bail, Context, Result}; +use log::warn; +use std::os::unix::fs::DirBuilderExt; +use std::path::PathBuf; +use tokio::net::{UnixListener, UnixStream}; +use tokio::sync::mpsc; +use users; +use xdg; + +pub async fn handle_browser_open_impl( + messages: mpsc::Sender, +) -> Result<()> { + let path = socket_path().context("Error getting socket path")?; + handle_browser_open_with_path(messages, path).await +} + +async fn handle_browser_open_with_path( + messages: mpsc::Sender, + path: PathBuf, +) -> Result<()> { + let _ = std::fs::remove_file(&path); + let listener = UnixListener::bind(&path) + .with_context(|| format!("Failed to bind to {}", path.display()))?; + loop { + let (socket, _addr) = listener + .accept() + .await + .context("Error accepting connection")?; + + let sender = messages.clone(); + tokio::spawn(async move { + if let Err(e) = handle_connection(socket, sender).await { + warn!("Error handling socket connection: {:?}", e); + } + }); + } +} + +pub fn socket_path() -> Result { + let mut socket_path = socket_directory()?; + + std::fs::DirBuilder::new() + .recursive(true) + .mode(0o700) + .create(&socket_path) + .context("Error creating socket directory")?; + + // TODO: check mode of directory + + socket_path.push("browser"); + Ok(socket_path) +} + +fn socket_directory() -> Result { + let base_directories = xdg::BaseDirectories::new() + .context("Error creating BaseDirectories")?; + match base_directories.place_runtime_file("fwd") { + Ok(path) => Ok(path), + Err(_) => { + let mut path = std::env::temp_dir(); + path.push(format!("fwd{}", users::get_current_uid())); + Ok(path) + } + } +} + +async fn handle_connection( + socket: UnixStream, + sender: mpsc::Sender, +) -> Result<()> { + let mut reader = MessageReader::new(socket); + let message = reader.read().await.context("Error reading message")?; + match message { + Message::Browse(url) => sender.send(Message::Browse(url)).await?, + _ => bail!("Unsupported message: {:?}", message), + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::message::MessageWriter; + use tempdir::TempDir; + + #[test] + fn socket_path_repeats() { + assert_eq!( + socket_path().expect("Could not get socket path a"), + socket_path().expect("Could not get socket path b") + ); + } + + #[tokio::test] + async fn url_to_message() { + let (sender, mut receiver) = mpsc::channel(64); + + let tmp_dir = + TempDir::new("url_to_message").expect("Error getting tmpdir"); + let path = tmp_dir.path().join("socket"); + + let path_override = path.clone(); + tokio::spawn(async move { + handle_browser_open_with_path(sender, path_override) + .await + .expect("Error in server!"); + }); + + let mut attempt = 0; + let stream = loop { + match UnixStream::connect(&path).await { + Ok(stream) => break Ok(stream), + Err(e) => { + if attempt == 5 { + break Err(e) + .context("Maximum retries exceeded, last error"); + } + attempt += 1; + } + } + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + .expect("Error connecting to socket"); + let mut writer = MessageWriter::new(stream); + let sent = Message::Browse("https://google.com/".to_string()); + writer + .write(sent.clone()) + .await + .expect("Error writing browse message"); + + let received = receiver.recv().await.expect("Error receiving message"); + assert_eq!(sent, received); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 0309ae7..07b171a 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -4,6 +4,7 @@ use log::{error, warn}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter}; use tokio::sync::mpsc; +mod browser; mod refresh; // We drive writes through an mpsc queue, because we not only handle requests @@ -76,11 +77,13 @@ async fn server_main< let mut writer = MessageWriter::new(writer); let mut reader = MessageReader::new(reader); - let (_, result) = tokio::join!( - write_driver(&mut receiver, &mut writer), - server_loop(&mut reader, &mut sender) - ); - result + let browse_sender = sender.clone(); + + tokio::select! { + _ = write_driver(&mut receiver, &mut writer) => Ok(()), + r = server_loop(&mut reader, &mut sender) => r, + r = browser::handle_browser_open(browse_sender) => r, + } } pub async fn run_server() {