mirror of https://github.com/FreeRDP/FreeRDP
Merge pull request #3905 from ilammy/x11-cliprdr/file-clipping
Local-to-remote file clipping for xfreerdp
This commit is contained in:
commit
5ef9232703
|
@ -336,3 +336,181 @@ UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UI
|
|||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT64 filetime_to_uint64(FILETIME value)
|
||||
{
|
||||
UINT64 converted = 0;
|
||||
converted |= (UINT32) value.dwHighDateTime;
|
||||
converted <<= 32;
|
||||
converted |= (UINT32) value.dwLowDateTime;
|
||||
return converted;
|
||||
}
|
||||
|
||||
static FILETIME uint64_to_filetime(UINT64 value)
|
||||
{
|
||||
FILETIME converted;
|
||||
converted.dwLowDateTime = (UINT32) (value >> 0);
|
||||
converted.dwHighDateTime = (UINT32) (value >> 32);
|
||||
return converted;
|
||||
}
|
||||
|
||||
#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 8 + 520)
|
||||
|
||||
/**
|
||||
* Parse a packed file list.
|
||||
*
|
||||
* The resulting array must be freed with the `free()` function.
|
||||
*
|
||||
* @param [in] format_data packed `CLIPRDR_FILELIST` to parse.
|
||||
* @param [in] format_data_length length of `format_data` in bytes.
|
||||
* @param [out] file_descriptor_array parsed array of `FILEDESCRIPTOR` structs.
|
||||
* @param [out] file_descriptor_count number of elements in `file_descriptor_array`.
|
||||
*
|
||||
* @returns 0 on success, otherwise a Win32 error code.
|
||||
*/
|
||||
UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length,
|
||||
FILEDESCRIPTOR** file_descriptor_array, UINT32* file_descriptor_count)
|
||||
{
|
||||
UINT result = NO_ERROR;
|
||||
UINT32 i;
|
||||
UINT32 count = 0;
|
||||
wStream* s = NULL;
|
||||
|
||||
if (!format_data || !file_descriptor_array || !file_descriptor_count)
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
|
||||
s = Stream_New((BYTE*) format_data, format_data_length);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
if (Stream_GetRemainingLength(s) < 4)
|
||||
{
|
||||
WLog_ERR(TAG, "invalid packed file list");
|
||||
|
||||
result = ERROR_INCORRECT_SIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
Stream_Read_UINT32(s, count); /* cItems (4 bytes) */
|
||||
|
||||
if (Stream_GetRemainingLength(s) / CLIPRDR_FILEDESCRIPTOR_SIZE < count)
|
||||
{
|
||||
WLog_ERR(TAG, "packed file list is too short: expected %"PRIuz", have %"PRIuz,
|
||||
((size_t) count) * CLIPRDR_FILEDESCRIPTOR_SIZE,
|
||||
Stream_GetRemainingLength(s));
|
||||
|
||||
result = ERROR_INCORRECT_SIZE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
*file_descriptor_count = count;
|
||||
*file_descriptor_array = calloc(count, sizeof(FILEDESCRIPTOR));
|
||||
if (!*file_descriptor_array)
|
||||
{
|
||||
result = ERROR_NOT_ENOUGH_MEMORY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
int c;
|
||||
UINT64 lastWriteTime;
|
||||
FILEDESCRIPTOR* file = &((*file_descriptor_array)[i]);
|
||||
|
||||
Stream_Read_UINT32(s, file->dwFlags); /* flags (4 bytes) */
|
||||
Stream_Seek(s, 32); /* reserved1 (32 bytes) */
|
||||
Stream_Read_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */
|
||||
Stream_Seek(s, 16); /* reserved2 (16 bytes) */
|
||||
Stream_Read_UINT64(s, lastWriteTime); /* lastWriteTime (8 bytes) */
|
||||
file->ftLastWriteTime = uint64_to_filetime(lastWriteTime);
|
||||
Stream_Read_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
|
||||
Stream_Read_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */
|
||||
for (c = 0; c < 260; c++) /* cFileName (520 bytes) */
|
||||
Stream_Read_UINT16(s, file->cFileName[c]);
|
||||
}
|
||||
|
||||
if (Stream_GetRemainingLength(s) > 0)
|
||||
WLog_WARN(TAG, "packed file list has %"PRIuz" excess bytes",
|
||||
Stream_GetRemainingLength(s));
|
||||
out:
|
||||
Stream_Free(s, FALSE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CLIPRDR_MAX_FILE_SIZE (2U * 1024 * 1024 * 1024)
|
||||
|
||||
/**
|
||||
* Serialize a packed file list.
|
||||
*
|
||||
* The resulting format data must be freed with the `free()` function.
|
||||
*
|
||||
* @param [in] file_descriptor_array array of `FILEDESCRIPTOR` structs to serialize.
|
||||
* @param [in] file_descriptor_count number of elements in `file_descriptor_array`.
|
||||
* @param [out] format_data serialized CLIPRDR_FILELIST.
|
||||
* @param [out] format_data_length length of `format_data` in bytes.
|
||||
*
|
||||
* @returns 0 on success, otherwise a Win32 error code.
|
||||
*/
|
||||
UINT cliprdr_serialize_file_list(const FILEDESCRIPTOR* file_descriptor_array,
|
||||
UINT32 file_descriptor_count, BYTE** format_data, UINT32* format_data_length)
|
||||
{
|
||||
UINT result = NO_ERROR;
|
||||
UINT32 i;
|
||||
wStream* s = NULL;
|
||||
|
||||
if (!file_descriptor_array || !format_data || !format_data_length)
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
|
||||
s = Stream_New(NULL, 4 + file_descriptor_count * CLIPRDR_FILEDESCRIPTOR_SIZE);
|
||||
if (!s)
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
|
||||
Stream_Write_UINT32(s, file_descriptor_count); /* cItems (4 bytes) */
|
||||
|
||||
for (i = 0; i < file_descriptor_count; i++)
|
||||
{
|
||||
int c;
|
||||
UINT64 lastWriteTime;
|
||||
const FILEDESCRIPTOR* file = &file_descriptor_array[i];
|
||||
|
||||
/*
|
||||
* There is a known issue with Windows server getting stuck in
|
||||
* an infinite loop when downloading files that are larger than
|
||||
* 2 gigabytes. Do not allow clients to send such file lists.
|
||||
*
|
||||
* https://support.microsoft.com/en-us/help/2258090
|
||||
*/
|
||||
if ((file->nFileSizeHigh > 0) || (file->nFileSizeLow >= CLIPRDR_MAX_FILE_SIZE))
|
||||
{
|
||||
WLog_ERR(TAG, "cliprdr does not support files over 2 GB");
|
||||
result = ERROR_FILE_TOO_LARGE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
Stream_Write_UINT32(s, file->dwFlags); /* flags (4 bytes) */
|
||||
Stream_Zero(s, 32); /* reserved1 (32 bytes) */
|
||||
Stream_Write_UINT32(s, file->dwFileAttributes); /* fileAttributes (4 bytes) */
|
||||
Stream_Zero(s, 16); /* reserved2 (16 bytes) */
|
||||
lastWriteTime = filetime_to_uint64(file->ftLastWriteTime);
|
||||
Stream_Write_UINT64(s, lastWriteTime); /* lastWriteTime (8 bytes) */
|
||||
Stream_Write_UINT32(s, file->nFileSizeHigh); /* fileSizeHigh (4 bytes) */
|
||||
Stream_Write_UINT32(s, file->nFileSizeLow); /* fileSizeLow (4 bytes) */
|
||||
for (c = 0; c < 260; c++) /* cFileName (520 bytes) */
|
||||
Stream_Write_UINT16(s, file->cFileName[c]);
|
||||
}
|
||||
|
||||
Stream_SealLength(s);
|
||||
|
||||
Stream_GetBuffer(s, *format_data);
|
||||
Stream_GetLength(s, *format_data_length);
|
||||
|
||||
Stream_Free(s, FALSE);
|
||||
|
||||
return result;
|
||||
|
||||
error:
|
||||
Stream_Free(s, TRUE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -321,6 +321,8 @@ static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr,
|
|||
request.msgType = CB_FILECONTENTS_REQUEST;
|
||||
request.msgFlags = flags;
|
||||
request.dataLen = length;
|
||||
request.haveClipDataId = FALSE;
|
||||
|
||||
Stream_Read_UINT32(s, request.streamId); /* streamId (4 bytes) */
|
||||
Stream_Read_UINT32(s, request.listIndex); /* listIndex (4 bytes) */
|
||||
Stream_Read_UINT32(s, request.dwFlags); /* dwFlags (4 bytes) */
|
||||
|
@ -329,9 +331,10 @@ static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr,
|
|||
Stream_Read_UINT32(s, request.cbRequested); /* cbRequested (4 bytes) */
|
||||
|
||||
if (Stream_GetRemainingLength(s) >= 4)
|
||||
{
|
||||
Stream_Read_UINT32(s, request.clipDataId); /* clipDataId (4 bytes) */
|
||||
else
|
||||
request.clipDataId = 0;
|
||||
request.haveClipDataId = TRUE;
|
||||
}
|
||||
|
||||
IFCALLRET(context->ServerFileContentsRequest, error, context, &request);
|
||||
|
||||
|
@ -900,20 +903,19 @@ static UINT cliprdr_client_file_contents_request(CliprdrClientContext* context,
|
|||
}
|
||||
|
||||
Stream_Write_UINT32(s, fileContentsRequest->streamId); /* streamId (4 bytes) */
|
||||
Stream_Write_UINT32(s,
|
||||
fileContentsRequest->listIndex); /* listIndex (4 bytes) */
|
||||
Stream_Write_UINT32(s, fileContentsRequest->listIndex); /* listIndex (4 bytes) */
|
||||
Stream_Write_UINT32(s, fileContentsRequest->dwFlags); /* dwFlags (4 bytes) */
|
||||
Stream_Write_UINT32(s,
|
||||
fileContentsRequest->nPositionLow); /* nPositionLow (4 bytes) */
|
||||
Stream_Write_UINT32(s,
|
||||
fileContentsRequest->nPositionHigh); /* nPositionHigh (4 bytes) */
|
||||
Stream_Write_UINT32(s,
|
||||
fileContentsRequest->cbRequested); /* cbRequested (4 bytes) */
|
||||
Stream_Write_UINT32(s,
|
||||
fileContentsRequest->clipDataId); /* clipDataId (4 bytes) */
|
||||
Stream_Write_UINT32(s, fileContentsRequest->nPositionLow); /* nPositionLow (4 bytes) */
|
||||
Stream_Write_UINT32(s, fileContentsRequest->nPositionHigh); /* nPositionHigh (4 bytes) */
|
||||
Stream_Write_UINT32(s, fileContentsRequest->cbRequested); /* cbRequested (4 bytes) */
|
||||
|
||||
if (fileContentsRequest->haveClipDataId)
|
||||
Stream_Write_UINT32(s, fileContentsRequest->clipDataId); /* clipDataId (4 bytes) */
|
||||
|
||||
WLog_Print(cliprdr->log, WLOG_DEBUG,
|
||||
"ClientFileContentsRequest: streamId: 0x%08"PRIX32"",
|
||||
fileContentsRequest->streamId);
|
||||
|
||||
return cliprdr_packet_send(cliprdr, s);
|
||||
}
|
||||
|
||||
|
@ -928,9 +930,6 @@ static UINT cliprdr_client_file_contents_response(CliprdrClientContext* context,
|
|||
wStream* s;
|
||||
cliprdrPlugin* cliprdr = (cliprdrPlugin*) context->handle;
|
||||
|
||||
if (fileContentsResponse->dwFlags & FILECONTENTS_SIZE)
|
||||
fileContentsResponse->cbRequested = sizeof(UINT64);
|
||||
|
||||
s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, fileContentsResponse->msgFlags,
|
||||
4 + fileContentsResponse->cbRequested);
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <freerdp/log.h>
|
||||
#include <freerdp/client/cliprdr.h>
|
||||
#include <freerdp/channels/channels.h>
|
||||
#include <freerdp/channels/cliprdr.h>
|
||||
|
||||
#include "xf_cliprdr.h"
|
||||
|
||||
|
@ -61,6 +62,7 @@ struct xf_clipboard
|
|||
CliprdrClientContext* context;
|
||||
|
||||
wClipboard* system;
|
||||
wClipboardDelegate* delegate;
|
||||
|
||||
Window root_window;
|
||||
Atom clipboard_atom;
|
||||
|
@ -102,9 +104,13 @@ struct xf_clipboard
|
|||
int xfixes_event_base;
|
||||
int xfixes_error_base;
|
||||
BOOL xfixes_supported;
|
||||
|
||||
/* File clipping */
|
||||
BOOL streams_supported;
|
||||
BOOL file_formats_registered;
|
||||
};
|
||||
|
||||
UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard);
|
||||
static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard);
|
||||
|
||||
static void xf_cliprdr_check_owner(xfClipboard* clipboard)
|
||||
{
|
||||
|
@ -590,6 +596,10 @@ static void xf_cliprdr_process_requested_data(xfClipboard* clipboard,
|
|||
case CB_FORMAT_HTML:
|
||||
srcFormatId = ClipboardGetFormatId(clipboard->system, "text/html");
|
||||
break;
|
||||
|
||||
case CB_FORMAT_TEXTURILIST:
|
||||
srcFormatId = ClipboardGetFormatId(clipboard->system, "text/uri-list");
|
||||
break;
|
||||
}
|
||||
|
||||
SrcSize = (UINT32) size;
|
||||
|
@ -612,6 +622,31 @@ static void xf_cliprdr_process_requested_data(xfClipboard* clipboard,
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* File lists require a bit of postprocessing to convert them from WinPR's FILDESCRIPTOR
|
||||
* format to CLIPRDR_FILELIST expected by the server.
|
||||
*
|
||||
* We check for "FileGroupDescriptorW" format being registered (i.e., nonzero) in order
|
||||
* to not process CF_RAW as a file list in case WinPR does not support file transfers.
|
||||
*/
|
||||
if (dstFormatId &&
|
||||
(dstFormatId == ClipboardGetFormatId(clipboard->system, "FileGroupDescriptorW")))
|
||||
{
|
||||
UINT error = NO_ERROR;
|
||||
FILEDESCRIPTOR* file_array = (FILEDESCRIPTOR*) pDstData;
|
||||
UINT32 file_count = DstSize / sizeof(FILEDESCRIPTOR);
|
||||
|
||||
pDstData = NULL;
|
||||
DstSize = 0;
|
||||
|
||||
error = cliprdr_serialize_file_list(file_array, file_count, &pDstData, &DstSize);
|
||||
|
||||
if (error)
|
||||
WLog_ERR(TAG, "failed to serialize CLIPRDR_FILELIST: 0x%08X", error);
|
||||
|
||||
free(file_array);
|
||||
}
|
||||
|
||||
xf_cliprdr_send_data_response(clipboard, pDstData, (int) DstSize);
|
||||
free(pDstData);
|
||||
}
|
||||
|
@ -1029,7 +1064,7 @@ void xf_cliprdr_handle_xevent(xfContext* xfc, XEvent* event)
|
|||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
|
||||
static UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
|
||||
{
|
||||
CLIPRDR_CAPABILITIES capabilities;
|
||||
CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
|
||||
|
@ -1040,6 +1075,11 @@ UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
|
|||
generalCapabilitySet.capabilitySetLength = 12;
|
||||
generalCapabilitySet.version = CB_CAPS_VERSION_2;
|
||||
generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
|
||||
|
||||
if (clipboard->streams_supported && clipboard->file_formats_registered)
|
||||
generalCapabilitySet.generalFlags |=
|
||||
CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS;
|
||||
|
||||
return clipboard->context->ClientCapabilities(clipboard->context,
|
||||
&capabilities);
|
||||
}
|
||||
|
@ -1049,7 +1089,7 @@ UINT xf_cliprdr_send_client_capabilities(xfClipboard* clipboard)
|
|||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard)
|
||||
static UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard)
|
||||
{
|
||||
UINT32 i, numFormats;
|
||||
CLIPRDR_FORMAT* formats = NULL;
|
||||
|
@ -1095,7 +1135,7 @@ UINT xf_cliprdr_send_client_format_list(xfClipboard* clipboard)
|
|||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard,
|
||||
static UINT xf_cliprdr_send_client_format_list_response(xfClipboard* clipboard,
|
||||
BOOL status)
|
||||
{
|
||||
CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
|
||||
|
@ -1135,7 +1175,31 @@ static UINT xf_cliprdr_monitor_ready(CliprdrClientContext* context,
|
|||
static UINT xf_cliprdr_server_capabilities(CliprdrClientContext* context,
|
||||
CLIPRDR_CAPABILITIES* capabilities)
|
||||
{
|
||||
//xfClipboard* clipboard = (xfClipboard*) context->custom;
|
||||
UINT32 i;
|
||||
const CLIPRDR_CAPABILITY_SET* caps;
|
||||
const CLIPRDR_GENERAL_CAPABILITY_SET* generalCaps;
|
||||
const BYTE* capsPtr = (const BYTE*) capabilities->capabilitySets;
|
||||
xfClipboard* clipboard = (xfClipboard*) context->custom;
|
||||
|
||||
clipboard->streams_supported = FALSE;
|
||||
|
||||
for (i = 0; i < capabilities->cCapabilitiesSets; i++)
|
||||
{
|
||||
caps = (const CLIPRDR_CAPABILITY_SET*) capsPtr;
|
||||
|
||||
if (caps->capabilitySetType == CB_CAPSTYPE_GENERAL)
|
||||
{
|
||||
generalCaps = (const CLIPRDR_GENERAL_CAPABILITY_SET*) caps;
|
||||
|
||||
if (generalCaps->generalFlags & CB_STREAM_FILECLIP_ENABLED)
|
||||
{
|
||||
clipboard->streams_supported = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
capsPtr += caps->capabilitySetLength;
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
|
@ -1390,8 +1454,156 @@ static UINT xf_cliprdr_server_format_data_response(CliprdrClientContext*
|
|||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_server_file_size_request(xfClipboard* clipboard,
|
||||
const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
|
||||
{
|
||||
wClipboardFileSizeRequest request;
|
||||
|
||||
ZeroMemory(&request, sizeof(request));
|
||||
|
||||
request.streamId = fileContentsRequest->streamId;
|
||||
request.listIndex = fileContentsRequest->listIndex;
|
||||
|
||||
if (fileContentsRequest->cbRequested != sizeof(UINT64))
|
||||
{
|
||||
WLog_WARN(TAG, "unexpected FILECONTENTS_SIZE request: %"PRIu32" bytes",
|
||||
fileContentsRequest->cbRequested);
|
||||
}
|
||||
|
||||
return clipboard->delegate->ClientRequestFileSize(clipboard->delegate, &request);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_server_file_range_request(xfClipboard* clipboard,
|
||||
const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
|
||||
{
|
||||
wClipboardFileRangeRequest request;
|
||||
|
||||
ZeroMemory(&request, sizeof(request));
|
||||
|
||||
request.streamId = fileContentsRequest->streamId;
|
||||
request.listIndex = fileContentsRequest->listIndex;
|
||||
request.nPositionLow = fileContentsRequest->nPositionLow;
|
||||
request.nPositionHigh = fileContentsRequest->nPositionHigh;
|
||||
request.cbRequested = fileContentsRequest->cbRequested;
|
||||
|
||||
return clipboard->delegate->ClientRequestFileRange(clipboard->delegate, &request);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_send_file_contents_failure(CliprdrClientContext* context,
|
||||
const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
|
||||
{
|
||||
CLIPRDR_FILE_CONTENTS_RESPONSE response;
|
||||
|
||||
ZeroMemory(&response, sizeof(response));
|
||||
|
||||
response.msgFlags = CB_RESPONSE_FAIL;
|
||||
response.streamId = fileContentsRequest->streamId;
|
||||
response.dwFlags = fileContentsRequest->dwFlags;
|
||||
|
||||
return context->ClientFileContentsResponse(context, &response);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_server_file_contents_request(CliprdrClientContext* context,
|
||||
CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
|
||||
{
|
||||
UINT error = NO_ERROR;
|
||||
xfClipboard* clipboard = context->custom;
|
||||
|
||||
/*
|
||||
* 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 UINT xf_cliprdr_clipboard_file_size_success(wClipboardDelegate* delegate,
|
||||
const wClipboardFileSizeRequest* request, UINT64 fileSize)
|
||||
{
|
||||
CLIPRDR_FILE_CONTENTS_RESPONSE response;
|
||||
xfClipboard* clipboard = delegate->custom;
|
||||
|
||||
ZeroMemory(&response, sizeof(response));
|
||||
|
||||
response.msgFlags = CB_RESPONSE_OK;
|
||||
response.streamId = request->streamId;
|
||||
response.dwFlags = FILECONTENTS_SIZE;
|
||||
response.cbRequested = sizeof(UINT64);
|
||||
response.requestedData = (BYTE*) &fileSize;
|
||||
|
||||
return clipboard->context->ClientFileContentsResponse(clipboard->context, &response);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_clipboard_file_size_failure(wClipboardDelegate* delegate,
|
||||
const wClipboardFileSizeRequest* request, UINT errorCode)
|
||||
{
|
||||
CLIPRDR_FILE_CONTENTS_RESPONSE response;
|
||||
xfClipboard* clipboard = delegate->custom;
|
||||
|
||||
ZeroMemory(&response, sizeof(response));
|
||||
|
||||
response.msgFlags = CB_RESPONSE_FAIL;
|
||||
response.streamId = request->streamId;
|
||||
response.dwFlags = FILECONTENTS_SIZE;
|
||||
|
||||
return clipboard->context->ClientFileContentsResponse(clipboard->context, &response);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_clipboard_file_range_success(wClipboardDelegate* delegate,
|
||||
const wClipboardFileRangeRequest* request, const BYTE* data, UINT32 size)
|
||||
{
|
||||
CLIPRDR_FILE_CONTENTS_RESPONSE response;
|
||||
xfClipboard* clipboard = delegate->custom;
|
||||
|
||||
ZeroMemory(&response, sizeof(response));
|
||||
|
||||
response.msgFlags = CB_RESPONSE_OK;
|
||||
response.streamId = request->streamId;
|
||||
response.dwFlags = FILECONTENTS_RANGE;
|
||||
response.cbRequested = size;
|
||||
response.requestedData = (BYTE*) data;
|
||||
|
||||
return clipboard->context->ClientFileContentsResponse(clipboard->context, &response);
|
||||
}
|
||||
|
||||
static UINT xf_cliprdr_clipboard_file_range_failure(wClipboardDelegate* delegate,
|
||||
const wClipboardFileRangeRequest* request, UINT errorCode)
|
||||
{
|
||||
CLIPRDR_FILE_CONTENTS_RESPONSE response;
|
||||
xfClipboard* clipboard = delegate->custom;
|
||||
|
||||
ZeroMemory(&response, sizeof(response));
|
||||
|
||||
response.msgFlags = CB_RESPONSE_FAIL;
|
||||
response.streamId = request->streamId;
|
||||
response.dwFlags = FILECONTENTS_RANGE;
|
||||
|
||||
return clipboard->context->ClientFileContentsResponse(clipboard->context, &response);
|
||||
}
|
||||
|
||||
xfClipboard* xf_clipboard_new(xfContext* xfc)
|
||||
{
|
||||
int i;
|
||||
int n;
|
||||
rdpChannels* channels;
|
||||
xfClipboard* clipboard;
|
||||
|
@ -1414,19 +1626,18 @@ xfClipboard* xf_clipboard_new(xfContext* xfc)
|
|||
if (clipboard->clipboard_atom == None)
|
||||
{
|
||||
WLog_ERR(TAG, "unable to get CLIPBOARD atom");
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
clipboard->property_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR", FALSE);
|
||||
clipboard->raw_transfer_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_RAW",
|
||||
FALSE);
|
||||
clipboard->raw_format_list_atom = XInternAtom(xfc->display,
|
||||
"_FREERDP_CLIPRDR_FORMATS", FALSE);
|
||||
clipboard->raw_transfer_atom = XInternAtom(xfc->display, "_FREERDP_CLIPRDR_RAW", FALSE);
|
||||
clipboard->raw_format_list_atom =
|
||||
XInternAtom(xfc->display, "_FREERDP_CLIPRDR_FORMATS", FALSE);
|
||||
xf_cliprdr_set_raw_transfer_enabled(clipboard, TRUE);
|
||||
XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask);
|
||||
#ifdef WITH_XFIXES
|
||||
|
||||
XSelectInput(xfc->display, clipboard->root_window, PropertyChangeMask);
|
||||
|
||||
#ifdef WITH_XFIXES
|
||||
if (XFixesQueryExtension(xfc->display, &clipboard->xfixes_event_base,
|
||||
&clipboard->xfixes_error_base))
|
||||
{
|
||||
|
@ -1452,53 +1663,85 @@ xfClipboard* xf_clipboard_new(xfContext* xfc)
|
|||
WLog_ERR(TAG,
|
||||
"Warning: Using clipboard redirection without XFIXES extension is strongly discouraged!");
|
||||
#endif
|
||||
|
||||
n = 0;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "_FREERDP_RAW",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "_FREERDP_RAW", False);
|
||||
clipboard->clientFormats[n].formatId = CF_RAW;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "UTF8_STRING",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "UTF8_STRING", False);
|
||||
clipboard->clientFormats[n].formatId = CF_UNICODETEXT;
|
||||
n++;
|
||||
|
||||
clipboard->clientFormats[n].atom = XA_STRING;
|
||||
clipboard->clientFormats[n].formatId = CF_TEXT;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/png",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/png", False);
|
||||
clipboard->clientFormats[n].formatId = CB_FORMAT_PNG;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/jpeg",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/jpeg", False);
|
||||
clipboard->clientFormats[n].formatId = CB_FORMAT_JPEG;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/gif",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/gif", False);
|
||||
clipboard->clientFormats[n].formatId = CB_FORMAT_GIF;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/bmp",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "image/bmp", False);
|
||||
clipboard->clientFormats[n].formatId = CF_DIB;
|
||||
n++;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "text/html",
|
||||
False);
|
||||
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "text/html", False);
|
||||
clipboard->clientFormats[n].formatId = CB_FORMAT_HTML;
|
||||
clipboard->clientFormats[n].formatName = _strdup("HTML Format");
|
||||
|
||||
if (!clipboard->clientFormats[n].formatName)
|
||||
goto error;
|
||||
n++;
|
||||
|
||||
/*
|
||||
* Existence of registered format IDs for file formats does not guarantee that they are
|
||||
* in fact supported by wClipboard (as further initialization may have failed after format
|
||||
* registration). However, they are definitely not supported if there are no registered
|
||||
* formats. In this case we should not list file formats in TARGETS.
|
||||
*/
|
||||
if (ClipboardGetFormatId(clipboard->system, "text/uri-list"))
|
||||
{
|
||||
ClipboardDestroy(clipboard->system);
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
clipboard->file_formats_registered = TRUE;
|
||||
clipboard->clientFormats[n].atom = XInternAtom(xfc->display, "text/uri-list", False);
|
||||
clipboard->clientFormats[n].formatId = CB_FORMAT_TEXTURILIST;
|
||||
clipboard->clientFormats[n].formatName = _strdup("FileGroupDescriptorW");
|
||||
if (!clipboard->clientFormats[n].formatName)
|
||||
goto error;
|
||||
n++;
|
||||
}
|
||||
|
||||
n++;
|
||||
clipboard->numClientFormats = n;
|
||||
clipboard->targets[0] = XInternAtom(xfc->display, "TIMESTAMP", FALSE);
|
||||
clipboard->targets[1] = XInternAtom(xfc->display, "TARGETS", FALSE);
|
||||
clipboard->numTargets = 2;
|
||||
clipboard->incr_atom = XInternAtom(xfc->display, "INCR", FALSE);
|
||||
|
||||
clipboard->delegate = ClipboardGetDelegate(clipboard->system);
|
||||
clipboard->delegate->custom = clipboard;
|
||||
clipboard->delegate->ClipboardFileSizeSuccess = xf_cliprdr_clipboard_file_size_success;
|
||||
clipboard->delegate->ClipboardFileSizeFailure = xf_cliprdr_clipboard_file_size_failure;
|
||||
clipboard->delegate->ClipboardFileRangeSuccess = xf_cliprdr_clipboard_file_range_success;
|
||||
clipboard->delegate->ClipboardFileRangeFailure = xf_cliprdr_clipboard_file_range_failure;
|
||||
|
||||
return clipboard;
|
||||
|
||||
error:
|
||||
for (i = 0; i < n; i++)
|
||||
free(clipboard->clientFormats[i].formatName);
|
||||
|
||||
ClipboardDestroy(clipboard->system);
|
||||
|
||||
free(clipboard);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void xf_clipboard_free(xfClipboard* clipboard)
|
||||
|
@ -1542,6 +1785,7 @@ 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;
|
||||
}
|
||||
|
||||
void xf_cliprdr_uninit(xfContext* xfc, CliprdrClientContext* cliprdr)
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#include <winpr/shell.h>
|
||||
|
||||
#define CLIPRDR_SVC_CHANNEL_NAME "cliprdr"
|
||||
|
||||
/**
|
||||
|
@ -33,6 +35,7 @@
|
|||
#define CB_FORMAT_PNG 0xD011
|
||||
#define CB_FORMAT_JPEG 0xD012
|
||||
#define CB_FORMAT_GIF 0xD013
|
||||
#define CB_FORMAT_TEXTURILIST 0xD014
|
||||
|
||||
/* CLIPRDR_HEADER.msgType */
|
||||
#define CB_MONITOR_READY 0x0001
|
||||
|
@ -84,32 +87,18 @@ struct _CLIPRDR_MFPICT
|
|||
};
|
||||
typedef struct _CLIPRDR_MFPICT CLIPRDR_MFPICT;
|
||||
|
||||
struct _CLIPRDR_FILEDESCRIPTOR
|
||||
{
|
||||
DWORD dwFlags;
|
||||
BYTE clsid[16];
|
||||
BYTE sizel[8];
|
||||
BYTE pointl[8];
|
||||
DWORD dwFileAttributes;
|
||||
FILETIME ftCreationTime;
|
||||
FILETIME ftLastAccessTime;
|
||||
FILETIME ftLastWriteTime;
|
||||
DWORD nFileSizeHigh;
|
||||
DWORD nFileSizeLow;
|
||||
union
|
||||
{
|
||||
WCHAR w[260];
|
||||
CHAR c[520];
|
||||
} cFileName;
|
||||
};
|
||||
typedef struct _CLIPRDR_FILEDESCRIPTOR CLIPRDR_FILEDESCRIPTOR;
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct _CLIPRDR_FILELIST
|
||||
{
|
||||
UINT32 cItems;
|
||||
CLIPRDR_FILEDESCRIPTOR* fileDescriptorArray;
|
||||
};
|
||||
typedef struct _CLIPRDR_FILELIST CLIPRDR_FILELIST;
|
||||
FREERDP_API UINT cliprdr_parse_file_list(const BYTE* format_data, UINT32 format_data_length,
|
||||
FILEDESCRIPTOR** file_descriptor_array, UINT32* file_descriptor_count);
|
||||
FREERDP_API UINT cliprdr_serialize_file_list(const FILEDESCRIPTOR* file_descriptor_array,
|
||||
UINT32 file_descriptor_count, BYTE** format_data, UINT32* format_data_length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Clipboard Messages */
|
||||
|
||||
|
@ -228,6 +217,7 @@ struct _CLIPRDR_FILE_CONTENTS_REQUEST
|
|||
UINT32 nPositionLow;
|
||||
UINT32 nPositionHigh;
|
||||
UINT32 cbRequested;
|
||||
BOOL haveClipDataId;
|
||||
UINT32 clipDataId;
|
||||
};
|
||||
typedef struct _CLIPRDR_FILE_CONTENTS_REQUEST CLIPRDR_FILE_CONTENTS_REQUEST;
|
||||
|
|
|
@ -27,6 +27,43 @@ typedef struct _wClipboard wClipboard;
|
|||
|
||||
typedef void* (*CLIPBOARD_SYNTHESIZE_FN)(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32* pSize);
|
||||
|
||||
struct _wClipboardFileSizeRequest
|
||||
{
|
||||
UINT32 streamId;
|
||||
UINT32 listIndex;
|
||||
};
|
||||
typedef struct _wClipboardFileSizeRequest wClipboardFileSizeRequest;
|
||||
|
||||
struct _wClipboardFileRangeRequest
|
||||
{
|
||||
UINT32 streamId;
|
||||
UINT32 listIndex;
|
||||
UINT32 nPositionLow;
|
||||
UINT32 nPositionHigh;
|
||||
UINT32 cbRequested;
|
||||
};
|
||||
typedef struct _wClipboardFileRangeRequest wClipboardFileRangeRequest;
|
||||
|
||||
typedef struct _wClipboardDelegate wClipboardDelegate;
|
||||
|
||||
struct _wClipboardDelegate
|
||||
{
|
||||
wClipboard* clipboard;
|
||||
void* custom;
|
||||
|
||||
UINT (*ClientRequestFileSize)(wClipboardDelegate*, const wClipboardFileSizeRequest*);
|
||||
UINT (*ClipboardFileSizeSuccess)(wClipboardDelegate*, const wClipboardFileSizeRequest*,
|
||||
UINT64 fileSize);
|
||||
UINT (*ClipboardFileSizeFailure)(wClipboardDelegate*, const wClipboardFileSizeRequest*,
|
||||
UINT errorCode);
|
||||
|
||||
UINT (*ClientRequestFileRange)(wClipboardDelegate*, const wClipboardFileRangeRequest*);
|
||||
UINT (*ClipboardFileRangeSuccess)(wClipboardDelegate*, const wClipboardFileRangeRequest*,
|
||||
const BYTE* data, UINT32 size);
|
||||
UINT (*ClipboardFileRangeFailure)(wClipboardDelegate*, const wClipboardFileRangeRequest*,
|
||||
UINT errorCode);
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -53,6 +90,8 @@ WINPR_API BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const vo
|
|||
WINPR_API UINT64 ClipboardGetOwner(wClipboard* clipboard);
|
||||
WINPR_API void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId);
|
||||
|
||||
WINPR_API wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard);
|
||||
|
||||
WINPR_API wClipboard* ClipboardCreate();
|
||||
WINPR_API void ClipboardDestroy(wClipboard* clipboard);
|
||||
|
||||
|
|
|
@ -410,6 +410,8 @@ typedef struct _HANDLE_CREATOR
|
|||
pcCreateFileA CreateFileA;
|
||||
} HANDLE_CREATOR, *PHANDLE_CREATOR, *LPHANDLE_CREATOR;
|
||||
|
||||
WINPR_API BOOL ValidFileNameComponent(LPCWSTR lpFileName);
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#ifdef _UWP
|
||||
|
|
|
@ -27,7 +27,43 @@
|
|||
#include <winpr/winpr.h>
|
||||
#include <winpr/wtypes.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#else
|
||||
|
||||
/* Shell clipboard formats */
|
||||
|
||||
struct _FILEDESCRIPTOR {
|
||||
DWORD dwFlags;
|
||||
BYTE clsid[16];
|
||||
BYTE sizel[8];
|
||||
BYTE pointl[8];
|
||||
DWORD dwFileAttributes;
|
||||
FILETIME ftCreationTime;
|
||||
FILETIME ftLastAccessTime;
|
||||
FILETIME ftLastWriteTime;
|
||||
DWORD nFileSizeHigh;
|
||||
DWORD nFileSizeLow;
|
||||
WCHAR cFileName[260];
|
||||
};
|
||||
typedef struct _FILEDESCRIPTOR FILEDESCRIPTOR;
|
||||
|
||||
/* FILEDESCRIPTOR.dwFlags */
|
||||
#define FD_ATTRIBUTES 0x00000004
|
||||
#define FD_FILESIZE 0x00000040
|
||||
#define FD_WRITESTIME 0x00000020
|
||||
#define FD_SHOWPROGRESSUI 0x00004000
|
||||
|
||||
/* FILEDESCRIPTOR.dwFileAttributes */
|
||||
#define FILE_ATTRIBUTE_READONLY 0x00000001
|
||||
#define FILE_ATTRIBUTE_HIDDEN 0x00000002
|
||||
#define FILE_ATTRIBUTE_SYSTEM 0x00000004
|
||||
#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
|
||||
#define FILE_ATTRIBUTE_ARCHIVE 0x00000020
|
||||
#define FILE_ATTRIBUTE_NORMAL 0x00000080
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
|
|
@ -20,6 +20,14 @@ winpr_module_add(
|
|||
clipboard.c
|
||||
clipboard.h)
|
||||
|
||||
if(HAVE_UNISTD_H)
|
||||
winpr_definition_add(-DWITH_WCLIPBOARD_POSIX)
|
||||
winpr_module_add(
|
||||
posix.h
|
||||
posix.c
|
||||
)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
|
|
@ -23,11 +23,19 @@
|
|||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/collections.h>
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
#include <winpr/clipboard.h>
|
||||
|
||||
#include "clipboard.h"
|
||||
|
||||
#ifdef WITH_WCLIPBOARD_POSIX
|
||||
#include "posix.h"
|
||||
#endif
|
||||
|
||||
#include "../log.h"
|
||||
#define TAG WINPR_TAG("clipboard")
|
||||
|
||||
/**
|
||||
* Clipboard (Windows):
|
||||
* msdn.microsoft.com/en-us/library/windows/desktop/ms648709/
|
||||
|
@ -365,23 +373,22 @@ BOOL ClipboardInitFormats(wClipboard* clipboard)
|
|||
ZeroMemory(format, sizeof(wClipboardFormat));
|
||||
format->formatId = formatId;
|
||||
format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]);
|
||||
|
||||
if (!format->formatName)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = formatId - 1; i >= 0; --i)
|
||||
{
|
||||
format = &(clipboard->formats[--clipboard->numFormats]);
|
||||
free((void*)format->formatName);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
goto error;
|
||||
}
|
||||
|
||||
ClipboardInitSynthesizers(clipboard);
|
||||
if (!ClipboardInitSynthesizers(clipboard))
|
||||
goto error;
|
||||
|
||||
return TRUE;
|
||||
|
||||
error:
|
||||
for (formatId = 0; formatId < clipboard->numFormats; formatId++)
|
||||
{
|
||||
free(clipboard->formats[formatId].formatName);
|
||||
free(clipboard->formats[formatId].synthesizers);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
|
||||
|
@ -506,43 +513,74 @@ void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
|
|||
clipboard->ownerId = ownerId;
|
||||
}
|
||||
|
||||
wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
|
||||
{
|
||||
if (!clipboard)
|
||||
return NULL;
|
||||
|
||||
return &clipboard->delegate;
|
||||
}
|
||||
|
||||
void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
|
||||
{
|
||||
/*
|
||||
* There can be only one local file subsystem active.
|
||||
* Return as soon as initialization succeeds.
|
||||
*/
|
||||
|
||||
#ifdef WITH_WCLIPBOARD_POSIX
|
||||
if (ClipboardInitPosixFileSubsystem(clipboard))
|
||||
{
|
||||
WLog_INFO(TAG, "initialized POSIX local file subsystem");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
WLog_WARN(TAG, "failed to initialize POSIX local file subsystem");
|
||||
}
|
||||
#endif
|
||||
|
||||
WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
|
||||
}
|
||||
|
||||
wClipboard* ClipboardCreate()
|
||||
{
|
||||
wClipboard* clipboard;
|
||||
|
||||
clipboard = (wClipboard*) calloc(1, sizeof(wClipboard));
|
||||
if (!clipboard)
|
||||
return NULL;
|
||||
|
||||
if (clipboard)
|
||||
{
|
||||
clipboard->nextFormatId = 0xC000;
|
||||
clipboard->sequenceNumber = 0;
|
||||
clipboard->nextFormatId = 0xC000;
|
||||
clipboard->sequenceNumber = 0;
|
||||
|
||||
if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
|
||||
{
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
}
|
||||
if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
|
||||
goto error_free_clipboard;
|
||||
|
||||
clipboard->numFormats = 0;
|
||||
clipboard->maxFormats = 64;
|
||||
clipboard->formats = (wClipboardFormat*) malloc(clipboard->maxFormats * sizeof(
|
||||
wClipboardFormat));
|
||||
clipboard->numFormats = 0;
|
||||
clipboard->maxFormats = 64;
|
||||
|
||||
if (!clipboard->formats)
|
||||
{
|
||||
DeleteCriticalSection(&(clipboard->lock));
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
}
|
||||
clipboard->formats = (wClipboardFormat*)
|
||||
calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
|
||||
if (!clipboard->formats)
|
||||
goto error_free_lock;
|
||||
|
||||
if (!ClipboardInitFormats(clipboard))
|
||||
{
|
||||
DeleteCriticalSection(&(clipboard->lock));
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (!ClipboardInitFormats(clipboard))
|
||||
goto error_free_formats;
|
||||
|
||||
clipboard->delegate.clipboard = clipboard;
|
||||
|
||||
ClipboardInitLocalFileSubsystem(clipboard);
|
||||
|
||||
return clipboard;
|
||||
|
||||
error_free_formats:
|
||||
free(clipboard->formats);
|
||||
error_free_lock:
|
||||
DeleteCriticalSection(&(clipboard->lock));
|
||||
error_free_clipboard:
|
||||
free(clipboard);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ClipboardDestroy(wClipboard* clipboard)
|
||||
|
@ -553,6 +591,9 @@ void ClipboardDestroy(wClipboard* clipboard)
|
|||
if (!clipboard)
|
||||
return;
|
||||
|
||||
ArrayList_Free(clipboard->localFiles);
|
||||
clipboard->localFiles = NULL;
|
||||
|
||||
for (index = 0; index < clipboard->numFormats; index++)
|
||||
{
|
||||
format = &(clipboard->formats[index]);
|
||||
|
|
|
@ -30,7 +30,7 @@ typedef struct _wClipboardSynthesizer wClipboardSynthesizer;
|
|||
struct _wClipboardFormat
|
||||
{
|
||||
UINT32 formatId;
|
||||
const char* formatName;
|
||||
char* formatName;
|
||||
|
||||
UINT32 numSynthesizers;
|
||||
wClipboardSynthesizer* synthesizers;
|
||||
|
@ -60,6 +60,13 @@ struct _wClipboard
|
|||
UINT32 formatId;
|
||||
UINT32 sequenceNumber;
|
||||
|
||||
/* clipboard file handling */
|
||||
|
||||
wArrayList* localFiles;
|
||||
UINT32 fileListSequenceNumber;
|
||||
|
||||
wClipboardDelegate delegate;
|
||||
|
||||
CRITICAL_SECTION lock;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,889 @@
|
|||
/**
|
||||
* WinPR: Windows Portable Runtime
|
||||
* Clipboard Functions: POSIX file handling
|
||||
*
|
||||
* Copyright 2017 Alexei Lozovsky <a.lozovsky@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <winpr/clipboard.h>
|
||||
#include <winpr/collections.h>
|
||||
#include <winpr/file.h>
|
||||
#include <winpr/shell.h>
|
||||
#include <winpr/string.h>
|
||||
#include <winpr/wlog.h>
|
||||
|
||||
#include "clipboard.h"
|
||||
#include "posix.h"
|
||||
|
||||
#include "../log.h"
|
||||
#define TAG WINPR_TAG("clipboard.posix")
|
||||
|
||||
struct posix_file
|
||||
{
|
||||
char* local_name;
|
||||
WCHAR* remote_name;
|
||||
BOOL is_directory;
|
||||
|
||||
int fd;
|
||||
off_t offset;
|
||||
off_t size;
|
||||
};
|
||||
|
||||
static struct posix_file* make_posix_file(const char* local_name, const WCHAR* remote_name)
|
||||
{
|
||||
struct posix_file* file = NULL;
|
||||
struct stat statbuf;
|
||||
|
||||
file = calloc(1, sizeof(*file));
|
||||
if (!file)
|
||||
return NULL;
|
||||
|
||||
file->fd = -1;
|
||||
file->offset = 0;
|
||||
|
||||
file->local_name = _strdup(local_name);
|
||||
file->remote_name = _wcsdup(remote_name);
|
||||
|
||||
if (!file->local_name || !file->remote_name)
|
||||
goto error;
|
||||
|
||||
if (stat(local_name, &statbuf))
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to stat %s: %s", local_name, strerror(err));
|
||||
goto error;
|
||||
}
|
||||
|
||||
file->is_directory = S_ISDIR(statbuf.st_mode);
|
||||
file->size = statbuf.st_size;
|
||||
|
||||
return file;
|
||||
|
||||
error:
|
||||
free(file->local_name);
|
||||
free(file->remote_name);
|
||||
free(file);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void free_posix_file(void* the_file)
|
||||
{
|
||||
struct posix_file* file = the_file;
|
||||
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
if (file->fd >= 0)
|
||||
{
|
||||
if (close(file->fd) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_WARN(TAG, "failed to close fd %d: %s", file->fd, strerror(err));
|
||||
}
|
||||
}
|
||||
|
||||
free(file->local_name);
|
||||
free(file->remote_name);
|
||||
free(file);
|
||||
}
|
||||
|
||||
static unsigned char hex_to_dec(char c, BOOL* valid)
|
||||
{
|
||||
if (('0' <= c) && (c <= '9'))
|
||||
return (c - '0');
|
||||
|
||||
if (('a' <= c) && (c <= 'f'))
|
||||
return (c - 'a') + 10;
|
||||
|
||||
if (('A' <= c) && (c <= 'F'))
|
||||
return (c - 'A') + 10;
|
||||
|
||||
*valid = FALSE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL decode_percent_encoded_byte(const char* str, const char* end, char* value)
|
||||
{
|
||||
BOOL valid = TRUE;
|
||||
|
||||
if ((end < str) || (end - str < strlen("%20")))
|
||||
return FALSE;
|
||||
|
||||
*value = 0;
|
||||
*value |= hex_to_dec(str[1], &valid);
|
||||
*value <<= 4;
|
||||
*value |= hex_to_dec(str[2], &valid);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
static char* decode_percent_encoded_string(const char* str, size_t len)
|
||||
{
|
||||
char* buffer = NULL;
|
||||
char* next = NULL;
|
||||
const char* cur = str;
|
||||
const char* lim = str + len;
|
||||
|
||||
/* Percent decoding shrinks data length, so len bytes will be enough. */
|
||||
buffer = calloc(len + 1, sizeof(char));
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
next = buffer;
|
||||
|
||||
while (cur < lim)
|
||||
{
|
||||
if (*cur != '%')
|
||||
{
|
||||
*next++ = *cur++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!decode_percent_encoded_byte(cur, lim, next))
|
||||
{
|
||||
WLog_ERR(TAG, "invalid percent encoding");
|
||||
goto error;
|
||||
}
|
||||
|
||||
cur += strlen("%20");
|
||||
next += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
|
||||
error:
|
||||
free(buffer);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that the function converts a single file name component,
|
||||
* it does not take care of component separators.
|
||||
*/
|
||||
static WCHAR* convert_local_name_component_to_remote(const char* local_name)
|
||||
{
|
||||
WCHAR* remote_name = NULL;
|
||||
|
||||
/*
|
||||
* Note that local file names are not actually guaranteed to be
|
||||
* encoded in UTF-8. Filesystems and users can use whatever they
|
||||
* want. The OS does not care, aside from special treatment of
|
||||
* '\0' and '/' bytes. But we need to make some decision here.
|
||||
* Assuming UTF-8 is currently the most sane thing.
|
||||
*/
|
||||
if (!ConvertToUnicode(CP_UTF8, 0, local_name, -1, &remote_name, 0))
|
||||
{
|
||||
WLog_ERR(TAG, "Unicode conversion failed for %s", local_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Some file names are not valid on Windows. Check for these now
|
||||
* so that we won't get ourselves into a trouble later as such names
|
||||
* are known to crash some Windows shells when pasted via clipboard.
|
||||
*/
|
||||
if (!ValidFileNameComponent(remote_name))
|
||||
{
|
||||
WLog_ERR(TAG, "invalid file name component: %s", local_name);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return remote_name;
|
||||
|
||||
error:
|
||||
free(remote_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char* concat_local_name(const char* dir, const char* file)
|
||||
{
|
||||
size_t len_dir = 0;
|
||||
size_t len_file = 0;
|
||||
char* buffer = NULL;
|
||||
|
||||
len_dir = strlen(dir);
|
||||
len_file = strlen(file);
|
||||
|
||||
buffer = calloc(len_dir + 1 + len_file + 1, sizeof(char));
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
memcpy(buffer, dir, len_dir * sizeof(char));
|
||||
buffer[len_dir] = '/';
|
||||
memcpy(buffer + len_dir + 1, file, len_file * sizeof(char));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static WCHAR* concat_remote_name(const WCHAR* dir, const WCHAR* file)
|
||||
{
|
||||
size_t len_dir = 0;
|
||||
size_t len_file = 0;
|
||||
WCHAR* buffer = NULL;
|
||||
|
||||
len_dir = _wcslen(dir);
|
||||
len_file = _wcslen(file);
|
||||
|
||||
buffer = calloc(len_dir + 1 + len_file + 1, sizeof(WCHAR));
|
||||
if (!buffer)
|
||||
return NULL;
|
||||
|
||||
memcpy(buffer, dir, len_dir * sizeof(WCHAR));
|
||||
buffer[len_dir] = L'\\';
|
||||
memcpy(buffer + len_dir + 1, file, len_file * sizeof(WCHAR));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static BOOL add_file_to_list(const char* local_name, const WCHAR* remote_name, wArrayList* files);
|
||||
|
||||
static BOOL add_directory_entry_to_list(const char* local_dir_name, const WCHAR* remote_dir_name,
|
||||
const struct dirent* entry, wArrayList* files)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
char* local_name = NULL;
|
||||
WCHAR* remote_name = NULL;
|
||||
WCHAR* remote_base_name = NULL;
|
||||
|
||||
/* Skip special directory entries. */
|
||||
if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0))
|
||||
return TRUE;
|
||||
|
||||
remote_base_name = convert_local_name_component_to_remote(entry->d_name);
|
||||
if (!remote_base_name)
|
||||
return FALSE;
|
||||
|
||||
local_name = concat_local_name(local_dir_name, entry->d_name);
|
||||
remote_name = concat_remote_name(remote_dir_name, remote_base_name);
|
||||
|
||||
if (local_name && remote_name)
|
||||
result = add_file_to_list(local_name, remote_name, files);
|
||||
|
||||
free(remote_base_name);
|
||||
free(remote_name);
|
||||
free(local_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL do_add_directory_contents_to_list(const char* local_name, const WCHAR* remote_name,
|
||||
DIR* dirp, wArrayList* files)
|
||||
{
|
||||
/*
|
||||
* For some reason POSIX does not require readdir() to be thread-safe.
|
||||
* However, readdir_r() has really insane interface and is pretty bad
|
||||
* replacement for it. Fortunately, most C libraries guarantee thread-
|
||||
* safety of readdir() when it is used for distinct directory streams.
|
||||
*
|
||||
* Thus we can use readdir() in multithreaded applications if we are
|
||||
* sure that it will not corrupt some global data. It would be nice
|
||||
* to have a compile-time check for this here, but some C libraries
|
||||
* do not provide a #define because of reasons (I'm looking at you,
|
||||
* musl). We should not be breaking people's builds because of that,
|
||||
* so we do nothing and proceed with fingers crossed.
|
||||
*/
|
||||
|
||||
for (;;)
|
||||
{
|
||||
struct dirent* entry = NULL;
|
||||
|
||||
errno = 0;
|
||||
entry = readdir(dirp);
|
||||
if (!entry)
|
||||
{
|
||||
int err = errno;
|
||||
if (!err)
|
||||
break;
|
||||
|
||||
WLog_ERR(TAG, "failed to read directory: %s", strerror(err));
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!add_directory_entry_to_list(local_name, remote_name, entry, files))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL add_directory_contents_to_list(const char* local_name, const WCHAR* remote_name,
|
||||
wArrayList* files)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
DIR* dirp = NULL;
|
||||
|
||||
WLog_VRB(TAG, "adding directory: %s", local_name);
|
||||
|
||||
dirp = opendir(local_name);
|
||||
if (!dirp)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to open directory %s: %s", local_name, strerror(err));
|
||||
goto out;
|
||||
}
|
||||
|
||||
result = do_add_directory_contents_to_list(local_name, remote_name, dirp, files);
|
||||
|
||||
if (closedir(dirp))
|
||||
{
|
||||
int err = errno;
|
||||
WLog_WARN(TAG, "failed to close directory: %s", strerror(err));
|
||||
}
|
||||
out:
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL add_file_to_list(const char* local_name, const WCHAR* remote_name, wArrayList* files)
|
||||
{
|
||||
struct posix_file* file = NULL;
|
||||
|
||||
WLog_VRB(TAG, "adding file: %s", local_name);
|
||||
|
||||
file = make_posix_file(local_name, remote_name);
|
||||
if (!file)
|
||||
return FALSE;
|
||||
|
||||
if (ArrayList_Add(files, file) < 0)
|
||||
{
|
||||
free_posix_file(file);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (file->is_directory)
|
||||
{
|
||||
/*
|
||||
* This is effectively a recursive call, but we do not track
|
||||
* recursion depth, thus filesystem loops can cause a crash.
|
||||
*/
|
||||
if (!add_directory_contents_to_list(local_name, remote_name, files))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static const char* basename(const char* name)
|
||||
{
|
||||
const char* c = name;
|
||||
const char* last_name = name;
|
||||
|
||||
while (*c++)
|
||||
{
|
||||
if (*c == '/')
|
||||
last_name = c + 1;
|
||||
}
|
||||
|
||||
return last_name;
|
||||
}
|
||||
|
||||
static BOOL process_file_name(const char* local_name, wArrayList* files)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
const char* base_name = NULL;
|
||||
WCHAR* remote_name = NULL;
|
||||
|
||||
/*
|
||||
* Start with the base name of the file. text/uri-list contains the
|
||||
* exact files selected by the user, and we want the remote files
|
||||
* to have names relative to that selection.
|
||||
*/
|
||||
base_name = basename(local_name);
|
||||
|
||||
remote_name = convert_local_name_component_to_remote(base_name);
|
||||
if (!remote_name)
|
||||
return FALSE;
|
||||
|
||||
result = add_file_to_list(local_name, remote_name, files);
|
||||
|
||||
free(remote_name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL process_uri(const char* uri, size_t uri_len, wArrayList* files)
|
||||
{
|
||||
BOOL result = FALSE;
|
||||
char* name = NULL;
|
||||
|
||||
WLog_VRB(TAG, "processing URI: %.*s", uri_len, uri);
|
||||
|
||||
if ((uri_len < strlen("file://")) || strncmp(uri, "file://", strlen("file://")))
|
||||
{
|
||||
WLog_ERR(TAG, "non-'file://' URI schemes are not supported");
|
||||
goto out;
|
||||
}
|
||||
|
||||
name = decode_percent_encoded_string(uri + strlen("file://"), uri_len - strlen("file://"));
|
||||
if (!name)
|
||||
goto out;
|
||||
|
||||
result = process_file_name(name, files);
|
||||
out:
|
||||
free(name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL process_uri_list(const char* data, size_t length, wArrayList* files)
|
||||
{
|
||||
const char* cur = data;
|
||||
const char* lim = data + length;
|
||||
const char* start = NULL;
|
||||
const char* stop = NULL;
|
||||
|
||||
WLog_VRB(TAG, "processing URI list:\n%.*s", length, data);
|
||||
|
||||
ArrayList_Clear(files);
|
||||
|
||||
/*
|
||||
* The "text/uri-list" Internet Media Type is specified by RFC 2483.
|
||||
*
|
||||
* While the RFCs 2046 and 2483 require the lines of text/... formats
|
||||
* to be terminated by CRLF sequence, be prepared for those who don't
|
||||
* read the spec, use plain LFs, and don't leave the trailing CRLF.
|
||||
*/
|
||||
|
||||
while (cur < lim)
|
||||
{
|
||||
BOOL comment = (*cur == '#');
|
||||
|
||||
start = cur;
|
||||
stop = cur;
|
||||
|
||||
for (stop = cur; stop < lim; stop++)
|
||||
{
|
||||
if (*stop == '\r')
|
||||
{
|
||||
if ((stop + 1 < lim) && (*(stop + 1) == '\n'))
|
||||
cur = stop + 2;
|
||||
else
|
||||
cur = stop + 1;
|
||||
break;
|
||||
}
|
||||
if (*stop == '\n')
|
||||
{
|
||||
cur = stop + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop == lim)
|
||||
cur = lim;
|
||||
|
||||
if (comment)
|
||||
continue;
|
||||
|
||||
if (!process_uri(start, stop - start, files))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL convert_local_file_to_filedescriptor(const struct posix_file* file,
|
||||
FILEDESCRIPTOR* descriptor)
|
||||
{
|
||||
size_t remote_len = 0;
|
||||
|
||||
descriptor->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_SHOWPROGRESSUI;
|
||||
|
||||
if (file->is_directory)
|
||||
{
|
||||
descriptor->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
|
||||
descriptor->nFileSizeLow = 0;
|
||||
descriptor->nFileSizeHigh = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
descriptor->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
descriptor->nFileSizeLow = (file->size >> 0) & 0xFFFFFFFF;
|
||||
descriptor->nFileSizeHigh = (file->size >> 32) & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
remote_len = _wcslen(file->remote_name);
|
||||
if (remote_len + 1 > ARRAYSIZE(descriptor->cFileName))
|
||||
{
|
||||
WLog_ERR(TAG, "file name too long (%"PRIuz" characters)", remote_len);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
memcpy(descriptor->cFileName, file->remote_name, remote_len * sizeof(WCHAR));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static FILEDESCRIPTOR* convert_local_file_list_to_filedescriptors(wArrayList* files)
|
||||
{
|
||||
int i;
|
||||
int count = 0;
|
||||
FILEDESCRIPTOR* descriptors = NULL;
|
||||
|
||||
count = ArrayList_Count(files);
|
||||
|
||||
descriptors = calloc(count, sizeof(descriptors[0]));
|
||||
if (!descriptors)
|
||||
goto error;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
const struct posix_file* file = ArrayList_GetItem(files, i);
|
||||
|
||||
if (!convert_local_file_to_filedescriptor(file, &descriptors[i]))
|
||||
goto error;
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
|
||||
error:
|
||||
free(descriptors);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void* convert_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
|
||||
const void* data, UINT32* pSize)
|
||||
{
|
||||
FILEDESCRIPTOR* descriptors = NULL;
|
||||
|
||||
if (!clipboard || !data || !pSize)
|
||||
return NULL;
|
||||
|
||||
if (formatId != ClipboardGetFormatId(clipboard, "text/uri-list"))
|
||||
return NULL;
|
||||
|
||||
if (!process_uri_list((const char*) data, *pSize, clipboard->localFiles))
|
||||
return NULL;
|
||||
|
||||
descriptors = convert_local_file_list_to_filedescriptors(clipboard->localFiles);
|
||||
if (!descriptors)
|
||||
return NULL;
|
||||
|
||||
*pSize = ArrayList_Count(clipboard->localFiles) * sizeof(FILEDESCRIPTOR);
|
||||
|
||||
clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard)
|
||||
{
|
||||
UINT32 file_group_format_id;
|
||||
UINT32 local_file_format_id;
|
||||
|
||||
file_group_format_id = ClipboardRegisterFormat(clipboard, "FileGroupDescriptorW");
|
||||
local_file_format_id = ClipboardRegisterFormat(clipboard, "text/uri-list");
|
||||
if (!file_group_format_id || !local_file_format_id)
|
||||
goto error;
|
||||
|
||||
clipboard->localFiles = ArrayList_New(FALSE);
|
||||
if (!clipboard->localFiles)
|
||||
goto error;
|
||||
|
||||
ArrayList_Object(clipboard->localFiles)->fnObjectFree = free_posix_file;
|
||||
|
||||
if (!ClipboardRegisterSynthesizer(clipboard,
|
||||
local_file_format_id, file_group_format_id,
|
||||
convert_uri_list_to_filedescriptors))
|
||||
goto error_free_local_files;
|
||||
|
||||
return TRUE;
|
||||
|
||||
error_free_local_files:
|
||||
ArrayList_Free(clipboard->localFiles);
|
||||
clipboard->localFiles = NULL;
|
||||
error:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static UINT posix_file_get_size(const struct posix_file* file, off_t* size)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (stat(file->local_name, &statbuf) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to stat %s: %s", file->local_name, strerror(err));
|
||||
return ERROR_FILE_INVALID;
|
||||
}
|
||||
|
||||
*size = statbuf.st_size;
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT posix_file_request_size(wClipboardDelegate* delegate,
|
||||
const wClipboardFileSizeRequest* request)
|
||||
{
|
||||
UINT error = NO_ERROR;
|
||||
off_t size = 0;
|
||||
struct posix_file* file = NULL;
|
||||
|
||||
if (!delegate || !delegate->clipboard || !request)
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
|
||||
if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
|
||||
return ERROR_INVALID_STATE;
|
||||
|
||||
file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
|
||||
if (!file)
|
||||
return ERROR_INDEX_ABSENT;
|
||||
|
||||
error = posix_file_get_size(file, &size);
|
||||
|
||||
if (error)
|
||||
error = delegate->ClipboardFileSizeFailure(delegate, request, error);
|
||||
else
|
||||
error = delegate->ClipboardFileSizeSuccess(delegate, request, size);
|
||||
|
||||
if (error)
|
||||
WLog_WARN(TAG, "failed to report file size result: 0x%08X", error);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT posix_file_read_open(struct posix_file* file)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (file->fd >= 0)
|
||||
return NO_ERROR;
|
||||
|
||||
file->fd = open(file->local_name, O_RDONLY);
|
||||
if (file->fd < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to open file %s: %s", file->local_name, strerror(err));
|
||||
return ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (fstat(file->fd, &statbuf) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to stat file: %s", strerror(err));
|
||||
return ERROR_FILE_INVALID;
|
||||
}
|
||||
|
||||
file->offset = 0;
|
||||
file->size = statbuf.st_size;
|
||||
|
||||
WLog_VRB(TAG, "open file %d -> %s", file->fd, file->local_name);
|
||||
WLog_VRB(TAG, "file %d size: %"PRIu64" bytes", file->fd, file->size);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT posix_file_read_seek(struct posix_file* file, UINT64 offset)
|
||||
{
|
||||
/*
|
||||
* We should avoid seeking when possible as some filesystems (e.g.,
|
||||
* an FTP server mapped via FUSE) may not support seeking. We keep
|
||||
* an accurate account of the current file offset and do not call
|
||||
* lseek() if the client requests file content sequentially.
|
||||
*/
|
||||
if (file->offset == offset)
|
||||
return NO_ERROR;
|
||||
|
||||
WLog_VRB(TAG, "file %d force seeking to %"PRIu64", current %"PRIu64, file->fd,
|
||||
offset, file->offset);
|
||||
|
||||
if (lseek(file->fd, offset, SEEK_SET) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to seek file: %s", strerror(err));
|
||||
return ERROR_SEEK;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT posix_file_read_perform(struct posix_file* file, UINT32 size,
|
||||
BYTE** actual_data, UINT32* actual_size)
|
||||
{
|
||||
BYTE* buffer = NULL;
|
||||
ssize_t amount = 0;
|
||||
|
||||
WLog_VRB(TAG, "file %d request read %"PRIu32" bytes", file->fd, size);
|
||||
|
||||
buffer = malloc(size);
|
||||
if (!buffer)
|
||||
{
|
||||
WLog_ERR(TAG, "failed to allocate %"PRIu32" buffer bytes", size);
|
||||
return ERROR_NOT_ENOUGH_MEMORY;
|
||||
}
|
||||
|
||||
amount = read(file->fd, buffer, size);
|
||||
if (amount < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_ERR(TAG, "failed to read file: %s", strerror(err));
|
||||
goto error;
|
||||
}
|
||||
|
||||
*actual_data = buffer;
|
||||
*actual_size = amount;
|
||||
file->offset += amount;
|
||||
|
||||
WLog_VRB(TAG, "file %d actual read %"PRIu32" bytes (offset %"PRIu64")", file->fd,
|
||||
amount, file->offset);
|
||||
|
||||
return NO_ERROR;
|
||||
|
||||
error:
|
||||
free(buffer);
|
||||
|
||||
return ERROR_READ_FAULT;
|
||||
}
|
||||
|
||||
static UINT posix_file_read_close(struct posix_file* file)
|
||||
{
|
||||
if (file->fd < 0)
|
||||
return NO_ERROR;
|
||||
|
||||
if (file->offset == file->size)
|
||||
{
|
||||
WLog_VRB(TAG, "close file %d", file->fd);
|
||||
|
||||
if (close(file->fd) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
WLog_WARN(TAG, "failed to close fd %d: %s", file->fd, strerror(err));
|
||||
}
|
||||
file->fd = -1;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT posix_file_get_range(struct posix_file* file, UINT64 offset, UINT32 size,
|
||||
BYTE** actual_data, UINT32* actual_size)
|
||||
{
|
||||
UINT error = NO_ERROR;
|
||||
|
||||
error = posix_file_read_open(file);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = posix_file_read_seek(file, offset);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = posix_file_read_perform(file, size, actual_data, actual_size);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = posix_file_read_close(file);
|
||||
if (error)
|
||||
goto out;
|
||||
out:
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT posix_file_request_range(wClipboardDelegate* delegate,
|
||||
const wClipboardFileRangeRequest* request)
|
||||
{
|
||||
UINT error = 0;
|
||||
BYTE* data = NULL;
|
||||
UINT32 size = 0;
|
||||
UINT64 offset = 0;
|
||||
struct posix_file* file = NULL;
|
||||
|
||||
if (!delegate || !delegate->clipboard || !request)
|
||||
return ERROR_BAD_ARGUMENTS;
|
||||
|
||||
if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
|
||||
return ERROR_INVALID_STATE;
|
||||
|
||||
file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
|
||||
if (!file)
|
||||
return ERROR_INDEX_ABSENT;
|
||||
|
||||
offset = (((UINT64) request->nPositionHigh) << 32) | ((UINT64) request->nPositionLow);
|
||||
error = posix_file_get_range(file, offset, request->cbRequested, &data, &size);
|
||||
|
||||
if (error)
|
||||
error = delegate->ClipboardFileRangeFailure(delegate, request, error);
|
||||
else
|
||||
error = delegate->ClipboardFileRangeSuccess(delegate, request, data, size);
|
||||
|
||||
if (error)
|
||||
WLog_WARN(TAG, "failed to report file range result: 0x%08X", error);
|
||||
|
||||
free(data);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
static UINT dummy_file_size_success(wClipboardDelegate* delegate, const wClipboardFileSizeRequest* request, UINT64 fileSize)
|
||||
{
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static UINT dummy_file_size_failure(wClipboardDelegate* delegate, const wClipboardFileSizeRequest* request, UINT errorCode)
|
||||
{
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static UINT dummy_file_range_success(wClipboardDelegate* delegate, const wClipboardFileRangeRequest* request, const BYTE* data, UINT32 size)
|
||||
{
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static UINT dummy_file_range_failure(wClipboardDelegate* delegate, const wClipboardFileRangeRequest* request, UINT errorCode)
|
||||
{
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
static void setup_delegate(wClipboardDelegate* delegate)
|
||||
{
|
||||
delegate->ClientRequestFileSize = posix_file_request_size;
|
||||
delegate->ClipboardFileSizeSuccess = dummy_file_size_success;
|
||||
delegate->ClipboardFileSizeFailure = dummy_file_size_failure;
|
||||
|
||||
delegate->ClientRequestFileRange = posix_file_request_range;
|
||||
delegate->ClipboardFileRangeSuccess = dummy_file_range_success;
|
||||
delegate->ClipboardFileRangeFailure = dummy_file_range_failure;
|
||||
}
|
||||
|
||||
BOOL ClipboardInitPosixFileSubsystem(wClipboard* clipboard)
|
||||
{
|
||||
if (!clipboard)
|
||||
return FALSE;
|
||||
|
||||
if (!register_file_formats_and_synthesizers(clipboard))
|
||||
return FALSE;
|
||||
|
||||
setup_delegate(&clipboard->delegate);
|
||||
|
||||
return TRUE;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* WinPR: Windows Portable Runtime
|
||||
* Clipboard Functions: POSIX file handling
|
||||
*
|
||||
* Copyright 2017 Alexei Lozovsky <a.lozovsky@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef WINPR_CLIPBOARD_POSIX_H
|
||||
#define WINPR_CLIPBOARD_POSIX_H
|
||||
|
||||
#include <winpr/clipboard.h>
|
||||
|
||||
BOOL ClipboardInitPosixFileSubsystem(wClipboard* clipboard);
|
||||
|
||||
#endif /* WINPR_CLIPBOARD_POSIX_H */
|
|
@ -814,6 +814,89 @@ BOOL GetDiskFreeSpaceW(LPCWSTR lpwRootPathName, LPDWORD lpSectorsPerCluster,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file name component is valid.
|
||||
*
|
||||
* Some file names are not valid on Windows. See "Naming Files, Paths, and Namespaces":
|
||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||
*/
|
||||
BOOL ValidFileNameComponent(LPCWSTR lpFileName)
|
||||
{
|
||||
LPCWSTR c = NULL;
|
||||
|
||||
if (!lpFileName)
|
||||
return FALSE;
|
||||
|
||||
/* CON */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'C' || lpFileName[0] == L'c')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'O' || lpFileName[1] == L'o')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'N' || lpFileName[2] == L'n')) &&
|
||||
(lpFileName[3] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* PRN */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'P' || lpFileName[0] == L'p')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'R' || lpFileName[1] == L'r')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'N' || lpFileName[2] == L'n')) &&
|
||||
(lpFileName[3] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* AUX */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'A' || lpFileName[0] == L'a')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'U' || lpFileName[1] == L'u')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'X' || lpFileName[2] == L'x')) &&
|
||||
(lpFileName[3] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* NUL */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'N' || lpFileName[0] == L'n')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'U' || lpFileName[1] == L'u')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'L' || lpFileName[2] == L'l')) &&
|
||||
(lpFileName[3] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* LPT0-9 */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'L' || lpFileName[0] == L'l')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'P' || lpFileName[1] == L'p')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'T' || lpFileName[2] == L't')) &&
|
||||
(lpFileName[3] != L'\0' && (L'0' <= lpFileName[3] && lpFileName[3] <= L'9')) &&
|
||||
(lpFileName[4] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* COM0-9 */
|
||||
if ((lpFileName[0] != L'\0' && (lpFileName[0] == L'C' || lpFileName[0] == L'c')) &&
|
||||
(lpFileName[1] != L'\0' && (lpFileName[1] == L'O' || lpFileName[1] == L'o')) &&
|
||||
(lpFileName[2] != L'\0' && (lpFileName[2] == L'M' || lpFileName[2] == L'm')) &&
|
||||
(lpFileName[3] != L'\0' && (L'0' <= lpFileName[3] && lpFileName[3] <= L'9')) &&
|
||||
(lpFileName[4] == L'\0'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Reserved characters */
|
||||
for (c = lpFileName; *c; c++)
|
||||
{
|
||||
if ((*c == L'<') || (*c == L'>') || (*c == L':') ||
|
||||
(*c == L'"') || (*c == L'/') || (*c == L'\\') ||
|
||||
(*c == L'|') || (*c == L'?') || (*c == L'*'))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#ifdef _UWP
|
||||
|
|
Loading…
Reference in New Issue