mirror of
https://github.com/morgan9e/grd
synced 2026-04-13 16:04:13 +09:00
2675 lines
95 KiB
C
2675 lines
95 KiB
C
/*
|
|
* Copyright (C) 2020-2021 Pascal Nowack
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
|
* 02111-1307, USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "grd-clipboard-rdp.h"
|
|
|
|
#include <glib/gstdio.h>
|
|
#include <winpr/clipboard.h>
|
|
|
|
#include "grd-rdp-fuse-clipboard.h"
|
|
#include "grd-session-rdp.h"
|
|
|
|
#define CLIPRDR_FILEDESCRIPTOR_SIZE (4 + 32 + 4 + 16 + 8 + 4 + 4 + 520)
|
|
#define MAX_WAIT_TIME_MS 4000
|
|
|
|
/* Format strings of formats with dynamically assigned ids */
|
|
#define GRD_CFSTR_MIME_HTML "HTML Format"
|
|
#define GRD_CFSTR_MIME_TEXT_HTML "text/html"
|
|
#define GRD_CFSTR_MIME_PNG "image/png"
|
|
#define GRD_CFSTR_MIME_JPEG "image/jpeg"
|
|
#define GRD_CFSTR_MIME_GIF "image/gif"
|
|
|
|
#define GRD_CF_HTML 0xD010
|
|
#define GRD_CF_PNG 0xD011
|
|
#define GRD_CF_JPEG 0xD012
|
|
#define GRD_CF_GIF 0xD013
|
|
#define GRD_CF_TEXT_URILIST 0xD014
|
|
|
|
typedef struct _ServerFormatListUpdateContext
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp;
|
|
GList *mime_type_tables;
|
|
} ServerFormatListUpdateContext;
|
|
|
|
typedef struct _ServerFormatDataRequestContext
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp;
|
|
GrdMimeType mime_type;
|
|
uint32_t src_format_id;
|
|
uint32_t dst_format_id;
|
|
gboolean needs_null_terminator;
|
|
gboolean needs_conversion;
|
|
} ServerFormatDataRequestContext;
|
|
|
|
typedef struct _ClientFormatDataRequestContext
|
|
{
|
|
GrdMimeTypeTable mime_type_table;
|
|
|
|
gboolean has_clip_data_id;
|
|
uint32_t clip_data_id;
|
|
} ClientFormatDataRequestContext;
|
|
|
|
typedef struct _ClipDataEntry
|
|
{
|
|
uint32_t id;
|
|
|
|
wClipboard *system;
|
|
wClipboardDelegate *delegate;
|
|
uint64_t serial;
|
|
|
|
gboolean is_independent;
|
|
gboolean has_file_list;
|
|
gboolean requests_allowed;
|
|
} ClipDataEntry;
|
|
|
|
typedef struct _FormatData
|
|
{
|
|
uint8_t *data;
|
|
uint32_t size;
|
|
} FormatData;
|
|
|
|
struct _GrdClipboardRdp
|
|
{
|
|
GrdClipboard parent;
|
|
|
|
CliprdrServerContext *cliprdr_context;
|
|
gboolean protocol_stopped;
|
|
|
|
gboolean relieve_filename_restriction;
|
|
|
|
wClipboard *system;
|
|
wClipboardDelegate *delegate;
|
|
gboolean has_file_list;
|
|
uint64_t serial;
|
|
|
|
GHashTable *allowed_server_formats;
|
|
GList *pending_server_formats;
|
|
GList *queued_server_formats;
|
|
gboolean server_file_contents_requests_allowed;
|
|
uint16_t format_list_response_msg_flags;
|
|
|
|
GHashTable *serial_entry_table;
|
|
GHashTable *clip_data_table;
|
|
struct
|
|
{
|
|
ClipDataEntry *entry;
|
|
ClipDataEntry *entry_to_replace;
|
|
gboolean serial_already_in_use;
|
|
} clipboard_retrieval_context;
|
|
struct
|
|
{
|
|
ClipDataEntry *entry;
|
|
} clipboard_destruction_context;
|
|
|
|
char *fuse_mount_path;
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard;
|
|
GHashTable *format_data_cache;
|
|
|
|
GrdMimeType which_unicode_format;
|
|
ServerFormatDataRequestContext *format_data_request_context;
|
|
|
|
GHashTable *pending_client_requests;
|
|
GQueue *ordered_client_requests;
|
|
|
|
GMutex client_request_mutex;
|
|
ClientFormatDataRequestContext *current_client_request;
|
|
CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response;
|
|
unsigned int client_format_data_response_id;
|
|
|
|
unsigned int pending_server_formats_drop_id;
|
|
unsigned int client_request_abort_id;
|
|
|
|
GCond completion_cond;
|
|
GMutex completion_mutex;
|
|
|
|
GMutex clip_data_entry_mutex;
|
|
unsigned int clipboard_retrieval_id;
|
|
unsigned int clipboard_destruction_id;
|
|
gboolean completed_clip_data_entry;
|
|
|
|
GMutex server_format_list_update_mutex;
|
|
unsigned int server_format_list_update_id;
|
|
gboolean completed_format_list;
|
|
|
|
GMutex client_format_list_response_mutex;
|
|
unsigned int client_format_list_response_id;
|
|
|
|
GMutex server_format_data_request_mutex;
|
|
unsigned int server_format_data_request_id;
|
|
gboolean completed_format_data_request;
|
|
};
|
|
|
|
G_DEFINE_TYPE (GrdClipboardRdp, grd_clipboard_rdp, GRD_TYPE_CLIPBOARD)
|
|
|
|
static gboolean
|
|
send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeTypeTable *mime_type_table);
|
|
|
|
static void
|
|
create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp);
|
|
|
|
static void
|
|
update_allowed_server_formats (GrdClipboardRdp *clipboard_rdp,
|
|
gboolean received_response)
|
|
{
|
|
GList *l;
|
|
|
|
if (received_response)
|
|
g_debug ("[RDP.CLIPRDR] Handling format list response from client");
|
|
|
|
if (received_response &&
|
|
clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_OK)
|
|
{
|
|
for (l = clipboard_rdp->pending_server_formats; l; l = l->next)
|
|
{
|
|
if (GPOINTER_TO_UINT (l->data) == GRD_MIME_TYPE_TEXT_URILIST)
|
|
{
|
|
ClipDataEntry *entry;
|
|
|
|
if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (clipboard_rdp->serial),
|
|
NULL, (gpointer *) &entry))
|
|
entry->requests_allowed = TRUE;
|
|
|
|
clipboard_rdp->server_file_contents_requests_allowed = TRUE;
|
|
}
|
|
|
|
g_hash_table_add (clipboard_rdp->allowed_server_formats, l->data);
|
|
}
|
|
|
|
g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
|
|
|
|
return;
|
|
}
|
|
|
|
if (received_response &&
|
|
!(clipboard_rdp->format_list_response_msg_flags & CB_RESPONSE_FAIL))
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Protocol violation: Client did not set response "
|
|
"flag. Assuming CB_RESPONSE_FAIL");
|
|
}
|
|
|
|
clipboard_rdp->server_file_contents_requests_allowed = FALSE;
|
|
g_hash_table_remove_all (clipboard_rdp->allowed_server_formats);
|
|
g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
|
|
}
|
|
|
|
static void
|
|
remove_clipboard_format_data_for_mime_type (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeType mime_type)
|
|
{
|
|
FormatData *format_data;
|
|
|
|
format_data = g_hash_table_lookup (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (mime_type));
|
|
if (!format_data)
|
|
return;
|
|
|
|
g_free (format_data->data);
|
|
g_free (format_data);
|
|
|
|
g_hash_table_remove (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (mime_type));
|
|
}
|
|
|
|
static void
|
|
remove_duplicated_clipboard_mime_types (GrdClipboardRdp *clipboard_rdp,
|
|
GList **mime_type_list)
|
|
{
|
|
GrdMimeType mime_type;
|
|
|
|
/**
|
|
* Remove the "x-special/gnome-copied-files" mimetype, since we use the
|
|
* "text/uri-list" mimetype instead.
|
|
*/
|
|
mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
|
|
if (g_list_find (*mime_type_list, GUINT_TO_POINTER (mime_type)))
|
|
{
|
|
*mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type));
|
|
remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
|
|
}
|
|
|
|
/**
|
|
* We can only advertise CF_UNICODETEXT as remote format once, so ignore the
|
|
* other local format if it exists.
|
|
*/
|
|
if (g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_PLAIN_UTF8)) &&
|
|
g_list_find (*mime_type_list, GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_UTF8_STRING)))
|
|
{
|
|
mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
|
|
*mime_type_list = g_list_remove (*mime_type_list, GUINT_TO_POINTER (mime_type));
|
|
remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_clipboard_serial (GrdClipboardRdp *clipboard_rdp)
|
|
{
|
|
++clipboard_rdp->serial;
|
|
while (g_hash_table_contains (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (clipboard_rdp->serial)))
|
|
++clipboard_rdp->serial;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Updated clipboard serial to %lu", clipboard_rdp->serial);
|
|
}
|
|
|
|
static void
|
|
send_mime_type_list (GrdClipboardRdp *clipboard_rdp,
|
|
GList *mime_type_list)
|
|
{
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FORMAT_LIST format_list = {0};
|
|
CLIPRDR_FORMAT *cliprdr_formats;
|
|
ClipDataEntry *entry;
|
|
GrdMimeType mime_type;
|
|
uint32_t n_formats;
|
|
uint32_t i;
|
|
GList *l;
|
|
|
|
if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (clipboard_rdp->serial),
|
|
NULL, (gpointer *) &entry))
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u and serial %lu is now "
|
|
"independent", entry->id, entry->serial);
|
|
entry->is_independent = TRUE;
|
|
|
|
create_new_winpr_clipboard (clipboard_rdp);
|
|
}
|
|
update_clipboard_serial (clipboard_rdp);
|
|
|
|
n_formats = g_list_length (mime_type_list);
|
|
cliprdr_formats = g_malloc0 (n_formats * sizeof (CLIPRDR_FORMAT));
|
|
for (i = 0, l = mime_type_list; i < n_formats; ++i, l = l->next)
|
|
{
|
|
mime_type = GPOINTER_TO_UINT (l->data);
|
|
switch (mime_type)
|
|
{
|
|
case GRD_MIME_TYPE_TEXT_PLAIN:
|
|
cliprdr_formats[i].formatId = CF_TEXT;
|
|
break;
|
|
case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
case GRD_MIME_TYPE_TEXT_UTF8_STRING:
|
|
cliprdr_formats[i].formatId = CF_UNICODETEXT;
|
|
clipboard_rdp->which_unicode_format = mime_type;
|
|
break;
|
|
case GRD_MIME_TYPE_TEXT_HTML:
|
|
cliprdr_formats[i].formatId = GRD_CF_HTML;
|
|
cliprdr_formats[i].formatName = GRD_CFSTR_MIME_HTML;
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_BMP:
|
|
cliprdr_formats[i].formatId = CF_DIB;
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_TIFF:
|
|
cliprdr_formats[i].formatId = CF_TIFF;
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_GIF:
|
|
cliprdr_formats[i].formatId = GRD_CF_GIF;
|
|
cliprdr_formats[i].formatName = GRD_CFSTR_MIME_GIF;
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_JPEG:
|
|
cliprdr_formats[i].formatId = GRD_CF_JPEG;
|
|
cliprdr_formats[i].formatName = GRD_CFSTR_MIME_JPEG;
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_PNG:
|
|
cliprdr_formats[i].formatId = GRD_CF_PNG;
|
|
cliprdr_formats[i].formatName = GRD_CFSTR_MIME_PNG;
|
|
break;
|
|
case GRD_MIME_TYPE_TEXT_URILIST:
|
|
/**
|
|
* Most formats don't have a consistent format id, but a dynamically
|
|
* assigned one. These formats are identified by their name.
|
|
*
|
|
* When the client requests the content of file lists, it MUST use the
|
|
* id that we told the client before when the clipboard format with
|
|
* the name "FileGroupDescriptorW" was advertised.
|
|
*
|
|
* See also 1.3.1.2 Clipboard Format
|
|
*/
|
|
cliprdr_formats[i].formatId = GRD_CF_TEXT_URILIST;
|
|
cliprdr_formats[i].formatName = "FileGroupDescriptorW";
|
|
clipboard_rdp->server_file_contents_requests_allowed = FALSE;
|
|
grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard);
|
|
grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
remove_clipboard_format_data_for_mime_type (clipboard_rdp, mime_type);
|
|
g_hash_table_remove (clipboard_rdp->allowed_server_formats, l->data);
|
|
|
|
clipboard_rdp->pending_server_formats =
|
|
g_list_append (clipboard_rdp->pending_server_formats, l->data);
|
|
}
|
|
|
|
format_list.common.msgType = CB_FORMAT_LIST;
|
|
format_list.formats = cliprdr_formats;
|
|
format_list.numFormats = n_formats;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Sending FormatList");
|
|
cliprdr_context->ServerFormatList (cliprdr_context, &format_list);
|
|
|
|
g_free (cliprdr_formats);
|
|
g_list_free (mime_type_list);
|
|
}
|
|
|
|
static gboolean
|
|
drop_pending_server_formats (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
GList *queued_server_formats;
|
|
|
|
g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send "
|
|
"format list response (Timeout reached)");
|
|
update_allowed_server_formats (clipboard_rdp, FALSE);
|
|
|
|
queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats);
|
|
if (queued_server_formats)
|
|
send_mime_type_list (clipboard_rdp, queued_server_formats);
|
|
|
|
clipboard_rdp->pending_server_formats_drop_id = 0;
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_update_client_mime_type_list (GrdClipboard *clipboard,
|
|
GList *mime_type_list)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard);
|
|
|
|
remove_duplicated_clipboard_mime_types (clipboard_rdp, &mime_type_list);
|
|
if (!mime_type_list)
|
|
return;
|
|
|
|
if (clipboard_rdp->pending_server_formats)
|
|
{
|
|
if (clipboard_rdp->queued_server_formats)
|
|
g_debug ("[RDP.CLIPRDR] Replacing queued server FormatList");
|
|
g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free);
|
|
|
|
g_debug ("[RDP.CLIPRDR] Queueing new FormatList");
|
|
clipboard_rdp->queued_server_formats = mime_type_list;
|
|
|
|
if (!clipboard_rdp->pending_server_formats_drop_id)
|
|
{
|
|
clipboard_rdp->pending_server_formats_drop_id =
|
|
g_timeout_add (MAX_WAIT_TIME_MS, drop_pending_server_formats, clipboard_rdp);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
send_mime_type_list (clipboard_rdp, mime_type_list);
|
|
}
|
|
|
|
void
|
|
grd_clipboard_rdp_lock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp,
|
|
uint32_t clip_data_id)
|
|
{
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = {0};
|
|
|
|
g_debug ("[RDP.CLIPRDR] Locking clients clipboard data with clipDataId %u",
|
|
clip_data_id);
|
|
|
|
lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
|
|
lock_clipboard_data.clipDataId = clip_data_id;
|
|
|
|
cliprdr_context->ServerLockClipboardData (cliprdr_context,
|
|
&lock_clipboard_data);
|
|
}
|
|
|
|
void
|
|
grd_clipboard_rdp_unlock_remote_clipboard_data (GrdClipboardRdp *clipboard_rdp,
|
|
uint32_t clip_data_id)
|
|
{
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = {0};
|
|
|
|
g_debug ("[RDP.CLIPRDR] Unlocking clients clipboard data associated to "
|
|
"clipDataId %u", clip_data_id);
|
|
|
|
unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
|
|
unlock_clipboard_data.clipDataId = clip_data_id;
|
|
|
|
cliprdr_context->ServerUnlockClipboardData (cliprdr_context,
|
|
&unlock_clipboard_data);
|
|
}
|
|
|
|
void
|
|
grd_clipboard_rdp_request_remote_file_size_async (GrdClipboardRdp *clipboard_rdp,
|
|
uint32_t stream_id,
|
|
uint32_t list_index,
|
|
gboolean has_clip_data_id,
|
|
uint32_t clip_data_id)
|
|
{
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0};
|
|
|
|
file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
|
|
file_contents_request.streamId = stream_id;
|
|
file_contents_request.listIndex = list_index;
|
|
file_contents_request.dwFlags = FILECONTENTS_SIZE;
|
|
file_contents_request.cbRequested = 0x8;
|
|
file_contents_request.haveClipDataId = has_clip_data_id;
|
|
file_contents_request.clipDataId = clip_data_id;
|
|
|
|
cliprdr_context->ServerFileContentsRequest (cliprdr_context,
|
|
&file_contents_request);
|
|
}
|
|
|
|
void
|
|
grd_clipboard_rdp_request_remote_file_range_async (GrdClipboardRdp *clipboard_rdp,
|
|
uint32_t stream_id,
|
|
uint32_t list_index,
|
|
uint64_t offset,
|
|
uint32_t requested_size,
|
|
gboolean has_clip_data_id,
|
|
uint32_t clip_data_id)
|
|
{
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = {0};
|
|
|
|
file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
|
|
file_contents_request.streamId = stream_id;
|
|
file_contents_request.listIndex = list_index;
|
|
file_contents_request.dwFlags = FILECONTENTS_RANGE;
|
|
file_contents_request.nPositionLow = offset & 0xFFFFFFFF;
|
|
file_contents_request.nPositionHigh = offset >> 32 & 0xFFFFFFFF;
|
|
file_contents_request.cbRequested = requested_size;
|
|
file_contents_request.haveClipDataId = has_clip_data_id;
|
|
file_contents_request.clipDataId = clip_data_id;
|
|
|
|
cliprdr_context->ServerFileContentsRequest (cliprdr_context,
|
|
&file_contents_request);
|
|
}
|
|
|
|
static void
|
|
track_serial_for_mime_type (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeType mime_type,
|
|
unsigned int serial)
|
|
{
|
|
GList *serials;
|
|
|
|
serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests,
|
|
GUINT_TO_POINTER (mime_type));
|
|
serials = g_list_append (serials, GUINT_TO_POINTER (serial));
|
|
|
|
g_hash_table_insert (clipboard_rdp->pending_client_requests,
|
|
GUINT_TO_POINTER (mime_type), serials);
|
|
}
|
|
|
|
static void
|
|
enqueue_mime_type_content_request (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeTypeTable *mime_type_table,
|
|
unsigned int serial)
|
|
{
|
|
GrdMimeType mime_type = mime_type_table->mime_type;
|
|
GrdMimeTypeTable *mime_type_table_copy;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Queueing mime type content request for mime type %s "
|
|
"with serial %u", grd_mime_type_to_string (mime_type), serial);
|
|
|
|
track_serial_for_mime_type (clipboard_rdp, mime_type, serial);
|
|
|
|
mime_type_table_copy = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
*mime_type_table_copy = *mime_type_table;
|
|
g_queue_push_tail (clipboard_rdp->ordered_client_requests,
|
|
GUINT_TO_POINTER (mime_type_table_copy));
|
|
}
|
|
|
|
static void
|
|
abort_client_requests_for_serials (GrdClipboardRdp *clipboard_rdp,
|
|
GList *serials)
|
|
{
|
|
GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
|
|
unsigned int serial;
|
|
GList *l;
|
|
|
|
for (l = serials; l; l = l->next)
|
|
{
|
|
serial = GPOINTER_TO_UINT (l->data);
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
NULL, 0);
|
|
}
|
|
g_list_free (serials);
|
|
}
|
|
|
|
static void
|
|
abort_client_requests_for_mime_type (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeType mime_type)
|
|
{
|
|
GList *serials;
|
|
|
|
if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests,
|
|
GUINT_TO_POINTER (mime_type),
|
|
NULL, (gpointer *) &serials))
|
|
return;
|
|
|
|
abort_client_requests_for_serials (clipboard_rdp, serials);
|
|
}
|
|
|
|
static void
|
|
abort_client_requests_for_context (GrdClipboardRdp *clipboard_rdp,
|
|
ClientFormatDataRequestContext *request_context)
|
|
{
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
GrdMimeTypeTable *mime_type_table = &request_context->mime_type_table;
|
|
GrdMimeType mime_type = mime_type_table->mime_type;
|
|
uint32_t clip_data_id = request_context->clip_data_id;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Aborting FormatDataRequest for mime type %s",
|
|
grd_mime_type_to_string (mime_type));
|
|
|
|
if (request_context->has_clip_data_id)
|
|
grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id);
|
|
|
|
abort_client_requests_for_mime_type (clipboard_rdp, mime_type);
|
|
}
|
|
|
|
static void
|
|
maybe_send_next_mime_type_content_request (GrdClipboardRdp *clipboard_rdp)
|
|
{
|
|
GrdMimeTypeTable *mime_type_table;
|
|
GrdMimeType mime_type;
|
|
GList *serials = NULL;
|
|
|
|
mime_type_table = g_queue_pop_head (clipboard_rdp->ordered_client_requests);
|
|
if (!mime_type_table)
|
|
return;
|
|
|
|
mime_type = mime_type_table->mime_type;
|
|
serials = g_hash_table_lookup (clipboard_rdp->pending_client_requests,
|
|
GUINT_TO_POINTER (mime_type));
|
|
if (!serials)
|
|
{
|
|
g_free (mime_type_table);
|
|
maybe_send_next_mime_type_content_request (clipboard_rdp);
|
|
return;
|
|
}
|
|
|
|
if (!send_mime_type_content_request (clipboard_rdp, mime_type_table))
|
|
{
|
|
abort_client_requests_for_mime_type (clipboard_rdp, mime_type);
|
|
g_free (mime_type_table);
|
|
|
|
maybe_send_next_mime_type_content_request (clipboard_rdp);
|
|
return;
|
|
}
|
|
|
|
g_free (mime_type_table);
|
|
}
|
|
|
|
static gboolean
|
|
abort_current_client_request (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
ClientFormatDataRequestContext *request_context;
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_request_mutex);
|
|
request_context = g_steal_pointer (&clipboard_rdp->current_client_request);
|
|
|
|
g_clear_handle_id (&clipboard_rdp->client_format_data_response_id,
|
|
g_source_remove);
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
g_warning ("[RDP.CLIPRDR] Possible protocol violation: Client did not send "
|
|
"format data response (Timeout reached)");
|
|
abort_client_requests_for_context (clipboard_rdp, request_context);
|
|
g_free (request_context);
|
|
|
|
clipboard_rdp->client_request_abort_id = 0;
|
|
maybe_send_next_mime_type_content_request (clipboard_rdp);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
send_mime_type_content_request (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeTypeTable *mime_type_table)
|
|
{
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FORMAT_DATA_REQUEST format_data_request = {0};
|
|
GrdMimeType mime_type = mime_type_table->mime_type;
|
|
ClientFormatDataRequestContext *request_context;
|
|
uint32_t clip_data_id = 0;
|
|
|
|
g_assert (mime_type != GRD_MIME_TYPE_NONE);
|
|
g_assert (!clipboard_rdp->current_client_request);
|
|
g_assert (!clipboard_rdp->client_format_data_response_id);
|
|
g_assert (!clipboard_rdp->client_request_abort_id);
|
|
|
|
request_context = g_malloc0 (sizeof (ClientFormatDataRequestContext));
|
|
request_context->mime_type_table = *mime_type_table;
|
|
|
|
if (clipboard_rdp->cliprdr_context->canLockClipData &&
|
|
(mime_type == GRD_MIME_TYPE_TEXT_URILIST ||
|
|
mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES))
|
|
{
|
|
clip_data_id = grd_rdp_fuse_clipboard_clip_data_id_new (rdp_fuse_clipboard);
|
|
request_context->clip_data_id = clip_data_id;
|
|
request_context->has_clip_data_id = TRUE;
|
|
}
|
|
|
|
format_data_request.common.msgType = CB_FORMAT_DATA_REQUEST;
|
|
format_data_request.common.dataLen = 4;
|
|
format_data_request.requestedFormatId = mime_type_table->rdp.format_id;
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_request_mutex);
|
|
if (cliprdr_context->ServerFormatDataRequest (cliprdr_context,
|
|
&format_data_request))
|
|
{
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
if (request_context->has_clip_data_id)
|
|
grd_rdp_fuse_clipboard_clip_data_id_free (rdp_fuse_clipboard, clip_data_id);
|
|
|
|
g_free (request_context);
|
|
|
|
return FALSE;
|
|
}
|
|
g_debug ("[RDP.CLIPRDR] Sent FormatDataRequest for mime type %s",
|
|
grd_mime_type_to_string (mime_type));
|
|
|
|
clipboard_rdp->current_client_request = request_context;
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
clipboard_rdp->client_request_abort_id =
|
|
g_timeout_add (MAX_WAIT_TIME_MS, abort_current_client_request, clipboard_rdp);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_request_client_content_for_mime_type (GrdClipboard *clipboard,
|
|
GrdMimeTypeTable *mime_type_table,
|
|
unsigned int serial)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard);
|
|
GrdMimeType mime_type = mime_type_table->mime_type;
|
|
gboolean pending_format_list_update;
|
|
FormatData *format_data;
|
|
|
|
if (mime_type == GRD_MIME_TYPE_NONE)
|
|
{
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
NULL, 0);
|
|
return;
|
|
}
|
|
|
|
if (g_hash_table_lookup_extended (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (mime_type),
|
|
NULL, (gpointer *) &format_data))
|
|
{
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
format_data->data,
|
|
format_data->size);
|
|
return;
|
|
}
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex);
|
|
pending_format_list_update = clipboard_rdp->server_format_list_update_id != 0;
|
|
g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex);
|
|
|
|
if (pending_format_list_update)
|
|
{
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
NULL, 0);
|
|
return;
|
|
}
|
|
|
|
if (clipboard_rdp->current_client_request)
|
|
{
|
|
enqueue_mime_type_content_request (clipboard_rdp, mime_type_table, serial);
|
|
return;
|
|
}
|
|
|
|
g_assert (g_queue_is_empty (clipboard_rdp->ordered_client_requests));
|
|
if (!send_mime_type_content_request (clipboard_rdp, mime_type_table))
|
|
{
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
NULL, 0);
|
|
return;
|
|
}
|
|
|
|
track_serial_for_mime_type (clipboard_rdp, mime_type, serial);
|
|
}
|
|
|
|
static void
|
|
serialize_file_list (FILEDESCRIPTORW *files,
|
|
uint32_t n_files,
|
|
uint8_t **dst_data,
|
|
uint32_t *dst_size)
|
|
{
|
|
FILEDESCRIPTORW *file;
|
|
wStream* s = NULL;
|
|
uint64_t last_write_time;
|
|
uint32_t i, j;
|
|
|
|
if (!files || !dst_data || !dst_size)
|
|
return;
|
|
|
|
if (!(s = Stream_New (NULL, 4 + n_files * CLIPRDR_FILEDESCRIPTOR_SIZE)))
|
|
return;
|
|
|
|
Stream_Write_UINT32 (s, n_files); /* cItems */
|
|
for (i = 0; i < n_files; ++i)
|
|
{
|
|
file = &files[i];
|
|
|
|
Stream_Write_UINT32 (s, file->dwFlags); /* flags */
|
|
Stream_Zero (s, 32); /* reserved1 */
|
|
Stream_Write_UINT32 (s, file->dwFileAttributes); /* fileAttributes */
|
|
Stream_Zero (s, 16); /* reserved2 */
|
|
|
|
last_write_time = file->ftLastWriteTime.dwHighDateTime;
|
|
last_write_time <<= 32;
|
|
last_write_time += file->ftLastWriteTime.dwLowDateTime;
|
|
|
|
Stream_Write_UINT64 (s, last_write_time); /* lastWriteTime */
|
|
Stream_Write_UINT32 (s, file->nFileSizeHigh); /* fileSizeHigh */
|
|
Stream_Write_UINT32 (s, file->nFileSizeLow); /* fileSizeLow */
|
|
|
|
for (j = 0; j < 260; j++) /* cFileName */
|
|
Stream_Write_UINT16 (s, file->cFileName[j]);
|
|
}
|
|
|
|
Stream_SealLength (s);
|
|
Stream_GetLength (s, *dst_size);
|
|
Stream_GetBuffer (s, *dst_data);
|
|
|
|
Stream_Free (s, FALSE);
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_submit_requested_server_content (GrdClipboard *clipboard,
|
|
uint8_t *src_data,
|
|
uint32_t src_size)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (clipboard);
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
ServerFormatDataRequestContext *request_context;
|
|
CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0};
|
|
GrdMimeType mime_type;
|
|
uint32_t src_format_id;
|
|
uint32_t dst_format_id;
|
|
uint8_t *dst_data = NULL;
|
|
uint32_t dst_size = 0;
|
|
BOOL success;
|
|
|
|
request_context = g_steal_pointer (&clipboard_rdp->format_data_request_context);
|
|
mime_type = request_context->mime_type;
|
|
src_format_id = request_context->src_format_id;
|
|
dst_format_id = request_context->dst_format_id;
|
|
|
|
if (src_data)
|
|
{
|
|
if (request_context->needs_conversion)
|
|
{
|
|
if (request_context->needs_null_terminator)
|
|
{
|
|
char *pnull_terminator;
|
|
|
|
src_data = g_realloc (src_data, src_size + 1);
|
|
pnull_terminator = (char *) src_data + src_size;
|
|
*pnull_terminator = '\0';
|
|
++src_size;
|
|
}
|
|
|
|
success = ClipboardSetData (clipboard_rdp->system,
|
|
src_format_id, src_data, src_size);
|
|
if (!success)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: "
|
|
"Failed to set data to convert");
|
|
}
|
|
else
|
|
{
|
|
dst_data = ClipboardGetData (clipboard_rdp->system,
|
|
dst_format_id, &dst_size);
|
|
|
|
if (dst_data && mime_type == GRD_MIME_TYPE_TEXT_URILIST)
|
|
{
|
|
uint64_t serial = clipboard_rdp->serial;
|
|
ClipDataEntry *entry;
|
|
FILEDESCRIPTORW *files;
|
|
uint32_t n_files;
|
|
|
|
files = (FILEDESCRIPTORW *) dst_data;
|
|
n_files = dst_size / sizeof (FILEDESCRIPTORW);
|
|
|
|
dst_data = NULL;
|
|
dst_size = 0;
|
|
serialize_file_list (files, n_files, &dst_data, &dst_size);
|
|
|
|
g_free (files);
|
|
|
|
clipboard_rdp->has_file_list = TRUE;
|
|
if (g_hash_table_lookup_extended (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (serial),
|
|
NULL, (gpointer *) &entry))
|
|
entry->has_file_list = TRUE;
|
|
}
|
|
}
|
|
if (!dst_data)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Converting clipboard content for "
|
|
"client failed: Failed to get converted data");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dst_data = g_steal_pointer (&src_data);
|
|
dst_size = src_size;
|
|
}
|
|
}
|
|
|
|
format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE;
|
|
format_data_response.common.msgFlags = dst_data ? CB_RESPONSE_OK
|
|
: CB_RESPONSE_FAIL;
|
|
format_data_response.common.dataLen = dst_size;
|
|
format_data_response.requestedFormatData = dst_data;
|
|
|
|
cliprdr_context->ServerFormatDataResponse (cliprdr_context,
|
|
&format_data_response);
|
|
|
|
g_free (src_data);
|
|
g_free (dst_data);
|
|
g_free (request_context);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->completed_format_data_request = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
}
|
|
|
|
/**
|
|
* FreeRDP already updated our capabilites after the client told us
|
|
* about its capabilities, there is nothing to do here
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_capabilities (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_CAPABILITIES *capabilities)
|
|
{
|
|
g_autoptr (GStrvBuilder) client_capabilities = NULL;
|
|
char **client_caps_strings;
|
|
g_autofree char *caps_string = NULL;
|
|
|
|
client_capabilities = g_strv_builder_new ();
|
|
|
|
if (cliprdr_context->useLongFormatNames)
|
|
g_strv_builder_add (client_capabilities, "long format names");
|
|
if (cliprdr_context->streamFileClipEnabled)
|
|
g_strv_builder_add (client_capabilities, "stream file clip");
|
|
if (cliprdr_context->fileClipNoFilePaths)
|
|
g_strv_builder_add (client_capabilities, "file clip no file paths");
|
|
if (cliprdr_context->canLockClipData)
|
|
g_strv_builder_add (client_capabilities, "can lock clip data");
|
|
if (cliprdr_context->hasHugeFileSupport)
|
|
g_strv_builder_add (client_capabilities, "huge file support");
|
|
|
|
client_caps_strings = g_strv_builder_end (client_capabilities);
|
|
caps_string = g_strjoinv (", ", client_caps_strings);
|
|
g_message ("[RDP.CLIPRDR] Client capabilities: %s", caps_string);
|
|
|
|
g_strfreev (client_caps_strings);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
/**
|
|
* Client sent us a Temporary Directory PDU.
|
|
* We don't handle the CF_HDROP format however. It's a relict of the past.
|
|
*/
|
|
static uint32_t
|
|
cliprdr_temp_directory (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_TEMP_DIRECTORY *temp_directory)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] Client sent a Temporary Directory PDU with path \"%s\"",
|
|
temp_directory->szTempDir);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static void
|
|
abort_all_client_requests (GrdClipboardRdp *clipboard_rdp)
|
|
{
|
|
ClientFormatDataRequestContext *request_context;
|
|
GHashTableIter iter;
|
|
GList *serials;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Aborting all pending client requests");
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_request_mutex);
|
|
request_context = g_steal_pointer (&clipboard_rdp->current_client_request);
|
|
|
|
g_clear_handle_id (&clipboard_rdp->client_format_data_response_id,
|
|
g_source_remove);
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
if (request_context)
|
|
abort_client_requests_for_context (clipboard_rdp, request_context);
|
|
|
|
g_free (request_context);
|
|
g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove);
|
|
|
|
g_hash_table_iter_init (&iter, clipboard_rdp->pending_client_requests);
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &serials))
|
|
{
|
|
abort_client_requests_for_serials (clipboard_rdp, serials);
|
|
g_hash_table_iter_remove (&iter);
|
|
}
|
|
|
|
g_queue_clear_full (clipboard_rdp->ordered_client_requests, g_free);
|
|
}
|
|
|
|
static gboolean
|
|
update_server_format_list (gpointer user_data)
|
|
{
|
|
ServerFormatListUpdateContext *update_context = user_data;
|
|
GrdClipboardRdp *clipboard_rdp = update_context->clipboard_rdp;
|
|
GrdClipboard *clipboard = GRD_CLIPBOARD (update_context->clipboard_rdp);
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FORMAT_LIST_RESPONSE format_list_response = {0};
|
|
GList *mime_type_tables;
|
|
GrdMimeTypeTable *mime_type_table;
|
|
GList *l;
|
|
|
|
mime_type_tables = g_steal_pointer (&update_context->mime_type_tables);
|
|
for (l = mime_type_tables; l; l = l->next)
|
|
{
|
|
mime_type_table = l->data;
|
|
|
|
/* Also indirectly handles the GRD_MIME_TYPE_XS_GNOME_COPIED_FILES case */
|
|
if (mime_type_table->mime_type == GRD_MIME_TYPE_TEXT_URILIST)
|
|
{
|
|
clipboard_rdp->server_file_contents_requests_allowed = FALSE;
|
|
|
|
grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard);
|
|
grd_rdp_fuse_clipboard_lazily_clear_all_cdi_selections (rdp_fuse_clipboard);
|
|
}
|
|
|
|
remove_clipboard_format_data_for_mime_type (clipboard_rdp,
|
|
mime_type_table->mime_type);
|
|
g_hash_table_remove (clipboard_rdp->allowed_server_formats,
|
|
GUINT_TO_POINTER (mime_type_table->mime_type));
|
|
}
|
|
|
|
abort_all_client_requests (clipboard_rdp);
|
|
grd_clipboard_update_server_mime_type_list (clipboard, mime_type_tables);
|
|
|
|
format_list_response.common.msgType = CB_FORMAT_LIST_RESPONSE;
|
|
format_list_response.common.msgFlags = CB_RESPONSE_OK;
|
|
|
|
cliprdr_context->ServerFormatListResponse (cliprdr_context,
|
|
&format_list_response);
|
|
|
|
/**
|
|
* Any FileContentsRequest that is still waiting for a FileContentsResponse
|
|
* won't get a response any more, when the client does not support clipboard
|
|
* data locking. So, drop all requests without clipDataId here.
|
|
*/
|
|
if (!cliprdr_context->canLockClipData)
|
|
grd_rdp_fuse_clipboard_dismiss_all_no_cdi_requests (rdp_fuse_clipboard);
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex);
|
|
clipboard_rdp->server_format_list_update_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->completed_format_list = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
update_context_free (gpointer data)
|
|
{
|
|
ServerFormatListUpdateContext *update_context = data;
|
|
|
|
g_clear_list (&update_context->mime_type_tables, g_free);
|
|
|
|
g_free (update_context);
|
|
}
|
|
|
|
/**
|
|
* Client notifies us that its clipboard is updated with new clipboard data
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_format_list (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FORMAT_LIST *format_list)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
ServerFormatListUpdateContext *update_context;
|
|
GHashTable *found_mime_types;
|
|
GrdMimeTypeTable *mime_type_table = NULL;
|
|
GList *mime_type_tables = NULL;
|
|
GrdMimeType mime_type;
|
|
gboolean already_has_text_format = FALSE;
|
|
uint32_t i;
|
|
|
|
found_mime_types = g_hash_table_new (NULL, NULL);
|
|
|
|
/**
|
|
* The text format CF_TEXT can depend on the CF_LOCALE content. In such
|
|
* situations, we might not get the correct content from WinPR.
|
|
* CF_UNICODETEXT is not affected by the CF_LOCALE content, so try to find
|
|
* the CF_UNICODETEXT first to ensure that we use CF_UNICODETEXT, when both
|
|
* formats are available and CF_TEXT is listed first.
|
|
*/
|
|
for (i = 0; i < format_list->numFormats; ++i)
|
|
{
|
|
if (format_list->formats[i].formatId == CF_UNICODETEXT)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
|
|
|
|
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
mime_type_table->mime_type = mime_type;
|
|
mime_type_table->rdp.format_id = format_list->formats[i].formatId;
|
|
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
|
|
|
|
g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type));
|
|
mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING;
|
|
|
|
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
mime_type_table->mime_type = mime_type;
|
|
mime_type_table->rdp.format_id = format_list->formats[i].formatId;
|
|
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
|
|
|
|
g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type));
|
|
already_has_text_format = TRUE;
|
|
g_debug ("[RDP.CLIPRDR] Force using CF_UNICODETEXT over CF_TEXT as "
|
|
"external format for text/plain;charset=utf-8 and UTF8_STRING");
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < format_list->numFormats; ++i)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_NONE;
|
|
|
|
/**
|
|
* First check formats with dynamically assigned format ids. These formats
|
|
* don't have consistent ids. Their name, however, is always the same.
|
|
*
|
|
* If the client uses short format names, the formatName is truncated to
|
|
* either 32 ASCII 8 characters or 16 UTF-16 characters.
|
|
*/
|
|
if (format_list->formats[i].formatName &&
|
|
(strcmp (format_list->formats[i].formatName, "FileGroupDescriptorW") == 0 ||
|
|
strcmp (format_list->formats[i].formatName, "FileGroupDescri") == 0))
|
|
{
|
|
if (!g_hash_table_contains (found_mime_types,
|
|
GUINT_TO_POINTER (GRD_MIME_TYPE_TEXT_URILIST)))
|
|
{
|
|
/**
|
|
* Advertise the "x-special/gnome-copied-files" format in addition to
|
|
* the "text/uri-list" format
|
|
*/
|
|
mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
|
|
|
|
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
mime_type_table->mime_type = mime_type;
|
|
mime_type_table->rdp.format_id = format_list->formats[i].formatId;
|
|
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
|
|
|
|
g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type));
|
|
}
|
|
|
|
mime_type = GRD_MIME_TYPE_TEXT_URILIST;
|
|
}
|
|
else if (format_list->formats[i].formatName &&
|
|
strcmp (format_list->formats[i].formatName,
|
|
GRD_CFSTR_MIME_HTML) == 0)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_TEXT_HTML;
|
|
}
|
|
else if (format_list->formats[i].formatName &&
|
|
strcmp (format_list->formats[i].formatName,
|
|
GRD_CFSTR_MIME_GIF) == 0)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_IMAGE_GIF;
|
|
}
|
|
else if (format_list->formats[i].formatName &&
|
|
strcmp (format_list->formats[i].formatName,
|
|
GRD_CFSTR_MIME_PNG) == 0)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_IMAGE_PNG;
|
|
}
|
|
else if (format_list->formats[i].formatName &&
|
|
strcmp (format_list->formats[i].formatName,
|
|
GRD_CFSTR_MIME_JPEG) == 0)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_IMAGE_JPEG;
|
|
}
|
|
else
|
|
{
|
|
switch (format_list->formats[i].formatId)
|
|
{
|
|
case CF_TEXT:
|
|
case CF_UNICODETEXT:
|
|
case CF_OEMTEXT:
|
|
if (!already_has_text_format)
|
|
{
|
|
mime_type = GRD_MIME_TYPE_TEXT_PLAIN_UTF8;
|
|
|
|
g_assert (!g_hash_table_contains (found_mime_types,
|
|
GUINT_TO_POINTER (mime_type)));
|
|
|
|
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
mime_type_table->mime_type = mime_type;
|
|
mime_type_table->rdp.format_id = format_list->formats[i].formatId;
|
|
mime_type_tables = g_list_append (mime_type_tables,
|
|
mime_type_table);
|
|
g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type));
|
|
|
|
mime_type = GRD_MIME_TYPE_TEXT_UTF8_STRING;
|
|
already_has_text_format = TRUE;
|
|
g_debug ("[RDP.CLIPRDR] Client advertised data for "
|
|
"text/plain;charset=utf-8 and UTF8_STRING "
|
|
"(external format: id: %u, name: %s)",
|
|
format_list->formats[i].formatId,
|
|
format_list->formats[i].formatName);
|
|
}
|
|
break;
|
|
case CF_DIB:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_BMP;
|
|
break;
|
|
case CF_TIFF:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_TIFF;
|
|
break;
|
|
default:
|
|
g_debug ("[RDP.CLIPRDR] Client advertised unknown format: id: %u, "
|
|
"name: %s", format_list->formats[i].formatId,
|
|
format_list->formats[i].formatName);
|
|
}
|
|
}
|
|
|
|
if (mime_type != GRD_MIME_TYPE_NONE &&
|
|
!g_hash_table_contains (found_mime_types, GUINT_TO_POINTER (mime_type)))
|
|
{
|
|
mime_type_table = g_malloc0 (sizeof (GrdMimeTypeTable));
|
|
mime_type_table->mime_type = mime_type;
|
|
mime_type_table->rdp.format_id = format_list->formats[i].formatId;
|
|
mime_type_tables = g_list_append (mime_type_tables, mime_type_table);
|
|
|
|
g_hash_table_add (found_mime_types, GUINT_TO_POINTER (mime_type));
|
|
}
|
|
else if (mime_type != GRD_MIME_TYPE_NONE)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] Ignoring duplicated format: id: %u, name: %s",
|
|
format_list->formats[i].formatId,
|
|
format_list->formats[i].formatName);
|
|
}
|
|
}
|
|
|
|
g_hash_table_destroy (found_mime_types);
|
|
|
|
if (clipboard_rdp->server_format_list_update_id)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format list "
|
|
"without being able to response to last update first");
|
|
}
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
while (!clipboard_rdp->protocol_stopped &&
|
|
!clipboard_rdp->completed_format_list)
|
|
{
|
|
g_cond_wait (&clipboard_rdp->completion_cond,
|
|
&clipboard_rdp->completion_mutex);
|
|
}
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
if (clipboard_rdp->protocol_stopped)
|
|
{
|
|
g_list_free_full (mime_type_tables, g_free);
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
g_assert (clipboard_rdp->completed_format_list);
|
|
clipboard_rdp->completed_format_list = FALSE;
|
|
|
|
update_context = g_malloc0 (sizeof (ServerFormatListUpdateContext));
|
|
update_context->clipboard_rdp = clipboard_rdp;
|
|
update_context->mime_type_tables = mime_type_tables;
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_list_update_mutex);
|
|
clipboard_rdp->server_format_list_update_id =
|
|
g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, update_server_format_list,
|
|
update_context, update_context_free);
|
|
g_mutex_unlock (&clipboard_rdp->server_format_list_update_mutex);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static gboolean
|
|
handle_format_list_response (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
GList *queued_server_formats;
|
|
|
|
if (clipboard_rdp->pending_server_formats)
|
|
update_allowed_server_formats (clipboard_rdp, TRUE);
|
|
|
|
g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id,
|
|
g_source_remove);
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_format_list_response_mutex);
|
|
clipboard_rdp->client_format_list_response_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->client_format_list_response_mutex);
|
|
|
|
queued_server_formats = g_steal_pointer (&clipboard_rdp->queued_server_formats);
|
|
if (queued_server_formats)
|
|
send_mime_type_list (clipboard_rdp, queued_server_formats);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/**
|
|
* Client sent us a response to our format list pdu
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_format_list_response (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FORMAT_LIST_RESPONSE *format_list_response)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
g_autoptr (GMutexLocker) locker = NULL;
|
|
|
|
locker = g_mutex_locker_new (&clipboard_rdp->client_format_list_response_mutex);
|
|
if (clipboard_rdp->client_format_list_response_id != 0)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Wrong message sequence: Received an unexpected "
|
|
"format list response. Ignoring...");
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
clipboard_rdp->format_list_response_msg_flags =
|
|
format_list_response->common.msgFlags;
|
|
|
|
clipboard_rdp->client_format_list_response_id =
|
|
g_idle_add (handle_format_list_response, clipboard_rdp);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static gboolean
|
|
retrieve_current_clipboard (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
ClipDataEntry *entry = clipboard_rdp->clipboard_retrieval_context.entry;
|
|
ClipDataEntry *entry_to_replace;
|
|
uint64_t serial = clipboard_rdp->serial;
|
|
gboolean serial_already_in_use;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Tracking serial %lu for ClipDataEntry",
|
|
clipboard_rdp->serial);
|
|
|
|
entry_to_replace = clipboard_rdp->clipboard_retrieval_context.entry_to_replace;
|
|
if (entry_to_replace)
|
|
{
|
|
g_hash_table_remove (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (entry_to_replace->serial));
|
|
}
|
|
|
|
serial_already_in_use = g_hash_table_contains (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (serial));
|
|
if (!serial_already_in_use)
|
|
{
|
|
g_hash_table_insert (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (serial), entry);
|
|
}
|
|
clipboard_rdp->clipboard_retrieval_context.serial_already_in_use =
|
|
serial_already_in_use;
|
|
|
|
entry->serial = serial;
|
|
entry->has_file_list = clipboard_rdp->has_file_list;
|
|
entry->requests_allowed = clipboard_rdp->server_file_contents_requests_allowed;
|
|
|
|
entry->system = clipboard_rdp->system;
|
|
entry->delegate = clipboard_rdp->delegate;
|
|
|
|
g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex);
|
|
clipboard_rdp->clipboard_retrieval_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->completed_clip_data_entry = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean
|
|
is_clip_data_entry_user_of_serial (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
ClipDataEntry *entry = value;
|
|
uint64_t serial = GPOINTER_TO_UINT (user_data);
|
|
|
|
return entry->serial == serial;
|
|
}
|
|
|
|
/**
|
|
* Client requests us to retain all file stream data on the clipboard
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_lock_clipboard_data (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_LOCK_CLIPBOARD_DATA *lock_clipboard_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
uint32_t clip_data_id = lock_clipboard_data->clipDataId;
|
|
g_autofree ClipDataEntry *entry = NULL;
|
|
|
|
if (clipboard_rdp->protocol_stopped)
|
|
return CHANNEL_RC_OK;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Client requested a lock with clipDataId: %u",
|
|
clip_data_id);
|
|
|
|
clipboard_rdp->clipboard_retrieval_context.entry_to_replace = NULL;
|
|
if (g_hash_table_lookup_extended (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (clip_data_id),
|
|
NULL, (gpointer *) &entry))
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Protocol violation: Client requested a lock with"
|
|
" an existing clipDataId %u. Replacing existing ClipDataEntry",
|
|
clip_data_id);
|
|
|
|
clipboard_rdp->clipboard_retrieval_context.entry_to_replace = entry;
|
|
}
|
|
|
|
g_assert (clipboard_rdp->completed_clip_data_entry);
|
|
clipboard_rdp->completed_clip_data_entry = FALSE;
|
|
|
|
entry = g_malloc0 (sizeof (ClipDataEntry));
|
|
clipboard_rdp->clipboard_retrieval_context.entry = entry;
|
|
|
|
g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex);
|
|
clipboard_rdp->clipboard_retrieval_id =
|
|
g_idle_add (retrieve_current_clipboard, clipboard_rdp);
|
|
g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
while (!clipboard_rdp->protocol_stopped &&
|
|
!clipboard_rdp->completed_clip_data_entry)
|
|
{
|
|
g_cond_wait (&clipboard_rdp->completion_cond,
|
|
&clipboard_rdp->completion_mutex);
|
|
}
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
if (clipboard_rdp->protocol_stopped)
|
|
return CHANNEL_RC_OK;
|
|
|
|
g_assert (clipboard_rdp->completed_clip_data_entry);
|
|
|
|
if (clipboard_rdp->clipboard_retrieval_context.serial_already_in_use)
|
|
{
|
|
uint64_t serial = entry->serial;
|
|
|
|
g_warning ("[RDP.CLIPRDR] Protocol violation: Double lock detected ("
|
|
"Clipboard serial already in use). Replacing the clipDataId.");
|
|
|
|
g_free (entry);
|
|
entry = g_hash_table_find (clipboard_rdp->clip_data_table,
|
|
is_clip_data_entry_user_of_serial,
|
|
GUINT_TO_POINTER (serial));
|
|
g_hash_table_steal (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (entry->id));
|
|
}
|
|
|
|
entry->id = clip_data_id;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Tracking lock with clipDataId %u", clip_data_id);
|
|
g_hash_table_insert (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (clip_data_id),
|
|
g_steal_pointer (&entry));
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static gboolean
|
|
handle_clip_data_entry_destruction (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
ClipDataEntry *entry;
|
|
|
|
entry = g_steal_pointer (&clipboard_rdp->clipboard_destruction_context.entry);
|
|
|
|
g_debug ("[RDP.CLIPRDR] Deleting ClipDataEntry with serial %lu", entry->serial);
|
|
g_hash_table_remove (clipboard_rdp->serial_entry_table,
|
|
GUINT_TO_POINTER (entry->serial));
|
|
|
|
g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex);
|
|
clipboard_rdp->clipboard_destruction_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->completed_clip_data_entry = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/**
|
|
* Client notifies us that the file stream data for a specific clip data id MUST
|
|
* now be released
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_unlock_clipboard_data (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_UNLOCK_CLIPBOARD_DATA *unlock_clipboard_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
uint32_t clip_data_id = unlock_clipboard_data->clipDataId;
|
|
ClipDataEntry *entry;
|
|
|
|
if (clipboard_rdp->protocol_stopped)
|
|
return CHANNEL_RC_OK;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Client requested an unlock with clipDataId: %u",
|
|
clip_data_id);
|
|
if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (clip_data_id),
|
|
NULL, (gpointer *) &entry))
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Protocol violation: ClipDataEntry with id %u "
|
|
"does not exist", clip_data_id);
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
g_debug ("[RDP.CLIPRDR] Removing lock with clipDataId: %u", clip_data_id);
|
|
|
|
g_assert (clipboard_rdp->completed_clip_data_entry);
|
|
clipboard_rdp->completed_clip_data_entry = FALSE;
|
|
|
|
clipboard_rdp->clipboard_destruction_context.entry = entry;
|
|
|
|
g_mutex_lock (&clipboard_rdp->clip_data_entry_mutex);
|
|
clipboard_rdp->clipboard_destruction_id =
|
|
g_idle_add (handle_clip_data_entry_destruction, clipboard_rdp);
|
|
g_mutex_unlock (&clipboard_rdp->clip_data_entry_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
while (!clipboard_rdp->protocol_stopped &&
|
|
!clipboard_rdp->completed_clip_data_entry)
|
|
{
|
|
g_cond_wait (&clipboard_rdp->completion_cond,
|
|
&clipboard_rdp->completion_mutex);
|
|
}
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
g_debug ("[RDP.CLIPRDR] Untracking lock with clipDataId %u", clip_data_id);
|
|
g_hash_table_remove (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (clip_data_id));
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static gboolean
|
|
request_server_format_data (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
ServerFormatDataRequestContext *request_context;
|
|
GrdMimeType mime_type;
|
|
|
|
request_context = clipboard_rdp->format_data_request_context;
|
|
mime_type = request_context->mime_type;
|
|
|
|
if (!g_hash_table_contains (clipboard_rdp->allowed_server_formats,
|
|
GUINT_TO_POINTER (mime_type)))
|
|
{
|
|
CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0};
|
|
|
|
format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE;
|
|
format_data_response.common.msgFlags = CB_RESPONSE_FAIL;
|
|
|
|
cliprdr_context->ServerFormatDataResponse (cliprdr_context,
|
|
&format_data_response);
|
|
|
|
g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free);
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex);
|
|
clipboard_rdp->server_format_data_request_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->completed_format_data_request = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
grd_clipboard_request_server_content_for_mime_type_async (clipboard,
|
|
mime_type);
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex);
|
|
clipboard_rdp->server_format_data_request_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/**
|
|
* Client knows our format list, it requests now the data from our clipboard
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_format_data_request (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FORMAT_DATA_REQUEST *format_data_request)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
ServerFormatDataRequestContext *request_context;
|
|
CLIPRDR_FORMAT_DATA_RESPONSE format_data_response = {0};
|
|
GrdMimeType mime_type = GRD_MIME_TYPE_NONE;
|
|
uint32_t src_format_id = 0;
|
|
uint32_t dst_format_id;
|
|
gboolean needs_null_terminator = FALSE;
|
|
gboolean needs_conversion = FALSE;
|
|
|
|
dst_format_id = format_data_request->requestedFormatId;
|
|
switch (dst_format_id)
|
|
{
|
|
case CF_TEXT:
|
|
mime_type = GRD_MIME_TYPE_TEXT_PLAIN;
|
|
needs_null_terminator = TRUE;
|
|
needs_conversion = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"text/plain");
|
|
break;
|
|
case CF_UNICODETEXT:
|
|
mime_type = clipboard_rdp->which_unicode_format;
|
|
needs_null_terminator = TRUE;
|
|
needs_conversion = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"text/plain");
|
|
break;
|
|
case GRD_CF_HTML:
|
|
mime_type = GRD_MIME_TYPE_TEXT_HTML;
|
|
needs_null_terminator = TRUE;
|
|
needs_conversion = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
GRD_CFSTR_MIME_TEXT_HTML);
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
GRD_CFSTR_MIME_HTML);
|
|
break;
|
|
case CF_DIB:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_BMP;
|
|
needs_conversion = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"image/bmp");
|
|
break;
|
|
case CF_TIFF:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_TIFF;
|
|
break;
|
|
case GRD_CF_GIF:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_GIF;
|
|
break;
|
|
case GRD_CF_JPEG:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_JPEG;
|
|
break;
|
|
case GRD_CF_PNG:
|
|
mime_type = GRD_MIME_TYPE_IMAGE_PNG;
|
|
break;
|
|
case GRD_CF_TEXT_URILIST:
|
|
mime_type = GRD_MIME_TYPE_TEXT_URILIST;
|
|
needs_conversion = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"text/uri-list");
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"FileGroupDescriptorW");
|
|
break;
|
|
}
|
|
|
|
if (clipboard_rdp->server_format_data_request_id)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] Wrong message sequence: Got new format data "
|
|
"request without being able to push response for last request "
|
|
"yet.");
|
|
}
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
while (!clipboard_rdp->protocol_stopped &&
|
|
!clipboard_rdp->completed_format_data_request)
|
|
{
|
|
g_cond_wait (&clipboard_rdp->completion_cond,
|
|
&clipboard_rdp->completion_mutex);
|
|
}
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
if (clipboard_rdp->protocol_stopped)
|
|
return CHANNEL_RC_OK;
|
|
|
|
if (mime_type != GRD_MIME_TYPE_NONE)
|
|
{
|
|
g_assert (clipboard_rdp->completed_format_data_request);
|
|
clipboard_rdp->completed_format_data_request = FALSE;
|
|
|
|
request_context = g_malloc0 (sizeof (ServerFormatDataRequestContext));
|
|
request_context->clipboard_rdp = clipboard_rdp;
|
|
request_context->mime_type = mime_type;
|
|
request_context->src_format_id = src_format_id;
|
|
request_context->dst_format_id = dst_format_id;
|
|
request_context->needs_null_terminator = needs_null_terminator;
|
|
request_context->needs_conversion = needs_conversion;
|
|
|
|
g_assert (!clipboard_rdp->format_data_request_context);
|
|
clipboard_rdp->format_data_request_context = request_context;
|
|
|
|
g_mutex_lock (&clipboard_rdp->server_format_data_request_mutex);
|
|
clipboard_rdp->server_format_data_request_id =
|
|
g_idle_add (request_server_format_data, clipboard_rdp);
|
|
g_mutex_unlock (&clipboard_rdp->server_format_data_request_mutex);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
format_data_response.common.msgType = CB_FORMAT_DATA_RESPONSE;
|
|
format_data_response.common.msgFlags = CB_RESPONSE_FAIL;
|
|
format_data_response.common.dataLen = 0;
|
|
format_data_response.requestedFormatData = NULL;
|
|
|
|
return cliprdr_context->ServerFormatDataResponse (cliprdr_context,
|
|
&format_data_response);
|
|
}
|
|
|
|
static gboolean
|
|
extract_format_data_response (GrdClipboardRdp *clipboard_rdp,
|
|
CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response,
|
|
uint8_t **data,
|
|
uint32_t *size)
|
|
{
|
|
gboolean response_ok;
|
|
|
|
*data = (uint8_t *) format_data_response->requestedFormatData;
|
|
*size = format_data_response->common.dataLen;
|
|
|
|
response_ok = format_data_response->common.msgFlags & CB_RESPONSE_OK;
|
|
*size = response_ok && *data ? *size : 0;
|
|
|
|
if (!response_ok)
|
|
g_clear_pointer (data, g_free);
|
|
|
|
return response_ok && *data;
|
|
}
|
|
|
|
static gboolean
|
|
filedescriptorw_filename_is_valid (const WCHAR *filename)
|
|
{
|
|
uint16_t i;
|
|
|
|
if (filename[0] == L'\0')
|
|
return FALSE;
|
|
|
|
for (i = 1; i < 260; ++i)
|
|
{
|
|
if (filename[i] == L'\0')
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static uint8_t *
|
|
get_uri_list_from_packet_file_list (GrdClipboardRdp *clipboard_rdp,
|
|
uint8_t *src_data,
|
|
uint32_t src_size,
|
|
uint32_t *dst_size,
|
|
gboolean has_clip_data_id,
|
|
uint32_t clip_data_id)
|
|
{
|
|
g_autofree FILEDESCRIPTORW *files = NULL;
|
|
FILEDESCRIPTORW *file;
|
|
uint32_t n_files = 0;
|
|
g_autofree char *clip_data_dir_name = NULL;
|
|
char *filename = NULL;
|
|
char *escaped_name;
|
|
char *file_uri;
|
|
g_autoptr (GArray) dst_data = NULL;
|
|
uint32_t i;
|
|
|
|
clip_data_dir_name = has_clip_data_id ?
|
|
g_strdup_printf ("%u", clip_data_id) :
|
|
g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID);
|
|
|
|
*dst_size = 0;
|
|
dst_data = g_array_new (TRUE, TRUE, sizeof (char));
|
|
|
|
if (cliprdr_parse_file_list (src_data, src_size, &files, &n_files))
|
|
return NULL;
|
|
|
|
for (i = 0; i < n_files; ++i)
|
|
{
|
|
file = &files[i];
|
|
|
|
if (!filedescriptorw_filename_is_valid (file->cFileName))
|
|
return NULL;
|
|
|
|
filename = ConvertWCharToUtf8Alloc (file->cFileName, NULL);
|
|
if (!filename)
|
|
return NULL;
|
|
if (strchr (filename, '\\') || strchr (filename, '/'))
|
|
{
|
|
g_free (filename);
|
|
continue;
|
|
}
|
|
|
|
escaped_name = g_uri_escape_string (filename,
|
|
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
|
|
TRUE);
|
|
file_uri = g_strdup_printf ("file://%s/%s/%s\r\n",
|
|
clipboard_rdp->fuse_mount_path,
|
|
clip_data_dir_name, escaped_name);
|
|
g_array_append_vals (dst_data, file_uri, strlen (file_uri));
|
|
|
|
g_free (file_uri);
|
|
g_free (escaped_name);
|
|
g_free (filename);
|
|
}
|
|
|
|
*dst_size = dst_data->len;
|
|
|
|
return (uint8_t *) g_array_free (g_steal_pointer (&dst_data), FALSE);
|
|
}
|
|
|
|
static uint8_t *
|
|
convert_client_content_for_server (GrdClipboardRdp *clipboard_rdp,
|
|
uint8_t *src_data,
|
|
uint32_t src_size,
|
|
GrdMimeType mime_type,
|
|
uint32_t src_format_id,
|
|
uint32_t *dst_size,
|
|
gboolean has_clip_data_id,
|
|
uint32_t clip_data_id)
|
|
{
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
uint32_t dst_format_id;
|
|
uint8_t *dst_data;
|
|
gboolean is_null_terminated = FALSE;
|
|
BOOL success;
|
|
|
|
*dst_size = 0;
|
|
|
|
switch (mime_type)
|
|
{
|
|
case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
case GRD_MIME_TYPE_TEXT_UTF8_STRING:
|
|
is_null_terminated = TRUE;
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"text/plain");
|
|
break;
|
|
case GRD_MIME_TYPE_TEXT_HTML:
|
|
is_null_terminated = TRUE;
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
GRD_CFSTR_MIME_HTML);
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
GRD_CFSTR_MIME_TEXT_HTML);
|
|
break;
|
|
case GRD_MIME_TYPE_IMAGE_BMP:
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"image/bmp");
|
|
break;
|
|
case GRD_MIME_TYPE_TEXT_URILIST:
|
|
case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
|
|
src_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"FileGroupDescriptorW");
|
|
dst_format_id = ClipboardGetFormatId (clipboard_rdp->system,
|
|
"text/uri-list");
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
success = ClipboardSetData (clipboard_rdp->system,
|
|
src_format_id, src_data, src_size);
|
|
if (!success)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: "
|
|
"Failed to set data to convert");
|
|
return NULL;
|
|
}
|
|
|
|
if (mime_type == GRD_MIME_TYPE_TEXT_URILIST ||
|
|
mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
|
|
{
|
|
dst_data = get_uri_list_from_packet_file_list (clipboard_rdp,
|
|
src_data, src_size,
|
|
dst_size, has_clip_data_id,
|
|
clip_data_id);
|
|
}
|
|
else
|
|
{
|
|
dst_data = ClipboardGetData (clipboard_rdp->system, dst_format_id,
|
|
dst_size);
|
|
}
|
|
if (!dst_data)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Converting clipboard content failed: "
|
|
"Failed to get converted data");
|
|
return NULL;
|
|
}
|
|
|
|
if (is_null_terminated)
|
|
{
|
|
uint8_t *null_terminator_pos = memchr (dst_data, '\0', *dst_size);
|
|
if (null_terminator_pos)
|
|
*dst_size = null_terminator_pos - dst_data;
|
|
}
|
|
|
|
if (mime_type == GRD_MIME_TYPE_TEXT_URILIST ||
|
|
mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
|
|
{
|
|
FILEDESCRIPTORW *files = NULL;
|
|
FILEDESCRIPTORW *file;
|
|
uint32_t n_files = 0;
|
|
gboolean result;
|
|
g_autofree char *clip_data_dir_name = NULL;
|
|
char *filename = NULL;
|
|
char *escaped_name;
|
|
char *full_filepath;
|
|
GrdMimeType second_mime_type;
|
|
FormatData *format_data;
|
|
GArray *data_nautilus;
|
|
const char *nautilus_header;
|
|
uint8_t *dst_data_nautilus;
|
|
uint32_t dst_size_nautilus;
|
|
uint32_t i;
|
|
|
|
if (cliprdr_parse_file_list (src_data, src_size, &files, &n_files))
|
|
{
|
|
g_free (dst_data);
|
|
return NULL;
|
|
}
|
|
|
|
if (has_clip_data_id)
|
|
{
|
|
result = grd_rdp_fuse_clipboard_set_cdi_selection (rdp_fuse_clipboard,
|
|
files, n_files,
|
|
clip_data_id);
|
|
}
|
|
else
|
|
{
|
|
result = grd_rdp_fuse_clipboard_set_no_cdi_selection (rdp_fuse_clipboard,
|
|
files, n_files);
|
|
}
|
|
if (!result)
|
|
{
|
|
g_free (files);
|
|
g_free (dst_data);
|
|
return NULL;
|
|
}
|
|
|
|
clip_data_dir_name = has_clip_data_id ?
|
|
g_strdup_printf ("%u", clip_data_id) :
|
|
g_strdup_printf ("%lu", GRD_RDP_FUSE_CLIPBOARD_NO_CLIP_DATA_ID);
|
|
|
|
data_nautilus = g_array_new (TRUE, TRUE, sizeof (char));
|
|
nautilus_header = "copy";
|
|
g_array_append_vals (data_nautilus, nautilus_header,
|
|
strlen (nautilus_header));
|
|
for (i = 0; i < n_files; ++i)
|
|
{
|
|
file = &files[i];
|
|
|
|
filename = ConvertWCharToUtf8Alloc (file->cFileName, NULL);
|
|
if (!filename)
|
|
{
|
|
g_array_free (data_nautilus, TRUE);
|
|
grd_rdp_fuse_clipboard_clear_no_cdi_selection (rdp_fuse_clipboard);
|
|
g_free (files);
|
|
g_free (dst_data);
|
|
return NULL;
|
|
}
|
|
if (strchr (filename, '\\') || strchr (filename, '/'))
|
|
{
|
|
g_free (filename);
|
|
continue;
|
|
}
|
|
|
|
escaped_name = g_uri_escape_string (filename,
|
|
G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, TRUE);
|
|
full_filepath = g_strdup_printf ("\nfile://%s/%s/%s",
|
|
clipboard_rdp->fuse_mount_path,
|
|
clip_data_dir_name, escaped_name);
|
|
g_array_append_vals (data_nautilus, full_filepath,
|
|
strlen (full_filepath));
|
|
|
|
g_free (full_filepath);
|
|
g_free (escaped_name);
|
|
g_free (filename);
|
|
}
|
|
|
|
dst_size_nautilus = data_nautilus->len;
|
|
dst_data_nautilus = (uint8_t *) g_array_free (data_nautilus, FALSE);
|
|
|
|
format_data = g_malloc0 (sizeof (FormatData));
|
|
if (mime_type == GRD_MIME_TYPE_TEXT_URILIST)
|
|
{
|
|
/**
|
|
* Also create the format data for the
|
|
* "x-special/gnome-copied-files" mimetype
|
|
*/
|
|
second_mime_type = GRD_MIME_TYPE_XS_GNOME_COPIED_FILES;
|
|
format_data->size = dst_size_nautilus;
|
|
format_data->data = dst_data_nautilus;
|
|
}
|
|
else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
|
|
{
|
|
/**
|
|
* Also create the format data for the "text/uri-list" mimetype
|
|
*/
|
|
second_mime_type = GRD_MIME_TYPE_TEXT_URILIST;
|
|
format_data->size = *dst_size;
|
|
format_data->data = dst_data;
|
|
|
|
*dst_size = dst_size_nautilus;
|
|
dst_data = dst_data_nautilus;
|
|
}
|
|
else
|
|
{
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
g_hash_table_insert (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (second_mime_type),
|
|
format_data);
|
|
|
|
g_free (files);
|
|
}
|
|
|
|
if (!dst_data)
|
|
g_warning ("[RDP.CLIPRDR] Converting clipboard content failed");
|
|
|
|
return dst_data;
|
|
}
|
|
|
|
static gboolean
|
|
prepare_client_content_for_server (GrdClipboardRdp *clipboard_rdp,
|
|
uint8_t **src_data,
|
|
uint32_t src_size,
|
|
GrdMimeTypeTable *mime_type_table,
|
|
gboolean has_clip_data_id,
|
|
uint32_t clip_data_id)
|
|
{
|
|
GrdMimeType mime_type = mime_type_table->mime_type;
|
|
uint32_t src_format_id = mime_type_table->rdp.format_id;
|
|
FormatData *format_data;
|
|
uint8_t *dst_data = NULL;
|
|
uint32_t dst_size = 0;
|
|
|
|
switch (mime_type)
|
|
{
|
|
case GRD_MIME_TYPE_TEXT_PLAIN_UTF8:
|
|
case GRD_MIME_TYPE_TEXT_UTF8_STRING:
|
|
case GRD_MIME_TYPE_TEXT_HTML:
|
|
case GRD_MIME_TYPE_IMAGE_BMP:
|
|
case GRD_MIME_TYPE_TEXT_URILIST:
|
|
case GRD_MIME_TYPE_XS_GNOME_COPIED_FILES:
|
|
dst_data = convert_client_content_for_server (clipboard_rdp,
|
|
*src_data, src_size,
|
|
mime_type, src_format_id,
|
|
&dst_size, has_clip_data_id,
|
|
clip_data_id);
|
|
break;
|
|
default:
|
|
dst_data = g_steal_pointer (src_data);
|
|
dst_size = src_size;
|
|
}
|
|
|
|
if (!dst_data)
|
|
return FALSE;
|
|
|
|
format_data = g_malloc0 (sizeof (FormatData));
|
|
format_data->size = dst_size;
|
|
format_data->data = dst_data;
|
|
|
|
g_hash_table_insert (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (mime_type),
|
|
format_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
serve_mime_type_content (GrdClipboardRdp *clipboard_rdp,
|
|
GrdMimeType mime_type)
|
|
{
|
|
GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
|
|
FormatData *format_data;
|
|
GList *serials;
|
|
unsigned int serial;
|
|
GList *l;
|
|
|
|
if (!g_hash_table_steal_extended (clipboard_rdp->pending_client_requests,
|
|
GUINT_TO_POINTER (mime_type),
|
|
NULL, (gpointer *) &serials))
|
|
return;
|
|
|
|
if (!g_hash_table_lookup_extended (clipboard_rdp->format_data_cache,
|
|
GUINT_TO_POINTER (mime_type),
|
|
NULL, (gpointer *) &format_data))
|
|
g_assert_not_reached ();
|
|
|
|
for (l = serials; l; l = l->next)
|
|
{
|
|
serial = GPOINTER_TO_UINT (l->data);
|
|
grd_clipboard_submit_client_content_for_mime_type (clipboard, serial,
|
|
format_data->data,
|
|
format_data->size);
|
|
}
|
|
g_list_free (serials);
|
|
}
|
|
|
|
static gboolean
|
|
handle_format_data_response (gpointer user_data)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = user_data;
|
|
ClientFormatDataRequestContext *request_context;
|
|
CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response;
|
|
GrdMimeTypeTable *mime_type_table;
|
|
GrdMimeType mime_type;
|
|
uint8_t *src_data;
|
|
uint32_t src_size;
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_request_mutex);
|
|
request_context = g_steal_pointer (&clipboard_rdp->current_client_request);
|
|
format_data_response = g_steal_pointer (&clipboard_rdp->format_data_response);
|
|
|
|
clipboard_rdp->client_format_data_response_id = 0;
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
mime_type_table = &request_context->mime_type_table;
|
|
mime_type = mime_type_table->mime_type;
|
|
|
|
if (extract_format_data_response (clipboard_rdp, format_data_response,
|
|
&src_data, &src_size) &&
|
|
prepare_client_content_for_server (clipboard_rdp, &src_data, src_size,
|
|
mime_type_table,
|
|
request_context->has_clip_data_id,
|
|
request_context->clip_data_id))
|
|
{
|
|
serve_mime_type_content (clipboard_rdp, mime_type);
|
|
|
|
if (mime_type == GRD_MIME_TYPE_TEXT_URILIST)
|
|
serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_XS_GNOME_COPIED_FILES);
|
|
else if (mime_type == GRD_MIME_TYPE_XS_GNOME_COPIED_FILES)
|
|
serve_mime_type_content (clipboard_rdp, GRD_MIME_TYPE_TEXT_URILIST);
|
|
}
|
|
else
|
|
{
|
|
abort_client_requests_for_context (clipboard_rdp, request_context);
|
|
}
|
|
g_free (src_data);
|
|
g_free (format_data_response);
|
|
g_free (request_context);
|
|
|
|
g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove);
|
|
|
|
maybe_send_next_mime_type_content_request (clipboard_rdp);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
/**
|
|
* Clients response to our data request that we sent
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_format_data_response (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FORMAT_DATA_RESPONSE *format_data_response)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
|
|
g_mutex_lock (&clipboard_rdp->client_request_mutex);
|
|
if (!clipboard_rdp->current_client_request)
|
|
{
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
if (clipboard_rdp->format_data_response)
|
|
{
|
|
g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData);
|
|
g_clear_pointer (&clipboard_rdp->format_data_response, g_free);
|
|
}
|
|
|
|
clipboard_rdp->format_data_response =
|
|
g_memdup2 (format_data_response,
|
|
sizeof (CLIPRDR_FORMAT_DATA_RESPONSE));
|
|
clipboard_rdp->format_data_response->requestedFormatData =
|
|
g_memdup2 (format_data_response->requestedFormatData,
|
|
format_data_response->common.dataLen);
|
|
|
|
if (!clipboard_rdp->client_format_data_response_id)
|
|
{
|
|
clipboard_rdp->client_format_data_response_id =
|
|
g_idle_add (handle_format_data_response, clipboard_rdp);
|
|
}
|
|
g_mutex_unlock (&clipboard_rdp->client_request_mutex);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
delegate_request_file_contents_size (wClipboardDelegate *delegate,
|
|
const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
|
|
{
|
|
wClipboardFileSizeRequest file_size_request = {0};
|
|
|
|
file_size_request.streamId = file_contents_request->streamId;
|
|
file_size_request.listIndex = file_contents_request->listIndex;
|
|
|
|
return delegate->ClientRequestFileSize (delegate, &file_size_request);
|
|
}
|
|
|
|
static uint32_t
|
|
delegate_request_file_contents_range (wClipboardDelegate *delegate,
|
|
const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
|
|
{
|
|
wClipboardFileRangeRequest file_range_request = {0};
|
|
|
|
file_range_request.streamId = file_contents_request->streamId;
|
|
file_range_request.listIndex = file_contents_request->listIndex;
|
|
file_range_request.nPositionLow = file_contents_request->nPositionLow;
|
|
file_range_request.nPositionHigh = file_contents_request->nPositionHigh;
|
|
file_range_request.cbRequested = file_contents_request->cbRequested;
|
|
|
|
return delegate->ClientRequestFileRange (delegate, &file_range_request);
|
|
}
|
|
|
|
static uint32_t
|
|
send_file_contents_response_failure (CliprdrServerContext *cliprdr_context,
|
|
uint32_t stream_id)
|
|
{
|
|
CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
|
|
|
|
file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE;
|
|
file_contents_response.common.msgFlags = CB_RESPONSE_FAIL;
|
|
file_contents_response.streamId = stream_id;
|
|
|
|
return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
|
|
&file_contents_response);
|
|
}
|
|
|
|
/**
|
|
* Client requests us to send either the size of the remote file or a portion
|
|
* of the data in the file
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_file_contents_request (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FILE_CONTENTS_REQUEST *file_contents_request)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
wClipboardDelegate *delegate = clipboard_rdp->delegate;
|
|
gboolean has_file_list = clipboard_rdp->has_file_list;
|
|
gboolean requests_allowed = clipboard_rdp->server_file_contents_requests_allowed;
|
|
uint32_t clip_data_id = file_contents_request->clipDataId;
|
|
uint32_t stream_id = file_contents_request->streamId;
|
|
ClipDataEntry *entry = NULL;
|
|
uint32_t error = NO_ERROR;
|
|
|
|
if (file_contents_request->haveClipDataId)
|
|
g_debug ("[RDP.CLIPRDR] FileContentsRequest has clipDataId %u", clip_data_id);
|
|
else
|
|
g_debug ("[RDP.CLIPRDR] FileContentsRequest does not have a clipDataId");
|
|
|
|
if (file_contents_request->haveClipDataId)
|
|
{
|
|
if (!g_hash_table_lookup_extended (clipboard_rdp->clip_data_table,
|
|
GUINT_TO_POINTER (clip_data_id),
|
|
NULL, (gpointer *) &entry))
|
|
return send_file_contents_response_failure (cliprdr_context, stream_id);
|
|
|
|
delegate = entry->delegate;
|
|
has_file_list = entry->has_file_list;
|
|
requests_allowed = entry->requests_allowed;
|
|
|
|
if (!requests_allowed)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] ClipDataEntry with id %u is not eligible of "
|
|
"requesting file contents.", clip_data_id);
|
|
}
|
|
}
|
|
|
|
if (!requests_allowed || !has_file_list)
|
|
return send_file_contents_response_failure (cliprdr_context, stream_id);
|
|
|
|
if (file_contents_request->dwFlags & FILECONTENTS_SIZE &&
|
|
file_contents_request->dwFlags & FILECONTENTS_RANGE)
|
|
{
|
|
/**
|
|
* FILECONTENTS_SIZE and FILECONTENTS_RANGE are not allowed
|
|
* to be set at the same time
|
|
*/
|
|
return send_file_contents_response_failure (cliprdr_context, stream_id);
|
|
}
|
|
|
|
if (file_contents_request->dwFlags & FILECONTENTS_SIZE)
|
|
{
|
|
error = delegate_request_file_contents_size (delegate,
|
|
file_contents_request);
|
|
}
|
|
else if (file_contents_request->dwFlags & FILECONTENTS_RANGE)
|
|
{
|
|
error = delegate_request_file_contents_range (delegate,
|
|
file_contents_request);
|
|
}
|
|
else
|
|
{
|
|
error = ERROR_INVALID_DATA;
|
|
}
|
|
|
|
if (error)
|
|
return send_file_contents_response_failure (cliprdr_context, stream_id);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
/**
|
|
* Clients response to our file contents request that we sent
|
|
*/
|
|
static uint32_t
|
|
cliprdr_client_file_contents_response (CliprdrServerContext *cliprdr_context,
|
|
const CLIPRDR_FILE_CONTENTS_RESPONSE *file_contents_response)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = cliprdr_context->custom;
|
|
GrdRdpFuseClipboard *rdp_fuse_clipboard = clipboard_rdp->rdp_fuse_clipboard;
|
|
|
|
grd_rdp_fuse_clipboard_submit_file_contents_response (
|
|
rdp_fuse_clipboard, file_contents_response->streamId,
|
|
file_contents_response->common.msgFlags & CB_RESPONSE_OK,
|
|
file_contents_response->requestedData,
|
|
file_contents_response->cbRequested);
|
|
|
|
return CHANNEL_RC_OK;
|
|
}
|
|
|
|
static uint32_t
|
|
cliprdr_file_size_success (wClipboardDelegate *delegate,
|
|
const wClipboardFileSizeRequest *file_size_request,
|
|
uint64_t file_size)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = delegate->custom;
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
|
|
|
|
file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE;
|
|
file_contents_response.common.msgFlags = CB_RESPONSE_OK;
|
|
file_contents_response.streamId = file_size_request->streamId;
|
|
file_contents_response.cbRequested = sizeof (uint64_t);
|
|
file_contents_response.requestedData = (uint8_t *) &file_size;
|
|
|
|
return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
|
|
&file_contents_response);
|
|
}
|
|
|
|
static uint32_t
|
|
cliprdr_file_size_failure (wClipboardDelegate *delegate,
|
|
const wClipboardFileSizeRequest *file_size_request,
|
|
uint32_t error)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = delegate->custom;
|
|
|
|
return send_file_contents_response_failure (clipboard_rdp->cliprdr_context,
|
|
file_size_request->streamId);
|
|
}
|
|
|
|
static uint32_t
|
|
cliprdr_file_range_success (wClipboardDelegate *delegate,
|
|
const wClipboardFileRangeRequest *file_range_request,
|
|
const uint8_t *data,
|
|
uint32_t size)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = delegate->custom;
|
|
CliprdrServerContext *cliprdr_context = clipboard_rdp->cliprdr_context;
|
|
CLIPRDR_FILE_CONTENTS_RESPONSE file_contents_response = {0};
|
|
|
|
file_contents_response.common.msgType = CB_FILECONTENTS_RESPONSE;
|
|
file_contents_response.common.msgFlags = CB_RESPONSE_OK;
|
|
file_contents_response.streamId = file_range_request->streamId;
|
|
file_contents_response.cbRequested = size;
|
|
file_contents_response.requestedData = data;
|
|
|
|
return cliprdr_context->ServerFileContentsResponse (cliprdr_context,
|
|
&file_contents_response);
|
|
}
|
|
|
|
static uint32_t
|
|
cliprdr_file_range_failure (wClipboardDelegate *delegate,
|
|
const wClipboardFileRangeRequest *file_range_request,
|
|
uint32_t error)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = delegate->custom;
|
|
|
|
return send_file_contents_response_failure (clipboard_rdp->cliprdr_context,
|
|
file_range_request->streamId);
|
|
}
|
|
|
|
static BOOL
|
|
is_valid_unix_filename (LPCWSTR filename)
|
|
{
|
|
LPCWSTR c;
|
|
|
|
if (!filename)
|
|
return FALSE;
|
|
|
|
if (filename[0] == L'\0')
|
|
return FALSE;
|
|
|
|
/* Reserved characters */
|
|
for (c = filename; *c; ++c)
|
|
{
|
|
if (*c == L'/')
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
create_new_winpr_clipboard (GrdClipboardRdp *clipboard_rdp)
|
|
{
|
|
g_debug ("[RDP.CLIPRDR] Creating new WinPR clipboard");
|
|
|
|
clipboard_rdp->system = ClipboardCreate ();
|
|
clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system);
|
|
clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success;
|
|
clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure;
|
|
clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success;
|
|
clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure;
|
|
clipboard_rdp->delegate->basePath = NULL;
|
|
clipboard_rdp->delegate->custom = clipboard_rdp;
|
|
|
|
if (clipboard_rdp->relieve_filename_restriction)
|
|
clipboard_rdp->delegate->IsFileNameComponentValid = is_valid_unix_filename;
|
|
|
|
clipboard_rdp->has_file_list = FALSE;
|
|
}
|
|
|
|
GrdClipboardRdp *
|
|
grd_clipboard_rdp_new (GrdSessionRdp *session_rdp,
|
|
HANDLE vcm,
|
|
gboolean relieve_filename_restriction)
|
|
{
|
|
g_autoptr (GrdClipboardRdp) clipboard_rdp = NULL;
|
|
GrdClipboard *clipboard;
|
|
CliprdrServerContext *cliprdr_context;
|
|
|
|
clipboard_rdp = g_object_new (GRD_TYPE_CLIPBOARD_RDP, NULL);
|
|
cliprdr_context = cliprdr_server_context_new (vcm);
|
|
if (!cliprdr_context)
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Failed to create server context");
|
|
return NULL;
|
|
}
|
|
|
|
clipboard_rdp->cliprdr_context = cliprdr_context;
|
|
clipboard_rdp->relieve_filename_restriction = relieve_filename_restriction;
|
|
|
|
clipboard = GRD_CLIPBOARD (clipboard_rdp);
|
|
grd_clipboard_initialize (clipboard, GRD_SESSION (session_rdp));
|
|
|
|
cliprdr_context->useLongFormatNames = TRUE;
|
|
cliprdr_context->streamFileClipEnabled = TRUE;
|
|
cliprdr_context->fileClipNoFilePaths = TRUE;
|
|
cliprdr_context->canLockClipData = TRUE;
|
|
cliprdr_context->hasHugeFileSupport = TRUE;
|
|
|
|
cliprdr_context->ClientCapabilities = cliprdr_client_capabilities;
|
|
cliprdr_context->TempDirectory = cliprdr_temp_directory;
|
|
cliprdr_context->ClientFormatList = cliprdr_client_format_list;
|
|
cliprdr_context->ClientFormatListResponse = cliprdr_client_format_list_response;
|
|
cliprdr_context->ClientLockClipboardData = cliprdr_client_lock_clipboard_data;
|
|
cliprdr_context->ClientUnlockClipboardData = cliprdr_client_unlock_clipboard_data;
|
|
cliprdr_context->ClientFormatDataRequest = cliprdr_client_format_data_request;
|
|
cliprdr_context->ClientFormatDataResponse = cliprdr_client_format_data_response;
|
|
cliprdr_context->ClientFileContentsRequest = cliprdr_client_file_contents_request;
|
|
cliprdr_context->ClientFileContentsResponse = cliprdr_client_file_contents_response;
|
|
cliprdr_context->custom = clipboard_rdp;
|
|
|
|
if (relieve_filename_restriction)
|
|
{
|
|
g_message ("[RDP.CLIPRDR] Relieving CLIPRDR filename restriction");
|
|
clipboard_rdp->delegate->IsFileNameComponentValid = is_valid_unix_filename;
|
|
}
|
|
|
|
if (cliprdr_context->Start (cliprdr_context))
|
|
{
|
|
g_warning ("[RDP.CLIPRDR] Failed to open CLIPRDR channel");
|
|
g_clear_pointer (&clipboard_rdp->cliprdr_context,
|
|
cliprdr_server_context_free);
|
|
return NULL;
|
|
}
|
|
|
|
return g_steal_pointer (&clipboard_rdp);
|
|
}
|
|
|
|
static gboolean
|
|
clear_format_data (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
FormatData *format_data = value;
|
|
|
|
g_free (format_data->data);
|
|
g_free (format_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
clear_client_requests (gpointer key,
|
|
gpointer value,
|
|
gpointer user_data)
|
|
{
|
|
GList *serials = value;
|
|
|
|
g_list_free (serials);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_dispose (GObject *object)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object);
|
|
GrdClipboard *clipboard = GRD_CLIPBOARD (clipboard_rdp);
|
|
|
|
g_mutex_lock (&clipboard_rdp->completion_mutex);
|
|
clipboard_rdp->protocol_stopped = TRUE;
|
|
g_cond_signal (&clipboard_rdp->completion_cond);
|
|
g_mutex_unlock (&clipboard_rdp->completion_mutex);
|
|
|
|
if (clipboard_rdp->cliprdr_context)
|
|
{
|
|
clipboard_rdp->cliprdr_context->Stop (clipboard_rdp->cliprdr_context);
|
|
grd_clipboard_disable_clipboard (clipboard);
|
|
}
|
|
|
|
g_clear_pointer (&clipboard_rdp->current_client_request, g_free);
|
|
if (clipboard_rdp->format_data_response)
|
|
{
|
|
g_free ((uint8_t *) clipboard_rdp->format_data_response->requestedFormatData);
|
|
g_clear_pointer (&clipboard_rdp->format_data_response, g_free);
|
|
}
|
|
|
|
if (clipboard_rdp->clipboard_retrieval_id)
|
|
g_clear_pointer (&clipboard_rdp->clipboard_retrieval_context.entry, g_free);
|
|
|
|
g_clear_pointer (&clipboard_rdp->format_data_request_context, g_free);
|
|
g_clear_pointer (&clipboard_rdp->queued_server_formats, g_list_free);
|
|
g_clear_pointer (&clipboard_rdp->pending_server_formats, g_list_free);
|
|
|
|
if (clipboard_rdp->ordered_client_requests)
|
|
{
|
|
g_queue_free_full (clipboard_rdp->ordered_client_requests, g_free);
|
|
clipboard_rdp->ordered_client_requests = NULL;
|
|
}
|
|
g_hash_table_foreach_remove (clipboard_rdp->pending_client_requests,
|
|
clear_client_requests,
|
|
NULL);
|
|
g_hash_table_foreach_remove (clipboard_rdp->format_data_cache,
|
|
clear_format_data,
|
|
NULL);
|
|
|
|
g_clear_object (&clipboard_rdp->rdp_fuse_clipboard);
|
|
rmdir (clipboard_rdp->fuse_mount_path);
|
|
g_clear_pointer (&clipboard_rdp->fuse_mount_path, g_free);
|
|
|
|
g_assert (g_hash_table_size (clipboard_rdp->pending_client_requests) == 0);
|
|
g_assert (g_hash_table_size (clipboard_rdp->format_data_cache) == 0);
|
|
g_clear_pointer (&clipboard_rdp->pending_client_requests, g_hash_table_unref);
|
|
g_clear_pointer (&clipboard_rdp->format_data_cache, g_hash_table_unref);
|
|
g_clear_pointer (&clipboard_rdp->clip_data_table, g_hash_table_destroy);
|
|
g_clear_pointer (&clipboard_rdp->serial_entry_table, g_hash_table_destroy);
|
|
g_clear_pointer (&clipboard_rdp->allowed_server_formats, g_hash_table_destroy);
|
|
g_clear_pointer (&clipboard_rdp->system, ClipboardDestroy);
|
|
g_clear_pointer (&clipboard_rdp->cliprdr_context, cliprdr_server_context_free);
|
|
|
|
g_clear_handle_id (&clipboard_rdp->pending_server_formats_drop_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->client_request_abort_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->clipboard_retrieval_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->clipboard_destruction_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->server_format_list_update_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->server_format_data_request_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->client_format_list_response_id, g_source_remove);
|
|
g_clear_handle_id (&clipboard_rdp->client_format_data_response_id, g_source_remove);
|
|
|
|
G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_finalize (GObject *object)
|
|
{
|
|
GrdClipboardRdp *clipboard_rdp = GRD_CLIPBOARD_RDP (object);
|
|
|
|
g_cond_clear (&clipboard_rdp->completion_cond);
|
|
g_mutex_clear (&clipboard_rdp->completion_mutex);
|
|
g_mutex_clear (&clipboard_rdp->server_format_data_request_mutex);
|
|
g_mutex_clear (&clipboard_rdp->client_format_list_response_mutex);
|
|
g_mutex_clear (&clipboard_rdp->server_format_list_update_mutex);
|
|
g_mutex_clear (&clipboard_rdp->clip_data_entry_mutex);
|
|
g_mutex_clear (&clipboard_rdp->client_request_mutex);
|
|
|
|
G_OBJECT_CLASS (grd_clipboard_rdp_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
clip_data_entry_free (gpointer data)
|
|
{
|
|
ClipDataEntry *entry = data;
|
|
|
|
g_debug ("[RDP.CLIPRDR] Freeing ClipDataEntry with id %u and serial %lu. "
|
|
"ClipDataEntry is independent: %s", entry->id, entry->serial,
|
|
entry->is_independent ? "true" : "false");
|
|
|
|
if (entry->is_independent)
|
|
g_clear_pointer (&entry->system, ClipboardDestroy);
|
|
|
|
g_free (entry);
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_init (GrdClipboardRdp *clipboard_rdp)
|
|
{
|
|
const char *grd_path = "/gnome-remote-desktop";
|
|
const char *cliprdr_template = "/cliprdr-XXXXXX";
|
|
g_autofree char *base_path = NULL;
|
|
g_autofree char *template_path = NULL;
|
|
|
|
base_path = g_strdup_printf ("%s%s", g_get_user_runtime_dir (), grd_path);
|
|
template_path = g_strdup_printf ("%s%s", base_path, cliprdr_template);
|
|
|
|
if (g_access (base_path, F_OK))
|
|
{
|
|
if (mkdir (base_path, 0700))
|
|
{
|
|
g_error ("Failed to create base runtime directory for "
|
|
"gnome-remote-desktop: %s", g_strerror (errno));
|
|
}
|
|
}
|
|
if (!mkdtemp (template_path))
|
|
{
|
|
g_error ("Failed to create clipboard file directory %s: %s",
|
|
template_path, g_strerror (errno));
|
|
}
|
|
|
|
clipboard_rdp->system = ClipboardCreate ();
|
|
clipboard_rdp->delegate = ClipboardGetDelegate (clipboard_rdp->system);
|
|
clipboard_rdp->delegate->ClipboardFileSizeSuccess = cliprdr_file_size_success;
|
|
clipboard_rdp->delegate->ClipboardFileSizeFailure = cliprdr_file_size_failure;
|
|
clipboard_rdp->delegate->ClipboardFileRangeSuccess = cliprdr_file_range_success;
|
|
clipboard_rdp->delegate->ClipboardFileRangeFailure = cliprdr_file_range_failure;
|
|
clipboard_rdp->delegate->basePath = NULL;
|
|
clipboard_rdp->delegate->custom = clipboard_rdp;
|
|
|
|
clipboard_rdp->completed_clip_data_entry = TRUE;
|
|
clipboard_rdp->completed_format_list = TRUE;
|
|
clipboard_rdp->completed_format_data_request = TRUE;
|
|
|
|
clipboard_rdp->allowed_server_formats = g_hash_table_new (NULL, NULL);
|
|
clipboard_rdp->serial_entry_table = g_hash_table_new_full (NULL, NULL, NULL,
|
|
clip_data_entry_free);
|
|
clipboard_rdp->clip_data_table = g_hash_table_new (NULL, NULL);
|
|
clipboard_rdp->format_data_cache = g_hash_table_new (NULL, NULL);
|
|
clipboard_rdp->pending_client_requests = g_hash_table_new (NULL, NULL);
|
|
clipboard_rdp->ordered_client_requests = g_queue_new ();
|
|
|
|
g_mutex_init (&clipboard_rdp->client_request_mutex);
|
|
g_mutex_init (&clipboard_rdp->clip_data_entry_mutex);
|
|
g_mutex_init (&clipboard_rdp->server_format_list_update_mutex);
|
|
g_mutex_init (&clipboard_rdp->client_format_list_response_mutex);
|
|
g_mutex_init (&clipboard_rdp->server_format_data_request_mutex);
|
|
g_mutex_init (&clipboard_rdp->completion_mutex);
|
|
g_cond_init (&clipboard_rdp->completion_cond);
|
|
|
|
clipboard_rdp->fuse_mount_path = g_steal_pointer (&template_path);
|
|
clipboard_rdp->rdp_fuse_clipboard =
|
|
grd_rdp_fuse_clipboard_new (clipboard_rdp, clipboard_rdp->fuse_mount_path);
|
|
}
|
|
|
|
static void
|
|
grd_clipboard_rdp_class_init (GrdClipboardRdpClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GrdClipboardClass *clipboard_class = GRD_CLIPBOARD_CLASS (klass);
|
|
|
|
object_class->dispose = grd_clipboard_rdp_dispose;
|
|
object_class->finalize = grd_clipboard_rdp_finalize;
|
|
|
|
clipboard_class->update_client_mime_type_list =
|
|
grd_clipboard_rdp_update_client_mime_type_list;
|
|
clipboard_class->request_client_content_for_mime_type =
|
|
grd_clipboard_rdp_request_client_content_for_mime_type;
|
|
clipboard_class->submit_requested_server_content =
|
|
grd_clipboard_rdp_submit_requested_server_content;
|
|
}
|