diff options
Diffstat (limited to 'winpr/libwinpr/clipboard/clipboard.c')
-rw-r--r-- | winpr/libwinpr/clipboard/clipboard.c | 738 |
1 files changed, 738 insertions, 0 deletions
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; +} |