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 (*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;

View File

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

View File

@@ -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<String>,
file_list: Vec<LocalFile>,
first_file_index: usize,
files_pdu: Vec<u8>,
}
@@ -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::<Vec<_>>();
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<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(
&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<ClipboardFile, CliprdrError> {
) -> Vec<Result<ClipboardFile, CliprdrError>> {
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> {

View File

@@ -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,

View File

@@ -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,