diff --git a/client/X11/CMakeLists.txt b/client/X11/CMakeLists.txt index 5a5fa275f..ea1cc7ee5 100644 --- a/client/X11/CMakeLists.txt +++ b/client/X11/CMakeLists.txt @@ -41,9 +41,7 @@ set(${MODULE_PREFIX}_SRCS xf_channels.c xf_channels.h xf_cliprdr.c - xf_cliprdr.h - xf_cliprdr_file.c - xf_cliprdr_file.h + xf_cliprdr.h xf_monitor.c xf_monitor.h xf_disp.c @@ -226,36 +224,6 @@ if(WITH_XFIXES) set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} ${XFIXES_LIBRARIES}) endif() -if (NOT APPLE AND NOT WIN32 AND NOT ANDROID) - set(OPT_FUSE_DEFAULT ON) -else() - set(OPT_FUSE_DEFAULT OFF) -endif() - -option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT}) -if(WITH_FUSE) - include(FindPkgConfig) - find_package(PkgConfig REQUIRED) - - pkg_check_modules(FUSE3 fuse3) - if (FUSE3_FOUND) - message("[FUSE3] using ${FUSE3_LIBRARIES}") - - include_directories(${FUSE3_INCLUDE_DIRS}) - add_definitions(-DWITH_FUSE3) - list(APPEND ${MODULE_PREFIX}_LIBS ${FUSE3_LIBRARIES}) - else() - pkg_check_modules(FUSE2 REQUIRED fuse) - - message("[FUSE2] using ${FUSE2_LIBRARIES}") - add_definitions(-DWITH_FUSE2) - include_directories(${FUSE2_INCLUDE_DIRS}) - list(APPEND ${MODULE_PREFIX}_LIBS ${FUSE2_LIBRARIES}) - endif() - - add_definitions(-D_FILE_OFFSET_BITS=64) -endif() - include_directories(${PROJECT_SOURCE_DIR}/resources) set(${MODULE_PREFIX}_LIBS ${${MODULE_PREFIX}_LIBS} freerdp-client freerdp m) diff --git a/client/X11/xf_cliprdr.c b/client/X11/xf_cliprdr.c index d92bb4c44..3badb3930 100644 --- a/client/X11/xf_cliprdr.c +++ b/client/X11/xf_cliprdr.c @@ -44,8 +44,9 @@ #include #include +#include + #include "xf_cliprdr.h" -#include "xf_cliprdr_file.h" #include "xf_event.h" #define TAG CLIENT_TAG("x11.cliprdr") @@ -123,10 +124,6 @@ struct xf_clipboard int xfixes_error_base; BOOL xfixes_supported; - /* File clipping */ - BOOL streams_supported; - BOOL file_formats_registered; - UINT32 file_capability_flags; /* last sent data */ CLIPRDR_FORMAT* lastSentFormats; UINT32 lastSentNumFormats; @@ -773,6 +770,7 @@ static UINT xf_cliprdr_send_format_list(xfClipboard* clipboard, const CLIPRDR_FO xf_clipboard_copy_formats(clipboard, formats, numFormats); /* Ensure all pending requests are answered. */ xf_cliprdr_send_data_response(clipboard, NULL, NULL, 0); + cliprdr_file_context_clear(clipboard->file); WINPR_ASSERT(clipboard->context); WINPR_ASSERT(clipboard->context->ClientFormatList); @@ -867,12 +865,19 @@ static void xf_cliprdr_process_requested_data(xfClipboard* clipboard, BOOL hasDa UINT32 file_count = DstSize / sizeof(FILEDESCRIPTORW); pDstData = NULL; DstSize = 0; - error = cliprdr_serialize_file_list_ex(clipboard->file_capability_flags, file_array, - file_count, &pDstData, &DstSize); + + const UINT32 flags = cliprdr_file_context_remote_get_flags(clipboard->file); + error = cliprdr_serialize_file_list_ex(flags, file_array, file_count, &pDstData, &DstSize); if (error) WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error); + UINT32 formatId = ClipboardGetFormatId(clipboard->system, mime_uri_list); + UINT32 url_size = 0; + char* url = ClipboardGetData(clipboard->system, formatId, &url_size); + cliprdr_file_context_update_client_data(clipboard->file, url, url_size); + free(url); + free(file_array); } @@ -1453,11 +1458,7 @@ static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard) generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES; WINPR_ASSERT(clipboard); - if (clipboard->streams_supported && clipboard->file_formats_registered) - generalCapabilitySet.generalFlags |= - CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS | CB_HUGE_FILE_SUPPORT_ENABLED; - - clipboard->file_capability_flags = generalCapabilitySet.generalFlags; + generalCapabilitySet.generalFlags |= cliprdr_file_context_current_flags(clipboard->file); WINPR_ASSERT(clipboard->context); WINPR_ASSERT(clipboard->context->ClientCapabilities); @@ -1584,7 +1585,7 @@ static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context, capsPtr = (const BYTE*)capabilities->capabilitySets; WINPR_ASSERT(capsPtr); - clipboard->streams_supported = FALSE; + cliprdr_file_context_remote_set_flags(clipboard->file, 0); for (i = 0; i < capabilities->cCapabilitiesSets; i++) { @@ -1594,10 +1595,7 @@ static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context, { generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*)caps; - if (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED) - { - clipboard->streams_supported = TRUE; - } + cliprdr_file_context_remote_set_flags(clipboard->file, generalCaps->generalFlags); } capsPtr += caps->capabilitySetLength; @@ -1878,7 +1876,7 @@ xf_cliprdr_server_format_data_response(CliprdrClientContext* context, if (strcmp(clipboard->requestedFormat->formatName, type_FileGroupDescriptorW) == 0) { - if (!cliprdr_file_context_update_data(clipboard->file, data, size)) + if (!cliprdr_file_context_update_server_data(clipboard->file, data, size)) WLog_WARN(TAG, "failed to update file descriptors"); srcFormatId = ClipboardGetFormatId(clipboard->system, type_FileGroupDescriptorW); @@ -1997,82 +1995,6 @@ xf_cliprdr_server_format_data_response(CliprdrClientContext* context, return CHANNEL_RC_OK; } - -static UINT -xf_cliprdr_send_file_contents_failure(CliprdrClientContext* context, - const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) -{ - CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; - - WINPR_ASSERT(fileContentsRequest); - - response.common.msgFlags = CB_RESPONSE_FAIL; - response.streamId = fileContentsRequest->streamId; - - WINPR_ASSERT(context); - WINPR_ASSERT(context->ClientFileContentsResponse); - return context->ClientFileContentsResponse(context, &response); -} - -static UINT -xf_cliprdr_server_file_contents_request(CliprdrClientContext* context, - const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) -{ - UINT error = NO_ERROR; - xfClipboard* clipboard; - - WINPR_ASSERT(context); - WINPR_ASSERT(fileContentsRequest); - - clipboard = cliprdr_file_context_get_context(context->custom); - WINPR_ASSERT(clipboard); - - /* - * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST): - * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time. - */ - if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) == - (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) - { - WLog_ERR(TAG, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); - return xf_cliprdr_send_file_contents_failure(context, fileContentsRequest); - } - - if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) - error = xf_cliprdr_server_file_size_request(clipboard, fileContentsRequest); - - if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) - error = xf_cliprdr_server_file_range_request(clipboard, fileContentsRequest); - - if (error) - { - WLog_ERR(TAG, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", error); - return xf_cliprdr_send_file_contents_failure(context, fileContentsRequest); - } - - return CHANNEL_RC_OK; -} - -static BOOL xf_cliprdr_clipboard_is_valid_unix_filename(LPCWSTR filename) -{ - LPCWSTR c; - - if (!filename) - return FALSE; - - if (filename[0] == L'\0') - return FALSE; - - /* Reserved characters */ - for (c = filename; *c; ++c) - { - if (*c == L'/') - return FALSE; - } - - return TRUE; -} - xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) { int n = 0; @@ -2090,6 +2012,10 @@ xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) return NULL; } + clipboard->file = cliprdr_file_context_new(clipboard); + if (!clipboard->file) + goto fail; + xfc->clipboard = clipboard; clipboard->xfc = xfc; channels = xfc->common.context.channels; @@ -2199,7 +2125,7 @@ xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) const UINT32 uid = ClipboardGetFormatId(clipboard->system, mime_uri_list); if (uid) { - clipboard->file_formats_registered = TRUE; + cliprdr_file_context_set_locally_available(clipboard->file, TRUE); clientFormat->atom = XInternAtom(xfc->display, mime_uri_list, False); clientFormat->localFormat = uid; clientFormat->formatToRequest = fgid; @@ -2214,7 +2140,7 @@ xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) const UINT32 gid = ClipboardGetFormatId(clipboard->system, mime_gnome_copied_files); if (gid != 0) { - clipboard->file_formats_registered = TRUE; + cliprdr_file_context_set_locally_available(clipboard->file, TRUE); clientFormat->atom = XInternAtom(xfc->display, mime_gnome_copied_files, False); clientFormat->localFormat = gid; clientFormat->formatToRequest = fgid; @@ -2229,7 +2155,7 @@ xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) const UINT32 mid = ClipboardGetFormatId(clipboard->system, mime_mate_copied_files); if (mid != 0) { - clipboard->file_formats_registered = TRUE; + cliprdr_file_context_set_locally_available(clipboard->file, TRUE); clientFormat->atom = XInternAtom(xfc->display, mime_mate_copied_files, False); clientFormat->localFormat = mid; clientFormat->formatToRequest = fgid; @@ -2245,10 +2171,6 @@ xfClipboard* xf_clipboard_new(xfContext* xfc, BOOL relieveFilenameRestriction) clipboard->numTargets = 2; clipboard->incr_atom = XInternAtom(xfc->display, "INCR", FALSE); - clipboard->file = cliprdr_file_context_new(clipboard); - if (!clipboard->file) - goto fail; - return clipboard; fail: @@ -2297,7 +2219,6 @@ void xf_cliprdr_init(xfContext* xfc, CliprdrClientContext* cliprdr) cliprdr->ServerFormatListResponse = xf_cliprdr_server_format_list_response; cliprdr->ServerFormatDataRequest = xf_cliprdr_server_format_data_request; cliprdr->ServerFormatDataResponse = xf_cliprdr_server_format_data_response; - cliprdr->ServerFileContentsRequest = xf_cliprdr_server_file_contents_request; cliprdr_file_context_init(xfc->clipboard->file, cliprdr); } diff --git a/client/common/CMakeLists.txt b/client/common/CMakeLists.txt index fb9066365..97583961f 100644 --- a/client/common/CMakeLists.txt +++ b/client/common/CMakeLists.txt @@ -30,6 +30,7 @@ set(SRCS client_rails.c cmdline.c file.c + client_cliprdr_file.c geometry.c smartcard_cli.c) @@ -39,6 +40,35 @@ foreach(FREERDP_CHANNELS_CLIENT_SRC ${FREERDP_CHANNELS_CLIENT_SRCS}) list(APPEND SRCS "${FREERDP_CHANNELS_CLIENT_SRC}") endforeach() +if (NOT APPLE AND NOT WIN32 AND NOT ANDROID) + set(OPT_FUSE_DEFAULT ON) +else() + set(OPT_FUSE_DEFAULT OFF) +endif() + +option(WITH_FUSE "Build clipboard with FUSE file copy support" ${OPT_FUSE_DEFAULT}) +if(WITH_FUSE) + include(FindPkgConfig) + find_package(PkgConfig REQUIRED) + + pkg_check_modules(FUSE3 fuse3) + if (FUSE3_FOUND) + message("[FUSE3] using ${FUSE3_LIBRARIES}") + + include_directories(${FUSE3_INCLUDE_DIRS}) + add_definitions(-DWITH_FUSE3) + list(APPEND ${MODULE_PREFIX}_LIBS ${FUSE3_LIBRARIES}) + else() + pkg_check_modules(FUSE2 REQUIRED fuse) + + message("[FUSE2] using ${FUSE2_LIBRARIES}") + add_definitions(-DWITH_FUSE2) + include_directories(${FUSE2_INCLUDE_DIRS}) + list(APPEND ${MODULE_PREFIX}_LIBS ${FUSE2_LIBRARIES}) + endif() + + add_definitions(-D_FILE_OFFSET_BITS=64) +endif() # On windows create dll version information. # Vendor, product and year are already set in top level CMakeLists.txt diff --git a/client/X11/xf_cliprdr_file.c b/client/common/client_cliprdr_file.c similarity index 63% rename from client/X11/xf_cliprdr_file.c rename to client/common/client_cliprdr_file.c index 1a8a6ab62..1202e402c 100644 --- a/client/X11/xf_cliprdr_file.c +++ b/client/common/client_cliprdr_file.c @@ -53,19 +53,17 @@ #include #include -#include "xf_cliprdr_file.h" - -#define TAG CLIENT_TAG("x11.cliprdr.file") +#include #define MAX_CLIPBOARD_FORMATS 255 #define WIN32_FILETIME_TO_UNIX_EPOCH_USEC UINT64_C(116444736000000000) #ifdef WITH_DEBUG_CLIPRDR -#define DEBUG_CLIPRDR(...) WLog_DBG(TAG, __VA_ARGS__) +#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__) #else -#define DEBUG_CLIPRDR(...) \ - do \ - { \ +#define DEBUG_CLIPRDR(log, ...) \ + do \ + { \ } while (0) #endif @@ -94,6 +92,21 @@ typedef struct } CliprdrFuseInode; #endif +typedef struct +{ + char* name; + FILE* fp; +} CliprdrLocalFile; + +typedef struct +{ + UINT32 streamID; + + BOOL locked; + size_t count; + CliprdrLocalFile* files; +} CliprdrLocalStream; + struct cliprdr_file_context { #if defined(WITH_FUSE2) || defined(WITH_FUSE3) @@ -107,16 +120,27 @@ struct cliprdr_file_context wArrayList* ino_list; #endif + /* File clipping */ + BOOL file_formats_registered; + UINT32 file_capability_flags; + + UINT32 local_stream_id; + UINT32 remote_stream_id; + + wHashTable* local_streams; + wLog* log; void* clipboard; CliprdrClientContext* context; char* path; BYTE hash[WINPR_SHA256_DIGEST_LENGTH]; }; -void cliprdr_file_session_terminate(CliprdrFileContext* file); +static CliprdrLocalStream* cliprdr_local_stream_new(UINT32 streamID, const char* data, size_t size); +static void cliprdr_file_session_terminate(CliprdrFileContext* file); +static BOOL local_stream_discard(const void* key, void* value, void* arg); #if defined(WITH_FUSE2) || defined(WITH_FUSE3) -static BOOL xf_fuse_repopulate(wArrayList* list); +static BOOL xf_fuse_repopulate(CliprdrFileContext* file, wArrayList* list); static UINT xf_cliprdr_send_client_file_contents(CliprdrFileContext* file, UINT32 streamId, UINT32 listIndex, UINT32 dwFlags, UINT32 nPositionLow, UINT32 nPositionHigh, @@ -566,9 +590,11 @@ static void fuse_abort(int sig, const char* signame, void* context) { CliprdrFileContext* file = (CliprdrFileContext*)context; - WLog_INFO(TAG, "signal %s [%d] aborting session", signame, sig); if (file) + { + WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig); cliprdr_file_session_terminate(file); + } } static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg) @@ -577,7 +603,7 @@ static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg) WINPR_ASSERT(file); - DEBUG_CLIPRDR("Starting fuse with mountpoint '%s'", file->path); + DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path); struct fuse_args args = FUSE_ARGS_INIT(0, NULL); #if FUSE_USE_VERSION >= 30 @@ -616,14 +642,16 @@ static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg) #endif fuse_opt_free_args(&args); - DEBUG_CLIPRDR("Quitting fuse with mountpoint '%s'", file->path); + DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path); ExitThread(0); return 0; } -static CliprdrFuseInode* cliprdr_file_fuse_create_root_node(void) +static CliprdrFuseInode* cliprdr_file_fuse_create_root_node(CliprdrFileContext* file) { + WINPR_ASSERT(file); + CliprdrFuseInode* rootNode = (CliprdrFuseInode*)calloc(1, sizeof(CliprdrFuseInode)); if (!rootNode) return NULL; @@ -640,20 +668,20 @@ static CliprdrFuseInode* cliprdr_file_fuse_create_root_node(void) if (!rootNode->child_inos || !rootNode->name) { cliprdr_file_fuse_inode_free(rootNode); - WLog_ERR(TAG, "fail to alloc rootNode's member"); + WLog_Print(file->log, WLOG_ERROR, "fail to alloc rootNode's member"); return NULL; } return rootNode; } -static BOOL xf_fuse_repopulate(wArrayList* list) +static BOOL xf_fuse_repopulate(CliprdrFileContext* file, wArrayList* list) { if (!list) return FALSE; ArrayList_Lock(list); ArrayList_Clear(list); - ArrayList_Append(list, cliprdr_file_fuse_create_root_node()); + ArrayList_Append(list, cliprdr_file_fuse_create_root_node(file)); ArrayList_Unlock(list); return TRUE; } @@ -705,9 +733,8 @@ static UINT xf_cliprdr_send_client_file_contents(CliprdrFileContext* file, UINT3 * * @return 0 on success, otherwise a Win32 error code */ -static UINT -xf_cliprdr_server_file_contents_response(CliprdrClientContext* context, - const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) +static UINT cliprdr_file_context_server_file_contents_response( + CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse) { size_t count; size_t index; @@ -819,16 +846,18 @@ static const char* cliprdr_file_fuse_split_basename(const char* name, size_t len return NULL; } -static BOOL cliprdr_file_fuse_check_stream(wStream* s, size_t count) +static BOOL cliprdr_file_fuse_check_stream(CliprdrFileContext* file, wStream* s, size_t count) { UINT32 nrDescriptors; - if (!Stream_CheckAndLogRequiredLength(TAG, s, sizeof(UINT32))) + + WINPR_ASSERT(file); + if (!Stream_CheckAndLogRequiredLengthWLog(file->log, s, sizeof(UINT32))) return FALSE; Stream_Read_UINT32(s, nrDescriptors); if (count != nrDescriptors) { - WLog_WARN(TAG, "format data response mismatch"); + WLog_Print(file->log, WLOG_WARN, "format data response mismatch"); return FALSE; } return TRUE; @@ -850,7 +879,7 @@ static BOOL cliprdr_file_fuse_create_nodes(CliprdrFileContext* file, wStream* s, mapDir = HashTable_New(TRUE); if (!mapDir) { - WLog_ERR(TAG, "fail to alloc hashtable"); + WLog_Print(file->log, WLOG_ERROR, "fail to alloc hashtable"); goto error; } if (!HashTable_SetupForStringData(mapDir, FALSE)) @@ -859,7 +888,7 @@ static BOOL cliprdr_file_fuse_create_nodes(CliprdrFileContext* file, wStream* s, FILEDESCRIPTORW* descriptor = (FILEDESCRIPTORW*)calloc(1, sizeof(FILEDESCRIPTORW)); if (!descriptor) { - WLog_ERR(TAG, "fail to alloc FILEDESCRIPTORW"); + WLog_Print(file->log, WLOG_ERROR, "fail to alloc FILEDESCRIPTORW"); goto error; } /* here we assume that parent folder always appears before its children */ @@ -869,7 +898,7 @@ static BOOL cliprdr_file_fuse_create_nodes(CliprdrFileContext* file, wStream* s, inode = (CliprdrFuseInode*)calloc(1, sizeof(CliprdrFuseInode)); if (!inode) { - WLog_ERR(TAG, "fail to alloc ino"); + WLog_Print(file->log, WLOG_ERROR, "fail to alloc ino"); break; } @@ -972,7 +1001,7 @@ static BOOL cliprdr_file_fuse_create_nodes(CliprdrFileContext* file, wStream* s, { /* baseName is freed in cliprdr_file_fuse_inode_free*/ cliprdr_file_fuse_inode_free(inode); - xf_fuse_repopulate(file->ino_list); + xf_fuse_repopulate(file, file->ino_list); } else { @@ -1003,7 +1032,7 @@ static BOOL cliprdr_file_fuse_generate_list(CliprdrFileContext* file, const BYTE if (size < 4) { - WLog_ERR(TAG, "size of format data response invalid : %" PRIu32, size); + WLog_Print(file->log, WLOG_ERROR, "size of format data response invalid : %" PRIu32, size); return FALSE; } size_t count = (size - 4) / sizeof(FILEDESCRIPTORW); @@ -1011,9 +1040,9 @@ static BOOL cliprdr_file_fuse_generate_list(CliprdrFileContext* file, const BYTE return FALSE; s = Stream_StaticConstInit(&sbuffer, data, size); - if (!s || !cliprdr_file_fuse_check_stream(s, count)) + if (!s || !cliprdr_file_fuse_check_stream(file, s, count)) { - WLog_ERR(TAG, "Stream_New failed"); + WLog_Print(file->log, WLOG_ERROR, "Stream_New failed"); goto error; } @@ -1024,7 +1053,7 @@ static BOOL cliprdr_file_fuse_generate_list(CliprdrFileContext* file, const BYTE if (!rootNode) { cliprdr_file_fuse_inode_free(rootNode); - WLog_ERR(TAG, "fail to alloc rootNode to ino_list"); + WLog_Print(file->log, WLOG_ERROR, "fail to alloc rootNode to ino_list"); goto error2; } @@ -1037,6 +1066,232 @@ error: } #endif +static UINT cliprdr_file_context_send_file_contents_failure( + CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + + WINPR_ASSERT(fileContentsRequest); + + response.common.msgFlags = CB_RESPONSE_FAIL; + response.streamId = fileContentsRequest->streamId; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->ClientFileContentsResponse); + return context->ClientFileContentsResponse(context, &response); +} + +static UINT cliprdr_file_context_send_contents(CliprdrClientContext* context, + const CLIPRDR_FILE_CONTENTS_REQUEST* request, + const void* data, size_t size) +{ + CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 }; + + WINPR_ASSERT(request); + + response.common.msgFlags = CB_RESPONSE_OK; + response.streamId = request->streamId; + response.requestedData = data; + response.cbRequested = size; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->ClientFileContentsResponse); + return context->ClientFileContentsResponse(context, &response); +} + +static FILE* file_for_request(CliprdrFileContext* file, UINT32 streamId, UINT32 listIndex) +{ + FILE* fp = NULL; + HashTable_Lock(file->local_streams); + + CliprdrLocalStream* cur = + HashTable_GetItemValue(file->local_streams, (void*)(uintptr_t)streamId); + if (cur) + { + if (listIndex < cur->count) + { + CliprdrLocalFile* f = &cur->files[listIndex]; + if (f->fp) + fp = f->fp; + else + { + if (strncmp("file://", f->name, 7) == 0) + fp = fopen(&f->name[7], "rb"); + else + fp = fopen(f->name, "rb"); + f->fp = fp; + } + } + } + + HashTable_Unlock(file->local_streams); + + return fp; +} + +static UINT cliprdr_file_context_server_file_size_request( + CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + wClipboardFileSizeRequest request = { 0 }; + + WINPR_ASSERT(fileContentsRequest); + + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + + if (fileContentsRequest->cbRequested != sizeof(UINT64)) + { + WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes", + fileContentsRequest->cbRequested); + } + + FILE* fp = + file_for_request(file, fileContentsRequest->streamId, fileContentsRequest->listIndex); + if (!fp) + return cliprdr_file_context_send_file_contents_failure(file->context, fileContentsRequest); + + if (_fseeki64(fp, 0, SEEK_END) < 0) + return cliprdr_file_context_send_file_contents_failure(file->context, fileContentsRequest); + + const INT64 size = _ftelli64(fp); + return cliprdr_file_context_send_contents(file->context, fileContentsRequest, &size, + sizeof(size)); +} + +static UINT cliprdr_file_context_server_file_range_request( + CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + BYTE* data = NULL; + wClipboardFileRangeRequest request = { 0 }; + + WINPR_ASSERT(fileContentsRequest); + + request.streamId = fileContentsRequest->streamId; + request.listIndex = fileContentsRequest->listIndex; + request.nPositionLow = fileContentsRequest->nPositionLow; + request.nPositionHigh = fileContentsRequest->nPositionHigh; + request.cbRequested = fileContentsRequest->cbRequested; + FILE* fp = + file_for_request(file, fileContentsRequest->streamId, fileContentsRequest->listIndex); + if (!fp) + goto fail; + + const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) | + ((UINT64)fileContentsRequest->nPositionLow); + if (_fseeki64(fp, offset, SEEK_SET) < 0) + goto fail; + + data = malloc(fileContentsRequest->cbRequested); + if (!data) + goto fail; + + const size_t r = fread(data, 1, fileContentsRequest->cbRequested, fp); + const UINT rc = cliprdr_file_context_send_contents(file->context, fileContentsRequest, data, r); + free(data); + return rc; +fail: + free(data); + return cliprdr_file_context_send_file_contents_failure(file->context, fileContentsRequest); +} + +static UINT change_lock(CliprdrFileContext* file, UINT32 streamID, BOOL lock) +{ + WINPR_ASSERT(file); + + HashTable_Lock(file->local_streams); + CliprdrLocalStream* stream = + HashTable_GetItemValue(file->local_streams, (void*)(uintptr_t)streamID); + if (lock && !stream) + { + stream = cliprdr_local_stream_new(streamID, NULL, 0); + HashTable_Insert(file->local_streams, (void*)(uintptr_t)streamID, stream); + file->local_stream_id = streamID; + } + if (stream) + stream->locked = lock; + + if (!lock) + HashTable_Foreach(file->local_streams, local_stream_discard, file); + HashTable_Unlock(file->local_streams); + return CHANNEL_RC_OK; +} + +static UINT cliprdr_file_context_lock(CliprdrClientContext* context, + const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(lockClipboardData); + CliprdrFileContext* file = (context->custom); + return change_lock(file, lockClipboardData->clipDataId, TRUE); +} + +static UINT cliprdr_file_context_unlock(CliprdrClientContext* context, + const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(unlockClipboardData); + CliprdrFileContext* file = (context->custom); + return change_lock(file, unlockClipboardData->clipDataId, FALSE); +} + +static UINT cliprdr_file_context_server_file_contents_request( + CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest) +{ + UINT error = NO_ERROR; + + WINPR_ASSERT(context); + WINPR_ASSERT(fileContentsRequest); + + CliprdrFileContext* file = (context->custom); + WINPR_ASSERT(file); + + /* + * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST): + * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time. + */ + if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) == + (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) + { + WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags"); + return cliprdr_file_context_send_file_contents_failure(context, fileContentsRequest); + } + + if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE) + error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest); + + if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE) + error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest); + + if (error) + { + WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X", + error); + return cliprdr_file_context_send_file_contents_failure(context, fileContentsRequest); + } + + return CHANNEL_RC_OK; +} + +static BOOL xf_cliprdr_clipboard_is_valid_unix_filename(LPCWSTR filename) +{ + LPCWSTR c; + + if (!filename) + return FALSE; + + if (filename[0] == L'\0') + return FALSE; + + /* Reserved characters */ + for (c = filename; *c; ++c) + { + if (*c == L'/') + return FALSE; + } + + return TRUE; +} + BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr) { WINPR_ASSERT(file); @@ -1044,8 +1299,12 @@ BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* c cliprdr->custom = file; file->context = cliprdr; + + cliprdr->ServerLockClipboardData = cliprdr_file_context_lock; + cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock; + cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request; #if defined(WITH_FUSE2) || defined(WITH_FUSE3) - cliprdr->ServerFileContentsResponse = xf_cliprdr_server_file_contents_response; + cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response; #endif return TRUE; @@ -1079,7 +1338,8 @@ static BOOL cliprdr_file_content_changed_and_update(CliprdrFileContext* file, co return changed; } -BOOL cliprdr_file_context_update_data(CliprdrFileContext* file, const void* data, size_t size) +BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file, const void* data, + size_t size) { WINPR_ASSERT(file); @@ -1109,127 +1369,269 @@ const char* cliprdr_file_context_base_path(CliprdrFileContext* file) void cliprdr_file_session_terminate(CliprdrFileContext* file) { - if (!file) - return; + if (!file) + return; #if defined(WITH_FUSE2) || defined(WITH_FUSE3) - if (file->fuse_sess) - fuse_session_exit(file->fuse_sess); + if (file->fuse_sess) + fuse_session_exit(file->fuse_sess); #endif - /* not elegant but works for umounting FUSE - fuse_chan must receieve a oper buf to unblock fuse_session_receive_buf function. - */ - winpr_PathFileExists(file->path); + /* not elegant but works for umounting FUSE + fuse_chan must receieve a oper buf to unblock fuse_session_receive_buf function. + */ + winpr_PathFileExists(file->path); } void cliprdr_file_context_free(CliprdrFileContext* file) { - if (!file) - return; + if (!file) + return; #if defined(WITH_FUSE2) || defined(WITH_FUSE3) - if (file->fuse_thread) - { - cliprdr_file_session_terminate(file); - WaitForSingleObject(file->fuse_thread, INFINITE); - CloseHandle(file->fuse_thread); - } + if (file->fuse_thread) + { + cliprdr_file_session_terminate(file); + WaitForSingleObject(file->fuse_thread, INFINITE); + CloseHandle(file->fuse_thread); + } - // fuse related - ArrayList_Free(file->stream_list); - ArrayList_Free(file->ino_list); + // fuse related + ArrayList_Free(file->stream_list); + ArrayList_Free(file->ino_list); #endif - winpr_RemoveDirectory(file->path); - free(file->path); - free(file); + HashTable_Free(file->local_streams); + winpr_RemoveDirectory(file->path); + free(file->path); + free(file); } static BOOL create_base_path(CliprdrFileContext* file) { - WINPR_ASSERT(file); + WINPR_ASSERT(file); - char base[64] = { 0 }; - _snprintf(base, sizeof(base), "/.xfreerdp.cliprdr.%" PRIu32, GetCurrentProcessId()); + char base[64] = { 0 }; + _snprintf(base, sizeof(base), "/.xfreerdp.cliprdr.%" PRIu32, GetCurrentProcessId()); - file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base); - if (!file->path) - return FALSE; + file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base); + if (!file->path) + return FALSE; - if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0)) - { - WLog_ERR(TAG, "Failed to create directory '%s'", file->path); - return FALSE; - } - return TRUE; + if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, 0)) + { + WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path); + return FALSE; + } + return TRUE; +} + +static void cliprdr_local_file_free(CliprdrLocalFile* file) +{ + if (!file) + return; + if (file->fp) + fclose(file->fp); + free(file->name); +} + +static void cliprdr_local_stream_free(void* obj) +{ + CliprdrLocalStream* stream = (CliprdrLocalStream*)obj; + if (stream) + { + for (size_t x = 0; x < stream->count; x++) + cliprdr_local_file_free(&stream->files[x]); + free(stream->files); + } + free(stream); +} + +static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size) +{ + WINPR_ASSERT(stream); + if (size == 0) + return TRUE; + + free(stream->files); + stream->files = calloc(size, sizeof(CliprdrLocalFile)); + if (!stream->files) + return FALSE; + + char* copy = strndup(data, size); + if (!copy) + return FALSE; + char* ptr = strtok(copy, "\r\n"); + while (ptr) + { + stream->files[stream->count++].name = _strdup(ptr); + ptr = strtok(NULL, "\r\n"); + } + free(copy); + return TRUE; +} + +CliprdrLocalStream* cliprdr_local_stream_new(UINT32 streamID, const char* data, size_t size) +{ + CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream)); + if (!stream) + return NULL; + + if (!cliprdr_local_stream_update(stream, data, size)) + goto fail; + + stream->streamID = streamID; + return stream; + +fail: + cliprdr_local_stream_free(stream); + return NULL; } CliprdrFileContext* cliprdr_file_context_new(void* context) { - CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext)); - if (!file) - return NULL; + CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext)); + if (!file) + return NULL; - file->clipboard = context; - if (!create_base_path(file)) - goto fail; + file->log = WLog_Get(CLIENT_TAG("x11.cliprdr.file")); + file->clipboard = context; + if (!create_base_path(file)) + goto fail; + + file->local_streams = HashTable_New(FALSE); + if (!file->local_streams) + goto fail; + + wObject* hobj = HashTable_ValueObject(file->local_streams); + WINPR_ASSERT(hobj); + hobj->fnObjectFree = cliprdr_local_stream_free; #if defined(WITH_FUSE2) || defined(WITH_FUSE3) - file->current_stream_id = 0; - file->stream_list = ArrayList_New(TRUE); - if (!file->stream_list) - { - WLog_ERR(TAG, "failed to allocate stream_list"); - goto fail; - } - wObject* obj = ArrayList_Object(file->stream_list); - obj->fnObjectFree = free; + file->current_stream_id = 0; + file->stream_list = ArrayList_New(TRUE); + if (!file->stream_list) + { + WLog_Print(file->log, WLOG_ERROR, "failed to allocate stream_list"); + goto fail; + } + wObject* obj = ArrayList_Object(file->stream_list); + obj->fnObjectFree = free; - file->ino_list = ArrayList_New(TRUE); - if (!file->ino_list) - { - WLog_ERR(TAG, "failed to allocate stream_list"); - goto fail; - } - obj = ArrayList_Object(file->ino_list); - obj->fnObjectFree = cliprdr_file_fuse_inode_free; + file->ino_list = ArrayList_New(TRUE); + if (!file->ino_list) + { + WLog_Print(file->log, WLOG_ERROR, "failed to allocate stream_list"); + goto fail; + } + obj = ArrayList_Object(file->ino_list); + obj->fnObjectFree = cliprdr_file_fuse_inode_free; - if (!xf_fuse_repopulate(file->ino_list)) - goto fail; + if (!xf_fuse_repopulate(file, file->ino_list)) + goto fail; - if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL))) - { - goto fail; - } + if (!(file->fuse_thread = CreateThread(NULL, 0, cliprdr_file_fuse_thread, file, 0, NULL))) + { + goto fail; + } #endif - return file; + return file; fail: - cliprdr_file_context_free(file); - return NULL; + cliprdr_file_context_free(file); + return NULL; +} + +BOOL local_stream_discard(const void* key, void* value, void* arg) +{ + CliprdrFileContext* file = arg; + CliprdrLocalStream* stream = value; + WINPR_ASSERT(file); + WINPR_ASSERT(stream); + + if (!stream->locked) + HashTable_Remove(file->local_streams, key); + return TRUE; } BOOL cliprdr_file_context_clear(CliprdrFileContext* file) { + HashTable_Foreach(file->local_streams, local_stream_discard, file); + #if defined(WITH_FUSE2) || defined(WITH_FUSE3) - if (file->stream_list) - { - size_t index; - size_t count; - CliprdrFuseStream* stream; - ArrayList_Lock(file->stream_list); - file->current_stream_id = 0; - /* reply error to all req first don't care request type*/ - count = ArrayList_Count(file->stream_list); - for (index = 0; index < count; index++) - { - stream = (CliprdrFuseStream*)ArrayList_GetItem(file->stream_list, index); - fuse_reply_err(stream->req, EIO); - } - ArrayList_Unlock(file->stream_list); + if (file->stream_list) + { + size_t index; + size_t count; + CliprdrFuseStream* stream; + ArrayList_Lock(file->stream_list); + file->current_stream_id = 0; + /* reply error to all req first don't care request type*/ + count = ArrayList_Count(file->stream_list); + for (index = 0; index < count; index++) + { + stream = (CliprdrFuseStream*)ArrayList_GetItem(file->stream_list, index); + fuse_reply_err(stream->req, EIO); + } + ArrayList_Unlock(file->stream_list); - ArrayList_Clear(file->stream_list); - } + ArrayList_Clear(file->stream_list); + } - xf_fuse_repopulate(file->ino_list); + xf_fuse_repopulate(file, file->ino_list); #endif + return TRUE; +} + +BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data, + size_t size) +{ + BOOL rc = FALSE; + UINT32 streamID = file->local_stream_id++; + + HashTable_Lock(file->local_streams); + CliprdrLocalStream* stream = + HashTable_GetItemValue(file->local_streams, (void*)(uintptr_t)streamID); + + if (stream) + rc = cliprdr_local_stream_update(stream, data, size); + else + { + stream = cliprdr_local_stream_new(streamID, data, size); + rc = HashTable_Insert(file->local_streams, (void*)(uintptr_t)streamID, stream); + } + HashTable_Unlock(file->local_streams); + return rc; +} + +UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + + if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0) + return 0; + + if (!file->file_formats_registered) + return 0; + + return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS | CB_HUGE_FILE_SUPPORT_ENABLED | + CB_CAN_LOCK_CLIPDATA; +} + +BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available) +{ + WINPR_ASSERT(file); + file->file_formats_registered = available; + return TRUE; +} + +BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags) +{ + WINPR_ASSERT(file); + file->file_capability_flags = flags; + return TRUE; +} + +UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file) +{ + WINPR_ASSERT(file); + return file->file_capability_flags; } diff --git a/client/X11/xf_cliprdr_file.h b/include/freerdp/client/client_cliprdr_file.h similarity index 68% rename from client/X11/xf_cliprdr_file.h rename to include/freerdp/client/client_cliprdr_file.h index f96f03e21..10149c6d4 100644 --- a/client/X11/xf_cliprdr_file.h +++ b/include/freerdp/client/client_cliprdr_file.h @@ -33,6 +33,13 @@ extern "C" FREERDP_API CliprdrFileContext* cliprdr_file_context_new(void* context); FREERDP_API void cliprdr_file_context_free(CliprdrFileContext* file); + FREERDP_API BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, + BOOL available); + FREERDP_API BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags); + FREERDP_API UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file); + + FREERDP_API UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file); + FREERDP_API void* cliprdr_file_context_get_context(CliprdrFileContext* file); FREERDP_API const char* cliprdr_file_context_base_path(CliprdrFileContext* file); @@ -43,8 +50,11 @@ extern "C" FREERDP_API BOOL cliprdr_file_context_clear(CliprdrFileContext* file); - FREERDP_API BOOL cliprdr_file_context_update_data(CliprdrFileContext* file, const void* data, - size_t size); + FREERDP_API BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, + const char* data, size_t count); + + FREERDP_API BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file, + const void* data, size_t size); #ifdef __cplusplus }