From a9bcc81f821d7c66f623779fa5147e728eb3c388 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 03:24:41 +0200 Subject: Adding upstream version 3.3.0+dfsg1. Signed-off-by: Daniel Baumann --- winpr/libwinpr/clipboard/CMakeLists.txt | 28 + winpr/libwinpr/clipboard/ModuleOptions.cmake | 9 + winpr/libwinpr/clipboard/clipboard.c | 738 ++++++++++++ winpr/libwinpr/clipboard/clipboard.h | 77 ++ winpr/libwinpr/clipboard/synthetic.c | 826 +++++++++++++ winpr/libwinpr/clipboard/synthetic_file.c | 1262 ++++++++++++++++++++ winpr/libwinpr/clipboard/synthetic_file.h | 27 + winpr/libwinpr/clipboard/test/CMakeLists.txt | 27 + .../libwinpr/clipboard/test/TestClipboardFormats.c | 82 ++ winpr/libwinpr/clipboard/test/TestUri.c | 69 ++ 10 files changed, 3145 insertions(+) create mode 100644 winpr/libwinpr/clipboard/CMakeLists.txt create mode 100644 winpr/libwinpr/clipboard/ModuleOptions.cmake create mode 100644 winpr/libwinpr/clipboard/clipboard.c create mode 100644 winpr/libwinpr/clipboard/clipboard.h create mode 100644 winpr/libwinpr/clipboard/synthetic.c create mode 100644 winpr/libwinpr/clipboard/synthetic_file.c create mode 100644 winpr/libwinpr/clipboard/synthetic_file.h create mode 100644 winpr/libwinpr/clipboard/test/CMakeLists.txt create mode 100644 winpr/libwinpr/clipboard/test/TestClipboardFormats.c create mode 100644 winpr/libwinpr/clipboard/test/TestUri.c (limited to 'winpr/libwinpr/clipboard') diff --git a/winpr/libwinpr/clipboard/CMakeLists.txt b/winpr/libwinpr/clipboard/CMakeLists.txt new file mode 100644 index 0000000..898067d --- /dev/null +++ b/winpr/libwinpr/clipboard/CMakeLists.txt @@ -0,0 +1,28 @@ +# WinPR: Windows Portable Runtime +# libwinpr-clipboard cmake build script +# +# Copyright 2014 Marc-Andre Moreau +# +# 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. + +winpr_module_add( + synthetic.c + clipboard.c + clipboard.h + synthetic_file.h + synthetic_file.c + ) + +if(BUILD_TESTING) + add_subdirectory(test) +endif() diff --git a/winpr/libwinpr/clipboard/ModuleOptions.cmake b/winpr/libwinpr/clipboard/ModuleOptions.cmake new file mode 100644 index 0000000..f8ebeaa --- /dev/null +++ b/winpr/libwinpr/clipboard/ModuleOptions.cmake @@ -0,0 +1,9 @@ + +set(MINWIN_LAYER "0") +set(MINWIN_GROUP "none") +set(MINWIN_MAJOR_VERSION "0") +set(MINWIN_MINOR_VERSION "0") +set(MINWIN_SHORT_NAME "clipboard") +set(MINWIN_LONG_NAME "Clipboard Functions") +set(MODULE_LIBRARY_NAME "clipboard") + diff --git a/winpr/libwinpr/clipboard/clipboard.c b/winpr/libwinpr/clipboard/clipboard.c new file mode 100644 index 0000000..9d17fbc --- /dev/null +++ b/winpr/libwinpr/clipboard/clipboard.c @@ -0,0 +1,738 @@ +/** + * WinPR: Windows Portable Runtime + * Clipboard Functions + * + * Copyright 2014 Marc-Andre Moreau + * + * 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. + */ + +#include + +#include +#include +#include + +#include + +#include "clipboard.h" + +#include "synthetic_file.h" + +#include "../log.h" +#define TAG WINPR_TAG("clipboard") + +const char* mime_text_plain = "text/plain"; + +/** + * Clipboard (Windows): + * msdn.microsoft.com/en-us/library/windows/desktop/ms648709/ + * + * W3C Clipboard API and events: + * http://www.w3.org/TR/clipboard-apis/ + */ + +static const char* CF_STANDARD_STRINGS[] = { + "CF_RAW", /* 0 */ + "CF_TEXT", /* 1 */ + "CF_BITMAP", /* 2 */ + "CF_METAFILEPICT", /* 3 */ + "CF_SYLK", /* 4 */ + "CF_DIF", /* 5 */ + "CF_TIFF", /* 6 */ + "CF_OEMTEXT", /* 7 */ + "CF_DIB", /* 8 */ + "CF_PALETTE", /* 9 */ + "CF_PENDATA", /* 10 */ + "CF_RIFF", /* 11 */ + "CF_WAVE", /* 12 */ + "CF_UNICODETEXT", /* 13 */ + "CF_ENHMETAFILE", /* 14 */ + "CF_HDROP", /* 15 */ + "CF_LOCALE", /* 16 */ + "CF_DIBV5" /* 17 */ +}; + +const char* ClipboardGetFormatIdString(UINT32 formatId) +{ + if (formatId < ARRAYSIZE(CF_STANDARD_STRINGS)) + return CF_STANDARD_STRINGS[formatId]; + return "CF_REGISTERED_FORMAT"; +} + +static wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId, + const char* name) +{ + wClipboardFormat* format = NULL; + + if (!clipboard) + return NULL; + + if (formatId) + { + for (UINT32 index = 0; index < clipboard->numFormats; index++) + { + wClipboardFormat* cformat = &clipboard->formats[index]; + if (formatId == cformat->formatId) + { + format = cformat; + break; + } + } + } + else if (name) + { + for (UINT32 index = 0; index < clipboard->numFormats; index++) + { + wClipboardFormat* cformat = &clipboard->formats[index]; + if (!cformat->formatName) + continue; + + if (strcmp(name, cformat->formatName) == 0) + { + format = cformat; + break; + } + } + } + else + { + /* special "CF_RAW" case */ + if (clipboard->numFormats > 0) + { + format = &clipboard->formats[0]; + + if (format->formatId) + return NULL; + + if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0)) + return format; + } + } + + return format; +} + +static wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId) +{ + if (!format) + return NULL; + + for (UINT32 index = 0; index < format->numSynthesizers; index++) + { + wClipboardSynthesizer* synthesizer = &(format->synthesizers[index]); + + if (formatId == synthesizer->syntheticId) + return synthesizer; + } + + return NULL; +} + +void ClipboardLock(wClipboard* clipboard) +{ + if (!clipboard) + return; + + EnterCriticalSection(&(clipboard->lock)); +} + +void ClipboardUnlock(wClipboard* clipboard) +{ + if (!clipboard) + return; + + LeaveCriticalSection(&(clipboard->lock)); +} + +BOOL ClipboardEmpty(wClipboard* clipboard) +{ + if (!clipboard) + return FALSE; + + if (clipboard->data) + { + free((void*)clipboard->data); + clipboard->data = NULL; + } + + clipboard->size = 0; + clipboard->formatId = 0; + clipboard->sequenceNumber++; + return TRUE; +} + +UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard) +{ + if (!clipboard) + return 0; + + return clipboard->numFormats; +} + +UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds) +{ + UINT32* pFormatIds = NULL; + wClipboardFormat* format = NULL; + + if (!clipboard) + return 0; + + if (!ppFormatIds) + return 0; + + pFormatIds = *ppFormatIds; + + if (!pFormatIds) + { + pFormatIds = calloc(clipboard->numFormats, sizeof(UINT32)); + + if (!pFormatIds) + return 0; + + *ppFormatIds = pFormatIds; + } + + for (UINT32 index = 0; index < clipboard->numFormats; index++) + { + format = &(clipboard->formats[index]); + pFormatIds[index] = format->formatId; + } + + return clipboard->numFormats; +} + +UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name) +{ + wClipboardFormat* format = NULL; + + if (!clipboard) + return 0; + + format = ClipboardFindFormat(clipboard, 0, name); + + if (format) + return format->formatId; + + if ((clipboard->numFormats + 1) >= clipboard->maxFormats) + { + UINT32 numFormats = clipboard->maxFormats * 2; + wClipboardFormat* tmpFormat = NULL; + tmpFormat = + (wClipboardFormat*)realloc(clipboard->formats, numFormats * sizeof(wClipboardFormat)); + + if (!tmpFormat) + return 0; + + clipboard->formats = tmpFormat; + clipboard->maxFormats = numFormats; + } + + format = &(clipboard->formats[clipboard->numFormats]); + ZeroMemory(format, sizeof(wClipboardFormat)); + + if (name) + { + format->formatName = _strdup(name); + + if (!format->formatName) + return 0; + } + + format->formatId = clipboard->nextFormatId++; + clipboard->numFormats++; + return format->formatId; +} + +BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId, + CLIPBOARD_SYNTHESIZE_FN pfnSynthesize) +{ + UINT32 index = 0; + wClipboardFormat* format = NULL; + wClipboardSynthesizer* synthesizer = NULL; + + if (!clipboard) + return FALSE; + + format = ClipboardFindFormat(clipboard, formatId, NULL); + + if (!format) + return FALSE; + + if (format->formatId == syntheticId) + return FALSE; + + synthesizer = ClipboardFindSynthesizer(format, formatId); + + if (!synthesizer) + { + wClipboardSynthesizer* tmpSynthesizer = NULL; + UINT32 numSynthesizers = format->numSynthesizers + 1; + tmpSynthesizer = (wClipboardSynthesizer*)realloc( + format->synthesizers, numSynthesizers * sizeof(wClipboardSynthesizer)); + + if (!tmpSynthesizer) + return FALSE; + + format->synthesizers = tmpSynthesizer; + format->numSynthesizers = numSynthesizers; + index = numSynthesizers - 1; + synthesizer = &(format->synthesizers[index]); + } + + synthesizer->syntheticId = syntheticId; + synthesizer->pfnSynthesize = pfnSynthesize; + return TRUE; +} + +UINT32 ClipboardCountFormats(wClipboard* clipboard) +{ + UINT32 count = 0; + wClipboardFormat* format = NULL; + + if (!clipboard) + return 0; + + format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL); + + if (!format) + return 0; + + count = 1 + format->numSynthesizers; + return count; +} + +UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds) +{ + UINT32 count = 0; + UINT32* pFormatIds = NULL; + wClipboardFormat* format = NULL; + wClipboardSynthesizer* synthesizer = NULL; + + if (!clipboard) + return 0; + + format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL); + + if (!format) + return 0; + + count = 1 + format->numSynthesizers; + + if (!ppFormatIds) + return 0; + + pFormatIds = *ppFormatIds; + + if (!pFormatIds) + { + pFormatIds = calloc(count, sizeof(UINT32)); + + if (!pFormatIds) + return 0; + + *ppFormatIds = pFormatIds; + } + + pFormatIds[0] = format->formatId; + + for (UINT32 index = 1; index < count; index++) + { + synthesizer = &(format->synthesizers[index - 1]); + pFormatIds[index] = synthesizer->syntheticId; + } + + return count; +} + +static void ClipboardUninitFormats(wClipboard* clipboard) +{ + WINPR_ASSERT(clipboard); + for (UINT32 formatId = 0; formatId < clipboard->numFormats; formatId++) + { + wClipboardFormat* format = &clipboard->formats[formatId]; + free(format->formatName); + free(format->synthesizers); + format->formatName = NULL; + format->synthesizers = NULL; + } +} + +static BOOL ClipboardInitFormats(wClipboard* clipboard) +{ + UINT32 formatId = 0; + wClipboardFormat* format = NULL; + + if (!clipboard) + return FALSE; + + for (formatId = 0; formatId < CF_MAX; formatId++, clipboard->numFormats++) + { + format = &(clipboard->formats[clipboard->numFormats]); + ZeroMemory(format, sizeof(wClipboardFormat)); + format->formatId = formatId; + format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]); + + if (!format->formatName) + goto error; + } + + if (!ClipboardInitSynthesizers(clipboard)) + goto error; + + return TRUE; +error: + + ClipboardUninitFormats(clipboard); + return FALSE; +} + +UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name) +{ + wClipboardFormat* format = NULL; + + if (!clipboard) + return 0; + + format = ClipboardFindFormat(clipboard, 0, name); + + if (!format) + return 0; + + return format->formatId; +} + +const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId) +{ + wClipboardFormat* format = NULL; + + if (!clipboard) + return NULL; + + format = ClipboardFindFormat(clipboard, formatId, NULL); + + if (!format) + return NULL; + + return format->formatName; +} + +void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize) +{ + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + void* pSrcData = NULL; + void* pDstData = NULL; + wClipboardFormat* format = NULL; + wClipboardSynthesizer* synthesizer = NULL; + + if (!clipboard) + return NULL; + + if (!pSize) + return NULL; + + *pSize = 0; + format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL); + + if (!format) + return NULL; + + SrcSize = clipboard->size; + pSrcData = (void*)clipboard->data; + + if (formatId == format->formatId) + { + DstSize = SrcSize; + pDstData = malloc(DstSize); + + if (!pDstData) + return NULL; + + CopyMemory(pDstData, pSrcData, SrcSize); + *pSize = DstSize; + } + else + { + synthesizer = ClipboardFindSynthesizer(format, formatId); + + if (!synthesizer || !synthesizer->pfnSynthesize) + return NULL; + + DstSize = SrcSize; + pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize); + if (pDstData) + *pSize = DstSize; + } + + return pDstData; +} + +BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size) +{ + wClipboardFormat* format = NULL; + + if (!clipboard) + return FALSE; + + format = ClipboardFindFormat(clipboard, formatId, NULL); + + if (!format) + return FALSE; + + free((void*)clipboard->data); + clipboard->data = malloc(size); + + if (!clipboard->data) + return FALSE; + + memcpy(clipboard->data, data, size); + clipboard->size = size; + clipboard->formatId = formatId; + clipboard->sequenceNumber++; + return TRUE; +} + +UINT64 ClipboardGetOwner(wClipboard* clipboard) +{ + if (!clipboard) + return 0; + + return clipboard->ownerId; +} + +void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId) +{ + if (!clipboard) + return; + + clipboard->ownerId = ownerId; +} + +wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard) +{ + if (!clipboard) + return NULL; + + return &clipboard->delegate; +} + +static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard) +{ + /* + * There can be only one local file subsystem active. + * Return as soon as initialization succeeds. + */ + if (ClipboardInitSyntheticFileSubsystem(clipboard)) + { + WLog_DBG(TAG, "initialized synthetic local file subsystem"); + return; + } + else + { + WLog_WARN(TAG, "failed to initialize synthetic local file subsystem"); + } + + WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available"); +} + +wClipboard* ClipboardCreate(void) +{ + wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard)); + + if (!clipboard) + return NULL; + + clipboard->nextFormatId = 0xC000; + clipboard->sequenceNumber = 0; + + if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000)) + goto fail; + + clipboard->numFormats = 0; + clipboard->maxFormats = 64; + clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat)); + + if (!clipboard->formats) + goto fail; + + if (!ClipboardInitFormats(clipboard)) + goto fail; + + clipboard->delegate.clipboard = clipboard; + ClipboardInitLocalFileSubsystem(clipboard); + return clipboard; +fail: + ClipboardDestroy(clipboard); + return NULL; +} + +void ClipboardDestroy(wClipboard* clipboard) +{ + if (!clipboard) + return; + + ArrayList_Free(clipboard->localFiles); + clipboard->localFiles = NULL; + + ClipboardUninitFormats(clipboard); + + free((void*)clipboard->data); + clipboard->data = NULL; + clipboard->size = 0; + clipboard->numFormats = 0; + free(clipboard->formats); + DeleteCriticalSection(&(clipboard->lock)); + free(clipboard); +} + +static BOOL is_dos_drive(const char* path, size_t len) +{ + if (len < 2) + return FALSE; + + WINPR_ASSERT(path); + if (path[1] == ':' || path[1] == '|') + { + if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z'))) + return TRUE; + } + return FALSE; +} + +char* parse_uri_to_local_file(const char* uri, size_t uri_len) +{ + // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089 + const char prefix[] = "file:"; + const char prefixTraditional[] = "file://"; + const char* localName = NULL; + size_t localLen = 0; + char* buffer = NULL; + const size_t prefixLen = strnlen(prefix, sizeof(prefix)); + const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional)); + + WINPR_ASSERT(uri || (uri_len == 0)); + + WLog_VRB(TAG, "processing URI: %.*s", uri_len, uri); + + if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen)) + { + WLog_ERR(TAG, "non-'file:' URI schemes are not supported"); + return NULL; + } + + do + { + /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F + * - The minimal representation of a local file in a DOS- or Windows- + * based environment with no authority field and an absolute path + * that begins with a drive letter. + * + * "file:c:/path/to/file" + * + * - Regular DOS or Windows file URIs with vertical line characters in + * the drive letter construct. + * + * "file:c|/path/to/file" + * + */ + if (uri[prefixLen] != '/') + { + + if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen)) + { + // Dos and Windows file URI + localName = &uri[prefixLen]; + localLen = uri_len - prefixLen; + break; + } + else + { + WLog_ERR(TAG, "URI format are not supported: %s", uri); + return NULL; + } + } + + /* + * - The minimal representation of a local file with no authority field + * and an absolute path that begins with a slash "/". For example: + * + * "file:/path/to/file" + * + */ + else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/')) + { + if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1)) + { + // Dos and Windows file URI + localName = (const char*)(uri + prefixLen + 1); + localLen = uri_len - prefixLen - 1; + } + else + { + localName = &uri[prefixLen]; + localLen = uri_len - prefixLen; + } + break; + } + + /* + * - A traditional file URI for a local file with an empty authority. + * + * "file:///path/to/file" + */ + if ((uri_len < prefixTraditionalLen) || + strncmp(uri, prefixTraditional, prefixTraditionalLen)) + { + WLog_ERR(TAG, "non-'file:' URI schemes are not supported"); + return NULL; + } + + localName = &uri[prefixTraditionalLen]; + localLen = uri_len - prefixTraditionalLen; + + if (localLen < 1) + { + WLog_ERR(TAG, "empty 'file:' URI schemes are not supported"); + return NULL; + } + + /* + * "file:///c:/path/to/file" + * "file:///c|/path/to/file" + */ + if (localName[0] != '/') + { + WLog_ERR(TAG, "URI format are not supported: %s", uri); + return NULL; + } + + if (is_dos_drive(&localName[1], localLen - 1)) + { + localName++; + localLen--; + } + + } while (0); + + buffer = winpr_str_url_decode(localName, localLen); + if (buffer) + { + if (buffer[1] == '|' && + ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z'))) + buffer[1] = ':'; + return buffer; + } + + return NULL; +} diff --git a/winpr/libwinpr/clipboard/clipboard.h b/winpr/libwinpr/clipboard/clipboard.h new file mode 100644 index 0000000..b3dc4d0 --- /dev/null +++ b/winpr/libwinpr/clipboard/clipboard.h @@ -0,0 +1,77 @@ +/** + * WinPR: Windows Portable Runtime + * Clipboard Functions + * + * Copyright 2014 Marc-Andre Moreau + * + * 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_PRIVATE_H +#define WINPR_CLIPBOARD_PRIVATE_H + +#include +#include + +#include + +typedef struct +{ + UINT32 syntheticId; + CLIPBOARD_SYNTHESIZE_FN pfnSynthesize; +} wClipboardSynthesizer; + +typedef struct +{ + UINT32 formatId; + char* formatName; + + UINT32 numSynthesizers; + wClipboardSynthesizer* synthesizers; +} wClipboardFormat; + +struct s_wClipboard +{ + UINT64 ownerId; + + /* clipboard formats */ + + UINT32 numFormats; + UINT32 maxFormats; + UINT32 nextFormatId; + wClipboardFormat* formats; + + /* clipboard data */ + + UINT32 size; + void* data; + UINT32 formatId; + UINT32 sequenceNumber; + + /* clipboard file handling */ + + wArrayList* localFiles; + UINT32 fileListSequenceNumber; + + wClipboardDelegate delegate; + + CRITICAL_SECTION lock; +}; + +WINPR_LOCAL BOOL ClipboardInitSynthesizers(wClipboard* clipboard); + +WINPR_LOCAL char* parse_uri_to_local_file(const char* uri, size_t uri_len); + +extern const char* mime_text_plain; + +#endif /* WINPR_CLIPBOARD_PRIVATE_H */ diff --git a/winpr/libwinpr/clipboard/synthetic.c b/winpr/libwinpr/clipboard/synthetic.c new file mode 100644 index 0000000..db8af71 --- /dev/null +++ b/winpr/libwinpr/clipboard/synthetic.c @@ -0,0 +1,826 @@ +/** + * WinPR: Windows Portable Runtime + * Clipboard Functions + * + * Copyright 2014 Marc-Andre Moreau + * + * 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. + */ + +#include + +#include +#include +#include +#include + +#include "clipboard.h" + +static const char* mime_bitmap[] = { "image/bmp", "image/x-bmp", "image/x-MS-bmp", + "image/x-win-bitmap" }; + +#if defined(WINPR_UTILS_IMAGE_WEBP) +static const char mime_webp[] = "image/webp"; +#endif +#if defined(WINPR_UTILS_IMAGE_PNG) +static const char mime_png[] = "image/png"; +#endif +#if defined(WINPR_UTILS_IMAGE_JPEG) +static const char mime_jpeg[] = "image/jpeg"; +#endif +/** + * Standard Clipboard Formats: + * http://msdn.microsoft.com/en-us/library/windows/desktop/ff729168/ + */ + +/** + * "CF_TEXT": + * + * Null-terminated ANSI text with CR/LF line endings. + */ + +static void* clipboard_synthesize_cf_text(wClipboard* clipboard, UINT32 formatId, const void* data, + UINT32* pSize) +{ + size_t size = 0; + char* pDstData = NULL; + + if (formatId == CF_UNICODETEXT) + { + char* str = ConvertWCharNToUtf8Alloc(data, *pSize / sizeof(WCHAR), &size); + + if (!str) + return NULL; + + pDstData = ConvertLineEndingToCRLF(str, &size); + free(str); + *pSize = size; + return pDstData; + } + else if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) || + (formatId == ClipboardGetFormatId(clipboard, mime_text_plain))) + { + size = *pSize; + pDstData = ConvertLineEndingToCRLF(data, &size); + + if (!pDstData) + return NULL; + + *pSize = size; + return pDstData; + } + + return NULL; +} + +/** + * "CF_OEMTEXT": + * + * Null-terminated OEM text with CR/LF line endings. + */ + +static void* clipboard_synthesize_cf_oemtext(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_cf_text(clipboard, formatId, data, pSize); +} + +/** + * "CF_LOCALE": + * + * System locale identifier associated with CF_TEXT + */ + +static void* clipboard_synthesize_cf_locale(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + UINT32* pDstData = NULL; + pDstData = (UINT32*)malloc(sizeof(UINT32)); + + if (!pDstData) + return NULL; + + *pDstData = 0x0409; /* English - United States */ + return (void*)pDstData; +} + +/** + * "CF_UNICODETEXT": + * + * Null-terminated UTF-16 text with CR/LF line endings. + */ + +static void* clipboard_synthesize_cf_unicodetext(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + size_t size = 0; + char* crlfStr = NULL; + WCHAR* pDstData = NULL; + + if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) || + (formatId == ClipboardGetFormatId(clipboard, mime_text_plain))) + { + size_t len = 0; + if (!pSize || (*pSize > INT32_MAX)) + return NULL; + + size = *pSize; + crlfStr = ConvertLineEndingToCRLF((const char*)data, &size); + + if (!crlfStr) + return NULL; + + pDstData = ConvertUtf8NToWCharAlloc(crlfStr, size, &len); + free(crlfStr); + + if ((len < 1) || (len > UINT32_MAX / sizeof(WCHAR))) + { + free(pDstData); + return NULL; + } + + *pSize = (len + 1) * sizeof(WCHAR); + } + + return (void*)pDstData; +} + +/** + * mime_utf8_string: + * + * Null-terminated UTF-8 string with LF line endings. + */ + +static void* clipboard_synthesize_utf8_string(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + size_t size = 0; + char* pDstData = NULL; + + if (formatId == CF_UNICODETEXT) + { + pDstData = ConvertWCharNToUtf8Alloc(data, *pSize / sizeof(WCHAR), &size); + + if (!pDstData) + return NULL; + + size = ConvertLineEndingToLF(pDstData, size); + *pSize = (UINT32)size; + return pDstData; + } + else if ((formatId == CF_TEXT) || (formatId == CF_OEMTEXT) || + (formatId == ClipboardGetFormatId(clipboard, mime_text_plain))) + { + int rc = 0; + size = *pSize; + pDstData = (char*)malloc(size); + + if (!pDstData) + return NULL; + + CopyMemory(pDstData, data, size); + rc = ConvertLineEndingToLF(pDstData, size); + if (rc < 0) + { + free(pDstData); + return NULL; + } + *pSize = (UINT32)rc; + return pDstData; + } + + return NULL; +} + +static BOOL is_format_bitmap(wClipboard* clipboard, UINT32 formatId) +{ + for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++) + { + const char* mime = mime_bitmap[x]; + const UINT32 altFormatId = ClipboardGetFormatId(clipboard, mime); + if (altFormatId == formatId) + return TRUE; + } + + return FALSE; +} + +/** + * "CF_DIB": + * + * BITMAPINFO structure followed by the bitmap bits. + */ + +static void* clipboard_synthesize_cf_dib(wClipboard* clipboard, UINT32 formatId, const void* data, + UINT32* pSize) +{ + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + SrcSize = *pSize; + + if (formatId == CF_DIBV5) + { + } + else if (is_format_bitmap(clipboard, formatId)) + { + const BITMAPFILEHEADER* pFileHeader = NULL; + + if (SrcSize < (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER))) + return NULL; + + pFileHeader = (const BITMAPFILEHEADER*)data; + + if (pFileHeader->bfType != 0x4D42) + return NULL; + + DstSize = SrcSize - sizeof(BITMAPFILEHEADER); + pDstData = (BYTE*)malloc(DstSize); + + if (!pDstData) + return NULL; + + data = (const void*)&((const BYTE*)data)[sizeof(BITMAPFILEHEADER)]; + CopyMemory(pDstData, data, DstSize); + *pSize = DstSize; + return pDstData; + } + + return NULL; +} + +/** + * "CF_DIBV5": + * + * BITMAPV5HEADER structure followed by the bitmap color space information and the bitmap bits. + */ + +static void* clipboard_synthesize_cf_dibv5(wClipboard* clipboard, UINT32 formatId, const void* data, + UINT32* pSize) +{ + if (formatId == CF_DIB) + { + } + else if (is_format_bitmap(clipboard, formatId)) + { + } + + return NULL; +} + +static void* clipboard_prepend_bmp_header(const BITMAPINFOHEADER* pInfoHeader, const void* data, + size_t size, UINT32* pSize) +{ + WINPR_ASSERT(pInfoHeader); + WINPR_ASSERT(pSize); + + *pSize = 0; + if ((pInfoHeader->biBitCount < 1) || (pInfoHeader->biBitCount > 32)) + return NULL; + + const size_t DstSize = sizeof(BITMAPFILEHEADER) + size; + BYTE* pDstData = (BYTE*)malloc(DstSize); + + if (!pDstData) + return NULL; + + BITMAPFILEHEADER* pFileHeader = (BITMAPFILEHEADER*)pDstData; + pFileHeader->bfType = 0x4D42; + pFileHeader->bfSize = DstSize; + pFileHeader->bfReserved1 = 0; + pFileHeader->bfReserved2 = 0; + pFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + unsigned char* pDst = &pDstData[sizeof(BITMAPFILEHEADER)]; + CopyMemory(pDst, data, size); + *pSize = DstSize; + return pDstData; +} + +/** + * "image/bmp": + * + * Bitmap file format. + */ + +static void* clipboard_synthesize_image_bmp(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + UINT32 SrcSize = *pSize; + + if (formatId == CF_DIB) + { + if (SrcSize < sizeof(BITMAPINFOHEADER)) + return NULL; + + const BITMAPINFOHEADER* pInfoHeader = (const BITMAPINFOHEADER*)data; + return clipboard_prepend_bmp_header(pInfoHeader, data, SrcSize, pSize); + } + else if (formatId == CF_DIBV5) + { + } + + return NULL; +} + +static void* clipboard_synthesize_image_bmp_to_format(wClipboard* clipboard, UINT32 formatId, + UINT32 bmpFormat, const void* data, + UINT32* pSize) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(data); + WINPR_ASSERT(pSize); + + size_t dsize = 0; + void* result = NULL; + + wImage* img = winpr_image_new(); + void* bmp = clipboard_synthesize_image_bmp(clipboard, formatId, data, pSize); + const UINT32 SrcSize = *pSize; + *pSize = 0; + + if (!bmp || !img) + goto fail; + + if (winpr_image_read_buffer(img, bmp, SrcSize) <= 0) + goto fail; + + result = winpr_image_write_buffer(img, bmpFormat, &dsize); + if (result) + *pSize = dsize; + +fail: + free(bmp); + winpr_image_free(img, TRUE); + return result; +} + +#if defined(WINPR_UTILS_IMAGE_PNG) +static void* clipboard_synthesize_image_bmp_to_png(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_PNG, data, + pSize); +} + +static void* clipboard_synthesize_image_format_to_bmp(wClipboard* clipboard, UINT32 srcFormatId, + const void* data, UINT32* pSize) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(data); + WINPR_ASSERT(pSize); + + void* dst = NULL; + const UINT32 SrcSize = *pSize; + size_t size = 0; + wImage* image = winpr_image_new(); + if (!image) + goto fail; + + const int res = winpr_image_read_buffer(image, data, SrcSize); + if (res <= 0) + goto fail; + + dst = winpr_image_write_buffer(image, WINPR_IMAGE_BITMAP, &size); + if ((size < sizeof(WINPR_BITMAP_FILE_HEADER)) || (size > UINT32_MAX)) + { + free(dst); + dst = NULL; + goto fail; + } + *pSize = (UINT32)size; + +fail: + winpr_image_free(image, TRUE); + + if (dst) + memmove(dst, &dst[sizeof(WINPR_BITMAP_FILE_HEADER)], + size - sizeof(WINPR_BITMAP_FILE_HEADER)); + return dst; +} + +static void* clipboard_synthesize_image_png_to_bmp(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize); +} +#endif + +#if defined(WINPR_UTILS_IMAGE_WEBP) +static void* clipboard_synthesize_image_bmp_to_webp(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_WEBP, data, + pSize); +} + +static void* clipboard_synthesize_image_webp_to_bmp(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize); +} +#endif + +#if defined(WINPR_UTILS_IMAGE_JPEG) +static void* clipboard_synthesize_image_bmp_to_jpeg(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_bmp_to_format(clipboard, formatId, WINPR_IMAGE_JPEG, data, + pSize); +} + +static void* clipboard_synthesize_image_jpeg_to_bmp(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return clipboard_synthesize_image_format_to_bmp(clipboard, formatId, data, pSize); +} +#endif + +/** + * "HTML Format": + * + * HTML clipboard format: msdn.microsoft.com/en-us/library/windows/desktop/ms649015/ + */ + +static void* clipboard_synthesize_html_format(wClipboard* clipboard, UINT32 formatId, + const void* pData, UINT32* pSize) +{ + union + { + const void* cpv; + const char* cpc; + const BYTE* cpb; + WCHAR* pv; + } pSrcData; + char* pDstData = NULL; + + pSrcData.cpv = NULL; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(pSize); + + if (formatId == ClipboardGetFormatId(clipboard, "text/html")) + { + const INT64 SrcSize = (INT64)*pSize; + const size_t DstSize = SrcSize + 200; + char* body = NULL; + char num[20] = { 0 }; + + /* Create a copy, we modify the input data */ + pSrcData.pv = calloc(1, SrcSize + 1); + if (!pSrcData.pv) + goto fail; + memcpy(pSrcData.pv, pData, SrcSize); + + if (SrcSize > 2) + { + if (SrcSize > INT_MAX) + goto fail; + + /* Check the BOM (Byte Order Mark) */ + if ((pSrcData.cpb[0] == 0xFE) && (pSrcData.cpb[1] == 0xFF)) + ByteSwapUnicode(pSrcData.pv, (SrcSize / 2)); + + /* Check if we have WCHAR, convert to UTF-8 */ + if ((pSrcData.cpb[0] == 0xFF) && (pSrcData.cpb[1] == 0xFE)) + { + char* utfString = + ConvertWCharNToUtf8Alloc(&pSrcData.pv[1], SrcSize / sizeof(WCHAR), NULL); + free(pSrcData.pv); + pSrcData.cpc = utfString; + if (!utfString) + goto fail; + } + } + + pDstData = (char*)calloc(1, DstSize); + + if (!pDstData) + goto fail; + + sprintf_s(pDstData, DstSize, + "Version:0.9\r\n" + "StartHTML:0000000000\r\n" + "EndHTML:0000000000\r\n" + "StartFragment:0000000000\r\n" + "EndFragment:0000000000\r\n"); + body = strstr(pSrcData.cpc, "", pDstData, DstSize, NULL)) + goto fail; + } + + if (!winpr_str_append("", pDstData, DstSize, NULL)) + goto fail; + + /* StartFragment */ + sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, SrcSize + 200)); + CopyMemory(&pDstData[69], num, 10); + + if (!winpr_str_append(pSrcData.cpc, pDstData, DstSize, NULL)) + goto fail; + + /* EndFragment */ + sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, SrcSize + 200)); + CopyMemory(&pDstData[93], num, 10); + + if (!winpr_str_append("", pDstData, DstSize, NULL)) + goto fail; + + if (!body) + { + if (!winpr_str_append("", pDstData, DstSize, NULL)) + goto fail; + } + + /* EndHTML */ + sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, DstSize)); + CopyMemory(&pDstData[43], num, 10); + *pSize = (UINT32)strnlen(pDstData, DstSize) + 1; + } +fail: + free(pSrcData.pv); + return pDstData; +} + +/** + * "text/html": + * + * HTML text format. + */ + +static void* clipboard_synthesize_text_html(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + long beg = 0; + long end = 0; + const char* str = NULL; + char* begStr = NULL; + char* endStr = NULL; + long DstSize = -1; + BYTE* pDstData = NULL; + + if (formatId == ClipboardGetFormatId(clipboard, "HTML Format")) + { + INT64 SrcSize = 0; + str = (const char*)data; + SrcSize = (INT64)*pSize; + begStr = strstr(str, "StartHTML:"); + endStr = strstr(str, "EndHTML:"); + + if (!begStr || !endStr) + return NULL; + + errno = 0; + beg = strtol(&begStr[10], NULL, 10); + + if (errno != 0) + return NULL; + + end = strtol(&endStr[8], NULL, 10); + + if (beg < 0 || end < 0 || (beg > SrcSize) || (end > SrcSize) || (beg >= end) || + (errno != 0)) + return NULL; + + DstSize = end - beg; + pDstData = (BYTE*)malloc((size_t)(SrcSize - beg + 1)); + + if (!pDstData) + return NULL; + + CopyMemory(pDstData, &str[beg], DstSize); + DstSize = ConvertLineEndingToLF((char*)pDstData, DstSize); + *pSize = (UINT32)DstSize; + } + + return (void*)pDstData; +} + +BOOL ClipboardInitSynthesizers(wClipboard* clipboard) +{ + /** + * CF_TEXT + */ + { + ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_OEMTEXT, + clipboard_synthesize_cf_oemtext); + ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_UNICODETEXT, + clipboard_synthesize_cf_unicodetext); + ClipboardRegisterSynthesizer(clipboard, CF_TEXT, CF_LOCALE, clipboard_synthesize_cf_locale); + + UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain); + ClipboardRegisterSynthesizer(clipboard, CF_TEXT, altFormatId, + clipboard_synthesize_utf8_string); + } + /** + * CF_OEMTEXT + */ + { + ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_TEXT, clipboard_synthesize_cf_text); + ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_UNICODETEXT, + clipboard_synthesize_cf_unicodetext); + ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, CF_LOCALE, + clipboard_synthesize_cf_locale); + UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain); + ClipboardRegisterSynthesizer(clipboard, CF_OEMTEXT, altFormatId, + clipboard_synthesize_utf8_string); + } + /** + * CF_UNICODETEXT + */ + { + ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_TEXT, + clipboard_synthesize_cf_text); + ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_OEMTEXT, + clipboard_synthesize_cf_oemtext); + ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, CF_LOCALE, + clipboard_synthesize_cf_locale); + UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_text_plain); + ClipboardRegisterSynthesizer(clipboard, CF_UNICODETEXT, altFormatId, + clipboard_synthesize_utf8_string); + } + /** + * UTF8_STRING + */ + { + UINT32 formatId = ClipboardRegisterFormat(clipboard, mime_text_plain); + + if (formatId) + { + ClipboardRegisterSynthesizer(clipboard, formatId, CF_TEXT, + clipboard_synthesize_cf_text); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_OEMTEXT, + clipboard_synthesize_cf_oemtext); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_UNICODETEXT, + clipboard_synthesize_cf_unicodetext); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_LOCALE, + clipboard_synthesize_cf_locale); + } + } + /** + * text/plain + */ + { + UINT32 formatId = ClipboardRegisterFormat(clipboard, mime_text_plain); + + if (formatId) + { + ClipboardRegisterSynthesizer(clipboard, formatId, CF_TEXT, + clipboard_synthesize_cf_text); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_OEMTEXT, + clipboard_synthesize_cf_oemtext); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_UNICODETEXT, + clipboard_synthesize_cf_unicodetext); + ClipboardRegisterSynthesizer(clipboard, formatId, CF_LOCALE, + clipboard_synthesize_cf_locale); + } + } + /** + * CF_DIB + */ + { + ClipboardRegisterSynthesizer(clipboard, CF_DIB, CF_DIBV5, clipboard_synthesize_cf_dibv5); + for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++) + { + const char* mime = mime_bitmap[x]; + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime); + if (altFormatId == 0) + continue; + ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId, + clipboard_synthesize_image_bmp); + } + } + + /** + * CF_DIBV5 + */ + + if (0) + { + ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, CF_DIB, clipboard_synthesize_cf_dib); + + for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++) + { + const char* mime = mime_bitmap[x]; + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime); + if (altFormatId == 0) + continue; + ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId, + clipboard_synthesize_image_bmp); + } + } + + /** + * image/bmp + */ + for (size_t x = 0; x < ARRAYSIZE(mime_bitmap); x++) + { + const char* mime = mime_bitmap[x]; + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime); + if (altFormatId == 0) + continue; + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB, clipboard_synthesize_cf_dib); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5, + clipboard_synthesize_cf_dibv5); + } + + /** + * image/png + */ +#if defined(WINPR_UTILS_IMAGE_PNG) + { + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_png); + ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId, + clipboard_synthesize_image_bmp_to_png); + ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId, + clipboard_synthesize_image_bmp_to_png); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB, + clipboard_synthesize_image_png_to_bmp); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5, + clipboard_synthesize_image_png_to_bmp); + } +#endif + + /** + * image/webp + */ +#if defined(WINPR_UTILS_IMAGE_WEBP) + { + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_webp); + ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId, + clipboard_synthesize_image_bmp_to_webp); + ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId, + clipboard_synthesize_image_webp_to_bmp); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB, + clipboard_synthesize_image_bmp_to_webp); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5, + clipboard_synthesize_image_webp_to_bmp); + } +#endif + + /** + * image/jpeg + */ +#if defined(WINPR_UTILS_IMAGE_JPEG) + { + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, mime_jpeg); + ClipboardRegisterSynthesizer(clipboard, CF_DIB, altFormatId, + clipboard_synthesize_image_bmp_to_jpeg); + ClipboardRegisterSynthesizer(clipboard, CF_DIBV5, altFormatId, + clipboard_synthesize_image_jpeg_to_bmp); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIB, + clipboard_synthesize_image_bmp_to_jpeg); + ClipboardRegisterSynthesizer(clipboard, altFormatId, CF_DIBV5, + clipboard_synthesize_image_jpeg_to_bmp); + } +#endif + + /** + * HTML Format + */ + { + UINT32 formatId = ClipboardRegisterFormat(clipboard, "HTML Format"); + + if (formatId) + { + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, "text/html"); + ClipboardRegisterSynthesizer(clipboard, formatId, altFormatId, + clipboard_synthesize_text_html); + } + } + + /** + * text/html + */ + { + UINT32 formatId = ClipboardRegisterFormat(clipboard, "text/html"); + + if (formatId) + { + const UINT32 altFormatId = ClipboardRegisterFormat(clipboard, "HTML Format"); + ClipboardRegisterSynthesizer(clipboard, formatId, altFormatId, + clipboard_synthesize_html_format); + } + } + + return TRUE; +} diff --git a/winpr/libwinpr/clipboard/synthetic_file.c b/winpr/libwinpr/clipboard/synthetic_file.c new file mode 100644 index 0000000..1421980 --- /dev/null +++ b/winpr/libwinpr/clipboard/synthetic_file.c @@ -0,0 +1,1262 @@ +/** + * WinPR: Windows Portable Runtime + * Clipboard Functions: POSIX file handling + * + * Copyright 2017 Alexei Lozovsky + * + * 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. + */ + +#include +#include + +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO + +#define _FILE_OFFSET_BITS 64 + +WINPR_PRAGMA_DIAG_POP + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clipboard.h" +#include "synthetic_file.h" + +#include "../log.h" +#define TAG WINPR_TAG("clipboard.synthetic.file") + +static const char* mime_uri_list = "text/uri-list"; +static const char* mime_FileGroupDescriptorW = "FileGroupDescriptorW"; +static const char* mime_gnome_copied_files = "x-special/gnome-copied-files"; +static const char* mime_mate_copied_files = "x-special/mate-copied-files"; + +struct synthetic_file +{ + WCHAR* local_name; + WCHAR* remote_name; + + HANDLE fd; + INT64 offset; + + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +}; + +void free_synthetic_file(struct synthetic_file* file); + +static struct synthetic_file* make_synthetic_file(const WCHAR* local_name, const WCHAR* remote_name) +{ + struct synthetic_file* file = NULL; + WIN32_FIND_DATAW fd = { 0 }; + HANDLE hFind = NULL; + + WINPR_ASSERT(local_name); + WINPR_ASSERT(remote_name); + + hFind = FindFirstFileW(local_name, &fd); + if (INVALID_HANDLE_VALUE == hFind) + { + WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError()); + return NULL; + } + FindClose(hFind); + + file = calloc(1, sizeof(*file)); + if (!file) + return NULL; + + file->fd = INVALID_HANDLE_VALUE; + file->offset = 0; + file->local_name = _wcsdup(local_name); + if (!file->local_name) + goto fail; + + file->remote_name = _wcsdup(remote_name); + if (!file->remote_name) + goto fail; + + const size_t len = _wcslen(file->remote_name); + PathCchConvertStyleW(file->remote_name, len, PATH_STYLE_WINDOWS); + + file->dwFileAttributes = fd.dwFileAttributes; + file->ftCreationTime = fd.ftCreationTime; + file->ftLastWriteTime = fd.ftLastWriteTime; + file->ftLastAccessTime = fd.ftLastAccessTime; + file->nFileSizeHigh = fd.nFileSizeHigh; + file->nFileSizeLow = fd.nFileSizeLow; + + return file; +fail: + free_synthetic_file(file); + return NULL; +} + +static UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force); + +void free_synthetic_file(struct synthetic_file* file) +{ + if (!file) + return; + + synthetic_file_read_close(file, TRUE); + + free(file->local_name); + free(file->remote_name); + free(file); +} + +/* + * 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(wClipboard* clipboard, const WCHAR* local_name) +{ + wClipboardDelegate* delegate = ClipboardGetDelegate(clipboard); + WCHAR* remote_name = NULL; + + WINPR_ASSERT(delegate); + + remote_name = _wcsdup(local_name); + + /* + * 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. + * + * The IsFileNameComponentValid callback can be overridden by the API + * user, if it is known, that the connected peer is not on the + * Windows platform. + */ + if (!delegate->IsFileNameComponentValid(remote_name)) + { + WLog_ERR(TAG, "invalid file name component: %s", local_name); + goto error; + } + + return remote_name; +error: + free(remote_name); + return NULL; +} + +static WCHAR* concat_file_name(const WCHAR* dir, const WCHAR* file) +{ + size_t len_dir = 0; + size_t len_file = 0; + const WCHAR slash = '/'; + WCHAR* buffer = NULL; + + WINPR_ASSERT(dir); + WINPR_ASSERT(file); + + len_dir = _wcslen(dir); + len_file = _wcslen(file); + buffer = calloc(len_dir + 1 + len_file + 2, sizeof(WCHAR)); + + if (!buffer) + return NULL; + + memcpy(buffer, dir, len_dir * sizeof(WCHAR)); + buffer[len_dir] = slash; + memcpy(buffer + len_dir + 1, file, len_file * sizeof(WCHAR)); + return buffer; +} + +static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name, + const WCHAR* remote_name, wArrayList* files); + +static BOOL add_directory_entry_to_list(wClipboard* clipboard, const WCHAR* local_dir_name, + const WCHAR* remote_dir_name, + const LPWIN32_FIND_DATAW pFileData, wArrayList* files) +{ + BOOL result = FALSE; + WCHAR* local_name = NULL; + WCHAR* remote_name = NULL; + WCHAR* remote_base_name = NULL; + + WCHAR dotbuffer[6] = { 0 }; + WCHAR dotdotbuffer[6] = { 0 }; + const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer)); + const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer)); + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(local_dir_name); + WINPR_ASSERT(remote_dir_name); + WINPR_ASSERT(pFileData); + WINPR_ASSERT(files); + + /* Skip special directory entries. */ + + if ((_wcscmp(pFileData->cFileName, dot) == 0) || (_wcscmp(pFileData->cFileName, dotdot) == 0)) + return TRUE; + + remote_base_name = convert_local_name_component_to_remote(clipboard, pFileData->cFileName); + + if (!remote_base_name) + return FALSE; + + local_name = concat_file_name(local_dir_name, pFileData->cFileName); + remote_name = concat_file_name(remote_dir_name, remote_base_name); + + if (local_name && remote_name) + result = add_file_to_list(clipboard, 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(wClipboard* clipboard, const WCHAR* local_name, + const WCHAR* remote_name, WCHAR* namebuf, + wArrayList* files) +{ + WINPR_ASSERT(clipboard); + WINPR_ASSERT(local_name); + WINPR_ASSERT(remote_name); + WINPR_ASSERT(files); + WINPR_ASSERT(namebuf); + + WIN32_FIND_DATAW FindData = { 0 }; + HANDLE hFind = FindFirstFileW(namebuf, &FindData); + if (INVALID_HANDLE_VALUE == hFind) + { + WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError()); + return FALSE; + } + while (TRUE) + { + if (!add_directory_entry_to_list(clipboard, local_name, remote_name, &FindData, files)) + { + FindClose(hFind); + return FALSE; + } + + BOOL bRet = FindNextFileW(hFind, &FindData); + if (!bRet) + { + FindClose(hFind); + if (ERROR_NO_MORE_FILES == GetLastError()) + return TRUE; + WLog_WARN(TAG, "FindNextFile failed (%" PRIu32 ")", GetLastError()); + return FALSE; + } + } + + return TRUE; +} + +static BOOL add_directory_contents_to_list(wClipboard* clipboard, const WCHAR* local_name, + const WCHAR* remote_name, wArrayList* files) +{ + BOOL result = FALSE; + const WCHAR* wildcard = "/\0*\0\0\0"; + const size_t wildcardLen = 3; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(local_name); + WINPR_ASSERT(remote_name); + WINPR_ASSERT(files); + + size_t len = _wcslen(local_name); + WCHAR* namebuf = calloc(len + wildcardLen, sizeof(WCHAR)); + if (!namebuf) + return FALSE; + + _wcsncat(namebuf, local_name, len); + _wcsncat(namebuf, wildcard, wildcardLen); + + result = do_add_directory_contents_to_list(clipboard, local_name, remote_name, namebuf, files); + + free(namebuf); + return result; +} + +static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name, + const WCHAR* remote_name, wArrayList* files) +{ + struct synthetic_file* file = NULL; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(local_name); + WINPR_ASSERT(remote_name); + WINPR_ASSERT(files); + + file = make_synthetic_file(local_name, remote_name); + + if (!file) + return FALSE; + + if (!ArrayList_Append(files, file)) + { + free_synthetic_file(file); + return FALSE; + } + + if (file->dwFileAttributes & FILE_ATTRIBUTE_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(clipboard, local_name, remote_name, files)) + return FALSE; + } + + return TRUE; +} + +static const WCHAR* get_basename(const WCHAR* name) +{ + const WCHAR* c = name; + const WCHAR* last_name = name; + const WCHAR slash = '/'; + + WINPR_ASSERT(name); + + while (*c++) + { + if (*c == slash) + last_name = c + 1; + } + + return last_name; +} + +static BOOL process_file_name(wClipboard* clipboard, const WCHAR* local_name, wArrayList* files) +{ + BOOL result = FALSE; + const WCHAR* base_name = NULL; + WCHAR* remote_name = NULL; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(local_name); + WINPR_ASSERT(files); + + /* + * 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 = get_basename(local_name); + remote_name = convert_local_name_component_to_remote(clipboard, base_name); + + if (!remote_name) + return FALSE; + + result = add_file_to_list(clipboard, local_name, remote_name, files); + free(remote_name); + return result; +} + +static BOOL process_uri(wClipboard* clipboard, const char* uri, size_t uri_len) +{ + // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089 + BOOL result = FALSE; + char* name = NULL; + + WINPR_ASSERT(clipboard); + + name = parse_uri_to_local_file(uri, uri_len); + if (name) + { + WCHAR* wname = 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. + */ + wname = ConvertUtf8ToWCharAlloc(name, NULL); + if (wname) + result = process_file_name(clipboard, wname, clipboard->localFiles); + + free(name); + free(wname); + } + + return result; +} + +static BOOL process_uri_list(wClipboard* clipboard, const char* data, size_t length) +{ + const char* cur = data; + const char* lim = data + length; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(data); + + WLog_VRB(TAG, "processing URI list:\n%.*s", length, data); + ArrayList_Clear(clipboard->localFiles); + + /* + * 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 == '#'); + const char* start = cur; + const char* stop = cur; + + for (; 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) + { + if (strnlen(start, stop - start) < 1) + return TRUE; + cur = lim; + } + + if (comment) + continue; + + if (!process_uri(clipboard, start, stop - start)) + return FALSE; + } + + return TRUE; +} + +static BOOL convert_local_file_to_filedescriptor(const struct synthetic_file* file, + FILEDESCRIPTORW* descriptor) +{ + size_t remote_len = 0; + + WINPR_ASSERT(file); + WINPR_ASSERT(descriptor); + + descriptor->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI; + + if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + descriptor->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; + descriptor->nFileSizeLow = 0; + descriptor->nFileSizeHigh = 0; + } + else + { + descriptor->dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + descriptor->nFileSizeLow = file->nFileSizeLow; + descriptor->nFileSizeHigh = file->nFileSizeHigh; + } + + descriptor->ftLastWriteTime = file->ftLastWriteTime; + + remote_len = _wcsnlen(file->remote_name, ARRAYSIZE(descriptor->cFileName)); + + if (remote_len >= 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 FILEDESCRIPTORW* convert_local_file_list_to_filedescriptors(wArrayList* files) +{ + size_t count = 0; + FILEDESCRIPTORW* descriptors = NULL; + + count = ArrayList_Count(files); + + descriptors = calloc(count, sizeof(FILEDESCRIPTORW)); + + if (!descriptors) + goto error; + + for (size_t i = 0; i < count; i++) + { + const struct synthetic_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_any_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId, + UINT32* pSize) +{ + FILEDESCRIPTORW* descriptors = NULL; + + WINPR_ASSERT(clipboard); + WINPR_ASSERT(pSize); + + descriptors = convert_local_file_list_to_filedescriptors(clipboard->localFiles); + *pSize = 0; + if (!descriptors) + return NULL; + + *pSize = (UINT32)ArrayList_Count(clipboard->localFiles) * sizeof(FILEDESCRIPTORW); + clipboard->fileListSequenceNumber = clipboard->sequenceNumber; + return descriptors; +} + +static void* convert_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + const UINT32 expected = ClipboardGetFormatId(clipboard, mime_uri_list); + if (formatId != expected) + return NULL; + if (!process_uri_list(clipboard, (const char*)data, *pSize)) + return NULL; + return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize); +} + +static BOOL process_files(wClipboard* clipboard, const char* data, UINT32 pSize, const char* prefix) +{ + WINPR_ASSERT(prefix); + + const size_t prefix_len = strlen(prefix); + + WINPR_ASSERT(clipboard); + + ArrayList_Clear(clipboard->localFiles); + + if (!data || (pSize < prefix_len)) + return FALSE; + if (strncmp(data, prefix, prefix_len) != 0) + return FALSE; + data += prefix_len; + pSize -= prefix_len; + + BOOL rc = FALSE; + char* copy = strndup(data, pSize); + if (!copy) + goto fail; + + char* endptr = NULL; + char* tok = strtok_s(copy, "\n", &endptr); + while (tok) + { + size_t tok_len = strnlen(tok, pSize); + if (!process_uri(clipboard, tok, tok_len)) + goto fail; + pSize -= tok_len; + tok = strtok_s(NULL, "\n", &endptr); + } + rc = TRUE; + +fail: + free(copy); + return rc; +} + +static BOOL process_gnome_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize) +{ + return process_files(clipboard, data, pSize, "copy\n"); +} + +static BOOL process_mate_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize) +{ + return process_files(clipboard, data, pSize, "copy\n"); +} + +static BOOL process_nautilus_clipboard(wClipboard* clipboard, const char* data, UINT32 pSize) +{ + return process_files(clipboard, data, pSize, "x-special/nautilus-clipboard\ncopy\n"); +} + +static void* convert_nautilus_clipboard_to_filedescriptors(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files); + if (formatId != expected) + return NULL; + if (!process_nautilus_clipboard(clipboard, (const char*)data, *pSize)) + return NULL; + return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize); +} + +static void* convert_gnome_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files); + if (formatId != expected) + return NULL; + if (!process_gnome_copied_files(clipboard, (const char*)data, *pSize)) + return NULL; + return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize); +} + +static void* convert_mate_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + const UINT32 expected = ClipboardGetFormatId(clipboard, mime_mate_copied_files); + if (formatId != expected) + return NULL; + + if (!process_mate_copied_files(clipboard, (const char*)data, *pSize)) + return NULL; + + return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize); +} + +static size_t count_special_chars(const WCHAR* str) +{ + size_t count = 0; + const WCHAR* start = str; + + WINPR_ASSERT(str); + while (*start) + { + const WCHAR sharp = '#'; + const WCHAR questionmark = '?'; + const WCHAR star = '*'; + const WCHAR exclamationmark = '!'; + const WCHAR percent = '%'; + + if ((*start == sharp) || (*start == questionmark) || (*start == star) || + (*start == exclamationmark) || (*start == percent)) + { + count++; + } + start++; + } + return count; +} + +static const char* stop_at_special_chars(const char* str) +{ + const char* start = str; + WINPR_ASSERT(str); + + while (*start) + { + if (*start == '#' || *start == '?' || *start == '*' || *start == '!' || *start == '%') + { + return start; + } + start++; + } + return NULL; +} + +/* The universal converter from filedescriptors to different file lists */ +static void* convert_filedescriptors_to_file_list(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize, + const char* header, const char* lineprefix, + const char* lineending, BOOL skip_last_lineending) +{ + union + { + char c[2]; + WCHAR w; + } backslash; + backslash.c[0] = '\\'; + backslash.c[1] = '\0'; + + const FILEDESCRIPTORW* descriptors = NULL; + UINT32 nrDescriptors = 0; + size_t count = 0; + size_t alloc = 0; + size_t pos = 0; + size_t baseLength = 0; + char* dst = NULL; + size_t header_len = strlen(header); + size_t lineprefix_len = strlen(lineprefix); + size_t lineending_len = strlen(lineending); + size_t decoration_len = 0; + + if (!clipboard || !data || !pSize) + return NULL; + + if (*pSize < sizeof(UINT32)) + return NULL; + + if (clipboard->delegate.basePath) + baseLength = strnlen(clipboard->delegate.basePath, MAX_PATH); + + if (baseLength < 1) + return NULL; + + wStream sbuffer = { 0 }; + wStream* s = Stream_StaticConstInit(&sbuffer, data, *pSize); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return NULL; + + Stream_Read_UINT32(s, nrDescriptors); + + count = (*pSize - 4) / sizeof(FILEDESCRIPTORW); + + if ((count < 1) || (count != nrDescriptors)) + return NULL; + + descriptors = Stream_ConstPointer(s); + + if (formatId != ClipboardGetFormatId(clipboard, mime_FileGroupDescriptorW)) + return NULL; + + /* Plus 1 for '/' between basepath and filename*/ + decoration_len = lineprefix_len + lineending_len + baseLength + 1; + alloc = header_len; + + /* Get total size of file/folder names under first level folder only */ + for (size_t x = 0; x < count; x++) + { + const FILEDESCRIPTORW* dsc = &descriptors[x]; + + if (_wcschr(dsc->cFileName, backslash.w) == NULL) + { + alloc += ARRAYSIZE(dsc->cFileName) * + 8; /* Overallocate, just take the biggest value the result path can have */ + /* # (1 char) -> %23 (3 chars) , the first char is replaced inplace */ + alloc += count_special_chars(dsc->cFileName) * 2; + alloc += decoration_len; + } + } + + /* Append a prefix file:// and postfix \n for each file */ + /* We need to keep last \n since snprintf is null terminated!! */ + alloc++; + dst = calloc(alloc, sizeof(char)); + + if (!dst) + return NULL; + + _snprintf(&dst[0], alloc, "%s", header); + + pos = header_len; + + for (size_t x = 0; x < count; x++) + { + const FILEDESCRIPTORW* dsc = &descriptors[x]; + BOOL fail = TRUE; + if (_wcschr(dsc->cFileName, backslash.w) != NULL) + { + continue; + } + int rc = -1; + char curName[520] = { 0 }; + const char* stop_at = NULL; + const char* previous_at = NULL; + + if (ConvertWCharNToUtf8(dsc->cFileName, ARRAYSIZE(dsc->cFileName), curName, + ARRAYSIZE(curName)) < 0) + goto loop_fail; + + rc = _snprintf(&dst[pos], alloc - pos, "%s%s/", lineprefix, clipboard->delegate.basePath); + + if (rc < 0) + goto loop_fail; + + pos += (size_t)rc; + + previous_at = curName; + while ((stop_at = stop_at_special_chars(previous_at)) != NULL) + { + char* tmp = strndup(previous_at, stop_at - previous_at); + if (!tmp) + goto loop_fail; + + rc = _snprintf(&dst[pos], stop_at - previous_at + 1, "%s", tmp); + free(tmp); + if (rc < 0) + goto loop_fail; + + pos += (size_t)rc; + rc = _snprintf(&dst[pos], 4, "%%%x", *stop_at); + if (rc < 0) + goto loop_fail; + + pos += (size_t)rc; + previous_at = stop_at + 1; + } + + rc = _snprintf(&dst[pos], alloc - pos, "%s%s", previous_at, lineending); + + fail = FALSE; + loop_fail: + if ((rc < 0) || fail) + { + free(dst); + return NULL; + } + + pos += (size_t)rc; + } + + if (skip_last_lineending) + { + const size_t endlen = strlen(lineending); + if (alloc > endlen) + { + const size_t len = strnlen(dst, alloc); + if (len < endlen) + { + free(dst); + return NULL; + } + + if (memcmp(&dst[len - endlen], lineending, endlen) == 0) + { + memset(&dst[len - endlen], 0, endlen); + alloc -= endlen; + } + } + } + + alloc = strnlen(dst, alloc) + 1; + *pSize = (UINT32)alloc; + clipboard->fileListSequenceNumber = clipboard->sequenceNumber; + return dst; +} + +/* Prepend header of kde dolphin format to file list + * See: + * GTK: https://docs.gtk.org/glib/struct.Uri.html + * uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3 + * uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5 + */ +static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "", "file://", + "\r\n", FALSE); +} + +/* Prepend header of common gnome format to file list*/ +static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "copy\n", + "file://", "\n", TRUE); +} + +/* Prepend header of nautilus based filemanager's format to file list*/ +static void* convert_filedescriptors_to_nautilus_clipboard(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + /* Here Nemo (and Caja) have different behavior. They encounter error with the last \n . but + nautilus needs it. So user have to skip Nemo's error dialog to continue. Caja has different + TARGET , so it's easy to fix. see convert_filedescriptors_to_mate_copied_files + + The text based "x-special/nautilus-clipboard" type was introduced with GNOME 3.30 and + was necessary for the desktop icons extension, as gnome-shell at that time only + supported text based mime types for gnome extensions. With GNOME 3.38, gnome-shell got + support for non-text based mime types for gnome extensions. With GNOME 40, nautilus reverted + the mime type change to "x-special/gnome-copied-files" and removed support for the text based + mime type. So, in the near future, change this behaviour in favor for Nemo and Caja. + */ + /* see nautilus/src/nautilus-clipboard.c:convert_selection_data_to_str_list + see nemo/libnemo-private/nemo-clipboard.c:nemo_clipboard_get_uri_list_from_selection_data + */ + + return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, + "x-special/nautilus-clipboard\ncopy\n", "file://", + "\n", FALSE); +} + +static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId, + const void* data, UINT32* pSize) +{ + + char* pDstData = convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, + "copy\n", "file://", "\n", TRUE); + if (!pDstData) + { + return pDstData; + } + /* Replace last \n with \0 + see + mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data + */ + + pDstData[*pSize - 1] = '\0'; + *pSize = *pSize - 1; + return pDstData; +} + +static void array_free_synthetic_file(void* the_file) +{ + struct synthetic_file* file = the_file; + free_synthetic_file(file); +} + +static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard) +{ + wObject* obj = NULL; + + /* + 1. Gnome Nautilus based file manager (Nautilus only with version >= 3.30 AND < 40): + TARGET: UTF8_STRING + format: x-special/nautilus-clipboard\copy\n\file://path\n\0 + 2. Kde Dolpin and Qt: + TARGET: text/uri-list + format: file:path\r\n\0 + See: + GTK: https://docs.gtk.org/glib/struct.Uri.html + uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3 + uri-lists fomat: https://www.rfc-editor.org/rfc/rfc2483#section-5 + 3. Gnome and others (Unity/XFCE/Nautilus < 3.30/Nautilus >= 40): + TARGET: x-special/gnome-copied-files + format: copy\nfile://path\n\0 + 4. Mate Caja: + TARGET: x-special/mate-copied-files + format: copy\nfile://path\n + + TODO: other file managers do not use previous targets and formats. + */ + + const UINT32 local_gnome_file_format_id = + ClipboardRegisterFormat(clipboard, mime_gnome_copied_files); + const UINT32 local_mate_file_format_id = + ClipboardRegisterFormat(clipboard, mime_mate_copied_files); + const UINT32 file_group_format_id = + ClipboardRegisterFormat(clipboard, mime_FileGroupDescriptorW); + const UINT32 local_file_format_id = ClipboardRegisterFormat(clipboard, mime_uri_list); + + if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id || + !local_mate_file_format_id) + goto error; + + clipboard->localFiles = ArrayList_New(FALSE); + + if (!clipboard->localFiles) + goto error; + + obj = ArrayList_Object(clipboard->localFiles); + obj->fnObjectFree = array_free_synthetic_file; + + if (!ClipboardRegisterSynthesizer(clipboard, local_file_format_id, file_group_format_id, + convert_uri_list_to_filedescriptors)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_file_format_id, + convert_filedescriptors_to_uri_list)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, local_gnome_file_format_id, file_group_format_id, + convert_gnome_copied_files_to_filedescriptors)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id, + convert_filedescriptors_to_gnome_copied_files)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, local_mate_file_format_id, file_group_format_id, + convert_mate_copied_files_to_filedescriptors)) + goto error_free_local_files; + + if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id, + convert_filedescriptors_to_mate_copied_files)) + goto error_free_local_files; + + return TRUE; +error_free_local_files: + ArrayList_Free(clipboard->localFiles); + clipboard->localFiles = NULL; +error: + return FALSE; +} + +static UINT file_get_size(const struct synthetic_file* file, UINT64* size) +{ + UINT64 s = 0; + + if (!file || !size) + return E_INVALIDARG; + + s = file->nFileSizeHigh; + s <<= 32; + s |= file->nFileSizeLow; + *size = s; + return NO_ERROR; +} + +static UINT delegate_file_request_size(wClipboardDelegate* delegate, + const wClipboardFileSizeRequest* request) +{ + UINT error = NO_ERROR; + UINT64 size = 0; + struct synthetic_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 = 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; +} + +UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force) +{ + if (!file || INVALID_HANDLE_VALUE == file->fd) + return NO_ERROR; + + /* Always force close the file. Clipboard might open hundreds of files + * so avoid caching to prevent running out of available file descriptors */ + UINT64 size = 0; + file_get_size(file, &size); + if ((file->offset < 0) || ((UINT64)file->offset >= size) || force) + { + WLog_VRB(TAG, "close file %d", file->fd); + if (!CloseHandle(file->fd)) + { + WLog_WARN(TAG, "failed to close fd %d: %" PRIu32, file->fd, GetLastError()); + } + + file->fd = INVALID_HANDLE_VALUE; + } + + return NO_ERROR; +} + +static UINT file_get_range(struct synthetic_file* file, UINT64 offset, UINT32 size, + BYTE** actual_data, UINT32* actual_size) +{ + UINT error = NO_ERROR; + DWORD dwLow = 0; + DWORD dwHigh = 0; + BYTE* buffer = NULL; + + WINPR_ASSERT(file); + WINPR_ASSERT(actual_data); + WINPR_ASSERT(actual_size); + + if (INVALID_HANDLE_VALUE == file->fd) + { + BY_HANDLE_FILE_INFORMATION FileInfo = { 0 }; + + file->fd = CreateFileW(file->local_name, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE == file->fd) + { + error = GetLastError(); + WLog_ERR(TAG, "failed to open file %s: 0x%08" PRIx32, file->local_name, error); + return error; + } + + if (!GetFileInformationByHandle(file->fd, &FileInfo)) + { + file->fd = INVALID_HANDLE_VALUE; + CloseHandle(file->fd); + error = GetLastError(); + WLog_ERR(TAG, "Get file [%s] information fail: 0x%08" PRIx32, file->local_name, error); + return error; + } + + file->offset = 0; + file->nFileSizeHigh = FileInfo.nFileSizeHigh; + file->nFileSizeLow = FileInfo.nFileSizeLow; + + /* + { + UINT64 s = 0; + file_get_size(file, &s); + WLog_DBG(TAG, "open file %d -> %s", file->fd, file->local_name); + WLog_DBG(TAG, "file %d size: %" PRIu64 " bytes", file->fd, s); + } //*/ + } + + do + { + /* + * 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 (offset > INT64_MAX) + { + WLog_ERR(TAG, "offset [%" PRIu64 "] > INT64_MAX", offset); + error = ERROR_SEEK; + break; + } + + if (file->offset != (INT64)offset) + { + WLog_DBG(TAG, "file %d force seeking to %" PRIu64 ", current %" PRIu64, file->fd, + offset, file->offset); + + dwHigh = offset >> 32; + dwLow = offset & 0xFFFFFFFF; + if (INVALID_SET_FILE_POINTER == + SetFilePointer(file->fd, dwLow, (PLONG)&dwHigh, FILE_BEGIN)) + { + error = GetLastError(); + break; + } + } + + buffer = malloc(size); + if (!buffer) + { + error = ERROR_NOT_ENOUGH_MEMORY; + break; + } + if (!ReadFile(file->fd, buffer, size, (LPDWORD)actual_size, NULL)) + { + error = GetLastError(); + break; + } + + *actual_data = buffer; + file->offset += *actual_size; + WLog_VRB(TAG, "file %d actual read %" PRIu32 " bytes (offset %" PRIu64 ")", file->fd, + *actual_size, file->offset); + } while (0); + + if (NO_ERROR != error) + { + free(buffer); + } + synthetic_file_read_close(file, TRUE /* (error != NO_ERROR) && (size > 0) */); + return error; +} + +static UINT delegate_file_request_range(wClipboardDelegate* delegate, + const wClipboardFileRangeRequest* request) +{ + UINT error = 0; + BYTE* data = NULL; + UINT32 size = 0; + UINT64 offset = 0; + struct synthetic_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 = 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) +{ + WINPR_ASSERT(delegate); + + delegate->ClientRequestFileSize = delegate_file_request_size; + delegate->ClipboardFileSizeSuccess = dummy_file_size_success; + delegate->ClipboardFileSizeFailure = dummy_file_size_failure; + delegate->ClientRequestFileRange = delegate_file_request_range; + delegate->ClipboardFileRangeSuccess = dummy_file_range_success; + delegate->ClipboardFileRangeFailure = dummy_file_range_failure; + delegate->IsFileNameComponentValid = ValidFileNameComponent; +} + +BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard) +{ + if (!clipboard) + return FALSE; + + if (!register_file_formats_and_synthesizers(clipboard)) + return FALSE; + + setup_delegate(&clipboard->delegate); + return TRUE; +} diff --git a/winpr/libwinpr/clipboard/synthetic_file.h b/winpr/libwinpr/clipboard/synthetic_file.h new file mode 100644 index 0000000..a92a5da --- /dev/null +++ b/winpr/libwinpr/clipboard/synthetic_file.h @@ -0,0 +1,27 @@ +/** + * WinPR: Windows Portable Runtime + * Clipboard Functions: POSIX file handling + * + * Copyright 2017 Alexei Lozovsky + * + * 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 + +BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard); + +#endif /* WINPR_CLIPBOARD_POSIX_H */ diff --git a/winpr/libwinpr/clipboard/test/CMakeLists.txt b/winpr/libwinpr/clipboard/test/CMakeLists.txt new file mode 100644 index 0000000..90996c0 --- /dev/null +++ b/winpr/libwinpr/clipboard/test/CMakeLists.txt @@ -0,0 +1,27 @@ + +set(MODULE_NAME "TestClipboard") +set(MODULE_PREFIX "TEST_CLIPBOARD") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS + TestUri.c + TestClipboardFormats.c) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS + ${${MODULE_PREFIX}_DRIVER} + ${${MODULE_PREFIX}_TESTS}) + +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) +target_link_libraries(${MODULE_NAME} winpr) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY + "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test") + diff --git a/winpr/libwinpr/clipboard/test/TestClipboardFormats.c b/winpr/libwinpr/clipboard/test/TestClipboardFormats.c new file mode 100644 index 0000000..615f8a0 --- /dev/null +++ b/winpr/libwinpr/clipboard/test/TestClipboardFormats.c @@ -0,0 +1,82 @@ + +#include +#include +#include + +int TestClipboardFormats(int argc, char* argv[]) +{ + UINT32 count = 0; + UINT32* pFormatIds = NULL; + const char* formatName = NULL; + wClipboard* clipboard = NULL; + UINT32 utf8StringFormatId = 0; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + clipboard = ClipboardCreate(); + if (!clipboard) + return -1; + + ClipboardRegisterFormat(clipboard, "text/html"); + ClipboardRegisterFormat(clipboard, "image/bmp"); + ClipboardRegisterFormat(clipboard, "image/png"); + + utf8StringFormatId = ClipboardRegisterFormat(clipboard, "UTF8_STRING"); + pFormatIds = NULL; + count = ClipboardGetRegisteredFormatIds(clipboard, &pFormatIds); + + for (UINT32 index = 0; index < count; index++) + { + UINT32 formatId = pFormatIds[index]; + formatName = ClipboardGetFormatName(clipboard, formatId); + fprintf(stderr, "Format: 0x%08" PRIX32 " %s\n", formatId, formatName); + } + + free(pFormatIds); + + if (1) + { + BOOL bSuccess = 0; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + const char pSrcData[] = "this is a test string"; + char* pDstData = NULL; + + SrcSize = (UINT32)(strnlen(pSrcData, ARRAYSIZE(pSrcData)) + 1); + bSuccess = ClipboardSetData(clipboard, utf8StringFormatId, pSrcData, SrcSize); + fprintf(stderr, "ClipboardSetData: %" PRId32 "\n", bSuccess); + DstSize = 0; + pDstData = (char*)ClipboardGetData(clipboard, utf8StringFormatId, &DstSize); + fprintf(stderr, "ClipboardGetData: %s\n", pDstData); + free(pDstData); + } + + if (1) + { + UINT32 DstSize = 0; + char* pSrcData = NULL; + WCHAR* pDstData = NULL; + DstSize = 0; + pDstData = (WCHAR*)ClipboardGetData(clipboard, CF_UNICODETEXT, &DstSize); + pSrcData = ConvertWCharNToUtf8Alloc(pDstData, DstSize / sizeof(WCHAR), NULL); + + fprintf(stderr, "ClipboardGetData (synthetic): %s\n", pSrcData); + free(pDstData); + free(pSrcData); + } + + pFormatIds = NULL; + count = ClipboardGetFormatIds(clipboard, &pFormatIds); + + for (UINT32 index = 0; index < count; index++) + { + UINT32 formatId = pFormatIds[index]; + formatName = ClipboardGetFormatName(clipboard, formatId); + fprintf(stderr, "Format: 0x%08" PRIX32 " %s\n", formatId, formatName); + } + + free(pFormatIds); + ClipboardDestroy(clipboard); + return 0; +} diff --git a/winpr/libwinpr/clipboard/test/TestUri.c b/winpr/libwinpr/clipboard/test/TestUri.c new file mode 100644 index 0000000..c1cccd3 --- /dev/null +++ b/winpr/libwinpr/clipboard/test/TestUri.c @@ -0,0 +1,69 @@ + +#include +#include +#include +#include +#include +#include "winpr/wlog.h" + +#include "../clipboard.h" + +#define WINPR_TAG(tag) "com.winpr." tag +#define TAG WINPR_TAG("clipboard.posix") + +int TestUri(int argc, char* argv[]) +{ + int nRet = 0; + const char* input[] = { /*uri, file or NULL*/ + "file://root/a.txt", + NULL, + "file:a.txt", + NULL, + "file:///c:/windows/a.txt", + "c:/windows/a.txt", + "file:c:/windows/a.txt", + "c:/windows/a.txt", + "file:c|/windows/a.txt", + "c:/windows/a.txt", + "file:///root/a.txt", + "/root/a.txt", + "file:/root/a.txt", + "/root/a.txt" + }; + + const size_t nLen = ARRAYSIZE(input); + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + printf("input length:%" PRIuz "\n", nLen / 2); + + for (size_t i = 0; i < nLen; i += 2) + { + const char* in = input[i]; + const char* cmp = input[i + 1]; + int bTest = 0; + char* name = parse_uri_to_local_file(in, strlen(in)); + if (name) + { + bTest = !strcmp(name, cmp); + if (!bTest) + { + printf("Test error: input: %s; Expected value: %s; output: %s\n", in, cmp, name); + nRet++; + } + free(name); + } + else + { + if (cmp) + { + printf("Test error: input: %s; Expected value: %s; output: %s\n", in, cmp, name); + nRet++; + } + } + } + + printf("TestUri return value: %d\n", nRet); + return nRet; +} -- cgit v1.2.3