diff --git a/apps/solidcraft/src/control_server.rs b/apps/solidcraft/src/control_server.rs index e8dd82d3..0d79ca6b 100644 --- a/apps/solidcraft/src/control_server.rs +++ b/apps/solidcraft/src/control_server.rs @@ -123,6 +123,17 @@ fn unauthorized() -> Value { json!({"ok": false, "error": "unauthorized: send the control token as \"token\" (see docs/control-protocol.md)"}) } +/// Refuse the connection: the reply, then the end of what we send, then what the client had +/// already sent read and dropped (a little, briefly). Closing a socket with unread input resets +/// the connection, and a reset can reach the client before it has read the refusal. +fn refuse(out: &mut TcpStream, reader: &mut BufReader) { + use std::io::Read; + let _ = writeln!(out, "{}", unauthorized()); + let _ = out.shutdown(std::net::Shutdown::Write); + let _ = reader.get_ref().set_read_timeout(Some(Duration::from_millis(500))); + let _ = std::io::copy(&mut reader.by_ref().take(64 * 1024), &mut std::io::sink()); +} + fn serve(stream: TcpStream, token: &str, tx: Sender, ctx: egui::Context) { let Ok(read) = stream.try_clone() else { return }; let mut out = stream; @@ -148,7 +159,7 @@ fn serve(stream: TcpStream, token: &str, tx: Sender, ctx: egui:: match msg.get("token").and_then(Value::as_str) { Some(t) if control_auth::tokens_match(t, token) => authenticated = true, _ => { - let _ = writeln!(out, "{}", unauthorized()); + refuse(&mut out, &mut reader); break; } } @@ -170,7 +181,7 @@ fn serve(stream: TcpStream, token: &str, tx: Sender, ctx: egui:: r } Err(_) if !authenticated => { - let _ = writeln!(out, "{}", unauthorized()); + refuse(&mut out, &mut reader); break; } Err(e) => {