summaryrefslogtreecommitdiffstats
path: root/channels/cliprdr
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--channels/cliprdr/CMakeLists.txt26
-rw-r--r--channels/cliprdr/ChannelOptions.cmake13
-rw-r--r--channels/cliprdr/client/CMakeLists.txt32
-rw-r--r--channels/cliprdr/client/cliprdr_format.c243
-rw-r--r--channels/cliprdr/client/cliprdr_format.h37
-rw-r--r--channels/cliprdr/client/cliprdr_main.c1192
-rw-r--r--channels/cliprdr/client/cliprdr_main.h61
-rw-r--r--channels/cliprdr/cliprdr_common.c566
-rw-r--r--channels/cliprdr/cliprdr_common.h61
-rw-r--r--channels/cliprdr/server/CMakeLists.txt30
-rw-r--r--channels/cliprdr/server/cliprdr_main.c1528
-rw-r--r--channels/cliprdr/server/cliprdr_main.h47
12 files changed, 3836 insertions, 0 deletions
diff --git a/channels/cliprdr/CMakeLists.txt b/channels/cliprdr/CMakeLists.txt
new file mode 100644
index 0000000..c5cfd72
--- /dev/null
+++ b/channels/cliprdr/CMakeLists.txt
@@ -0,0 +1,26 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 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.
+
+define_channel("cliprdr")
+
+if(WITH_CLIENT_CHANNELS)
+ add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
+
+if(WITH_SERVER_CHANNELS)
+ add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
+endif()
diff --git a/channels/cliprdr/ChannelOptions.cmake b/channels/cliprdr/ChannelOptions.cmake
new file mode 100644
index 0000000..f175f3f
--- /dev/null
+++ b/channels/cliprdr/ChannelOptions.cmake
@@ -0,0 +1,13 @@
+
+set(OPTION_DEFAULT OFF)
+set(OPTION_CLIENT_DEFAULT ON)
+set(OPTION_SERVER_DEFAULT ON)
+
+define_channel_options(NAME "cliprdr" TYPE "static"
+ DESCRIPTION "Clipboard Virtual Channel Extension"
+ SPECIFICATIONS "[MS-RDPECLIP]"
+ DEFAULT ${OPTION_DEFAULT})
+
+define_channel_client_options(${OPTION_CLIENT_DEFAULT})
+define_channel_server_options(${OPTION_SERVER_DEFAULT})
+
diff --git a/channels/cliprdr/client/CMakeLists.txt b/channels/cliprdr/client/CMakeLists.txt
new file mode 100644
index 0000000..57819f0
--- /dev/null
+++ b/channels/cliprdr/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 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.
+
+define_channel_client("cliprdr")
+
+set(${MODULE_PREFIX}_SRCS
+ cliprdr_format.c
+ cliprdr_format.h
+ cliprdr_main.c
+ cliprdr_main.h
+ ../cliprdr_common.h
+ ../cliprdr_common.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr freerdp
+)
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntryEx")
diff --git a/channels/cliprdr/client/cliprdr_format.c b/channels/cliprdr/client/cliprdr_format.c
new file mode 100644
index 0000000..8b13af4
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_format.c
@@ -0,0 +1,243 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/types.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/settings.h>
+#include <freerdp/constants.h>
+#include <freerdp/client/cliprdr.h>
+
+#include "cliprdr_main.h"
+#include "cliprdr_format.h"
+#include "../cliprdr_common.h"
+
+CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
+ const UINT32 checkMask)
+{
+ const UINT32 maskData =
+ checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_REMOTE_TO_LOCAL);
+ const UINT32 maskFiles =
+ checkMask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ WINPR_ASSERT(list);
+
+ CLIPRDR_FORMAT_LIST filtered = { 0 };
+ filtered.common.msgType = CB_FORMAT_LIST;
+ filtered.numFormats = list->numFormats;
+ filtered.formats = calloc(filtered.numFormats, sizeof(CLIPRDR_FORMAT));
+
+ size_t wpos = 0;
+ if ((mask & checkMask) == checkMask)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[x];
+ cur->formatId = format->formatId;
+ if (format->formatName)
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ else if ((mask & maskFiles) != 0)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
+
+ if (!format->formatName)
+ continue;
+ if (strcmp(format->formatName, type_FileGroupDescriptorW) == 0 ||
+ strcmp(format->formatName, type_FileContents) == 0)
+ {
+ cur->formatId = format->formatId;
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ }
+ else if ((mask & maskData) != 0)
+ {
+ for (size_t x = 0; x < list->numFormats; x++)
+ {
+ const CLIPRDR_FORMAT* format = &list->formats[x];
+ CLIPRDR_FORMAT* cur = &filtered.formats[wpos];
+
+ if (!format->formatName ||
+ (strcmp(format->formatName, type_FileGroupDescriptorW) != 0 &&
+ strcmp(format->formatName, type_FileContents) != 0))
+ {
+ cur->formatId = format->formatId;
+ if (format->formatName)
+ cur->formatName = _strdup(format->formatName);
+ wpos++;
+ }
+ }
+ }
+ filtered.numFormats = wpos;
+ return filtered;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_LIST formatList = { 0 };
+ CLIPRDR_FORMAT_LIST filteredFormatList = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ formatList.common.msgType = CB_FORMAT_LIST;
+ formatList.common.msgFlags = msgFlags;
+ formatList.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_list(s, &formatList, cliprdr->useLongFormatNames)))
+ goto error_out;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ filteredFormatList = cliprdr_filter_format_list(
+ &formatList, mask, CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES);
+ if (filteredFormatList.numFormats == 0)
+ goto error_out;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatList: numFormats: %" PRIu32 "",
+ filteredFormatList.numFormats);
+
+ if (context->ServerFormatList)
+ {
+ if ((error = context->ServerFormatList(context, &filteredFormatList)))
+ WLog_ERR(TAG, "ServerFormatList failed with error %" PRIu32 "", error);
+ }
+
+error_out:
+ cliprdr_free_format_list(&filteredFormatList);
+ cliprdr_free_format_list(&formatList);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatListResponse");
+
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = msgFlags;
+ formatListResponse.common.dataLen = dataLen;
+
+ IFCALLRET(context->ServerFormatListResponse, error, context, &formatListResponse);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatListResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataRequest");
+
+ formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
+ formatDataRequest.common.msgFlags = msgFlags;
+ formatDataRequest.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
+ {
+ return cliprdr_send_error_response(cliprdr, CB_FORMAT_DATA_RESPONSE);
+ }
+
+ context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
+ IFCALLRET(context->ServerFormatDataRequest, error, context, &formatDataRequest);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatDataRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerFormatDataResponse");
+
+ formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ formatDataResponse.common.msgFlags = msgFlags;
+ formatDataResponse.common.dataLen = dataLen;
+
+ if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
+ {
+ WLog_WARN(TAG,
+ "Received ServerFormatDataResponse but remote -> local clipboard is disabled");
+ return CHANNEL_RC_OK;
+ }
+
+ IFCALLRET(context->ServerFormatDataResponse, error, context, &formatDataResponse);
+ if (error)
+ WLog_ERR(TAG, "ServerFormatDataResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
diff --git a/channels/cliprdr/client/cliprdr_format.h b/channels/cliprdr/client/cliprdr_format.h
new file mode 100644
index 0000000..a3ba1ed
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_format.h
@@ -0,0 +1,37 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
+#define FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H
+
+UINT cliprdr_process_format_list(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_list_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_data_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+UINT cliprdr_process_format_data_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 dataLen,
+ UINT16 msgFlags);
+CLIPRDR_FORMAT_LIST cliprdr_filter_format_list(const CLIPRDR_FORMAT_LIST* list, const UINT32 mask,
+ const UINT32 checkMask);
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_FORMAT_H */
diff --git a/channels/cliprdr/client/cliprdr_main.c b/channels/cliprdr/client/cliprdr_main.c
new file mode 100644
index 0000000..60ae27d
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_main.c
@@ -0,0 +1,1192 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 <freerdp/config.h>
+
+#include <winpr/wtypes.h>
+#include <winpr/assert.h>
+#include <winpr/crt.h>
+#include <winpr/print.h>
+
+#include <freerdp/types.h>
+#include <freerdp/constants.h>
+#include <freerdp/freerdp.h>
+#include <freerdp/client/cliprdr.h>
+
+#include "../../../channels/client/addin.h"
+
+#include "cliprdr_main.h"
+#include "cliprdr_format.h"
+#include "../cliprdr_common.h"
+
+const char* type_FileGroupDescriptorW = "FileGroupDescriptorW";
+const char* type_FileContents = "FileContents";
+
+static const char* CB_MSG_TYPE_STRINGS(UINT32 type)
+{
+ switch (type)
+ {
+ case CB_MONITOR_READY:
+ return "CB_MONITOR_READY";
+ case CB_FORMAT_LIST:
+ return "CB_FORMAT_LIST";
+ case CB_FORMAT_LIST_RESPONSE:
+ return "CB_FORMAT_LIST_RESPONSE";
+ case CB_FORMAT_DATA_REQUEST:
+ return "CB_FORMAT_DATA_REQUEST";
+ case CB_FORMAT_DATA_RESPONSE:
+ return "CB_FORMAT_DATA_RESPONSE";
+ case CB_TEMP_DIRECTORY:
+ return "CB_TEMP_DIRECTORY";
+ case CB_CLIP_CAPS:
+ return "CB_CLIP_CAPS";
+ case CB_FILECONTENTS_REQUEST:
+ return "CB_FILECONTENTS_REQUEST";
+ case CB_FILECONTENTS_RESPONSE:
+ return "CB_FILECONTENTS_RESPONSE";
+ case CB_LOCK_CLIPDATA:
+ return "CB_LOCK_CLIPDATA";
+ case CB_UNLOCK_CLIPDATA:
+ return "CB_UNLOCK_CLIPDATA";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr)
+{
+ CliprdrClientContext* pInterface = NULL;
+
+ if (!cliprdr)
+ return NULL;
+
+ pInterface = (CliprdrClientContext*)cliprdr->channelEntryPoints.pInterface;
+ return pInterface;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_packet_send(cliprdrPlugin* cliprdr, wStream* s)
+{
+ size_t pos = 0;
+ UINT32 dataLen = 0;
+ UINT status = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ pos = Stream_GetPosition(s);
+ dataLen = pos - 8;
+ Stream_SetPosition(s, 4);
+ Stream_Write_UINT32(s, dataLen);
+ Stream_SetPosition(s, pos);
+
+ WLog_DBG(TAG, "Cliprdr Sending (%" PRIu32 " bytes)", dataLen + 8);
+
+ if (!cliprdr)
+ {
+ status = CHANNEL_RC_BAD_INIT_HANDLE;
+ }
+ else
+ {
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelWriteEx);
+ status = cliprdr->channelEntryPoints.pVirtualChannelWriteEx(
+ cliprdr->InitHandle, cliprdr->OpenHandle, Stream_Buffer(s),
+ (UINT32)Stream_GetPosition(s), s);
+ }
+
+ if (status != CHANNEL_RC_OK)
+ {
+ Stream_Free(s, TRUE);
+ WLog_ERR(TAG, "VirtualChannelWrite failed with %s [%08" PRIX32 "]",
+ WTSErrorToString(status), status);
+ }
+
+ return status;
+}
+
+UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type)
+{
+ wStream* s = cliprdr_packet_new(type, CB_RESPONSE_FAIL, 0);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_OUTOFMEMORY;
+ }
+
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+static void cliprdr_print_general_capability_flags(UINT32 flags)
+{
+ WLog_DBG(TAG, "generalFlags (0x%08" PRIX32 ") {", flags);
+
+ if (flags & CB_USE_LONG_FORMAT_NAMES)
+ WLog_DBG(TAG, "\tCB_USE_LONG_FORMAT_NAMES");
+
+ if (flags & CB_STREAM_FILECLIP_ENABLED)
+ WLog_DBG(TAG, "\tCB_STREAM_FILECLIP_ENABLED");
+
+ if (flags & CB_FILECLIP_NO_FILE_PATHS)
+ WLog_DBG(TAG, "\tCB_FILECLIP_NO_FILE_PATHS");
+
+ if (flags & CB_CAN_LOCK_CLIPDATA)
+ WLog_DBG(TAG, "\tCB_CAN_LOCK_CLIPDATA");
+
+ if (flags & CB_HUGE_FILE_SUPPORT_ENABLED)
+ WLog_DBG(TAG, "\tCB_HUGE_FILE_SUPPORT_ENABLED");
+
+ WLog_DBG(TAG, "}");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_general_capability(cliprdrPlugin* cliprdr, wStream* s)
+{
+ UINT32 version = 0;
+ UINT32 generalFlags = 0;
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!context)
+ {
+ WLog_ERR(TAG, "cliprdr_get_client_interface failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, generalFlags); /* generalFlags (4 bytes) */
+ WLog_DBG(TAG, "Version: %" PRIu32 "", version);
+
+ cliprdr_print_general_capability_flags(generalFlags);
+
+ cliprdr->useLongFormatNames = (generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+ cliprdr->streamFileClipEnabled = (generalFlags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+ cliprdr->fileClipNoFilePaths = (generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+ cliprdr->canLockClipData = (generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+ cliprdr->hasHugeFileSupport = (generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+ cliprdr->capabilitiesReceived = TRUE;
+
+ capabilities.common.msgType = CB_CLIP_CAPS;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&(generalCapabilitySet);
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = 12;
+ generalCapabilitySet.version = version;
+ generalCapabilitySet.generalFlags = generalFlags;
+ IFCALLRET(context->ServerCapabilities, error, context, &capabilities);
+
+ if (error)
+ WLog_ERR(TAG, "ServerCapabilities failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_clip_caps(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ UINT16 lengthCapability = 0;
+ UINT16 cCapabilitiesSets = 0;
+ UINT16 capabilitySetType = 0;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad1 (2 bytes) */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ServerCapabilities");
+
+ for (UINT16 index = 0; index < cCapabilitiesSets; index++)
+ {
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Read_UINT16(s, lengthCapability); /* lengthCapability (2 bytes) */
+
+ if ((lengthCapability < 4) ||
+ (!Stream_CheckAndLogRequiredLength(TAG, s, lengthCapability - 4U)))
+ return ERROR_INVALID_DATA;
+
+ switch (capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ if ((error = cliprdr_process_general_capability(cliprdr, s)))
+ {
+ WLog_ERR(TAG,
+ "cliprdr_process_general_capability failed with error %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "", capabilitySetType);
+ return CHANNEL_RC_BAD_PROC;
+ }
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_monitor_ready(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_MONITOR_READY monitorReady = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "MonitorReady");
+
+ if (!cliprdr->capabilitiesReceived)
+ {
+ /**
+ * The clipboard capabilities pdu from server to client is optional,
+ * but a server using it must send it before sending the monitor ready pdu.
+ * When the server capabilities pdu is not used, default capabilities
+ * corresponding to a generalFlags field set to zero are assumed.
+ */
+ cliprdr->useLongFormatNames = FALSE;
+ cliprdr->streamFileClipEnabled = FALSE;
+ cliprdr->fileClipNoFilePaths = TRUE;
+ cliprdr->canLockClipData = FALSE;
+ }
+
+ monitorReady.common.msgType = CB_MONITOR_READY;
+ monitorReady.common.msgFlags = flags;
+ monitorReady.common.dataLen = length;
+ IFCALLRET(context->MonitorReady, error, context, &monitorReady);
+
+ if (error)
+ WLog_ERR(TAG, "MonitorReady failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_filecontents_request(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_FILE_CONTENTS_REQUEST request = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsRequest");
+
+ request.common.msgType = CB_FILECONTENTS_REQUEST;
+ request.common.msgFlags = flags;
+ request.common.dataLen = length;
+
+ if ((error = cliprdr_read_file_contents_request(s, &request)))
+ return error;
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) == 0)
+ {
+ WLog_WARN(TAG, "local -> remote file copy disabled, ignoring request");
+ return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE);
+ }
+ IFCALLRET(context->ServerFileContentsRequest, error, context, &request);
+
+ if (error)
+ WLog_ERR(TAG, "ServerFileContentsRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_filecontents_response(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "FileContentsResponse");
+
+ response.common.msgType = CB_FILECONTENTS_RESPONSE;
+ response.common.msgFlags = flags;
+ response.common.dataLen = length;
+
+ if ((error = cliprdr_read_file_contents_response(s, &response)))
+ return error;
+
+ IFCALLRET(context->ServerFileContentsResponse, error, context, &response);
+
+ if (error)
+ WLog_ERR(TAG, "ServerFileContentsResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_lock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "LockClipData");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ lockClipboardData.common.msgType = CB_LOCK_CLIPDATA;
+ lockClipboardData.common.msgFlags = flags;
+ lockClipboardData.common.dataLen = length;
+ Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */
+ IFCALLRET(context->ServerLockClipboardData, error, context, &lockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ServerLockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_process_unlock_clipdata(cliprdrPlugin* cliprdr, wStream* s, UINT32 length,
+ UINT16 flags)
+{
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = { 0 };
+ CliprdrClientContext* context = cliprdr_get_client_interface(cliprdr);
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "UnlockClipData");
+
+ if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData)))
+ return error;
+
+ unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlockClipboardData.common.msgFlags = flags;
+ unlockClipboardData.common.dataLen = length;
+
+ IFCALLRET(context->ServerUnlockClipboardData, error, context, &unlockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ServerUnlockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_order_recv(LPVOID userdata, wStream* s)
+{
+ cliprdrPlugin* cliprdr = userdata;
+ UINT16 msgType = 0;
+ UINT16 msgFlags = 0;
+ UINT32 dataLen = 0;
+ UINT error = 0;
+
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, msgFlags); /* msgFlags (2 bytes) */
+ Stream_Read_UINT32(s, dataLen); /* dataLen (4 bytes) */
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, dataLen))
+ return ERROR_INVALID_DATA;
+
+ WLog_DBG(TAG, "msgType: %s (%" PRIu16 "), msgFlags: %" PRIu16 " dataLen: %" PRIu32 "",
+ CB_MSG_TYPE_STRINGS(msgType), msgType, msgFlags, dataLen);
+
+ switch (msgType)
+ {
+ case CB_CLIP_CAPS:
+ if ((error = cliprdr_process_clip_caps(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_clip_caps failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case CB_MONITOR_READY:
+ if ((error = cliprdr_process_monitor_ready(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_monitor_ready failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST:
+ if ((error = cliprdr_process_format_list(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_list failed with error %" PRIu32 "!", error);
+
+ break;
+
+ case CB_FORMAT_LIST_RESPONSE:
+ if ((error = cliprdr_process_format_list_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_list_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_REQUEST:
+ if ((error = cliprdr_process_format_data_request(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_data_request failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_RESPONSE:
+ if ((error = cliprdr_process_format_data_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_format_data_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_REQUEST:
+ if ((error = cliprdr_process_filecontents_request(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_filecontents_request failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_RESPONSE:
+ if ((error = cliprdr_process_filecontents_response(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG,
+ "cliprdr_process_filecontents_response failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_LOCK_CLIPDATA:
+ if ((error = cliprdr_process_lock_clipdata(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_lock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_UNLOCK_CLIPDATA:
+ if ((error = cliprdr_process_unlock_clipdata(cliprdr, s, dataLen, msgFlags)))
+ WLog_ERR(TAG, "cliprdr_process_unlock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ default:
+ error = CHANNEL_RC_BAD_PROC;
+ WLog_ERR(TAG, "unknown msgType %" PRIu16 "", msgType);
+ break;
+ }
+
+ Stream_Free(s, TRUE);
+ return error;
+}
+
+/**
+ * Callback Interface
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_capabilities(CliprdrClientContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ wStream* s = NULL;
+ UINT32 flags = 0;
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT16(s, 1); /* cCapabilitiesSets */
+ Stream_Write_UINT16(s, 0); /* pad1 */
+ generalCapabilitySet = (const CLIPRDR_GENERAL_CAPABILITY_SET*)capabilities->capabilitySets;
+ Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetType); /* capabilitySetType */
+ Stream_Write_UINT16(s, generalCapabilitySet->capabilitySetLength); /* lengthCapability */
+ Stream_Write_UINT32(s, generalCapabilitySet->version); /* version */
+ flags = generalCapabilitySet->generalFlags;
+
+ /* Client capabilities are sent in response to server capabilities.
+ * -> Do not request features the server does not support.
+ * -> Update clipboard context feature state to what was agreed upon.
+ */
+ if (!cliprdr->useLongFormatNames)
+ flags &= ~CB_USE_LONG_FORMAT_NAMES;
+ if (!cliprdr->streamFileClipEnabled)
+ flags &= ~CB_STREAM_FILECLIP_ENABLED;
+ if (!cliprdr->fileClipNoFilePaths)
+ flags &= ~CB_FILECLIP_NO_FILE_PATHS;
+ if (!cliprdr->canLockClipData)
+ flags &= ~CB_CAN_LOCK_CLIPDATA;
+ if (!cliprdr->hasHugeFileSupport)
+ flags &= ~CB_HUGE_FILE_SUPPORT_ENABLED;
+
+ cliprdr->useLongFormatNames = (flags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+ cliprdr->streamFileClipEnabled = (flags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+ cliprdr->fileClipNoFilePaths = (flags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+ cliprdr->canLockClipData = (flags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+ cliprdr->hasHugeFileSupport = (flags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+
+ Stream_Write_UINT32(s, flags); /* generalFlags */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientCapabilities");
+
+ cliprdr->initialFormatListSent = FALSE;
+
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_temp_directory(CliprdrClientContext* context,
+ const CLIPRDR_TEMP_DIRECTORY* tempDirectory)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(tempDirectory);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const size_t tmpDirCharLen = sizeof(tempDirectory->szTempDir) / sizeof(WCHAR);
+ s = cliprdr_packet_new(CB_TEMP_DIRECTORY, 0, tmpDirCharLen * sizeof(WCHAR));
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (Stream_Write_UTF16_String_From_UTF8(s, tmpDirCharLen - 1, tempDirectory->szTempDir,
+ ARRAYSIZE(tempDirectory->szTempDir), TRUE) < 0)
+ return ERROR_INTERNAL_ERROR;
+ /* Path must be 260 UTF16 characters with '\0' termination.
+ * ensure this here */
+ Stream_Write_UINT16(s, 0);
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "TempDirectory: %s", tempDirectory->szTempDir);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_format_list(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ CLIPRDR_FORMAT_LIST filterList = cliprdr_filter_format_list(
+ formatList, mask, CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES);
+
+ /* Allow initial format list from monitor ready, but ignore later attempts */
+ if ((filterList.numFormats == 0) && cliprdr->initialFormatListSent)
+ {
+ cliprdr_free_format_list(&filterList);
+ return CHANNEL_RC_OK;
+ }
+ cliprdr->initialFormatListSent = TRUE;
+
+ s = cliprdr_packet_format_list_new(&filterList, cliprdr->useLongFormatNames);
+ cliprdr_free_format_list(&filterList);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatList: numFormats: %" PRIu32 "",
+ formatList->numFormats);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_format_list_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags, 0);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatListResponse");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_lock_clipboard_data(CliprdrClientContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_lock_clipdata_new(lockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientLockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ lockClipboardData->clipDataId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_unlock_clipboard_data(CliprdrClientContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientUnlockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ unlockClipboardData->clipDataId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_client_format_data_request(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & (CLIPRDR_FLAG_REMOTE_TO_LOCAL | CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES)) == 0)
+ {
+ WLog_WARN(TAG, "remote -> local copy disabled, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+ s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, 0, 4);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataRequest");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_format_data_response(CliprdrClientContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ WINPR_ASSERT((mask & (CLIPRDR_FLAG_LOCAL_TO_REMOTE | CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES)) != 0);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags,
+ formatDataResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen);
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFormatDataResponse");
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_file_contents_request(CliprdrClientContext* context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & CLIPRDR_FLAG_REMOTE_TO_LOCAL_FILES) == 0)
+ {
+ WLog_WARN(TAG, "remote -> local file copy disabled, ignoring request");
+ return CHANNEL_RC_OK;
+ }
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ if (!cliprdr)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!cliprdr->hasHugeFileSupport)
+ {
+ if (((UINT64)fileContentsRequest->cbRequested + fileContentsRequest->nPositionLow) >
+ UINT32_MAX)
+ return ERROR_INVALID_PARAMETER;
+ if (fileContentsRequest->nPositionHigh != 0)
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ s = cliprdr_packet_file_contents_request_new(fileContentsRequest);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsRequest: streamId: 0x%08" PRIX32 "",
+ fileContentsRequest->streamId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_client_file_contents_response(CliprdrClientContext* context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ wStream* s = NULL;
+ cliprdrPlugin* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsResponse);
+
+ cliprdr = (cliprdrPlugin*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ const UINT32 mask =
+ freerdp_settings_get_uint32(context->rdpcontext->settings, FreeRDP_ClipboardFeatureMask);
+ if ((mask & CLIPRDR_FLAG_LOCAL_TO_REMOTE_FILES) == 0)
+ return cliprdr_send_error_response(cliprdr, CB_FILECONTENTS_RESPONSE);
+
+ s = cliprdr_packet_file_contents_response_new(fileContentsResponse);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "ClientFileContentsResponse: streamId: 0x%08" PRIX32 "",
+ fileContentsResponse->streamId);
+ return cliprdr_packet_send(cliprdr, s);
+}
+
+static VOID VCAPITYPE cliprdr_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
+ UINT event, LPVOID pData,
+ UINT32 dataLength, UINT32 totalLength,
+ UINT32 dataFlags)
+{
+ UINT error = CHANNEL_RC_OK;
+ cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam;
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_DATA_RECEIVED:
+ if (!cliprdr || (cliprdr->OpenHandle != openHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+ if ((error = channel_client_post_message(cliprdr->MsgsHandle, pData, dataLength,
+ totalLength, dataFlags)))
+ WLog_ERR(TAG, "failed with error %" PRIu32 "", error);
+
+ break;
+
+ case CHANNEL_EVENT_WRITE_CANCELLED:
+ case CHANNEL_EVENT_WRITE_COMPLETE:
+ {
+ wStream* s = (wStream*)pData;
+ Stream_Free(s, TRUE);
+ }
+ break;
+
+ case CHANNEL_EVENT_USER:
+ break;
+ }
+
+ if (error && cliprdr && cliprdr->context->rdpcontext)
+ setChannelError(cliprdr->context->rdpcontext, error,
+ "cliprdr_virtual_channel_open_event_ex reported an error");
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_connected(cliprdrPlugin* cliprdr, LPVOID pData,
+ UINT32 dataLength)
+{
+ DWORD status = 0;
+ WINPR_ASSERT(cliprdr);
+ WINPR_ASSERT(cliprdr->context);
+
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelOpenEx);
+ status = cliprdr->channelEntryPoints.pVirtualChannelOpenEx(
+ cliprdr->InitHandle, &cliprdr->OpenHandle, cliprdr->channelDef.name,
+ cliprdr_virtual_channel_open_event_ex);
+ if (status != CHANNEL_RC_OK)
+ return status;
+
+ cliprdr->MsgsHandle = channel_client_create_handler(
+ cliprdr->context->rdpcontext, cliprdr, cliprdr_order_recv, CLIPRDR_SVC_CHANNEL_NAME);
+ if (!cliprdr->MsgsHandle)
+ return ERROR_INTERNAL_ERROR;
+
+ return status;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_disconnected(cliprdrPlugin* cliprdr)
+{
+ UINT rc = 0;
+
+ WINPR_ASSERT(cliprdr);
+
+ channel_client_quit_handler(cliprdr->MsgsHandle);
+ cliprdr->MsgsHandle = NULL;
+
+ if (cliprdr->OpenHandle == 0)
+ return CHANNEL_RC_OK;
+
+ WINPR_ASSERT(cliprdr->channelEntryPoints.pVirtualChannelCloseEx);
+ rc = cliprdr->channelEntryPoints.pVirtualChannelCloseEx(cliprdr->InitHandle,
+ cliprdr->OpenHandle);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelClose failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ return rc;
+ }
+
+ cliprdr->OpenHandle = 0;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_virtual_channel_event_terminated(cliprdrPlugin* cliprdr)
+{
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->InitHandle = 0;
+ free(cliprdr->context);
+ free(cliprdr);
+ return CHANNEL_RC_OK;
+}
+
+static VOID VCAPITYPE cliprdr_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
+ UINT event, LPVOID pData,
+ UINT dataLength)
+{
+ UINT error = CHANNEL_RC_OK;
+ cliprdrPlugin* cliprdr = (cliprdrPlugin*)lpUserParam;
+
+ if (!cliprdr || (cliprdr->InitHandle != pInitHandle))
+ {
+ WLog_ERR(TAG, "error no match");
+ return;
+ }
+
+ switch (event)
+ {
+ case CHANNEL_EVENT_CONNECTED:
+ if ((error = cliprdr_virtual_channel_event_connected(cliprdr, pData, dataLength)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_connected failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_DISCONNECTED:
+ if ((error = cliprdr_virtual_channel_event_disconnected(cliprdr)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_disconnected failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CHANNEL_EVENT_TERMINATED:
+ if ((error = cliprdr_virtual_channel_event_terminated(cliprdr)))
+ WLog_ERR(TAG,
+ "cliprdr_virtual_channel_event_terminated failed with error %" PRIu32 "!",
+ error);
+
+ break;
+ }
+
+ if (error && cliprdr->context->rdpcontext)
+ setChannelError(cliprdr->context->rdpcontext, error,
+ "cliprdr_virtual_channel_init_event reported an error");
+}
+
+/* cliprdr is always built-in */
+#define VirtualChannelEntryEx cliprdr_VirtualChannelEntryEx
+
+FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
+ PVOID pInitHandle))
+{
+ UINT rc = 0;
+ cliprdrPlugin* cliprdr = NULL;
+ CliprdrClientContext* context = NULL;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX* pEntryPointsEx = NULL;
+ cliprdr = (cliprdrPlugin*)calloc(1, sizeof(cliprdrPlugin));
+
+ if (!cliprdr)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ cliprdr->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
+ CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
+ sprintf_s(cliprdr->channelDef.name, ARRAYSIZE(cliprdr->channelDef.name),
+ CLIPRDR_SVC_CHANNEL_NAME);
+ pEntryPointsEx = (CHANNEL_ENTRY_POINTS_FREERDP_EX*)pEntryPoints;
+ WINPR_ASSERT(pEntryPointsEx);
+
+ if ((pEntryPointsEx->cbSize >= sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX)) &&
+ (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
+ {
+ context = (CliprdrClientContext*)calloc(1, sizeof(CliprdrClientContext));
+
+ if (!context)
+ {
+ free(cliprdr);
+ WLog_ERR(TAG, "calloc failed!");
+ return FALSE;
+ }
+
+ context->handle = (void*)cliprdr;
+ context->custom = NULL;
+ context->ClientCapabilities = cliprdr_client_capabilities;
+ context->TempDirectory = cliprdr_temp_directory;
+ context->ClientFormatList = cliprdr_client_format_list;
+ context->ClientFormatListResponse = cliprdr_client_format_list_response;
+ context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data;
+ context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data;
+ context->ClientFormatDataRequest = cliprdr_client_format_data_request;
+ context->ClientFormatDataResponse = cliprdr_client_format_data_response;
+ context->ClientFileContentsRequest = cliprdr_client_file_contents_request;
+ context->ClientFileContentsResponse = cliprdr_client_file_contents_response;
+ cliprdr->context = context;
+ context->rdpcontext = pEntryPointsEx->context;
+ }
+
+ cliprdr->log = WLog_Get(CHANNELS_TAG("channels.cliprdr.client"));
+ WLog_Print(cliprdr->log, WLOG_DEBUG, "VirtualChannelEntryEx");
+ CopyMemory(&(cliprdr->channelEntryPoints), pEntryPoints,
+ sizeof(CHANNEL_ENTRY_POINTS_FREERDP_EX));
+ cliprdr->InitHandle = pInitHandle;
+ rc = cliprdr->channelEntryPoints.pVirtualChannelInitEx(
+ cliprdr, context, pInitHandle, &cliprdr->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
+ cliprdr_virtual_channel_init_event_ex);
+
+ if (CHANNEL_RC_OK != rc)
+ {
+ WLog_ERR(TAG, "pVirtualChannelInit failed with %s [%08" PRIX32 "]", WTSErrorToString(rc),
+ rc);
+ free(cliprdr->context);
+ free(cliprdr);
+ return FALSE;
+ }
+
+ cliprdr->channelEntryPoints.pInterface = context;
+ return TRUE;
+}
diff --git a/channels/cliprdr/client/cliprdr_main.h b/channels/cliprdr/client/cliprdr_main.h
new file mode 100644
index 0000000..9e899f5
--- /dev/null
+++ b/channels/cliprdr/client/cliprdr_main.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel
+ *
+ * Copyright 2009-2011 Jay Sorg
+ * Copyright 2010-2011 Vic Lee
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
+#define FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H
+
+#include <winpr/stream.h>
+
+#include <freerdp/svc.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+#include <freerdp/client/cliprdr.h>
+
+#define TAG CHANNELS_TAG("cliprdr.client")
+
+typedef struct
+{
+ CHANNEL_DEF channelDef;
+ CHANNEL_ENTRY_POINTS_FREERDP_EX channelEntryPoints;
+
+ CliprdrClientContext* context;
+
+ wLog* log;
+ void* InitHandle;
+ DWORD OpenHandle;
+ void* MsgsHandle;
+
+ BOOL capabilitiesReceived;
+ BOOL useLongFormatNames;
+ BOOL streamFileClipEnabled;
+ BOOL fileClipNoFilePaths;
+ BOOL canLockClipData;
+ BOOL hasHugeFileSupport;
+ BOOL initialFormatListSent;
+} cliprdrPlugin;
+
+CliprdrClientContext* cliprdr_get_client_interface(cliprdrPlugin* cliprdr);
+UINT cliprdr_send_error_response(cliprdrPlugin* cliprdr, UINT16 type);
+
+extern const char* type_FileGroupDescriptorW;
+extern const char* type_FileContents;
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_CLIENT_MAIN_H */
diff --git a/channels/cliprdr/cliprdr_common.c b/channels/cliprdr/cliprdr_common.c
new file mode 100644
index 0000000..d346cb1
--- /dev/null
+++ b/channels/cliprdr/cliprdr_common.c
@@ -0,0 +1,566 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cliprdr common
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@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/crt.h>
+#include <winpr/stream.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("cliprdr.common")
+
+#include "cliprdr_common.h"
+
+static BOOL cliprdr_validate_file_contents_request(const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ /*
+ * [MS-RDPECLIP] 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST).
+ *
+ * A request for the size of the file identified by the lindex field. The size MUST be
+ * returned as a 64-bit, unsigned integer. The cbRequested field MUST be set to
+ * 0x00000008 and both the nPositionLow and nPositionHigh fields MUST be
+ * set to 0x00000000.
+ */
+
+ if (request->dwFlags & FILECONTENTS_SIZE)
+ {
+ if (request->cbRequested != sizeof(UINT64))
+ {
+ WLog_ERR(TAG, "cbRequested must be %" PRIu32 ", got %" PRIu32 "", sizeof(UINT64),
+ request->cbRequested);
+ return FALSE;
+ }
+
+ if (request->nPositionHigh != 0 || request->nPositionLow != 0)
+ {
+ WLog_ERR(TAG, "nPositionHigh and nPositionLow must be set to 0");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen)
+{
+ wStream* s = NULL;
+ s = Stream_New(NULL, dataLen + 8);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return NULL;
+ }
+
+ Stream_Write_UINT16(s, msgType);
+ Stream_Write_UINT16(s, msgFlags);
+ /* Write actual length after the entire packet has been constructed. */
+ Stream_Write_UINT32(s, 0);
+ return s;
+}
+
+static void cliprdr_write_file_contents_request(wStream* s,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ Stream_Write_UINT32(s, request->streamId); /* streamId (4 bytes) */
+ Stream_Write_UINT32(s, request->listIndex); /* listIndex (4 bytes) */
+ Stream_Write_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */
+ Stream_Write_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */
+ Stream_Write_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */
+ Stream_Write_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */
+
+ if (request->haveClipDataId)
+ Stream_Write_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */
+}
+
+static INLINE void cliprdr_write_lock_unlock_clipdata(wStream* s, UINT32 clipDataId)
+{
+ Stream_Write_UINT32(s, clipDataId);
+}
+
+static void cliprdr_write_lock_clipdata(wStream* s,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ cliprdr_write_lock_unlock_clipdata(s, lockClipboardData->clipDataId);
+}
+
+static void cliprdr_write_unlock_clipdata(wStream* s,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ cliprdr_write_lock_unlock_clipdata(s, unlockClipboardData->clipDataId);
+}
+
+static void cliprdr_write_file_contents_response(wStream* s,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ Stream_Write_UINT32(s, response->streamId); /* streamId (4 bytes) */
+ Stream_Write(s, response->requestedData, response->cbRequested);
+}
+
+wStream* cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+
+ if (!lockClipboardData)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_LOCK_CLIPDATA, 0, 4);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_lock_clipdata(s, lockClipboardData);
+ return s;
+}
+
+wStream*
+cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+
+ if (!unlockClipboardData)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_UNLOCK_CLIPDATA, 0, 4);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_unlock_clipdata(s, unlockClipboardData);
+ return s;
+}
+
+wStream* cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ wStream* s = NULL;
+
+ if (!request)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_FILECONTENTS_REQUEST, 0, 28);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_file_contents_request(s, request);
+ return s;
+}
+
+wStream* cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ wStream* s = NULL;
+
+ if (!response)
+ return NULL;
+
+ s = cliprdr_packet_new(CB_FILECONTENTS_RESPONSE, response->common.msgFlags,
+ 4 + response->cbRequested);
+
+ if (!s)
+ return NULL;
+
+ cliprdr_write_file_contents_response(s, response);
+ return s;
+}
+
+wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames)
+{
+ wStream* s = NULL;
+ size_t formatNameSize = 0;
+ char* szFormatName = NULL;
+ WCHAR* wszFormatName = NULL;
+ BOOL asciiNames = FALSE;
+ CLIPRDR_FORMAT* format = NULL;
+ UINT32 length = 0;
+
+ if (formatList->common.msgType != CB_FORMAT_LIST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatList->common.msgType);
+
+ if (!useLongFormatNames)
+ {
+ length = formatList->numFormats * 36;
+ s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return NULL;
+ }
+
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ size_t formatNameLength = 0;
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */
+ formatNameSize = 0;
+
+ szFormatName = format->formatName;
+
+ if (asciiNames)
+ {
+ if (szFormatName)
+ formatNameLength = strnlen(szFormatName, 32);
+
+ if (formatNameLength > 31)
+ formatNameLength = 31;
+
+ Stream_Write(s, szFormatName, formatNameLength);
+ Stream_Zero(s, 32 - formatNameLength);
+ }
+ else
+ {
+ wszFormatName = NULL;
+
+ if (szFormatName)
+ {
+ wszFormatName = ConvertUtf8ToWCharAlloc(szFormatName, &formatNameSize);
+
+ if (!wszFormatName)
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+ formatNameSize += 1; /* append terminating '\0' */
+ }
+
+ if (formatNameSize > 15)
+ formatNameSize = 15;
+
+ /* size in bytes instead of wchar */
+ formatNameSize *= sizeof(WCHAR);
+
+ if (wszFormatName)
+ Stream_Write(s, wszFormatName, (size_t)formatNameSize);
+
+ Stream_Zero(s, (size_t)(32 - formatNameSize));
+ free(wszFormatName);
+ }
+ }
+ }
+ else
+ {
+ length = 0;
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ length += 4;
+ formatNameSize = sizeof(WCHAR);
+
+ if (format->formatName)
+ {
+ SSIZE_T size = ConvertUtf8ToWChar(format->formatName, NULL, 0);
+ if (size < 0)
+ return NULL;
+ formatNameSize = (size_t)(size + 1) * sizeof(WCHAR);
+ }
+
+ length += (UINT32)formatNameSize;
+ }
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST, 0, length);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return NULL;
+ }
+
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ format = (CLIPRDR_FORMAT*)&(formatList->formats[index]);
+ Stream_Write_UINT32(s, format->formatId); /* formatId (4 bytes) */
+
+ if (format->formatName)
+ {
+ const size_t cap = Stream_Capacity(s);
+ const size_t pos = Stream_GetPosition(s);
+ const size_t rem = cap - pos;
+ if ((cap < pos) || ((rem / 2) > INT_MAX))
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+
+ const size_t len = strnlen(format->formatName, rem / sizeof(WCHAR));
+ if (Stream_Write_UTF16_String_From_UTF8(s, len + 1, format->formatName, len, TRUE) <
+ 0)
+ {
+ Stream_Free(s, TRUE);
+ return NULL;
+ }
+ }
+ else
+ {
+ Stream_Write_UINT16(s, 0);
+ }
+ }
+ }
+
+ return s;
+}
+UINT cliprdr_read_unlock_clipdata(wStream* s, CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, unlockClipboardData->clipDataId); /* clipDataId (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_data_request(wStream* s, CLIPRDR_FORMAT_DATA_REQUEST* request)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, request->requestedFormatId); /* requestedFormatId (4 bytes) */
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_data_response(wStream* s, CLIPRDR_FORMAT_DATA_RESPONSE* response)
+{
+ response->requestedFormatData = NULL;
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, response->common.dataLen))
+ return ERROR_INVALID_DATA;
+
+ if (response->common.dataLen)
+ response->requestedFormatData = Stream_ConstPointer(s);
+
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* request)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return ERROR_INVALID_DATA;
+
+ request->haveClipDataId = FALSE;
+ Stream_Read_UINT32(s, request->streamId); /* streamId (4 bytes) */
+ Stream_Read_UINT32(s, request->listIndex); /* listIndex (4 bytes) */
+ Stream_Read_UINT32(s, request->dwFlags); /* dwFlags (4 bytes) */
+ Stream_Read_UINT32(s, request->nPositionLow); /* nPositionLow (4 bytes) */
+ Stream_Read_UINT32(s, request->nPositionHigh); /* nPositionHigh (4 bytes) */
+ Stream_Read_UINT32(s, request->cbRequested); /* cbRequested (4 bytes) */
+
+ if (Stream_GetRemainingLength(s) >= 4)
+ {
+ Stream_Read_UINT32(s, request->clipDataId); /* clipDataId (4 bytes) */
+ request->haveClipDataId = TRUE;
+ }
+
+ if (!cliprdr_validate_file_contents_request(request))
+ return ERROR_BAD_ARGUMENTS;
+
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_file_contents_response(wStream* s, CLIPRDR_FILE_CONTENTS_RESPONSE* response)
+{
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, response->streamId); /* streamId (4 bytes) */
+ response->requestedData = Stream_ConstPointer(s); /* requestedFileContentsData */
+
+ WINPR_ASSERT(response->common.dataLen >= 4);
+ response->cbRequested = response->common.dataLen - 4;
+ return CHANNEL_RC_OK;
+}
+
+UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList, BOOL useLongFormatNames)
+{
+ UINT32 index = 0;
+ int formatNameLength = 0;
+ const char* szFormatName = NULL;
+ const WCHAR* wszFormatName = NULL;
+ wStream sub1buffer = { 0 };
+ CLIPRDR_FORMAT* formats = NULL;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ const BOOL asciiNames = (formatList->common.msgFlags & CB_ASCII_NAMES) ? TRUE : FALSE;
+
+ index = 0;
+ /* empty format list */
+ formatList->formats = NULL;
+ formatList->numFormats = 0;
+
+ wStream* sub1 =
+ Stream_StaticConstInit(&sub1buffer, Stream_ConstPointer(s), formatList->common.dataLen);
+ if (!Stream_SafeSeek(s, formatList->common.dataLen))
+ return ERROR_INVALID_DATA;
+
+ if (!formatList->common.dataLen)
+ {
+ }
+ else if (!useLongFormatNames)
+ {
+ const size_t cap = Stream_Capacity(sub1);
+ formatList->numFormats = (cap / 36);
+
+ if ((formatList->numFormats * 36) != cap)
+ {
+ WLog_ERR(TAG, "Invalid short format list length: %" PRIuz "", cap);
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (formatList->numFormats)
+ formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT));
+
+ if (!formats)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ formatList->formats = formats;
+
+ while (Stream_GetRemainingLength(sub1) >= 4)
+ {
+ CLIPRDR_FORMAT* format = &formats[index];
+
+ Stream_Read_UINT32(sub1, format->formatId); /* formatId (4 bytes) */
+
+ /* According to MS-RDPECLIP 2.2.3.1.1.1 formatName is "a 32-byte block containing
+ * the *null-terminated* name assigned to the Clipboard Format: (32 ASCII 8 characters
+ * or 16 Unicode characters)"
+ * However, both Windows RDSH and mstsc violate this specs as seen in the following
+ * example of a transferred short format name string: [R.i.c.h. .T.e.x.t. .F.o.r.m.a.t.]
+ * These are 16 unicode charaters - *without* terminating null !
+ */
+
+ szFormatName = Stream_ConstPointer(sub1);
+ wszFormatName = Stream_ConstPointer(sub1);
+ if (!Stream_SafeSeek(sub1, 32))
+ goto error_out;
+
+ free(format->formatName);
+ format->formatName = NULL;
+
+ if (asciiNames)
+ {
+ if (szFormatName[0])
+ {
+ /* ensure null termination */
+ format->formatName = strndup(szFormatName, 31);
+ if (!format->formatName)
+ {
+ WLog_ERR(TAG, "malloc failed!");
+ error = CHANNEL_RC_NO_MEMORY;
+ goto error_out;
+ }
+ }
+ }
+ else
+ {
+ if (wszFormatName[0])
+ {
+ format->formatName = ConvertWCharNToUtf8Alloc(wszFormatName, 16, NULL);
+ if (!format->formatName)
+ goto error_out;
+ }
+ }
+
+ index++;
+ }
+ }
+ else
+ {
+ wStream sub2buffer = sub1buffer;
+ wStream* sub2 = &sub2buffer;
+
+ while (Stream_GetRemainingLength(sub1) > 0)
+ {
+ size_t rest = 0;
+ if (!Stream_SafeSeek(sub1, 4)) /* formatId (4 bytes) */
+ goto error_out;
+
+ wszFormatName = Stream_ConstPointer(sub1);
+ rest = Stream_GetRemainingLength(sub1);
+ formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR));
+
+ if (!Stream_SafeSeek(sub1, (formatNameLength + 1) * sizeof(WCHAR)))
+ goto error_out;
+ formatList->numFormats++;
+ }
+
+ if (formatList->numFormats)
+ formats = (CLIPRDR_FORMAT*)calloc(formatList->numFormats, sizeof(CLIPRDR_FORMAT));
+
+ if (!formats)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ formatList->formats = formats;
+
+ while (Stream_GetRemainingLength(sub2) >= 4)
+ {
+ size_t rest = 0;
+ CLIPRDR_FORMAT* format = &formats[index];
+
+ Stream_Read_UINT32(sub2, format->formatId); /* formatId (4 bytes) */
+
+ free(format->formatName);
+ format->formatName = NULL;
+
+ wszFormatName = Stream_ConstPointer(sub2);
+ rest = Stream_GetRemainingLength(sub2);
+ formatNameLength = _wcsnlen(wszFormatName, rest / sizeof(WCHAR));
+ if (!Stream_SafeSeek(sub2, (formatNameLength + 1) * sizeof(WCHAR)))
+ goto error_out;
+
+ if (formatNameLength)
+ {
+ format->formatName =
+ ConvertWCharNToUtf8Alloc(wszFormatName, formatNameLength, NULL);
+ if (!format->formatName)
+ goto error_out;
+ }
+
+ index++;
+ }
+ }
+
+ return CHANNEL_RC_OK;
+
+error_out:
+ cliprdr_free_format_list(formatList);
+ return error;
+}
+
+void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList)
+{
+ if (formatList == NULL)
+ return;
+
+ if (formatList->formats)
+ {
+ for (UINT32 index = 0; index < formatList->numFormats; index++)
+ {
+ free(formatList->formats[index].formatName);
+ }
+
+ free(formatList->formats);
+ formatList->formats = NULL;
+ formatList->numFormats = 0;
+ }
+}
diff --git a/channels/cliprdr/cliprdr_common.h b/channels/cliprdr/cliprdr_common.h
new file mode 100644
index 0000000..b5d36b9
--- /dev/null
+++ b/channels/cliprdr/cliprdr_common.h
@@ -0,0 +1,61 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Cliprdr common
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
+ * Copyright 2019 Kobi Mizrachi <kmizrachi18@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 FREERDP_CHANNEL_RDPECLIP_COMMON_H
+#define FREERDP_CHANNEL_RDPECLIP_COMMON_H
+
+#include <winpr/crt.h>
+#include <winpr/stream.h>
+
+#include <freerdp/channels/cliprdr.h>
+#include <freerdp/api.h>
+
+FREERDP_LOCAL wStream* cliprdr_packet_new(UINT16 msgType, UINT16 msgFlags, UINT32 dataLen);
+FREERDP_LOCAL wStream*
+cliprdr_packet_lock_clipdata_new(const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+FREERDP_LOCAL wStream*
+cliprdr_packet_unlock_clipdata_new(const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+FREERDP_LOCAL wStream*
+cliprdr_packet_file_contents_request_new(const CLIPRDR_FILE_CONTENTS_REQUEST* request);
+FREERDP_LOCAL wStream*
+cliprdr_packet_file_contents_response_new(const CLIPRDR_FILE_CONTENTS_RESPONSE* response);
+FREERDP_LOCAL wStream* cliprdr_packet_format_list_new(const CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames);
+
+FREERDP_LOCAL UINT cliprdr_read_lock_clipdata(wStream* s,
+ CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData);
+FREERDP_LOCAL UINT cliprdr_read_unlock_clipdata(wStream* s,
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData);
+FREERDP_LOCAL UINT cliprdr_read_format_data_request(wStream* s,
+ CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest);
+FREERDP_LOCAL UINT cliprdr_read_format_data_response(wStream* s,
+ CLIPRDR_FORMAT_DATA_RESPONSE* response);
+FREERDP_LOCAL UINT
+cliprdr_read_file_contents_request(wStream* s, CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest);
+FREERDP_LOCAL UINT cliprdr_read_file_contents_response(wStream* s,
+ CLIPRDR_FILE_CONTENTS_RESPONSE* response);
+FREERDP_LOCAL UINT cliprdr_read_format_list(wStream* s, CLIPRDR_FORMAT_LIST* formatList,
+ BOOL useLongFormatNames);
+
+FREERDP_LOCAL void cliprdr_free_format_list(CLIPRDR_FORMAT_LIST* formatList);
+
+#endif /* FREERDP_CHANNEL_RDPECLIP_COMMON_H */
diff --git a/channels/cliprdr/server/CMakeLists.txt b/channels/cliprdr/server/CMakeLists.txt
new file mode 100644
index 0000000..09c6a7e
--- /dev/null
+++ b/channels/cliprdr/server/CMakeLists.txt
@@ -0,0 +1,30 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2012 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.
+
+define_channel_server("cliprdr")
+
+set(${MODULE_PREFIX}_SRCS
+ cliprdr_main.c
+ cliprdr_main.h
+ ../cliprdr_common.h
+ ../cliprdr_common.c
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "VirtualChannelEntry")
diff --git a/channels/cliprdr/server/cliprdr_main.c b/channels/cliprdr/server/cliprdr_main.c
new file mode 100644
index 0000000..9823f17
--- /dev/null
+++ b/channels/cliprdr/server/cliprdr_main.c
@@ -0,0 +1,1528 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2015 Thincast Technologies GmbH
+ * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.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 <freerdp/config.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/print.h>
+#include <winpr/stream.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/log.h>
+#include "cliprdr_main.h"
+#include "../cliprdr_common.h"
+
+/**
+ * Initialization Sequence\n
+ * Client Server\n
+ * | |\n
+ * |<----------------------Server Clipboard Capabilities PDU-----------------|\n
+ * |<-----------------------------Monitor Ready PDU--------------------------|\n
+ * |-----------------------Client Clipboard Capabilities PDU---------------->|\n
+ * |---------------------------Temporary Directory PDU---------------------->|\n
+ * |-------------------------------Format List PDU-------------------------->|\n
+ * |<--------------------------Format List Response PDU----------------------|\n
+ *
+ */
+
+/**
+ * Data Transfer Sequences\n
+ * Shared Local\n
+ * Clipboard Owner Clipboard Owner\n
+ * | |\n
+ * |-------------------------------------------------------------------------|\n _
+ * |-------------------------------Format List PDU-------------------------->|\n |
+ * |<--------------------------Format List Response PDU----------------------|\n _| Copy
+ * Sequence
+ * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n
+ * |-------------------------------------------------------------------------|\n
+ * |-------------------------------------------------------------------------|\n _
+ * |<--------------------------Format Data Request PDU-----------------------|\n | Paste
+ * Sequence Palette,
+ * |---------------------------Format Data Response PDU--------------------->|\n _| Metafile,
+ * File List Data
+ * |-------------------------------------------------------------------------|\n
+ * |-------------------------------------------------------------------------|\n _
+ * |<------------------------Format Contents Request PDU---------------------|\n | Paste
+ * Sequence
+ * |-------------------------Format Contents Response PDU------------------->|\n _| File
+ * Stream Data
+ * |<---------------------Lock Clipboard Data PDU (Optional)-----------------|\n
+ * |-------------------------------------------------------------------------|\n
+ *
+ */
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_packet_send(CliprdrServerPrivate* cliprdr, wStream* s)
+{
+ UINT rc = 0;
+ size_t pos = 0;
+ BOOL status = 0;
+ UINT32 dataLen = 0;
+ ULONG written = 0;
+
+ WINPR_ASSERT(cliprdr);
+
+ pos = Stream_GetPosition(s);
+ if ((pos < 8) || (pos > UINT32_MAX))
+ {
+ rc = ERROR_NO_DATA;
+ goto fail;
+ }
+
+ dataLen = (UINT32)(pos - 8);
+ Stream_SetPosition(s, 4);
+ Stream_Write_UINT32(s, dataLen);
+ if (pos > UINT32_MAX)
+ {
+ rc = ERROR_INVALID_DATA;
+ goto fail;
+ }
+
+ status = WTSVirtualChannelWrite(cliprdr->ChannelHandle, (PCHAR)Stream_Buffer(s), (UINT32)pos,
+ &written);
+ rc = status ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+fail:
+ Stream_Free(s, TRUE);
+ return rc;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_capabilities(CliprdrServerContext* context,
+ const CLIPRDR_CAPABILITIES* capabilities)
+{
+ size_t offset = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(capabilities);
+
+ CliprdrServerPrivate* cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (capabilities->common.msgType != CB_CLIP_CAPS)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, capabilities->common.msgType);
+
+ if (capabilities->cCapabilitiesSets > UINT16_MAX)
+ {
+ WLog_ERR(TAG, "Invalid number of capability sets in clipboard caps");
+ return ERROR_INVALID_PARAMETER;
+ }
+
+ s = cliprdr_packet_new(CB_CLIP_CAPS, 0, 4 + CB_CAPSTYPE_GENERAL_LEN);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT16(s,
+ (UINT16)capabilities->cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Write_UINT16(s, 0); /* pad1 (2 bytes) */
+ for (UINT32 x = 0; x < capabilities->cCapabilitiesSets; x++)
+ {
+ const CLIPRDR_CAPABILITY_SET* cap =
+ (const CLIPRDR_CAPABILITY_SET*)(((const BYTE*)capabilities->capabilitySets) + offset);
+ offset += cap->capabilitySetLength;
+
+ switch (cap->capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ {
+ const CLIPRDR_GENERAL_CAPABILITY_SET* generalCapabilitySet =
+ (const CLIPRDR_GENERAL_CAPABILITY_SET*)cap;
+ Stream_Write_UINT16(
+ s, generalCapabilitySet->capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Write_UINT16(
+ s, generalCapabilitySet->capabilitySetLength); /* lengthCapability (2 bytes) */
+ Stream_Write_UINT32(s, generalCapabilitySet->version); /* version (4 bytes) */
+ Stream_Write_UINT32(
+ s, generalCapabilitySet->generalFlags); /* generalFlags (4 bytes) */
+ }
+ break;
+
+ default:
+ WLog_WARN(TAG, "Unknown capability set type %08" PRIx16, cap->capabilitySetType);
+ if (!Stream_SafeSeek(s, cap->capabilitySetLength))
+ {
+ WLog_ERR(TAG, "short stream");
+ return ERROR_NO_DATA;
+ }
+ break;
+ }
+ }
+ WLog_DBG(TAG, "ServerCapabilities");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_monitor_ready(CliprdrServerContext* context,
+ const CLIPRDR_MONITOR_READY* monitorReady)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(monitorReady);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (monitorReady->common.msgType != CB_MONITOR_READY)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, monitorReady->common.msgType);
+
+ s = cliprdr_packet_new(CB_MONITOR_READY, monitorReady->common.msgFlags,
+ monitorReady->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerMonitorReady");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_format_list(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST* formatList)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatList);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ s = cliprdr_packet_format_list_new(formatList, context->useLongFormatNames);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_format_list_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFormatList: numFormats: %" PRIu32 "", formatList->numFormats);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_format_list_response(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_LIST_RESPONSE* formatListResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatListResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (formatListResponse->common.msgType != CB_FORMAT_LIST_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatListResponse->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_LIST_RESPONSE, formatListResponse->common.msgFlags,
+ formatListResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFormatListResponse");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_lock_clipboard_data(CliprdrServerContext* context,
+ const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(lockClipboardData);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (lockClipboardData->common.msgType != CB_LOCK_CLIPDATA)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, lockClipboardData->common.msgType);
+
+ s = cliprdr_packet_lock_clipdata_new(lockClipboardData);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_lock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerLockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ lockClipboardData->clipDataId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_unlock_clipboard_data(CliprdrServerContext* context,
+ const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(unlockClipboardData);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (unlockClipboardData->common.msgType != CB_UNLOCK_CLIPDATA)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, unlockClipboardData->common.msgType);
+
+ s = cliprdr_packet_unlock_clipdata_new(unlockClipboardData);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_unlock_clipdata_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerUnlockClipboardData: clipDataId: 0x%08" PRIX32 "",
+ unlockClipboardData->clipDataId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_format_data_request(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_DATA_REQUEST* formatDataRequest)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataRequest);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ if (formatDataRequest->common.msgType != CB_FORMAT_DATA_REQUEST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataRequest->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_REQUEST, formatDataRequest->common.msgFlags,
+ formatDataRequest->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write_UINT32(s, formatDataRequest->requestedFormatId); /* requestedFormatId (4 bytes) */
+ WLog_DBG(TAG, "ClientFormatDataRequest");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_format_data_response(CliprdrServerContext* context,
+ const CLIPRDR_FORMAT_DATA_RESPONSE* formatDataResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(formatDataResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (formatDataResponse->common.msgType != CB_FORMAT_DATA_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, formatDataResponse->common.msgType);
+
+ s = cliprdr_packet_new(CB_FORMAT_DATA_RESPONSE, formatDataResponse->common.msgFlags,
+ formatDataResponse->common.dataLen);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Write(s, formatDataResponse->requestedFormatData, formatDataResponse->common.dataLen);
+ WLog_DBG(TAG, "ServerFormatDataResponse");
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_file_contents_request(CliprdrServerContext* context,
+ const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsRequest);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (fileContentsRequest->common.msgType != CB_FILECONTENTS_REQUEST)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsRequest->common.msgType);
+
+ s = cliprdr_packet_file_contents_request_new(fileContentsRequest);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_request_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFileContentsRequest: streamId: 0x%08" PRIX32 "",
+ fileContentsRequest->streamId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT
+cliprdr_server_file_contents_response(CliprdrServerContext* context,
+ const CLIPRDR_FILE_CONTENTS_RESPONSE* fileContentsResponse)
+{
+ wStream* s = NULL;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(fileContentsResponse);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (fileContentsResponse->common.msgType != CB_FILECONTENTS_RESPONSE)
+ WLog_WARN(TAG, "called with invalid type %08" PRIx32, fileContentsResponse->common.msgType);
+
+ s = cliprdr_packet_file_contents_response_new(fileContentsResponse);
+ if (!s)
+ {
+ WLog_ERR(TAG, "cliprdr_packet_file_contents_response_new failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ WLog_DBG(TAG, "ServerFileContentsResponse: streamId: 0x%08" PRIX32 "",
+ fileContentsResponse->streamId);
+ return cliprdr_server_packet_send(cliprdr, s);
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_general_capability(CliprdrServerContext* context, wStream* s,
+ CLIPRDR_GENERAL_CAPABILITY_SET* cap_set)
+{
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(cap_set);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT32(s, cap_set->version); /* version (4 bytes) */
+ Stream_Read_UINT32(s, cap_set->generalFlags); /* generalFlags (4 bytes) */
+
+ if (context->useLongFormatNames)
+ context->useLongFormatNames =
+ (cap_set->generalFlags & CB_USE_LONG_FORMAT_NAMES) ? TRUE : FALSE;
+
+ if (context->streamFileClipEnabled)
+ context->streamFileClipEnabled =
+ (cap_set->generalFlags & CB_STREAM_FILECLIP_ENABLED) ? TRUE : FALSE;
+
+ if (context->fileClipNoFilePaths)
+ context->fileClipNoFilePaths =
+ (cap_set->generalFlags & CB_FILECLIP_NO_FILE_PATHS) ? TRUE : FALSE;
+
+ if (context->canLockClipData)
+ context->canLockClipData = (cap_set->generalFlags & CB_CAN_LOCK_CLIPDATA) ? TRUE : FALSE;
+
+ if (context->hasHugeFileSupport)
+ context->hasHugeFileSupport =
+ (cap_set->generalFlags & CB_HUGE_FILE_SUPPORT_ENABLED) ? TRUE : FALSE;
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_capabilities(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ UINT16 capabilitySetType = 0;
+ UINT16 capabilitySetLength = 0;
+ UINT error = ERROR_INVALID_DATA;
+ size_t cap_sets_size = 0;
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+ CLIPRDR_CAPABILITY_SET* capSet = NULL;
+
+ WINPR_ASSERT(context);
+ WINPR_UNUSED(header);
+
+ WLog_DBG(TAG, "CliprdrClientCapabilities");
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ Stream_Read_UINT16(s, capabilities.cCapabilitiesSets); /* cCapabilitiesSets (2 bytes) */
+ Stream_Seek_UINT16(s); /* pad1 (2 bytes) */
+
+ for (size_t index = 0; index < capabilities.cCapabilitiesSets; index++)
+ {
+ void* tmp = NULL;
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ goto out;
+ Stream_Read_UINT16(s, capabilitySetType); /* capabilitySetType (2 bytes) */
+ Stream_Read_UINT16(s, capabilitySetLength); /* capabilitySetLength (2 bytes) */
+
+ cap_sets_size += capabilitySetLength;
+
+ if (cap_sets_size > 0)
+ tmp = realloc(capabilities.capabilitySets, cap_sets_size);
+ if (tmp == NULL)
+ {
+ WLog_ERR(TAG, "capabilities.capabilitySets realloc failed!");
+ free(capabilities.capabilitySets);
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)tmp;
+
+ capSet = &(capabilities.capabilitySets[index]);
+
+ capSet->capabilitySetType = capabilitySetType;
+ capSet->capabilitySetLength = capabilitySetLength;
+
+ switch (capSet->capabilitySetType)
+ {
+ case CB_CAPSTYPE_GENERAL:
+ error = cliprdr_server_receive_general_capability(
+ context, s, (CLIPRDR_GENERAL_CAPABILITY_SET*)capSet);
+ if (error)
+ {
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_general_capability failed with error %" PRIu32
+ "",
+ error);
+ goto out;
+ }
+ break;
+
+ default:
+ WLog_ERR(TAG, "unknown cliprdr capability set: %" PRIu16 "",
+ capSet->capabilitySetType);
+ goto out;
+ }
+ }
+
+ error = CHANNEL_RC_OK;
+ IFCALLRET(context->ClientCapabilities, error, context, &capabilities);
+out:
+ free(capabilities.capabilitySets);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_temporary_directory(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ size_t length = 0;
+ CLIPRDR_TEMP_DIRECTORY tempDirectory = { 0 };
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_UNUSED(header);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s,
+ ARRAYSIZE(cliprdr->temporaryDirectory) * sizeof(WCHAR)))
+ return CHANNEL_RC_NO_MEMORY;
+
+ const WCHAR* wszTempDir = Stream_ConstPointer(s);
+
+ if (wszTempDir[ARRAYSIZE(cliprdr->temporaryDirectory) - 1] != 0)
+ {
+ WLog_ERR(TAG, "wszTempDir[259] was not 0");
+ return ERROR_INVALID_DATA;
+ }
+
+ if (ConvertWCharNToUtf8(wszTempDir, ARRAYSIZE(cliprdr->temporaryDirectory),
+ cliprdr->temporaryDirectory,
+ ARRAYSIZE(cliprdr->temporaryDirectory)) < 0)
+ {
+ WLog_ERR(TAG, "failed to convert temporary directory name");
+ return ERROR_INVALID_DATA;
+ }
+
+ length = strnlen(cliprdr->temporaryDirectory, ARRAYSIZE(cliprdr->temporaryDirectory));
+
+ if (length >= ARRAYSIZE(cliprdr->temporaryDirectory))
+ length = ARRAYSIZE(cliprdr->temporaryDirectory) - 1;
+
+ CopyMemory(tempDirectory.szTempDir, cliprdr->temporaryDirectory, length);
+ tempDirectory.szTempDir[length] = '\0';
+ WLog_DBG(TAG, "CliprdrTemporaryDirectory: %s", cliprdr->temporaryDirectory);
+ IFCALLRET(context->TempDirectory, error, context, &tempDirectory);
+
+ if (error)
+ WLog_ERR(TAG, "TempDirectory failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_list(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_LIST formatList = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ formatList.common.msgType = CB_FORMAT_LIST;
+ formatList.common.msgFlags = header->msgFlags;
+ formatList.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_list(s, &formatList, context->useLongFormatNames)))
+ goto out;
+
+ WLog_DBG(TAG, "ClientFormatList: numFormats: %" PRIu32 "", formatList.numFormats);
+ IFCALLRET(context->ClientFormatList, error, context, &formatList);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatList failed with error %" PRIu32 "!", error);
+
+out:
+ cliprdr_free_format_list(&formatList);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_list_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WINPR_UNUSED(s);
+ WLog_DBG(TAG, "CliprdrClientFormatListResponse");
+ formatListResponse.common.msgType = CB_FORMAT_LIST_RESPONSE;
+ formatListResponse.common.msgFlags = header->msgFlags;
+ formatListResponse.common.dataLen = header->dataLen;
+ IFCALLRET(context->ClientFormatListResponse, error, context, &formatListResponse);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatListResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_lock_clipdata(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_LOCK_CLIPBOARD_DATA lockClipboardData = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientLockClipData");
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
+ return ERROR_INVALID_DATA;
+
+ lockClipboardData.common.msgType = CB_LOCK_CLIPDATA;
+ lockClipboardData.common.msgFlags = header->msgFlags;
+ lockClipboardData.common.dataLen = header->dataLen;
+ Stream_Read_UINT32(s, lockClipboardData.clipDataId); /* clipDataId (4 bytes) */
+ IFCALLRET(context->ClientLockClipboardData, error, context, &lockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ClientLockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_unlock_clipdata(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_UNLOCK_CLIPBOARD_DATA unlockClipboardData = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientUnlockClipData");
+
+ unlockClipboardData.common.msgType = CB_UNLOCK_CLIPDATA;
+ unlockClipboardData.common.msgFlags = header->msgFlags;
+ unlockClipboardData.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_unlock_clipdata(s, &unlockClipboardData)))
+ return error;
+
+ IFCALLRET(context->ClientUnlockClipboardData, error, context, &unlockClipboardData);
+
+ if (error)
+ WLog_ERR(TAG, "ClientUnlockClipboardData failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_data_request(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_DATA_REQUEST formatDataRequest = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFormatDataRequest");
+ formatDataRequest.common.msgType = CB_FORMAT_DATA_REQUEST;
+ formatDataRequest.common.msgFlags = header->msgFlags;
+ formatDataRequest.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_data_request(s, &formatDataRequest)))
+ return error;
+
+ context->lastRequestedFormatId = formatDataRequest.requestedFormatId;
+ IFCALLRET(context->ClientFormatDataRequest, error, context, &formatDataRequest);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatDataRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_format_data_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FORMAT_DATA_RESPONSE formatDataResponse = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFormatDataResponse");
+ formatDataResponse.common.msgType = CB_FORMAT_DATA_RESPONSE;
+ formatDataResponse.common.msgFlags = header->msgFlags;
+ formatDataResponse.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_format_data_response(s, &formatDataResponse)))
+ return error;
+
+ IFCALLRET(context->ClientFormatDataResponse, error, context, &formatDataResponse);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFormatDataResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_filecontents_request(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FILE_CONTENTS_REQUEST request = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFileContentsRequest");
+ request.common.msgType = CB_FILECONTENTS_REQUEST;
+ request.common.msgFlags = header->msgFlags;
+ request.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_file_contents_request(s, &request)))
+ return error;
+
+ if (!context->hasHugeFileSupport)
+ {
+ if (request.nPositionHigh > 0)
+ return ERROR_INVALID_DATA;
+ if ((UINT64)request.nPositionLow + request.cbRequested > UINT32_MAX)
+ return ERROR_INVALID_DATA;
+ }
+ IFCALLRET(context->ClientFileContentsRequest, error, context, &request);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFileContentsRequest failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_filecontents_response(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ CLIPRDR_FILE_CONTENTS_RESPONSE response = { 0 };
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG, "CliprdrClientFileContentsResponse");
+
+ response.common.msgType = CB_FILECONTENTS_RESPONSE;
+ response.common.msgFlags = header->msgFlags;
+ response.common.dataLen = header->dataLen;
+
+ if ((error = cliprdr_read_file_contents_response(s, &response)))
+ return error;
+
+ IFCALLRET(context->ClientFileContentsResponse, error, context, &response);
+
+ if (error)
+ WLog_ERR(TAG, "ClientFileContentsResponse failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_receive_pdu(CliprdrServerContext* context, wStream* s,
+ const CLIPRDR_HEADER* header)
+{
+ UINT error = 0;
+
+ WINPR_ASSERT(context);
+ WINPR_ASSERT(header);
+
+ WLog_DBG(TAG,
+ "CliprdrServerReceivePdu: msgType: %" PRIu16 " msgFlags: 0x%04" PRIX16
+ " dataLen: %" PRIu32 "",
+ header->msgType, header->msgFlags, header->dataLen);
+
+ switch (header->msgType)
+ {
+ case CB_CLIP_CAPS:
+ if ((error = cliprdr_server_receive_capabilities(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_capabilities failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_TEMP_DIRECTORY:
+ if ((error = cliprdr_server_receive_temporary_directory(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_temporary_directory failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST:
+ if ((error = cliprdr_server_receive_format_list(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_format_list failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_LIST_RESPONSE:
+ if ((error = cliprdr_server_receive_format_list_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_list_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_LOCK_CLIPDATA:
+ if ((error = cliprdr_server_receive_lock_clipdata(context, s, header)))
+ WLog_ERR(TAG, "cliprdr_server_receive_lock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_UNLOCK_CLIPDATA:
+ if ((error = cliprdr_server_receive_unlock_clipdata(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_unlock_clipdata failed with error %" PRIu32 "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_REQUEST:
+ if ((error = cliprdr_server_receive_format_data_request(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_data_request failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FORMAT_DATA_RESPONSE:
+ if ((error = cliprdr_server_receive_format_data_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_format_data_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_REQUEST:
+ if ((error = cliprdr_server_receive_filecontents_request(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_filecontents_request failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ case CB_FILECONTENTS_RESPONSE:
+ if ((error = cliprdr_server_receive_filecontents_response(context, s, header)))
+ WLog_ERR(TAG,
+ "cliprdr_server_receive_filecontents_response failed with error %" PRIu32
+ "!",
+ error);
+
+ break;
+
+ default:
+ error = ERROR_INVALID_DATA;
+ WLog_ERR(TAG, "Unexpected clipboard PDU type: %" PRIu16 "", header->msgType);
+ break;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_init(CliprdrServerContext* context)
+{
+ UINT32 generalFlags = 0;
+ CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet = { 0 };
+ UINT error = 0;
+ CLIPRDR_MONITOR_READY monitorReady = { 0 };
+ CLIPRDR_CAPABILITIES capabilities = { 0 };
+
+ WINPR_ASSERT(context);
+
+ monitorReady.common.msgType = CB_MONITOR_READY;
+ capabilities.common.msgType = CB_CLIP_CAPS;
+
+ if (context->useLongFormatNames)
+ generalFlags |= CB_USE_LONG_FORMAT_NAMES;
+
+ if (context->streamFileClipEnabled)
+ generalFlags |= CB_STREAM_FILECLIP_ENABLED;
+
+ if (context->fileClipNoFilePaths)
+ generalFlags |= CB_FILECLIP_NO_FILE_PATHS;
+
+ if (context->canLockClipData)
+ generalFlags |= CB_CAN_LOCK_CLIPDATA;
+
+ if (context->hasHugeFileSupport)
+ generalFlags |= CB_HUGE_FILE_SUPPORT_ENABLED;
+
+ capabilities.common.msgType = CB_CLIP_CAPS;
+ capabilities.common.msgFlags = 0;
+ capabilities.common.dataLen = 4 + CB_CAPSTYPE_GENERAL_LEN;
+ capabilities.cCapabilitiesSets = 1;
+ capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET*)&generalCapabilitySet;
+ generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
+ generalCapabilitySet.capabilitySetLength = CB_CAPSTYPE_GENERAL_LEN;
+ generalCapabilitySet.version = CB_CAPS_VERSION_2;
+ generalCapabilitySet.generalFlags = generalFlags;
+
+ if ((error = context->ServerCapabilities(context, &capabilities)))
+ {
+ WLog_ERR(TAG, "ServerCapabilities failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ if ((error = context->MonitorReady(context, &monitorReady)))
+ {
+ WLog_ERR(TAG, "MonitorReady failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_read(CliprdrServerContext* context)
+{
+ wStream* s = NULL;
+ size_t position = 0;
+ DWORD BytesToRead = 0;
+ DWORD BytesReturned = 0;
+ CLIPRDR_HEADER header = { 0 };
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = 0;
+ DWORD status = 0;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ s = cliprdr->s;
+
+ if (Stream_GetPosition(s) < CLIPRDR_HEADER_LENGTH)
+ {
+ BytesReturned = 0;
+ BytesToRead = (UINT32)(CLIPRDR_HEADER_LENGTH - Stream_GetPosition(s));
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Seek(s, BytesReturned);
+ }
+
+ if (Stream_GetPosition(s) >= CLIPRDR_HEADER_LENGTH)
+ {
+ position = Stream_GetPosition(s);
+ Stream_SetPosition(s, 0);
+ Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */
+ Stream_Read_UINT32(s, header.dataLen); /* dataLen (4 bytes) */
+
+ if (!Stream_EnsureCapacity(s, (header.dataLen + CLIPRDR_HEADER_LENGTH)))
+ {
+ WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_SetPosition(s, position);
+
+ if (Stream_GetPosition(s) < (header.dataLen + CLIPRDR_HEADER_LENGTH))
+ {
+ BytesReturned = 0;
+ BytesToRead =
+ (UINT32)((header.dataLen + CLIPRDR_HEADER_LENGTH) - Stream_GetPosition(s));
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ Stream_Seek(s, BytesReturned);
+ }
+
+ if (Stream_GetPosition(s) >= (header.dataLen + CLIPRDR_HEADER_LENGTH))
+ {
+ Stream_SetPosition(s, (header.dataLen + CLIPRDR_HEADER_LENGTH));
+ Stream_SealLength(s);
+ Stream_SetPosition(s, CLIPRDR_HEADER_LENGTH);
+
+ if ((error = cliprdr_server_receive_pdu(context, s, &header)))
+ {
+ WLog_ERR(TAG, "cliprdr_server_receive_pdu failed with error code %" PRIu32 "!",
+ error);
+ return error;
+ }
+
+ Stream_SetPosition(s, 0);
+ /* check for trailing zero bytes */
+ status = WaitForSingleObject(cliprdr->ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ if (status == WAIT_TIMEOUT)
+ return CHANNEL_RC_OK;
+
+ BytesReturned = 0;
+ BytesToRead = 4;
+
+ if (!WTSVirtualChannelRead(cliprdr->ChannelHandle, 0, Stream_Pointer(s), BytesToRead,
+ &BytesReturned))
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (BytesReturned == 4)
+ {
+ Stream_Read_UINT16(s, header.msgType); /* msgType (2 bytes) */
+ Stream_Read_UINT16(s, header.msgFlags); /* msgFlags (2 bytes) */
+
+ if (!header.msgType)
+ {
+ /* ignore trailing bytes */
+ Stream_SetPosition(s, 0);
+ }
+ }
+ else
+ {
+ Stream_Seek(s, BytesReturned);
+ }
+ }
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+static DWORD WINAPI cliprdr_server_thread(LPVOID arg)
+{
+ DWORD status = 0;
+ DWORD nCount = 0;
+ HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 };
+ HANDLE ChannelEvent = NULL;
+ CliprdrServerContext* context = (CliprdrServerContext*)arg;
+ CliprdrServerPrivate* cliprdr = NULL;
+ UINT error = CHANNEL_RC_OK;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ ChannelEvent = context->GetEventHandle(context);
+
+ events[nCount++] = cliprdr->StopEvent;
+ events[nCount++] = ChannelEvent;
+
+ if (context->autoInitializationSequence)
+ {
+ if ((error = cliprdr_server_init(context)))
+ {
+ WLog_ERR(TAG, "cliprdr_server_init failed with error %" PRIu32 "!", error);
+ goto out;
+ }
+ }
+
+ while (1)
+ {
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ status = WaitForSingleObject(cliprdr->StopEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ break;
+
+ status = WaitForSingleObject(ChannelEvent, 0);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ goto out;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ {
+ if ((error = context->CheckEventHandle(context)))
+ {
+ WLog_ERR(TAG, "CheckEventHandle failed with error %" PRIu32 "!", error);
+ break;
+ }
+ }
+ }
+
+out:
+
+ if (error && context->rdpcontext)
+ setChannelError(context->rdpcontext, error, "cliprdr_server_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_open(CliprdrServerContext* context)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ cliprdr->ChannelHandle =
+ WTSVirtualChannelOpen(cliprdr->vcm, WTS_CURRENT_SESSION, CLIPRDR_SVC_CHANNEL_NAME);
+
+ if (!cliprdr->ChannelHandle)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelOpen for cliprdr failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ cliprdr->ChannelEvent = NULL;
+
+ if (WTSVirtualChannelQuery(cliprdr->ChannelHandle, WTSVirtualEventHandle, &buffer,
+ &BytesReturned))
+ {
+ if (BytesReturned != sizeof(HANDLE))
+ {
+ WLog_ERR(TAG, "BytesReturned has not size of HANDLE!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ CopyMemory(&(cliprdr->ChannelEvent), buffer, sizeof(HANDLE));
+ WTSFreeMemory(buffer);
+ }
+
+ if (!cliprdr->ChannelEvent)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelQuery for cliprdr failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_close(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (cliprdr->ChannelHandle)
+ {
+ WTSVirtualChannelClose(cliprdr->ChannelHandle);
+ cliprdr->ChannelHandle = NULL;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_start(CliprdrServerContext* context)
+{
+ UINT error = 0;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (!cliprdr->ChannelHandle)
+ {
+ if ((error = context->Open(context)))
+ {
+ WLog_ERR(TAG, "Open failed!");
+ return error;
+ }
+ }
+
+ if (!(cliprdr->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ WLog_ERR(TAG, "CreateEvent failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (!(cliprdr->Thread = CreateThread(NULL, 0, cliprdr_server_thread, (void*)context, 0, NULL)))
+ {
+ WLog_ERR(TAG, "CreateThread failed!");
+ CloseHandle(cliprdr->StopEvent);
+ cliprdr->StopEvent = NULL;
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_stop(CliprdrServerContext* context)
+{
+ UINT error = CHANNEL_RC_OK;
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+
+ if (cliprdr->StopEvent)
+ {
+ SetEvent(cliprdr->StopEvent);
+
+ if (WaitForSingleObject(cliprdr->Thread, INFINITE) == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error);
+ return error;
+ }
+
+ CloseHandle(cliprdr->Thread);
+ CloseHandle(cliprdr->StopEvent);
+ }
+
+ if (cliprdr->ChannelHandle)
+ return context->Close(context);
+
+ return error;
+}
+
+static HANDLE cliprdr_server_get_event_handle(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ WINPR_ASSERT(context);
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+ WINPR_ASSERT(cliprdr);
+ return cliprdr->ChannelEvent;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT cliprdr_server_check_event_handle(CliprdrServerContext* context)
+{
+ return cliprdr_server_read(context);
+}
+
+CliprdrServerContext* cliprdr_server_context_new(HANDLE vcm)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+ CliprdrServerContext* context = (CliprdrServerContext*)calloc(1, sizeof(CliprdrServerContext));
+
+ if (context)
+ {
+ context->autoInitializationSequence = TRUE;
+ context->Open = cliprdr_server_open;
+ context->Close = cliprdr_server_close;
+ context->Start = cliprdr_server_start;
+ context->Stop = cliprdr_server_stop;
+ context->GetEventHandle = cliprdr_server_get_event_handle;
+ context->CheckEventHandle = cliprdr_server_check_event_handle;
+ context->ServerCapabilities = cliprdr_server_capabilities;
+ context->MonitorReady = cliprdr_server_monitor_ready;
+ context->ServerFormatList = cliprdr_server_format_list;
+ context->ServerFormatListResponse = cliprdr_server_format_list_response;
+ context->ServerLockClipboardData = cliprdr_server_lock_clipboard_data;
+ context->ServerUnlockClipboardData = cliprdr_server_unlock_clipboard_data;
+ context->ServerFormatDataRequest = cliprdr_server_format_data_request;
+ context->ServerFormatDataResponse = cliprdr_server_format_data_response;
+ context->ServerFileContentsRequest = cliprdr_server_file_contents_request;
+ context->ServerFileContentsResponse = cliprdr_server_file_contents_response;
+ cliprdr = context->handle = (CliprdrServerPrivate*)calloc(1, sizeof(CliprdrServerPrivate));
+
+ if (cliprdr)
+ {
+ cliprdr->vcm = vcm;
+ cliprdr->s = Stream_New(NULL, 4096);
+
+ if (!cliprdr->s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ free(context->handle);
+ free(context);
+ return NULL;
+ }
+ }
+ else
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ free(context);
+ return NULL;
+ }
+ }
+
+ return context;
+}
+
+void cliprdr_server_context_free(CliprdrServerContext* context)
+{
+ CliprdrServerPrivate* cliprdr = NULL;
+
+ if (!context)
+ return;
+
+ cliprdr = (CliprdrServerPrivate*)context->handle;
+
+ if (cliprdr)
+ {
+ Stream_Free(cliprdr->s, TRUE);
+ }
+
+ free(context->handle);
+ free(context);
+}
diff --git a/channels/cliprdr/server/cliprdr_main.h b/channels/cliprdr/server/cliprdr_main.h
new file mode 100644
index 0000000..f2e7162
--- /dev/null
+++ b/channels/cliprdr/server/cliprdr_main.h
@@ -0,0 +1,47 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Clipboard Virtual Channel Extension
+ *
+ * Copyright 2013 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 FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
+#define FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H
+
+#include <winpr/crt.h>
+#include <winpr/synch.h>
+#include <winpr/stream.h>
+#include <winpr/thread.h>
+
+#include <freerdp/server/cliprdr.h>
+#include <freerdp/channels/log.h>
+
+#define TAG CHANNELS_TAG("cliprdr.server")
+
+#define CLIPRDR_HEADER_LENGTH 8
+
+typedef struct
+{
+ HANDLE vcm;
+ HANDLE Thread;
+ HANDLE StopEvent;
+ void* ChannelHandle;
+ HANDLE ChannelEvent;
+
+ wStream* s;
+ char temporaryDirectory[260];
+} CliprdrServerPrivate;
+
+#endif /* FREERDP_CHANNEL_CLIPRDR_SERVER_MAIN_H */