Merge pull request #3905 from ilammy/x11-cliprdr/file-clipping

Local-to-remote file clipping for xfreerdp
This commit is contained in:
David Fort 2017-06-07 21:20:34 +02:00 committed by GitHub
commit 5ef9232703
13 changed files with 1656 additions and 113 deletions

View File

@ -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;
}

View File

@ -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);

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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" {

View File

@ -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()

View File

@ -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]);

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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 */

View File

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