diff --git a/libs/clipboard/src/cliprdr.h b/libs/clipboard/src/cliprdr.h index 8b9cecef0..33e3d522a 100644 --- a/libs/clipboard/src/cliprdr.h +++ b/libs/clipboard/src/cliprdr.h @@ -170,6 +170,8 @@ extern "C" typedef UINT (*pcNotifyClipboardMsg)(UINT32 connID, const NOTIFICATION_MESSAGE *msg); + typedef UINT (*pcHandleClipboardFiles)(UINT32 connID, size_t nFiles, WCHAR **fileNames); + typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList); typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext *context, @@ -217,6 +219,7 @@ extern "C" pcCliprdrMonitorReady MonitorReady; pcCliprdrTempDirectory TempDirectory; pcNotifyClipboardMsg NotifyClipboardMsg; + pcHandleClipboardFiles HandleClipboardFiles; pcCliprdrClientFormatList ClientFormatList; pcCliprdrServerFormatList ServerFormatList; pcCliprdrClientFormatListResponse ClientFormatListResponse; diff --git a/libs/clipboard/src/lib.rs b/libs/clipboard/src/lib.rs index f28fe083d..5ce9afe28 100644 --- a/libs/clipboard/src/lib.rs +++ b/libs/clipboard/src/lib.rs @@ -132,6 +132,9 @@ pub enum ClipboardFile { requested_data: Vec, }, TryEmpty, + Files { + files: Vec<(String, u64)>, + }, } struct MsgChannel { diff --git a/libs/clipboard/src/platform/unix/serv_files.rs b/libs/clipboard/src/platform/unix/serv_files.rs index a401e0b5c..6f4fb54a4 100644 --- a/libs/clipboard/src/platform/unix/serv_files.rs +++ b/libs/clipboard/src/platform/unix/serv_files.rs @@ -5,7 +5,7 @@ use hbb_common::{ log, }; use parking_lot::Mutex; -use std::{path::PathBuf, sync::Arc}; +use std::{path::PathBuf, sync::Arc, usize}; lazy_static::lazy_static! { // local files are cached, this value should not be changed when copying files @@ -34,6 +34,7 @@ enum FileContentsRequest { struct ClipFiles { files: Vec, file_list: Vec, + first_file_index: usize, files_pdu: Vec, } @@ -41,6 +42,7 @@ impl ClipFiles { fn clear(&mut self) { self.files.clear(); self.file_list.clear(); + self.first_file_index = usize::MAX; self.files_pdu.clear(); } @@ -50,6 +52,11 @@ impl ClipFiles { .map(|s| PathBuf::from(s)) .collect::>(); self.file_list = construct_file_list(&clipboard_paths)?; + self.first_file_index = self + .file_list + .iter() + .position(|f| !f.path.is_dir()) + .unwrap_or(usize::MAX); self.files = clipboard_files.to_vec(); Ok(()) } @@ -63,6 +70,33 @@ impl ClipFiles { self.files_pdu = data.to_vec() } + fn get_files_for_audit(&self, request: &FileContentsRequest) -> Option { + if let FileContentsRequest::Range { + file_idx, offset, .. + } = request + { + if *file_idx == self.first_file_index && *offset == 0 { + let files: Vec<(String, u64)> = self + .file_list + .iter() + .filter_map(|f| { + if f.path.is_file() { + Some((f.path.to_string_lossy().to_string(), f.size)) + } else { + None + } + }) + .collect::<_>(); + if files.is_empty() { + return None; + } else { + return Some(ClipboardFile::Files { files }); + } + } + } + None + } + fn serve_file_contents( &mut self, conn_id: i32, @@ -192,7 +226,7 @@ pub fn read_file_contents( n_position_low: i32, n_position_high: i32, cb_requested: i32, -) -> Result { +) -> Vec> { let fcr = if dw_flags == 0x1 { FileContentsRequest::Size { stream_id, @@ -209,12 +243,18 @@ pub fn read_file_contents( length, } } else { - return Err(CliprdrError::InvalidRequest { + return vec![Err(CliprdrError::InvalidRequest { description: format!("got invalid FileContentsRequest, dw_flats: {dw_flags}"), - }); + })]; }; - CLIP_FILES.lock().serve_file_contents(conn_id, fcr) + let mut clip_files = CLIP_FILES.lock(); + let mut res = vec![]; + if let Some(files_res) = clip_files.get_files_for_audit(&fcr) { + res.push(Ok(files_res)); + } + res.push(clip_files.serve_file_contents(conn_id, fcr)); + res } pub fn sync_files(files: &[String]) -> Result<(), CliprdrError> { diff --git a/libs/clipboard/src/platform/windows.rs b/libs/clipboard/src/platform/windows.rs index 3734406e0..cdeb3e4b0 100644 --- a/libs/clipboard/src/platform/windows.rs +++ b/libs/clipboard/src/platform/windows.rs @@ -381,6 +381,9 @@ pub type pcCliprdrTempDirectory = ::std::option::Option< pub type pcNotifyClipboardMsg = ::std::option::Option< unsafe extern "C" fn(connID: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT, >; +pub type pcHandleClipboardFiles = ::std::option::Option< + unsafe extern "C" fn(connID: UINT32, nFiles: size_t, fileNames: *mut *mut WCHAR) -> UINT, +>; pub type pcCliprdrClientFormatList = ::std::option::Option< unsafe extern "C" fn( context: *mut CliprdrClientContext, @@ -492,6 +495,7 @@ pub struct _cliprdr_client_context { pub MonitorReady: pcCliprdrMonitorReady, pub TempDirectory: pcCliprdrTempDirectory, pub NotifyClipboardMsg: pcNotifyClipboardMsg, + pub HandleClipboardFiles: pcHandleClipboardFiles, pub ClientFormatList: pcCliprdrClientFormatList, pub ServerFormatList: pcCliprdrServerFormatList, pub ClientFormatListResponse: pcCliprdrClientFormatListResponse, @@ -529,6 +533,7 @@ impl CliprdrClientContext { enable_others: bool, response_wait_timeout_secs: u32, notify_callback: pcNotifyClipboardMsg, + handle_clipboard_files: pcHandleClipboardFiles, client_format_list: pcCliprdrClientFormatList, client_format_list_response: pcCliprdrClientFormatListResponse, client_format_data_request: pcCliprdrClientFormatDataRequest, @@ -547,6 +552,7 @@ impl CliprdrClientContext { MonitorReady: None, TempDirectory: None, NotifyClipboardMsg: notify_callback, + HandleClipboardFiles: handle_clipboard_files, ClientFormatList: client_format_list, ServerFormatList: None, ClientFormatListResponse: client_format_list_response, @@ -758,6 +764,9 @@ pub fn server_clip_file( ret ); } + ClipboardFile::Files { .. } => { + // unreachable + } } ret } @@ -967,6 +976,7 @@ pub fn create_cliprdr_context( enable_others, response_wait_timeout_secs, Some(notify_callback), + Some(handle_clipboard_files), Some(client_format_list), Some(client_format_list_response), Some(client_format_data_request), @@ -1021,6 +1031,61 @@ extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE) 0 } +extern "C" fn handle_clipboard_files( + conn_id: UINT32, + n_files: size_t, + file_names: *mut *mut WCHAR, +) -> UINT { + if n_files == 0 { + return 0; + } + + let data = unsafe { + let mut files = Vec::new(); + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + for i in 0..n_files { + let file_name_ptr = *file_names.offset(i as isize); + if !file_name_ptr.is_null() { + let mut len = 0; + while *file_name_ptr.offset(len) != 0 { + len += 1; + } + let slice = std::slice::from_raw_parts(file_name_ptr, len as usize); + let os_string = OsString::from_wide(slice); + match os_string.to_str() { + Some(n) => match std::fs::metadata(n) { + Ok(meta) => { + if meta.is_file() { + files.push((n.to_owned(), meta.len())); + } + } + Err(e) => { + log::warn!( + "handle_clipboard_files: Failed to get metadata for file '{}': {}", + n, + e + ); + } + }, + None => { + log::warn!("handle_clipboard_files: Failed to convert file name to UTF-8"); + } + }; + } + } + if files.is_empty() { + return 0; + } + + ClipboardFile::Files { files } + }; + // no need to handle result here + allow_err!(send_data(conn_id as _, data)); + + 0 +} + extern "C" fn client_format_list( _context: *mut CliprdrClientContext, clip_format_list: *const CLIPRDR_FORMAT_LIST, diff --git a/libs/clipboard/src/windows/wf_cliprdr.c b/libs/clipboard/src/windows/wf_cliprdr.c index e065be215..e1856863e 100644 --- a/libs/clipboard/src/windows/wf_cliprdr.c +++ b/libs/clipboard/src/windows/wf_cliprdr.c @@ -239,6 +239,7 @@ struct wf_clipboard size_t nFiles; size_t file_array_size; WCHAR **file_names; + size_t first_file_index; FILEDESCRIPTORW **fileDescriptor; BOOL legacyApi; @@ -2024,6 +2025,7 @@ static void clear_file_array(wfClipboard *clipboard) clipboard->file_array_size = 0; clipboard->nFiles = 0; + clipboard->first_file_index = (size_t)-1; } static BOOL wf_cliprdr_get_file_contents(WCHAR *file_name, BYTE *buffer, LONG positionLow, @@ -2179,6 +2181,11 @@ static BOOL wf_cliprdr_add_to_file_arrays(wfClipboard *clipboard, WCHAR *full_fi return FALSE; } + if ((clipboard->fileDescriptor[clipboard->nFiles]->dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY) == 0) { + clipboard->first_file_index = clipboard->nFiles; + } + clipboard->nFiles++; return TRUE; } @@ -2968,6 +2975,14 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context, { LARGE_INTEGER dlibMove; ULARGE_INTEGER dlibNewPosition; + + if (clipboard->nFiles > 0 && + fileContentsRequest->listIndex == (UINT32)clipboard->first_file_index && + fileContentsRequest->nPositionLow == 0 && + fileContentsRequest->nPositionHigh == 0) { + clipboard->context->HandleClipboardFiles(fileContentsRequest->connID, clipboard->nFiles, clipboard->file_names); + } + dlibMove.HighPart = fileContentsRequest->nPositionHigh; dlibMove.LowPart = fileContentsRequest->nPositionLow; hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); @@ -2999,6 +3014,13 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context, rc = ERROR_INTERNAL_ERROR; goto exit; } + + if (clipboard->nFiles > 0 && + fileContentsRequest->listIndex == (UINT32)clipboard->first_file_index && + fileContentsRequest->nPositionLow == 0 && + fileContentsRequest->nPositionHigh == 0) { + clipboard->context->HandleClipboardFiles(fileContentsRequest->connID, clipboard->nFiles, clipboard->file_names); + } bRet = wf_cliprdr_get_file_contents( clipboard->file_names[fileContentsRequest->listIndex], pData, fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested, diff --git a/libs/hbb_common b/libs/hbb_common index 024380d0f..221c2bfb3 160000 --- a/libs/hbb_common +++ b/libs/hbb_common @@ -1 +1 @@ -Subproject commit 024380d0f9f904e9b4371c416ad5056bcd151b77 +Subproject commit 221c2bfb3e60139cd0db6bfb06ec950afb17a66c diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index 7b0e73410..356b10d42 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -2257,7 +2257,7 @@ impl Remote { } #[cfg(feature = "unix-file-copy-paste")] if crate::is_support_file_copy_paste_num(self.handler.lc.read().unwrap().version) { - let mut out_msg = None; + let mut out_msgs = vec![]; #[cfg(target_os = "macos")] if clipboard::platform::unix::macos::should_handle_msg(&clip) { @@ -2269,7 +2269,7 @@ impl Remote { log::error!("failed to handle cliprdr msg: {}", e); } } else { - out_msg = unix_file_clip::serve_clip_messages( + out_msgs = unix_file_clip::serve_clip_messages( ClipboardSide::Client, clip, self.client_conn_id, @@ -2278,14 +2278,14 @@ impl Remote { #[cfg(not(target_os = "macos"))] { - out_msg = unix_file_clip::serve_clip_messages( + out_msgs = unix_file_clip::serve_clip_messages( ClipboardSide::Client, clip, self.client_conn_id, ); } - if let Some(msg) = out_msg { + for msg in out_msgs.into_iter() { allow_err!(_peer.send(&msg).await); } } diff --git a/src/clipboard_file.rs b/src/clipboard_file.rs index 8f3fa8431..724d8aea9 100644 --- a/src/clipboard_file.rs +++ b/src/clipboard_file.rs @@ -143,6 +143,40 @@ pub fn clip_2_msg(clip: ClipboardFile) -> Message { })), ..Default::default() }, + ClipboardFile::Files { files } => { + let files = files + .iter() + .filter_map(|(f, s)| { + if *s == 0 { + if let Ok(meta) = std::fs::metadata(f) { + Some(CliprdrFile { + name: f.to_owned(), + size: meta.len(), + ..Default::default() + }) + } else { + None + } + } else { + Some(CliprdrFile { + name: f.to_owned(), + size: *s, + ..Default::default() + }) + } + }) + .collect::>(); + Message { + union: Some(message::Union::Cliprdr(Cliprdr { + union: Some(cliprdr::Union::Files(CliprdrFiles { + files, + ..Default::default() + })), + ..Default::default() + })), + ..Default::default() + } + } } } @@ -243,7 +277,7 @@ pub mod unix_file_clip { side: ClipboardSide, clip: ClipboardFile, conn_id: i32, - ) -> Option { + ) -> Vec { log::debug!("got clipfile from client peer"); match clip { ClipboardFile::MonitorReady => { @@ -257,7 +291,7 @@ pub mod unix_file_clip { .is_some() { log::error!("no file contents format found"); - return None; + return vec![]; }; let Some(file_descriptor_id) = format_list .iter() @@ -265,13 +299,13 @@ pub mod unix_file_clip { .map(|(id, _)| *id) else { log::error!("no file descriptor format found"); - return None; + return vec![]; }; // sync file system from peer let data = ClipboardFile::FormatDataRequest { requested_format_id: file_descriptor_id, }; - return Some(clip_2_msg(data)); + return vec![clip_2_msg(data)]; } ClipboardFile::FormatListResponse { msg_flags: _msg_flags, @@ -282,13 +316,13 @@ pub mod unix_file_clip { log::debug!("requested format id: {}", _requested_format_id); let format_data = serv_files::get_file_list_pdu(); if !format_data.is_empty() { - return Some(clip_2_msg(ClipboardFile::FormatDataResponse { + return vec![clip_2_msg(ClipboardFile::FormatDataResponse { msg_flags: 1, format_data, - })); + })]; } // empty file list, send failure message - return Some(msg_resp_format_data_failure()); + return vec![msg_resp_format_data_failure()]; } #[cfg(target_os = "linux")] ClipboardFile::FormatDataResponse { @@ -329,7 +363,7 @@ pub mod unix_file_clip { .. } => { log::debug!("file contents request: stream_id: {}, list_index: {}, dw_flags: {}, n_position_low: {}, n_position_high: {}, cb_requested: {}", stream_id, list_index, dw_flags, n_position_low, n_position_high, cb_requested); - match serv_files::read_file_contents( + return serv_files::read_file_contents( conn_id, stream_id, list_index, @@ -337,15 +371,16 @@ pub mod unix_file_clip { n_position_low, n_position_high, cb_requested, - ) { - Ok(data) => { - return Some(clip_2_msg(data)); - } + ) + .into_iter() + .map(|res| match res { + Ok(data) => clip_2_msg(data), Err(e) => { log::error!("failed to read file contents: {:?}", e); - return Some(resp_file_contents_fail(stream_id)); + resp_file_contents_fail(stream_id) } - } + }) + .collect::<_>(); } #[cfg(target_os = "linux")] ClipboardFile::FileContentsResponse { @@ -387,6 +422,6 @@ pub mod unix_file_clip { log::error!("unsupported clipboard file type"); } } - None + vec![] } } diff --git a/src/server/connection.rs b/src/server/connection.rs index 5b1302b6a..bc4708f5f 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -633,7 +633,22 @@ impl Connection { } #[cfg(target_os = "windows")] ipc::Data::ClipboardFile(clip) => { - allow_err!(conn.stream.send(&clip_2_msg(clip)).await); + match clip { + clipboard::ClipboardFile::Files { files } => { + let files = files.into_iter().map(|(f, s)| { + (f, s as i64) + }).collect::>(); + conn.post_file_audit( + FileAuditType::RemoteSend, + "", + files, + json!({}), + ); + } + _ => { + allow_err!(conn.stream.send(&clip_2_msg(clip)).await); + } + } } ipc::Data::PrivacyModeState((_, state, impl_key)) => { let msg_out = match state { @@ -2463,14 +2478,25 @@ impl Connection { } #[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))] Some(message::Union::Cliprdr(clip)) => { - if let Some(clip) = msg_2_clip(clip) { + if let Some(cliprdr::Union::Files(files)) = &clip.union { + self.post_file_audit( + FileAuditType::RemoteReceive, + "", + files + .files + .iter() + .map(|f| (f.name.clone(), f.size as i64)) + .collect::>(), + json!({}), + ); + } else if let Some(clip) = msg_2_clip(clip) { #[cfg(target_os = "windows")] { self.send_to_cm(ipc::Data::ClipboardFile(clip)); } #[cfg(feature = "unix-file-copy-paste")] if crate::is_support_file_copy_paste(&self.lr.version) { - let mut out_msg = None; + let mut out_msgs = vec![]; #[cfg(target_os = "macos")] if clipboard::platform::unix::macos::should_handle_msg(&clip) { @@ -2485,7 +2511,7 @@ impl Connection { }); } } else { - out_msg = unix_file_clip::serve_clip_messages( + out_msgs = unix_file_clip::serve_clip_messages( ClipboardSide::Host, clip, self.inner.id(), @@ -2494,14 +2520,31 @@ impl Connection { #[cfg(not(target_os = "macos"))] { - out_msg = unix_file_clip::serve_clip_messages( + out_msgs = unix_file_clip::serve_clip_messages( ClipboardSide::Host, clip, self.inner.id(), ); } - if let Some(msg) = out_msg { + for msg in out_msgs.into_iter() { + if let Some(message::Union::Cliprdr(cliprdr)) = msg.union.as_ref() { + if let Some(cliprdr::Union::Files(files)) = + cliprdr.union.as_ref() + { + self.post_file_audit( + FileAuditType::RemoteSend, + "", + files + .files + .iter() + .map(|f| (f.name.clone(), f.size as i64)) + .collect::>(), + json!({}), + ); + continue; + } + } self.send(msg).await; } }