summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/clipboard
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:24:41 +0000
commita9bcc81f821d7c66f623779fa5147e728eb3c388 (patch)
tree98676963bcdd537ae5908a067a8eb110b93486a6 /winpr/libwinpr/clipboard
parentInitial commit. (diff)
downloadfreerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz
freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'winpr/libwinpr/clipboard')
-rw-r--r--winpr/libwinpr/clipboard/CMakeLists.txt28
-rw-r--r--winpr/libwinpr/clipboard/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/clipboard/clipboard.c738
-rw-r--r--winpr/libwinpr/clipboard/clipboard.h77
-rw-r--r--winpr/libwinpr/clipboard/synthetic.c826
-rw-r--r--winpr/libwinpr/clipboard/synthetic_file.c1262
-rw-r--r--winpr/libwinpr/clipboard/synthetic_file.h27
-rw-r--r--winpr/libwinpr/clipboard/test/CMakeLists.txt27
-rw-r--r--winpr/libwinpr/clipboard/test/TestClipboardFormats.c82
-rw-r--r--winpr/libwinpr/clipboard/test/TestUri.c69
10 files changed, 3145 insertions, 0 deletions
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 <marcandre.moreau@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.
+
+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 <marcandre.moreau@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.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/collections.h>
+#include <winpr/wlog.h>
+
+#include <winpr/clipboard.h>
+
+#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 <marcandre.moreau@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_PRIVATE_H
+#define WINPR_CLIPBOARD_PRIVATE_H
+
+#include <winpr/winpr.h>
+#include <winpr/clipboard.h>
+
+#include <winpr/collections.h>
+
+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 <marcandre.moreau@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.
+ */
+
+#include <winpr/config.h>
+
+#include <errno.h>
+#include <winpr/crt.h>
+#include <winpr/user.h>
+#include <winpr/image.h>
+
+#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, "<body");
+
+ if (!body)
+ body = strstr(pSrcData.cpc, "<BODY");
+
+ /* StartHTML */
+ sprintf_s(num, sizeof(num), "%010" PRIuz "", strnlen(pDstData, DstSize));
+ CopyMemory(&pDstData[23], num, 10);
+
+ if (!body)
+ {
+ if (!winpr_str_append("<HTML><BODY>", pDstData, DstSize, NULL))
+ goto fail;
+ }
+
+ if (!winpr_str_append("<!--StartFragment-->", 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("<!--EndFragment-->", pDstData, DstSize, NULL))
+ goto fail;
+
+ if (!body)
+ {
+ if (!winpr_str_append("</BODY></HTML>", 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 <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.
+ */
+
+#include <winpr/config.h>
+#include <winpr/platform.h>
+
+WINPR_PRAGMA_DIAG_PUSH
+WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
+
+#define _FILE_OFFSET_BITS 64
+
+WINPR_PRAGMA_DIAG_POP
+
+#include <errno.h>
+
+#include <winpr/wtypes.h>
+
+#include <winpr/crt.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 <winpr/path.h>
+#include <winpr/print.h>
+
+#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 <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 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 <winpr/crt.h>
+#include <winpr/print.h>
+#include <winpr/clipboard.h>
+
+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 <stdio.h>
+#include <string.h>
+#include <memory.h>
+#include <stdlib.h>
+#include <winpr/winpr.h>
+#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;
+}