full native (#754)

* full native

* fix: apply cargo fmt formatting

* fix: prevent zip path traversal in Chromium installer

Use enclosed_name() to sanitize zip entry paths, preventing malicious
archives from writing outside the extraction directory.

* improvements

* fix: apply cargo fmt formatting

* benchmarks

* bench

* updates

* fixes
This commit is contained in:
Chris Tate
2026-03-13 19:59:21 -05:00
committed by GitHub
parent d4b948c1d4
commit 8e43469c8b
88 changed files with 2511 additions and 22629 deletions
+40 -13
View File
@@ -4,7 +4,7 @@ use std::sync::Arc;
use futures_util::{SinkExt, StreamExt};
use tokio::net::TcpListener;
use tokio::sync::{broadcast, Mutex};
use tokio::sync::{broadcast, Mutex, RwLock};
use tokio_tungstenite::tungstenite::Message;
use super::cdp::client::CdpClient;
@@ -47,6 +47,27 @@ impl StreamServer {
client: Arc<CdpClient>,
session_id: String,
) -> Result<Self, String> {
let client_slot = Arc::new(RwLock::new(Some(client)));
let (server, _) = Self::start_inner(preferred_port, client_slot, session_id).await?;
Ok(server)
}
/// Start the stream server without a CDP client (e.g. at daemon startup before browser launch).
/// Returns the server and a shared slot to set the client when the browser launches.
/// Input messages are ignored until the client is set.
pub async fn start_without_client(
preferred_port: u16,
session_id: String,
) -> Result<(Self, Arc<RwLock<Option<Arc<CdpClient>>>>), String> {
let client_slot = Arc::new(RwLock::new(None::<Arc<CdpClient>>));
Self::start_inner(preferred_port, client_slot, session_id).await
}
async fn start_inner(
preferred_port: u16,
client_slot: Arc<RwLock<Option<Arc<CdpClient>>>>,
session_id: String,
) -> Result<(Self, Arc<RwLock<Option<Arc<CdpClient>>>>), String> {
let addr = format!("127.0.0.1:{}", preferred_port);
let listener = TcpListener::bind(&addr)
.await
@@ -62,23 +83,27 @@ impl StreamServer {
let frame_tx_clone = frame_tx.clone();
let client_count_clone = client_count.clone();
let client_slot_clone = client_slot.clone();
tokio::spawn(async move {
accept_loop(
listener,
frame_tx_clone,
client_count_clone,
client,
client_slot_clone,
session_id,
)
.await;
});
Ok(Self {
port,
frame_tx,
client_count,
})
Ok((
Self {
port,
frame_tx,
client_count,
},
client_slot,
))
}
pub fn port(&self) -> u16 {
@@ -140,17 +165,17 @@ async fn accept_loop(
listener: TcpListener,
frame_tx: broadcast::Sender<String>,
client_count: Arc<Mutex<usize>>,
cdp_client: Arc<CdpClient>,
client_slot: Arc<RwLock<Option<Arc<CdpClient>>>>,
session_id: String,
) {
while let Ok((stream, addr)) = listener.accept().await {
let frame_rx = frame_tx.subscribe();
let client_count = client_count.clone();
let cdp = cdp_client.clone();
let client_slot = client_slot.clone();
let sid = session_id.clone();
tokio::spawn(async move {
handle_ws_client(stream, addr, frame_rx, client_count, cdp, sid).await;
handle_ws_client(stream, addr, frame_rx, client_count, client_slot, sid).await;
});
}
}
@@ -161,10 +186,9 @@ async fn handle_ws_client(
_addr: SocketAddr,
mut frame_rx: broadcast::Receiver<String>,
client_count: Arc<Mutex<usize>>,
cdp_client: Arc<CdpClient>,
client_slot: Arc<RwLock<Option<Arc<CdpClient>>>>,
session_id: String,
) {
// Origin checking on WebSocket handshake
let callback =
|req: &tokio_tungstenite::tungstenite::handshake::server::Request,
resp: tokio_tungstenite::tungstenite::handshake::server::Response| {
@@ -211,7 +235,10 @@ async fn handle_ws_client(
msg = ws_rx.next() => {
match msg {
Some(Ok(Message::Text(text))) => {
handle_client_message(&text, &cdp_client, &session_id).await;
let guard = client_slot.read().await;
if let Some(ref client) = *guard {
handle_client_message(&text, client.as_ref(), &session_id).await;
}
}
Some(Ok(Message::Close(_))) | None => break,
_ => {}