diff --git a/crates/ironrdp-client/src/rdp.rs b/crates/ironrdp-client/src/rdp.rs index a6693fba2..426a1100d 100644 --- a/crates/ironrdp-client/src/rdp.rs +++ b/crates/ironrdp-client/src/rdp.rs @@ -565,6 +565,22 @@ async fn active_session( Some(cliprdr.initiate_paste(format) .map_err(|e| session::custom_err!("CLIPRDR", e))?) } + ClipboardMessage::SendLockClipboard { clip_data_id } => { + Some(cliprdr.lock_clipboard(clip_data_id) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } + ClipboardMessage::SendUnlockClipboard { clip_data_id } => { + Some(cliprdr.unlock_clipboard(clip_data_id) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } + ClipboardMessage::SendFileContentsRequest(request) => { + Some(cliprdr.request_file_contents(request) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } + ClipboardMessage::SendFileContentsResponse(response) => { + Some(cliprdr.submit_file_contents(response) + .map_err(|e| session::custom_err!("CLIPRDR", e))?) + } ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/crates/ironrdp-cliprdr/src/backend.rs b/crates/ironrdp-cliprdr/src/backend.rs index 7d2bd59c3..5a8d24d3d 100644 --- a/crates/ironrdp-cliprdr/src/backend.rs +++ b/crates/ironrdp-cliprdr/src/backend.rs @@ -33,6 +33,26 @@ pub enum ClipboardMessage { /// received. SendInitiatePaste(ClipboardFormatId), + /// Sent by clipboard backend when clipboard data should be locked on the remote. + /// + /// Implementation should send lock clipboard data PDU on `CLIPRDR` SVC when received. + SendLockClipboard { clip_data_id: u32 }, + + /// Sent by clipboard backend when clipboard data should be unlocked on the remote. + /// + /// Implementation should send unlock clipboard data PDU on `CLIPRDR` SVC when received. + SendUnlockClipboard { clip_data_id: u32 }, + + /// Sent by clipboard backend when file contents are needed from the remote. + /// + /// Implementation should send file contents request on `CLIPRDR` SVC when received. + SendFileContentsRequest(FileContentsRequest), + + /// Sent by clipboard backend when file contents data is ready to be sent to the remote. + /// + /// Implementation should send file contents response on `CLIPRDR` SVC when received. + SendFileContentsResponse(FileContentsResponse<'static>), + /// Failure received from the OS clipboard event loop. /// /// Client implementation should log/display this error. diff --git a/crates/ironrdp-cliprdr/src/lib.rs b/crates/ironrdp-cliprdr/src/lib.rs index 41f5d62ca..66dc6cb58 100644 --- a/crates/ironrdp-cliprdr/src/lib.rs +++ b/crates/ironrdp-cliprdr/src/lib.rs @@ -15,8 +15,8 @@ use ironrdp_svc::{ }; use pdu::{ Capabilities, ClientTemporaryDirectory, ClipboardFormat, ClipboardFormatId, ClipboardGeneralCapabilityFlags, - ClipboardPdu, ClipboardProtocolVersion, FileContentsResponse, FormatDataRequest, FormatListResponse, - OwnedFormatDataResponse, + ClipboardPdu, ClipboardProtocolVersion, FileContentsRequest, FileContentsResponse, FormatDataRequest, + FormatListResponse, LockDataId, OwnedFormatDataResponse, }; use tracing::{error, info}; @@ -276,6 +276,46 @@ impl Cliprdr { Ok(vec![into_cliprdr_message(pdu)].into()) } + + /// [2.2.4.6] Lock Clipboard Data PDU (CLIPRDR_LOCK_CLIPDATA) + /// + /// Locks clipboard data on the remote before file transfer. Should be called before + /// requesting file contents to ensure data stability during transfer. + /// + /// [2.2.4.6]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/150bac72-bc7f-42e5-9e8e-cb5a0ddc7dbc + pub fn lock_clipboard(&self, clip_data_id: u32) -> PduResult> { + ready_guard!(self, lock_clipboard); + + let pdu = ClipboardPdu::LockData(LockDataId(clip_data_id)); + Ok(vec![into_cliprdr_message(pdu)].into()) + } + + /// [2.2.4.7] Unlock Clipboard Data PDU (CLIPRDR_UNLOCK_CLIPDATA) + /// + /// Unlocks previously locked clipboard data. Should be called after file transfer + /// operations complete. + /// + /// [2.2.4.7]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/e587a20c-fb7c-47d1-8698-4bcb92c48a38 + pub fn unlock_clipboard(&self, clip_data_id: u32) -> PduResult> { + ready_guard!(self, unlock_clipboard); + + let pdu = ClipboardPdu::UnlockData(LockDataId(clip_data_id)); + Ok(vec![into_cliprdr_message(pdu)].into()) + } + + /// [2.2.5.3] File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST) + /// + /// Requests file contents from the Shared Clipboard Owner. Should be called when + /// the Local Clipboard Owner needs file data after receiving a file list format. + /// The remote will respond via [`CliprdrBackend::on_file_contents_response`]. + /// + /// [2.2.5.3]: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpeclip/cbc851d3-4e68-45f4-9292-26872a9209f2 + pub fn request_file_contents(&self, request: FileContentsRequest) -> PduResult> { + ready_guard!(self, request_file_contents); + + let pdu = ClipboardPdu::FileContentsRequest(request); + Ok(vec![into_cliprdr_message(pdu)].into()) + } } impl SvcProcessor for Cliprdr { diff --git a/crates/ironrdp-server/Cargo.toml b/crates/ironrdp-server/Cargo.toml index b1e4650cd..964a83f6f 100644 --- a/crates/ironrdp-server/Cargo.toml +++ b/crates/ironrdp-server/Cargo.toml @@ -39,7 +39,7 @@ ironrdp-svc = { path = "../ironrdp-svc", version = "0.5" } # public ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.5" } # public ironrdp-displaycontrol = { path = "../ironrdp-displaycontrol", version = "0.4" } # public ironrdp-dvc = { path = "../ironrdp-dvc", version = "0.4" } # public -ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.8" } +ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.8", features = ["reqwest"] } ironrdp-acceptor = { path = "../ironrdp-acceptor", version = "0.8" } # public ironrdp-graphics = { path = "../ironrdp-graphics", version = "0.7" } # public ironrdp-rdpsnd = { path = "../ironrdp-rdpsnd", version = "0.6" } # public diff --git a/crates/ironrdp-server/src/server.rs b/crates/ironrdp-server/src/server.rs index 9fe234d84..854020595 100644 --- a/crates/ironrdp-server/src/server.rs +++ b/crates/ironrdp-server/src/server.rs @@ -552,6 +552,12 @@ impl RdpServer { ClipboardMessage::SendInitiateCopy(formats) => cliprdr.initiate_copy(&formats), ClipboardMessage::SendFormatData(data) => cliprdr.submit_format_data(data), ClipboardMessage::SendInitiatePaste(format) => cliprdr.initiate_paste(format), + ClipboardMessage::SendLockClipboard { clip_data_id } => cliprdr.lock_clipboard(clip_data_id), + ClipboardMessage::SendUnlockClipboard { clip_data_id } => { + cliprdr.unlock_clipboard(clip_data_id) + } + ClipboardMessage::SendFileContentsRequest(request) => cliprdr.request_file_contents(request), + ClipboardMessage::SendFileContentsResponse(response) => cliprdr.submit_file_contents(response), ClipboardMessage::Error(error) => { error!(?error, "Handling clipboard event"); continue; diff --git a/crates/ironrdp-web/src/session.rs b/crates/ironrdp-web/src/session.rs index 6d0e018ac..b760ec18c 100644 --- a/crates/ironrdp-web/src/session.rs +++ b/crates/ironrdp-web/src/session.rs @@ -526,6 +526,22 @@ impl iron_remote_desktop::Session for Session { cliprdr.initiate_paste(format) .context("CLIPRDR initiate paste")? ), + ClipboardMessage::SendLockClipboard { clip_data_id } => Some( + cliprdr.lock_clipboard(clip_data_id) + .context("CLIPRDR lock clipboard")? + ), + ClipboardMessage::SendUnlockClipboard { clip_data_id } => Some( + cliprdr.unlock_clipboard(clip_data_id) + .context("CLIPRDR unlock clipboard")? + ), + ClipboardMessage::SendFileContentsRequest(request) => Some( + cliprdr.request_file_contents(request) + .context("CLIPRDR request file contents")? + ), + ClipboardMessage::SendFileContentsResponse(response) => Some( + cliprdr.submit_file_contents(response) + .context("CLIPRDR submit file contents")? + ), ClipboardMessage::Error(e) => { error!("Clipboard backend error: {}", e); None diff --git a/ffi/src/clipboard/message.rs b/ffi/src/clipboard/message.rs index 1ab5d003b..ee1cc34dc 100644 --- a/ffi/src/clipboard/message.rs +++ b/ffi/src/clipboard/message.rs @@ -14,6 +14,18 @@ pub mod ffi { ironrdp::cliprdr::backend::ClipboardMessage::SendInitiatePaste(_) => { ClipboardMessageType::SendInitiatePaste } + ironrdp::cliprdr::backend::ClipboardMessage::SendLockClipboard { .. } => { + ClipboardMessageType::SendLockClipboard + } + ironrdp::cliprdr::backend::ClipboardMessage::SendUnlockClipboard { .. } => { + ClipboardMessageType::SendUnlockClipboard + } + ironrdp::cliprdr::backend::ClipboardMessage::SendFileContentsRequest(_) => { + ClipboardMessageType::SendFileContentsRequest + } + ironrdp::cliprdr::backend::ClipboardMessage::SendFileContentsResponse(_) => { + ClipboardMessageType::SendFileContentsResponse + } ironrdp::cliprdr::backend::ClipboardMessage::Error(_) => ClipboardMessageType::Error, } } @@ -51,6 +63,10 @@ pub mod ffi { SendInitiateCopy, SendFormatData, SendInitiatePaste, + SendLockClipboard, + SendUnlockClipboard, + SendFileContentsRequest, + SendFileContentsResponse, Error, }