mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-20 11:23:14 +03:00
client: hold the clipboard until this round's login is accepted
The clipboard listener is one per process, started by the first session to log in, and its broadcast went to every session - one whose login was still waiting on a password, 2FA or the peer's consent included. What the user copied meanwhile went to a machine that had not admitted them; the peer drops it unread before authorization, but it is in that peer's hands. The check goes on the round, not the session: a session-level one reads a state and later a sender that a reconnect can have swapped in between, so a broadcast that passed for the round before could still queue on the next. Remote is the round - its queue, and is_connected set once its own PeerInfo is in - so a Clipboard or MultiClipboards that reaches handle_msg_from_ui before then is dropped there, on the line before it would go out. What the login itself sends through the same queue, Auth2FA among it, goes as before. The unauthenticated cap on the other side is how this came up: a clipboard over 128 KiB ended such a login with "Reset by the peer". The small case had always gone through quietly. A test drives a round's Remote over a loopback pair before any login: a clipboard does not reach the far end, a 2FA code does, and once the round is connected the clipboard does too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -641,6 +641,19 @@ impl<T: InvokeUiSession> Remote<T> {
|
||||
self.check_clipboard_file_context();
|
||||
}
|
||||
Data::Message(msg) => {
|
||||
// The Flutter clipboard broadcast is process-wide, so a clipboard can reach this
|
||||
// round's queue before the round has logged in; it is dropped here, on the round
|
||||
// itself.
|
||||
#[cfg(feature = "flutter")]
|
||||
if !self.is_connected
|
||||
&& matches!(
|
||||
msg.union.as_ref(),
|
||||
Some(message::Union::Clipboard(_))
|
||||
| Some(message::Union::MultiClipboards(_))
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
match &msg.union {
|
||||
Some(message::Union::Misc(misc)) => match misc.union {
|
||||
Some(misc::Union::RefreshVideo(_)) => {
|
||||
@@ -2643,3 +2656,72 @@ impl Drop for VideoThread {
|
||||
*self.discard_queue.write().unwrap() = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "flutter")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::flutter::FlutterHandler;
|
||||
|
||||
/// A round's `Remote` over a loopback pair, before any login: what it sends to `peer`
|
||||
/// arrives at `far`.
|
||||
async fn remote_and_peer() -> (Remote<FlutterHandler>, Stream, Stream) {
|
||||
let listener = hbb_common::tcp::new_listener("127.0.0.1:0", false)
|
||||
.await
|
||||
.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let (peer, accepted) = tokio::join!(
|
||||
hbb_common::socket_client::connect_tcp(addr.to_string(), 3000),
|
||||
listener.accept()
|
||||
);
|
||||
let (accepted, far_addr) = accepted.unwrap();
|
||||
let far = Stream::Tcp(hbb_common::tcp::FramedStream::from(accepted, far_addr));
|
||||
let (sender, receiver) = mpsc::unbounded_channel::<Data>();
|
||||
let remote = Remote::new(Session::<FlutterHandler>::default(), receiver, sender);
|
||||
(remote, peer.unwrap(), far)
|
||||
}
|
||||
|
||||
async fn arrives(far: &mut Stream) -> bool {
|
||||
matches!(hbb_common::timeout(300, far.next()).await, Ok(Some(Ok(_))))
|
||||
}
|
||||
|
||||
fn clipboard() -> Data {
|
||||
let mut msg = Message::new();
|
||||
msg.set_clipboard(Clipboard {
|
||||
content: b"copied while this login was pending".to_vec().into(),
|
||||
..Default::default()
|
||||
});
|
||||
Data::Message(msg)
|
||||
}
|
||||
|
||||
fn auth_2fa() -> Data {
|
||||
let mut msg = Message::new();
|
||||
msg.set_auth_2fa(Auth2FA {
|
||||
code: "123456".to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
Data::Message(msg)
|
||||
}
|
||||
|
||||
// A clipboard queued before this round's login stays here; what the login itself sends
|
||||
// through the same queue does not.
|
||||
#[tokio::test]
|
||||
async fn a_clipboard_queued_before_this_rounds_login_is_dropped() {
|
||||
let (mut remote, mut peer, mut far) = remote_and_peer().await;
|
||||
assert!(!remote.is_connected);
|
||||
assert!(remote.handle_msg_from_ui(clipboard(), &mut peer).await);
|
||||
assert!(
|
||||
!arrives(&mut far).await,
|
||||
"a clipboard went out before the login"
|
||||
);
|
||||
assert!(remote.handle_msg_from_ui(auth_2fa(), &mut peer).await);
|
||||
assert!(arrives(&mut far).await, "the 2FA code was held back");
|
||||
|
||||
remote.is_connected = true;
|
||||
assert!(remote.handle_msg_from_ui(clipboard(), &mut peer).await);
|
||||
assert!(
|
||||
arrives(&mut far).await,
|
||||
"a clipboard after the login was held back"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user