feat: clipboard files, audit (#12730)

Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
fufesou
2025-08-25 22:29:53 +08:00
committed by GitHub
parent f4fb31d7a1
commit 6381f43f01
9 changed files with 242 additions and 31 deletions

View File

@@ -170,6 +170,8 @@ extern "C"
typedef UINT (*pcNotifyClipboardMsg)(UINT32 connID, const NOTIFICATION_MESSAGE *msg); typedef UINT (*pcNotifyClipboardMsg)(UINT32 connID, const NOTIFICATION_MESSAGE *msg);
typedef UINT (*pcHandleClipboardFiles)(UINT32 connID, size_t nFiles, WCHAR **fileNames);
typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext *context, typedef UINT (*pcCliprdrClientFormatList)(CliprdrClientContext *context,
const CLIPRDR_FORMAT_LIST *formatList); const CLIPRDR_FORMAT_LIST *formatList);
typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext *context, typedef UINT (*pcCliprdrServerFormatList)(CliprdrClientContext *context,
@@ -217,6 +219,7 @@ extern "C"
pcCliprdrMonitorReady MonitorReady; pcCliprdrMonitorReady MonitorReady;
pcCliprdrTempDirectory TempDirectory; pcCliprdrTempDirectory TempDirectory;
pcNotifyClipboardMsg NotifyClipboardMsg; pcNotifyClipboardMsg NotifyClipboardMsg;
pcHandleClipboardFiles HandleClipboardFiles;
pcCliprdrClientFormatList ClientFormatList; pcCliprdrClientFormatList ClientFormatList;
pcCliprdrServerFormatList ServerFormatList; pcCliprdrServerFormatList ServerFormatList;
pcCliprdrClientFormatListResponse ClientFormatListResponse; pcCliprdrClientFormatListResponse ClientFormatListResponse;

View File

@@ -132,6 +132,9 @@ pub enum ClipboardFile {
requested_data: Vec<u8>, requested_data: Vec<u8>,
}, },
TryEmpty, TryEmpty,
Files {
files: Vec<(String, u64)>,
},
} }
struct MsgChannel { struct MsgChannel {

View File

@@ -5,7 +5,7 @@ use hbb_common::{
log, log,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc, usize};
lazy_static::lazy_static! { lazy_static::lazy_static! {
// local files are cached, this value should not be changed when copying files // local files are cached, this value should not be changed when copying files
@@ -34,6 +34,7 @@ enum FileContentsRequest {
struct ClipFiles { struct ClipFiles {
files: Vec<String>, files: Vec<String>,
file_list: Vec<LocalFile>, file_list: Vec<LocalFile>,
first_file_index: usize,
files_pdu: Vec<u8>, files_pdu: Vec<u8>,
} }
@@ -41,6 +42,7 @@ impl ClipFiles {
fn clear(&mut self) { fn clear(&mut self) {
self.files.clear(); self.files.clear();
self.file_list.clear(); self.file_list.clear();
self.first_file_index = usize::MAX;
self.files_pdu.clear(); self.files_pdu.clear();
} }
@@ -50,6 +52,11 @@ impl ClipFiles {
.map(|s| PathBuf::from(s)) .map(|s| PathBuf::from(s))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.file_list = construct_file_list(&clipboard_paths)?; 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(); self.files = clipboard_files.to_vec();
Ok(()) Ok(())
} }
@@ -63,6 +70,33 @@ impl ClipFiles {
self.files_pdu = data.to_vec() self.files_pdu = data.to_vec()
} }
fn get_files_for_audit(&self, request: &FileContentsRequest) -> Option<ClipboardFile> {
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( fn serve_file_contents(
&mut self, &mut self,
conn_id: i32, conn_id: i32,
@@ -192,7 +226,7 @@ pub fn read_file_contents(
n_position_low: i32, n_position_low: i32,
n_position_high: i32, n_position_high: i32,
cb_requested: i32, cb_requested: i32,
) -> Result<ClipboardFile, CliprdrError> { ) -> Vec<Result<ClipboardFile, CliprdrError>> {
let fcr = if dw_flags == 0x1 { let fcr = if dw_flags == 0x1 {
FileContentsRequest::Size { FileContentsRequest::Size {
stream_id, stream_id,
@@ -209,12 +243,18 @@ pub fn read_file_contents(
length, length,
} }
} else { } else {
return Err(CliprdrError::InvalidRequest { return vec![Err(CliprdrError::InvalidRequest {
description: format!("got invalid FileContentsRequest, dw_flats: {dw_flags}"), 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> { pub fn sync_files(files: &[String]) -> Result<(), CliprdrError> {

View File

@@ -381,6 +381,9 @@ pub type pcCliprdrTempDirectory = ::std::option::Option<
pub type pcNotifyClipboardMsg = ::std::option::Option< pub type pcNotifyClipboardMsg = ::std::option::Option<
unsafe extern "C" fn(connID: UINT32, msg: *const NOTIFICATION_MESSAGE) -> UINT, 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< pub type pcCliprdrClientFormatList = ::std::option::Option<
unsafe extern "C" fn( unsafe extern "C" fn(
context: *mut CliprdrClientContext, context: *mut CliprdrClientContext,
@@ -492,6 +495,7 @@ pub struct _cliprdr_client_context {
pub MonitorReady: pcCliprdrMonitorReady, pub MonitorReady: pcCliprdrMonitorReady,
pub TempDirectory: pcCliprdrTempDirectory, pub TempDirectory: pcCliprdrTempDirectory,
pub NotifyClipboardMsg: pcNotifyClipboardMsg, pub NotifyClipboardMsg: pcNotifyClipboardMsg,
pub HandleClipboardFiles: pcHandleClipboardFiles,
pub ClientFormatList: pcCliprdrClientFormatList, pub ClientFormatList: pcCliprdrClientFormatList,
pub ServerFormatList: pcCliprdrServerFormatList, pub ServerFormatList: pcCliprdrServerFormatList,
pub ClientFormatListResponse: pcCliprdrClientFormatListResponse, pub ClientFormatListResponse: pcCliprdrClientFormatListResponse,
@@ -529,6 +533,7 @@ impl CliprdrClientContext {
enable_others: bool, enable_others: bool,
response_wait_timeout_secs: u32, response_wait_timeout_secs: u32,
notify_callback: pcNotifyClipboardMsg, notify_callback: pcNotifyClipboardMsg,
handle_clipboard_files: pcHandleClipboardFiles,
client_format_list: pcCliprdrClientFormatList, client_format_list: pcCliprdrClientFormatList,
client_format_list_response: pcCliprdrClientFormatListResponse, client_format_list_response: pcCliprdrClientFormatListResponse,
client_format_data_request: pcCliprdrClientFormatDataRequest, client_format_data_request: pcCliprdrClientFormatDataRequest,
@@ -547,6 +552,7 @@ impl CliprdrClientContext {
MonitorReady: None, MonitorReady: None,
TempDirectory: None, TempDirectory: None,
NotifyClipboardMsg: notify_callback, NotifyClipboardMsg: notify_callback,
HandleClipboardFiles: handle_clipboard_files,
ClientFormatList: client_format_list, ClientFormatList: client_format_list,
ServerFormatList: None, ServerFormatList: None,
ClientFormatListResponse: client_format_list_response, ClientFormatListResponse: client_format_list_response,
@@ -758,6 +764,9 @@ pub fn server_clip_file(
ret ret
); );
} }
ClipboardFile::Files { .. } => {
// unreachable
}
} }
ret ret
} }
@@ -967,6 +976,7 @@ pub fn create_cliprdr_context(
enable_others, enable_others,
response_wait_timeout_secs, response_wait_timeout_secs,
Some(notify_callback), Some(notify_callback),
Some(handle_clipboard_files),
Some(client_format_list), Some(client_format_list),
Some(client_format_list_response), Some(client_format_list_response),
Some(client_format_data_request), Some(client_format_data_request),
@@ -1021,6 +1031,61 @@ extern "C" fn notify_callback(conn_id: UINT32, msg: *const NOTIFICATION_MESSAGE)
0 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( extern "C" fn client_format_list(
_context: *mut CliprdrClientContext, _context: *mut CliprdrClientContext,
clip_format_list: *const CLIPRDR_FORMAT_LIST, clip_format_list: *const CLIPRDR_FORMAT_LIST,

View File

@@ -239,6 +239,7 @@ struct wf_clipboard
size_t nFiles; size_t nFiles;
size_t file_array_size; size_t file_array_size;
WCHAR **file_names; WCHAR **file_names;
size_t first_file_index;
FILEDESCRIPTORW **fileDescriptor; FILEDESCRIPTORW **fileDescriptor;
BOOL legacyApi; BOOL legacyApi;
@@ -2024,6 +2025,7 @@ static void clear_file_array(wfClipboard *clipboard)
clipboard->file_array_size = 0; clipboard->file_array_size = 0;
clipboard->nFiles = 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, 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; return FALSE;
} }
if ((clipboard->fileDescriptor[clipboard->nFiles]->dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY) == 0) {
clipboard->first_file_index = clipboard->nFiles;
}
clipboard->nFiles++; clipboard->nFiles++;
return TRUE; return TRUE;
} }
@@ -2968,6 +2975,14 @@ wf_cliprdr_server_file_contents_request(CliprdrClientContext *context,
{ {
LARGE_INTEGER dlibMove; LARGE_INTEGER dlibMove;
ULARGE_INTEGER dlibNewPosition; 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.HighPart = fileContentsRequest->nPositionHigh;
dlibMove.LowPart = fileContentsRequest->nPositionLow; dlibMove.LowPart = fileContentsRequest->nPositionLow;
hRet = IStream_Seek(pStreamStc, dlibMove, STREAM_SEEK_SET, &dlibNewPosition); 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; rc = ERROR_INTERNAL_ERROR;
goto exit; 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( bRet = wf_cliprdr_get_file_contents(
clipboard->file_names[fileContentsRequest->listIndex], pData, clipboard->file_names[fileContentsRequest->listIndex], pData,
fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested, fileContentsRequest->nPositionLow, fileContentsRequest->nPositionHigh, cbRequested,

View File

@@ -2257,7 +2257,7 @@ impl<T: InvokeUiSession> Remote<T> {
} }
#[cfg(feature = "unix-file-copy-paste")] #[cfg(feature = "unix-file-copy-paste")]
if crate::is_support_file_copy_paste_num(self.handler.lc.read().unwrap().version) { 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")] #[cfg(target_os = "macos")]
if clipboard::platform::unix::macos::should_handle_msg(&clip) { if clipboard::platform::unix::macos::should_handle_msg(&clip) {
@@ -2269,7 +2269,7 @@ impl<T: InvokeUiSession> Remote<T> {
log::error!("failed to handle cliprdr msg: {}", e); log::error!("failed to handle cliprdr msg: {}", e);
} }
} else { } else {
out_msg = unix_file_clip::serve_clip_messages( out_msgs = unix_file_clip::serve_clip_messages(
ClipboardSide::Client, ClipboardSide::Client,
clip, clip,
self.client_conn_id, self.client_conn_id,
@@ -2278,14 +2278,14 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
{ {
out_msg = unix_file_clip::serve_clip_messages( out_msgs = unix_file_clip::serve_clip_messages(
ClipboardSide::Client, ClipboardSide::Client,
clip, clip,
self.client_conn_id, self.client_conn_id,
); );
} }
if let Some(msg) = out_msg { for msg in out_msgs.into_iter() {
allow_err!(_peer.send(&msg).await); allow_err!(_peer.send(&msg).await);
} }
} }

View File

@@ -143,6 +143,40 @@ pub fn clip_2_msg(clip: ClipboardFile) -> Message {
})), })),
..Default::default() ..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::<Vec<_>>();
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, side: ClipboardSide,
clip: ClipboardFile, clip: ClipboardFile,
conn_id: i32, conn_id: i32,
) -> Option<Message> { ) -> Vec<Message> {
log::debug!("got clipfile from client peer"); log::debug!("got clipfile from client peer");
match clip { match clip {
ClipboardFile::MonitorReady => { ClipboardFile::MonitorReady => {
@@ -257,7 +291,7 @@ pub mod unix_file_clip {
.is_some() .is_some()
{ {
log::error!("no file contents format found"); log::error!("no file contents format found");
return None; return vec![];
}; };
let Some(file_descriptor_id) = format_list let Some(file_descriptor_id) = format_list
.iter() .iter()
@@ -265,13 +299,13 @@ pub mod unix_file_clip {
.map(|(id, _)| *id) .map(|(id, _)| *id)
else { else {
log::error!("no file descriptor format found"); log::error!("no file descriptor format found");
return None; return vec![];
}; };
// sync file system from peer // sync file system from peer
let data = ClipboardFile::FormatDataRequest { let data = ClipboardFile::FormatDataRequest {
requested_format_id: file_descriptor_id, requested_format_id: file_descriptor_id,
}; };
return Some(clip_2_msg(data)); return vec![clip_2_msg(data)];
} }
ClipboardFile::FormatListResponse { ClipboardFile::FormatListResponse {
msg_flags: _msg_flags, msg_flags: _msg_flags,
@@ -282,13 +316,13 @@ pub mod unix_file_clip {
log::debug!("requested format id: {}", _requested_format_id); log::debug!("requested format id: {}", _requested_format_id);
let format_data = serv_files::get_file_list_pdu(); let format_data = serv_files::get_file_list_pdu();
if !format_data.is_empty() { if !format_data.is_empty() {
return Some(clip_2_msg(ClipboardFile::FormatDataResponse { return vec![clip_2_msg(ClipboardFile::FormatDataResponse {
msg_flags: 1, msg_flags: 1,
format_data, format_data,
})); })];
} }
// empty file list, send failure message // empty file list, send failure message
return Some(msg_resp_format_data_failure()); return vec![msg_resp_format_data_failure()];
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
ClipboardFile::FormatDataResponse { 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); 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, conn_id,
stream_id, stream_id,
list_index, list_index,
@@ -337,15 +371,16 @@ pub mod unix_file_clip {
n_position_low, n_position_low,
n_position_high, n_position_high,
cb_requested, cb_requested,
) { )
Ok(data) => { .into_iter()
return Some(clip_2_msg(data)); .map(|res| match res {
} Ok(data) => clip_2_msg(data),
Err(e) => { Err(e) => {
log::error!("failed to read file contents: {:?}", 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")] #[cfg(target_os = "linux")]
ClipboardFile::FileContentsResponse { ClipboardFile::FileContentsResponse {
@@ -387,6 +422,6 @@ pub mod unix_file_clip {
log::error!("unsupported clipboard file type"); log::error!("unsupported clipboard file type");
} }
} }
None vec![]
} }
} }

View File

@@ -633,7 +633,22 @@ impl Connection {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
ipc::Data::ClipboardFile(clip) => { 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::<Vec<_>>();
conn.post_file_audit(
FileAuditType::RemoteSend,
"",
files,
json!({}),
);
}
_ => {
allow_err!(conn.stream.send(&clip_2_msg(clip)).await);
}
}
} }
ipc::Data::PrivacyModeState((_, state, impl_key)) => { ipc::Data::PrivacyModeState((_, state, impl_key)) => {
let msg_out = match state { let msg_out = match state {
@@ -2463,14 +2478,25 @@ impl Connection {
} }
#[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))] #[cfg(any(target_os = "windows", feature = "unix-file-copy-paste"))]
Some(message::Union::Cliprdr(clip)) => { 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::<Vec<(String, i64)>>(),
json!({}),
);
} else if let Some(clip) = msg_2_clip(clip) {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
self.send_to_cm(ipc::Data::ClipboardFile(clip)); self.send_to_cm(ipc::Data::ClipboardFile(clip));
} }
#[cfg(feature = "unix-file-copy-paste")] #[cfg(feature = "unix-file-copy-paste")]
if crate::is_support_file_copy_paste(&self.lr.version) { if crate::is_support_file_copy_paste(&self.lr.version) {
let mut out_msg = None; let mut out_msgs = vec![];
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
if clipboard::platform::unix::macos::should_handle_msg(&clip) { if clipboard::platform::unix::macos::should_handle_msg(&clip) {
@@ -2485,7 +2511,7 @@ impl Connection {
}); });
} }
} else { } else {
out_msg = unix_file_clip::serve_clip_messages( out_msgs = unix_file_clip::serve_clip_messages(
ClipboardSide::Host, ClipboardSide::Host,
clip, clip,
self.inner.id(), self.inner.id(),
@@ -2494,14 +2520,31 @@ impl Connection {
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
{ {
out_msg = unix_file_clip::serve_clip_messages( out_msgs = unix_file_clip::serve_clip_messages(
ClipboardSide::Host, ClipboardSide::Host,
clip, clip,
self.inner.id(), 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::<Vec<(String, i64)>>(),
json!({}),
);
continue;
}
}
self.send(msg).await; self.send(msg).await;
} }
} }