1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2012-2012 Jean-Louis Dupond
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2021 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA  02110-1301, USA.
21  *
22  *  In addition, as a special exception, the copyright holders give
23  *  permission to link the code of portions of this program with the
24  *  OpenSSL library under certain conditions as described in each
25  *  individual source file, and distribute linked combinations
26  *  including the two.
27  *  You must obey the GNU General Public License in all respects
28  *  for all of the code used other than OpenSSL. *  If you modify
29  *  file(s) with this exception, you may extend this exception to your
30  *  version of the file(s), but you are not obligated to do so. *  If you
31  *  do not wish to do so, delete this exception statement from your
32  *  version. *  If you delete this exception statement from all source
33  *  files in the program, then also delete it here.
34  *
35  */
36 
37 #include "rdp_plugin.h"
38 #include "rdp_cliprdr.h"
39 #include "rdp_event.h"
40 
41 #include <freerdp/freerdp.h>
42 #include <freerdp/channels/channels.h>
43 #include <freerdp/client/cliprdr.h>
44 #include <sys/time.h>
45 
46 #define CLIPBOARD_TRANSFER_WAIT_TIME 6
47 
remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom)48 UINT32 remmina_rdp_cliprdr_get_format_from_gdkatom(GdkAtom atom)
49 {
50 	TRACE_CALL(__func__);
51 	UINT32 rc;
52 	gchar *name = gdk_atom_name(atom);
53 	rc = 0;
54 	if (g_strcmp0("UTF8_STRING", name) == 0 || g_strcmp0("text/plain;charset=utf-8", name) == 0)
55 		rc = CF_UNICODETEXT;
56 	if (g_strcmp0("TEXT", name) == 0 || g_strcmp0("text/plain", name) == 0)
57 		rc = CF_TEXT;
58 	if (g_strcmp0("text/html", name) == 0)
59 		rc = CB_FORMAT_HTML;
60 	if (g_strcmp0("image/png", name) == 0)
61 		rc = CB_FORMAT_PNG;
62 	if (g_strcmp0("image/jpeg", name) == 0)
63 		rc = CB_FORMAT_JPEG;
64 	if (g_strcmp0("image/bmp", name) == 0)
65 		rc = CF_DIB;
66 	if (g_strcmp0("text/uri-list", name) == 0)
67 		rc = CB_FORMAT_TEXTURILIST;
68 	g_free(name);
69 	return rc;
70 }
71 
remmina_rdp_cliprdr_get_target_types(UINT32 ** formats,UINT16 * size,GdkAtom * types,int count)72 void remmina_rdp_cliprdr_get_target_types(UINT32 **formats, UINT16 *size, GdkAtom *types, int count)
73 {
74 	TRACE_CALL(__func__);
75 	int i;
76 	*size = 1;
77 	*formats = (UINT32 *)malloc(sizeof(UINT32) * (count + 1));
78 
79 	*formats[0] = 0;
80 	for (i = 0; i < count; i++) {
81 		UINT32 format = remmina_rdp_cliprdr_get_format_from_gdkatom(types[i]);
82 		if (format != 0) {
83 			(*formats)[*size] = format;
84 			(*size)++;
85 		}
86 	}
87 
88 	*formats = realloc(*formats, sizeof(UINT32) * (*size));
89 }
90 
lf2crlf(UINT8 * data,int * size)91 static UINT8 *lf2crlf(UINT8 *data, int *size)
92 {
93 	TRACE_CALL(__func__);
94 	UINT8 c;
95 	UINT8 *outbuf;
96 	UINT8 *out;
97 	UINT8 *in_end;
98 	UINT8 *in;
99 	int out_size;
100 
101 	out_size = (*size) * 2 + 1;
102 	outbuf = (UINT8 *)malloc(out_size);
103 	out = outbuf;
104 	in = data;
105 	in_end = data + (*size);
106 
107 	while (in < in_end) {
108 		c = *in++;
109 		if (c == '\n') {
110 			*out++ = '\r';
111 			*out++ = '\n';
112 		} else {
113 			*out++ = c;
114 		}
115 	}
116 
117 	*out++ = 0;
118 	*size = out - outbuf;
119 
120 	return outbuf;
121 }
122 
crlf2lf(UINT8 * data,size_t * size)123 static void crlf2lf(UINT8 *data, size_t *size)
124 {
125 	TRACE_CALL(__func__);
126 	UINT8 c;
127 	UINT8 *out;
128 	UINT8 *in;
129 	UINT8 *in_end;
130 
131 	out = data;
132 	in = data;
133 	in_end = data + (*size);
134 
135 	while (in < in_end) {
136 		c = *in++;
137 		if (c != '\r')
138 			*out++ = c;
139 	}
140 
141 	*size = out - data;
142 }
143 
remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext * context,CLIPRDR_FILE_CONTENTS_REQUEST * fileContentsRequest)144 int remmina_rdp_cliprdr_server_file_contents_request(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_REQUEST *fileContentsRequest)
145 {
146 	TRACE_CALL(__func__);
147 	return -1;
148 }
remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext * context,CLIPRDR_FILE_CONTENTS_RESPONSE * fileContentsResponse)149 int remmina_rdp_cliprdr_server_file_contents_response(CliprdrClientContext *context, CLIPRDR_FILE_CONTENTS_RESPONSE *fileContentsResponse)
150 {
151 	TRACE_CALL(__func__);
152 	return 1;
153 }
154 
remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget * gp)155 void remmina_rdp_cliprdr_send_client_format_list(RemminaProtocolWidget *gp)
156 {
157 	TRACE_CALL(__func__);
158 	RemminaPluginRdpUiObject *ui;
159 	rfContext *rfi = GET_PLUGIN_DATA(gp);
160 	rfClipboard *clipboard;
161 	CLIPRDR_FORMAT_LIST *pFormatList;
162 	RemminaPluginRdpEvent rdp_event = { 0 };
163 
164 	if (!rfi || !rfi->connected || rfi->is_reconnecting)
165 		return;
166 
167 	clipboard = &(rfi->clipboard);
168 
169 	ui = g_new0(RemminaPluginRdpUiObject, 1);
170 	ui->type = REMMINA_RDP_UI_CLIPBOARD;
171 	ui->clipboard.clipboard = clipboard;
172 	ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_FORMATLIST;
173 	pFormatList = remmina_rdp_event_queue_ui_sync_retptr(gp, ui);
174 
175 	rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_LIST;
176 	rdp_event.clipboard_formatlist.pFormatList = pFormatList;
177 	remmina_rdp_event_event_push(gp, &rdp_event);
178 }
179 
remmina_rdp_cliprdr_send_client_capabilities(rfClipboard * clipboard)180 static void remmina_rdp_cliprdr_send_client_capabilities(rfClipboard *clipboard)
181 {
182 	TRACE_CALL(__func__);
183 	CLIPRDR_CAPABILITIES capabilities;
184 	CLIPRDR_GENERAL_CAPABILITY_SET generalCapabilitySet;
185 
186 	capabilities.cCapabilitiesSets = 1;
187 	capabilities.capabilitySets = (CLIPRDR_CAPABILITY_SET *)&(generalCapabilitySet);
188 
189 	generalCapabilitySet.capabilitySetType = CB_CAPSTYPE_GENERAL;
190 	generalCapabilitySet.capabilitySetLength = 12;
191 
192 	generalCapabilitySet.version = CB_CAPS_VERSION_2;
193 	generalCapabilitySet.generalFlags = CB_USE_LONG_FORMAT_NAMES;
194 
195 	clipboard->context->ClientCapabilities(clipboard->context, &capabilities);
196 }
197 
198 
remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext * context,const CLIPRDR_MONITOR_READY * monitorReady)199 static UINT remmina_rdp_cliprdr_monitor_ready(CliprdrClientContext *context, const CLIPRDR_MONITOR_READY *monitorReady)
200 {
201 	TRACE_CALL(__func__);
202 	rfClipboard *clipboard = (rfClipboard *)context->custom;
203 	RemminaProtocolWidget *gp;
204 
205 	remmina_rdp_cliprdr_send_client_capabilities(clipboard);
206 	gp = clipboard->rfi->protocol_widget;
207 	remmina_rdp_cliprdr_send_client_format_list(gp);
208 
209 	return CHANNEL_RC_OK;
210 }
211 
remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext * context,const CLIPRDR_CAPABILITIES * capabilities)212 static UINT remmina_rdp_cliprdr_server_capabilities(CliprdrClientContext *context, const CLIPRDR_CAPABILITIES *capabilities)
213 {
214 	TRACE_CALL(__func__);
215 	return CHANNEL_RC_OK;
216 }
217 
218 
remmina_rdp_cliprdr_server_format_list(CliprdrClientContext * context,const CLIPRDR_FORMAT_LIST * formatList)219 static UINT remmina_rdp_cliprdr_server_format_list(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST *formatList)
220 {
221 	TRACE_CALL(__func__);
222 
223 	/* Called when a user do a "Copy" on the server: we collect all formats
224 	 * the server send us and then setup the local clipboard with the appropriate
225 	 * targets to request server data */
226 
227 	RemminaPluginRdpUiObject *ui;
228 	RemminaProtocolWidget *gp;
229 	rfClipboard *clipboard;
230 	CLIPRDR_FORMAT *format;
231 	CLIPRDR_FORMAT_LIST_RESPONSE formatListResponse;
232 	const char *serverFormatName;
233 
234 	int has_dib_level = 0;
235 
236 	int i;
237 
238 	clipboard = (rfClipboard *)context->custom;
239 	gp = clipboard->rfi->protocol_widget;
240 	GtkTargetList *list = gtk_target_list_new(NULL, 0);
241 
242 	REMMINA_PLUGIN_DEBUG("format list from the server:");
243 	for (i = 0; i < formatList->numFormats; i++) {
244 		format = &formatList->formats[i];
245 		serverFormatName = format->formatName;
246 		if (format->formatId == CF_UNICODETEXT) {
247 			serverFormatName = "CF_UNICODETEXT";
248 			GdkAtom atom = gdk_atom_intern("UTF8_STRING", TRUE);
249 			gtk_target_list_add(list, atom, 0, CF_UNICODETEXT);
250 		} else if (format->formatId == CF_TEXT) {
251 			serverFormatName = "CF_TEXT";
252 			GdkAtom atom = gdk_atom_intern("TEXT", TRUE);
253 			gtk_target_list_add(list, atom, 0, CF_TEXT);
254 		} else if (format->formatId == CF_DIB) {
255 			serverFormatName = "CF_DIB";
256 			if (has_dib_level < 1)
257 				has_dib_level = 1;
258 		} else if (format->formatId == CF_DIBV5) {
259 			serverFormatName = "CF_DIBV5";
260 			if (has_dib_level < 5)
261 				has_dib_level = 5;
262 		} else if (format->formatId == CB_FORMAT_JPEG) {
263 			serverFormatName = "CB_FORMAT_JPEG";
264 			GdkAtom atom = gdk_atom_intern("image/jpeg", TRUE);
265 			gtk_target_list_add(list, atom, 0, CB_FORMAT_JPEG);
266 		} else if (format->formatId == CB_FORMAT_PNG) {
267 			serverFormatName = "CB_FORMAT_PNG";
268 			GdkAtom atom = gdk_atom_intern("image/png", TRUE);
269 			gtk_target_list_add(list, atom, 0, CB_FORMAT_PNG);
270 		} else if (format->formatId == CB_FORMAT_HTML) {
271 			serverFormatName = "CB_FORMAT_HTML";
272 			GdkAtom atom = gdk_atom_intern("text/html", TRUE);
273 			gtk_target_list_add(list, atom, 0, CB_FORMAT_HTML);
274 		} else if (format->formatId == CB_FORMAT_TEXTURILIST) {
275 			serverFormatName = "CB_FORMAT_TEXTURILIST";
276 			GdkAtom atom = gdk_atom_intern("text/uri-list", TRUE);
277 			gtk_target_list_add(list, atom, 0, CB_FORMAT_TEXTURILIST);
278 		} else if (format->formatId == CF_LOCALE) {
279 			serverFormatName = "CF_LOCALE";
280 		} else if (format->formatId == CF_METAFILEPICT) {
281 			serverFormatName = "CF_METAFILEPICT";
282 		}
283 		REMMINA_PLUGIN_DEBUG("the server has clipboard format %d: %s", format->formatId, serverFormatName);
284 	}
285 
286 	/* Keep only one DIB format, if present */
287 	if (has_dib_level) {
288 		GdkAtom atom = gdk_atom_intern("image/bmp", TRUE);
289 		if (has_dib_level == 5)
290 			gtk_target_list_add(list, atom, 0, CF_DIBV5);
291 		else
292 			gtk_target_list_add(list, atom, 0, CF_DIB);
293 	}
294 
295 	/* Now we tell GTK to change the local keyboard calling gtk_clipboard_set_with_owner
296 	 * via REMMINA_RDP_UI_CLIPBOARD_SET_DATA
297 	 * GTK will immediately fire an "owner-change" event, that we should ignore */
298 
299 	ui = g_new0(RemminaPluginRdpUiObject, 1);
300 	ui->type = REMMINA_RDP_UI_CLIPBOARD;
301 	ui->clipboard.clipboard = clipboard;
302 	ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_SET_DATA;
303 	ui->clipboard.targetlist = list;
304 	remmina_rdp_event_queue_ui_sync_retint(gp, ui);
305 
306 	/* Send FormatListResponse to server */
307 
308 	formatListResponse.msgType = CB_FORMAT_LIST_RESPONSE;
309 	formatListResponse.msgFlags = CB_RESPONSE_OK; // Can be CB_RESPONSE_FAIL in case of error
310 	formatListResponse.dataLen = 0;
311 
312 	return clipboard->context->ClientFormatListResponse(clipboard->context, &formatListResponse);
313 }
314 
remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext * context,const CLIPRDR_FORMAT_LIST_RESPONSE * formatListResponse)315 static UINT remmina_rdp_cliprdr_server_format_list_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_LIST_RESPONSE *formatListResponse)
316 {
317 	TRACE_CALL(__func__);
318 	return CHANNEL_RC_OK;
319 }
320 
321 
remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext * context,const CLIPRDR_FORMAT_DATA_REQUEST * formatDataRequest)322 static UINT remmina_rdp_cliprdr_server_format_data_request(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_REQUEST *formatDataRequest)
323 {
324 	TRACE_CALL(__func__);
325 
326 	RemminaPluginRdpUiObject *ui;
327 	RemminaProtocolWidget *gp;
328 	rfClipboard *clipboard;
329 
330 	clipboard = (rfClipboard *)context->custom;
331 	gp = clipboard->rfi->protocol_widget;
332 
333 	ui = g_new0(RemminaPluginRdpUiObject, 1);
334 	ui->type = REMMINA_RDP_UI_CLIPBOARD;
335 	ui->clipboard.clipboard = clipboard;
336 	ui->clipboard.type = REMMINA_RDP_UI_CLIPBOARD_GET_DATA;
337 	ui->clipboard.format = formatDataRequest->requestedFormatId;
338 	remmina_rdp_event_queue_ui_sync_retint(gp, ui);
339 
340 	return CHANNEL_RC_OK;
341 }
342 
remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext * context,const CLIPRDR_FORMAT_DATA_RESPONSE * formatDataResponse)343 static UINT remmina_rdp_cliprdr_server_format_data_response(CliprdrClientContext *context, const CLIPRDR_FORMAT_DATA_RESPONSE *formatDataResponse)
344 {
345 	TRACE_CALL(__func__);
346 
347 	const UINT8 *data;
348 	size_t size;
349 	rfContext *rfi;
350 	RemminaProtocolWidget *gp;
351 	rfClipboard *clipboard;
352 	GdkPixbufLoader *pixbuf;
353 	gpointer output = NULL;
354 
355 	clipboard = (rfClipboard *)context->custom;
356 	gp = clipboard->rfi->protocol_widget;
357 	rfi = GET_PLUGIN_DATA(gp);
358 
359 	data = formatDataResponse->requestedFormatData;
360 	size = formatDataResponse->dataLen;
361 
362 	// formatDataResponse->requestedFormatData is allocated
363 	//  by freerdp and freed after returning from this callback function.
364 	//  So we must make a copy if we need to preserve it
365 
366 	if (size > 0) {
367 		switch (rfi->clipboard.format) {
368 		case CF_UNICODETEXT:
369 		{
370 			size = ConvertFromUnicode(CP_UTF8, 0, (WCHAR *)data, size / 2, (CHAR **)&output, 0, NULL, NULL);
371 			crlf2lf(output, &size);
372 			break;
373 		}
374 
375 		case CF_TEXT:
376 		case CB_FORMAT_HTML:
377 		{
378 			output = (gpointer)calloc(1, size + 1);
379 			if (output) {
380 				memcpy(output, data, size);
381 				crlf2lf(output, &size);
382 			}
383 			break;
384 		}
385 
386 		case CF_DIBV5:
387 		case CF_DIB:
388 		{
389 			wStream *s;
390 			UINT32 offset;
391 			GError *perr;
392 			BITMAPINFOHEADER *pbi;
393 			BITMAPV5HEADER *pbi5;
394 
395 			pbi = (BITMAPINFOHEADER *)data;
396 
397 			// offset calculation inspired by http://downloads.poolelan.com/MSDN/MSDNLibrary6/Disk1/Samples/VC/OS/WindowsXP/GetImage/BitmapUtil.cpp
398 			offset = 14 + pbi->biSize;
399 			if (pbi->biClrUsed != 0)
400 				offset += sizeof(RGBQUAD) * pbi->biClrUsed;
401 			else if (pbi->biBitCount <= 8)
402 				offset += sizeof(RGBQUAD) * (1 << pbi->biBitCount);
403 			if (pbi->biSize == sizeof(BITMAPINFOHEADER)) {
404 				if (pbi->biCompression == 3)         // BI_BITFIELDS is 3
405 					offset += 12;
406 			} else if (pbi->biSize >= sizeof(BITMAPV5HEADER)) {
407 				pbi5 = (BITMAPV5HEADER *)pbi;
408 				if (pbi5->bV5ProfileData <= offset)
409 					offset += pbi5->bV5ProfileSize;
410 			}
411 			s = Stream_New(NULL, 14 + size);
412 			Stream_Write_UINT8(s, 'B');
413 			Stream_Write_UINT8(s, 'M');
414 			Stream_Write_UINT32(s, 14 + size);
415 			Stream_Write_UINT32(s, 0);
416 			Stream_Write_UINT32(s, offset);
417 			Stream_Write(s, data, size);
418 
419 			data = Stream_Buffer(s);
420 			size = Stream_Length(s);
421 
422 			pixbuf = gdk_pixbuf_loader_new();
423 			perr = NULL;
424 			if (!gdk_pixbuf_loader_write(pixbuf, data, size, &perr)) {
425 				Stream_Free(s, TRUE);
426 				g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_write() returned error %s\n", perr->message);
427 			} else {
428 				if (!gdk_pixbuf_loader_close(pixbuf, &perr)) {
429 					g_warning("[RDP] rdp_cliprdr: gdk_pixbuf_loader_close() returned error %s\n", perr->message);
430 					perr = NULL;
431 				}
432 				Stream_Free(s, TRUE);
433 				output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf));
434 			}
435 			g_object_unref(pixbuf);
436 			break;
437 		}
438 
439 		case CB_FORMAT_PNG:
440 		case CB_FORMAT_JPEG:
441 		{
442 			pixbuf = gdk_pixbuf_loader_new();
443 			gdk_pixbuf_loader_write(pixbuf, data, size, NULL);
444 			output = g_object_ref(gdk_pixbuf_loader_get_pixbuf(pixbuf));
445 			gdk_pixbuf_loader_close(pixbuf, NULL);
446 			g_object_unref(pixbuf);
447 			break;
448 		}
449 		}
450 	}
451 
452 	REMMINA_PLUGIN_DEBUG("clibpoard data arrived form server, signalling main GTK thread that we have some data.");
453 
454 	pthread_mutex_lock(&clipboard->transfer_clip_mutex);
455 	pthread_cond_signal(&clipboard->transfer_clip_cond);
456 	if (clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
457 		REMMINA_PLUGIN_DEBUG("clibpoard transfer from server completed.");
458 		clipboard->srv_data = output;
459 	} else {
460 		// Clipboard data arrived from server when we are not busywaiting on main loop
461 		// Unfortunately, we must discard it
462 		REMMINA_PLUGIN_DEBUG("clibpoard transfer from server completed. Data discarded due to abort or timeout.");
463 		clipboard->srv_clip_data_wait = SCDW_NONE;
464 	}
465 	pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
466 
467 	return CHANNEL_RC_OK;
468 }
469 
remmina_rdp_cliprdr_request_data(GtkClipboard * gtkClipboard,GtkSelectionData * selection_data,guint info,RemminaProtocolWidget * gp)470 void remmina_rdp_cliprdr_request_data(GtkClipboard *gtkClipboard, GtkSelectionData *selection_data, guint info, RemminaProtocolWidget *gp)
471 {
472 	TRACE_CALL(__func__);
473 
474 	/* Called by GTK when someone press "Paste" on the client side.
475 	 * We ask to the server the data we need */
476 
477 	CLIPRDR_FORMAT_DATA_REQUEST *pFormatDataRequest;
478 	rfClipboard *clipboard;
479 	rfContext *rfi = GET_PLUGIN_DATA(gp);
480 	RemminaPluginRdpEvent rdp_event = { 0 };
481 	struct timespec to;
482 	struct timeval tv;
483 	int rc;
484 	time_t tlimit;
485 
486 	REMMINA_PLUGIN_DEBUG("A local application has requested remote clipboard data for local format id %d", info);
487 
488 	clipboard = &(rfi->clipboard);
489 	if (clipboard->srv_clip_data_wait != SCDW_NONE) {
490 		g_message("[RDP] Cannot paste now, I’m already transferring clipboard data from server. Try again later\n");
491 		return;
492 	}
493 
494 	clipboard->format = info;
495 
496 	/* Request Clipboard content from the server, the request is async */
497 
498 	pthread_mutex_lock(&clipboard->transfer_clip_mutex);
499 
500 	pFormatDataRequest = (CLIPRDR_FORMAT_DATA_REQUEST *)malloc(sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
501 	ZeroMemory(pFormatDataRequest, sizeof(CLIPRDR_FORMAT_DATA_REQUEST));
502 	pFormatDataRequest->requestedFormatId = clipboard->format;
503 	clipboard->srv_clip_data_wait = SCDW_BUSY_WAIT;
504 
505 	REMMINA_PLUGIN_DEBUG("Requesting clipboard data with format %d from the server", clipboard->format);
506 
507 	rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_REQUEST;
508 	rdp_event.clipboard_formatdatarequest.pFormatDataRequest = pFormatDataRequest;
509 	remmina_rdp_event_event_push(gp, &rdp_event);
510 
511 	/* Busy wait clibpoard data for CLIPBOARD_TRANSFER_WAIT_TIME seconds.
512 	 *      In the meanwhile allow GTK event loop to proceed */
513 
514 	tlimit = time(NULL) + CLIPBOARD_TRANSFER_WAIT_TIME;
515 	rc = 100000;
516 	while (time(NULL) < tlimit && rc != 0 && clipboard->srv_clip_data_wait == SCDW_BUSY_WAIT) {
517 		gettimeofday(&tv, NULL);
518 		to.tv_sec = tv.tv_sec;
519 		to.tv_nsec = tv.tv_usec * 1000 + 40000000;      // wait for 40ms
520 		if (to.tv_nsec >= 1000000000) {
521 			to.tv_nsec -= 1000000000;
522 			to.tv_sec++;
523 		}
524 		rc = pthread_cond_timedwait(&clipboard->transfer_clip_cond, &clipboard->transfer_clip_mutex, &to);
525 		if (rc == 0)
526 			break;
527 		gtk_main_iteration_do(FALSE);
528 	}
529 
530 	if (rc == 0) {
531 		/* Data has arrived without timeout */
532 		if (clipboard->srv_data != NULL) {
533 			if (info == CB_FORMAT_PNG || info == CF_DIB || info == CF_DIBV5 || info == CB_FORMAT_JPEG) {
534 				gtk_selection_data_set_pixbuf(selection_data, clipboard->srv_data);
535 				g_object_unref(clipboard->srv_data);
536 			} else {
537 				gtk_selection_data_set_text(selection_data, clipboard->srv_data, -1);
538 				free(clipboard->srv_data);
539 			}
540 		}
541 		clipboard->srv_clip_data_wait = SCDW_NONE;
542 	} else {
543 		if (clipboard->srv_clip_data_wait == SCDW_ABORTING) {
544 			g_warning("[RDP] Clipboard data wait aborted.");
545 		} else {
546 			if (rc == ETIMEDOUT)
547 				g_warning("[RDP] Clipboard data from the server is not available in %d seconds. No data will be available to user.",
548 					  CLIPBOARD_TRANSFER_WAIT_TIME);
549 			else
550 				g_warning("[RDP] internal error: pthread_cond_timedwait() returned %d\n", rc);
551 		}
552 		clipboard->srv_clip_data_wait = SCDW_NONE;
553 	}
554 	pthread_mutex_unlock(&clipboard->transfer_clip_mutex);
555 }
556 
remmina_rdp_cliprdr_empty_clipboard(GtkClipboard * gtkClipboard,rfClipboard * clipboard)557 void remmina_rdp_cliprdr_empty_clipboard(GtkClipboard *gtkClipboard, rfClipboard *clipboard)
558 {
559 	TRACE_CALL(__func__);
560 	/* No need to do anything here */
561 }
562 
remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget * gp)563 CLIPRDR_FORMAT_LIST *remmina_rdp_cliprdr_get_client_format_list(RemminaProtocolWidget *gp)
564 {
565 	TRACE_CALL(__func__);
566 
567 	GtkClipboard *gtkClipboard;
568 	rfContext *rfi = GET_PLUGIN_DATA(gp);
569 	GdkAtom *targets;
570 	gboolean result = 0;
571 	gint loccount, srvcount;
572 	gint formatId, i;
573 	CLIPRDR_FORMAT *formats;
574 	gchar *name;
575 	struct retp_t {
576 		CLIPRDR_FORMAT_LIST	pFormatList;
577 		CLIPRDR_FORMAT		formats[];
578 	} *retp;
579 
580 	formats = NULL;
581 
582 	retp = NULL;
583 
584 	gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
585 	if (gtkClipboard)
586 		result = gtk_clipboard_wait_for_targets(gtkClipboard, &targets, &loccount);
587 	REMMINA_PLUGIN_DEBUG("Sending to server the following local clipboard content formats");
588 	if (result && loccount > 0) {
589 		formats = (CLIPRDR_FORMAT *)malloc(loccount * sizeof(CLIPRDR_FORMAT));
590 		srvcount = 0;
591 		for (i = 0; i < loccount; i++) {
592 			formatId = remmina_rdp_cliprdr_get_format_from_gdkatom(targets[i]);
593 			if (formatId != 0) {
594 				name = gdk_atom_name(targets[i]);
595 				REMMINA_PLUGIN_DEBUG("     local clipboard format %s will be sent to remote as %d", name, formatId);
596 				g_free(name);
597 				formats[srvcount].formatId = formatId;
598 				formats[srvcount].formatName = NULL;
599 				srvcount++;
600 			}
601 		}
602 		if (srvcount > 0) {
603 			retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT) * srvcount);
604 			retp->pFormatList.formats = retp->formats;
605 			retp->pFormatList.numFormats = srvcount;
606 			memcpy(retp->formats, formats, sizeof(CLIPRDR_FORMAT) * srvcount);
607 		} else {
608 			retp = (struct retp_t *)malloc(sizeof(struct retp_t));
609 			retp->pFormatList.formats = NULL;
610 			retp->pFormatList.numFormats = 0;
611 		}
612 		free(formats);
613 	} else {
614 		retp = (struct retp_t *)malloc(sizeof(struct retp_t) + sizeof(CLIPRDR_FORMAT));
615 		retp->pFormatList.formats = NULL;
616 		retp->pFormatList.numFormats = 0;
617 	}
618 
619 	if (result)
620 		g_free(targets);
621 
622 	retp->pFormatList.msgType = CB_FORMAT_LIST;
623 	retp->pFormatList.msgFlags = 0;
624 
625 	return (CLIPRDR_FORMAT_LIST *)retp;
626 }
627 
remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget * gp,RemminaPluginRdpUiObject * ui)628 static void remmina_rdp_cliprdr_mt_get_format_list(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
629 {
630 	TRACE_CALL(__func__);
631 	ui->retptr = (void *)remmina_rdp_cliprdr_get_client_format_list(gp);
632 }
633 
634 
remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget * gp,RemminaPluginRdpUiObject * ui)635 void remmina_rdp_cliprdr_get_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
636 {
637 	TRACE_CALL(__func__);
638 	GtkClipboard *gtkClipboard;
639 	UINT8 *inbuf = NULL;
640 	UINT8 *outbuf = NULL;
641 	GdkPixbuf *image = NULL;
642 	int size = 0;
643 	rfContext *rfi = GET_PLUGIN_DATA(gp);
644 	RemminaPluginRdpEvent rdp_event = { 0 };
645 
646 	gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
647 	if (gtkClipboard) {
648 		switch (ui->clipboard.format) {
649 		case CF_TEXT:
650 		case CF_UNICODETEXT:
651 		case CB_FORMAT_HTML:
652 		{
653 			inbuf = (UINT8 *)gtk_clipboard_wait_for_text(gtkClipboard);
654 			break;
655 		}
656 
657 		case CB_FORMAT_PNG:
658 		case CB_FORMAT_JPEG:
659 		case CF_DIB:
660 		case CF_DIBV5:
661 		{
662 			image = gtk_clipboard_wait_for_image(gtkClipboard);
663 			break;
664 		}
665 		}
666 	}
667 
668 	/* No data received, send nothing */
669 	if (inbuf != NULL || image != NULL) {
670 		switch (ui->clipboard.format) {
671 		case CF_TEXT:
672 		case CB_FORMAT_HTML:
673 		{
674 			size = strlen((char *)inbuf);
675 			outbuf = lf2crlf(inbuf, &size);
676 			break;
677 		}
678 		case CF_UNICODETEXT:
679 		{
680 			size = strlen((char *)inbuf);
681 			inbuf = lf2crlf(inbuf, &size);
682 			size = (ConvertToUnicode(CP_UTF8, 0, (CHAR *)inbuf, -1, (WCHAR **)&outbuf, 0)) * sizeof(WCHAR);
683 			g_free(inbuf);
684 			break;
685 		}
686 		case CB_FORMAT_PNG:
687 		{
688 			gchar *data;
689 			gsize buffersize;
690 			gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "png", NULL, NULL);
691 			outbuf = (UINT8 *)malloc(buffersize);
692 			memcpy(outbuf, data, buffersize);
693 			size = buffersize;
694 			g_object_unref(image);
695 			break;
696 		}
697 		case CB_FORMAT_JPEG:
698 		{
699 			gchar *data;
700 			gsize buffersize;
701 			gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "jpeg", NULL, NULL);
702 			outbuf = (UINT8 *)malloc(buffersize);
703 			memcpy(outbuf, data, buffersize);
704 			size = buffersize;
705 			g_object_unref(image);
706 			break;
707 		}
708 		case CF_DIB:
709 		case CF_DIBV5:
710 		{
711 			gchar *data;
712 			gsize buffersize;
713 			gdk_pixbuf_save_to_buffer(image, &data, &buffersize, "bmp", NULL, NULL);
714 			size = buffersize - 14;
715 			outbuf = (UINT8 *)malloc(size);
716 			memcpy(outbuf, data + 14, size);
717 			g_object_unref(image);
718 			break;
719 		}
720 		}
721 	}
722 
723 	rdp_event.type = REMMINA_RDP_EVENT_TYPE_CLIPBOARD_SEND_CLIENT_FORMAT_DATA_RESPONSE;
724 	rdp_event.clipboard_formatdataresponse.data = outbuf;
725 	rdp_event.clipboard_formatdataresponse.size = size;
726 	remmina_rdp_event_event_push(gp, &rdp_event);
727 }
728 
remmina_rdp_cliprdr_set_clipboard_content(RemminaProtocolWidget * gp,RemminaPluginRdpUiObject * ui)729 void remmina_rdp_cliprdr_set_clipboard_content(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
730 {
731 	TRACE_CALL(__func__);
732 	GtkClipboard *gtkClipboard;
733 	rfContext *rfi = GET_PLUGIN_DATA(gp);
734 
735 	gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
736 	if (ui->clipboard.format == CB_FORMAT_PNG || ui->clipboard.format == CF_DIB || ui->clipboard.format == CF_DIBV5 || ui->clipboard.format == CB_FORMAT_JPEG) {
737 		gtk_clipboard_set_image(gtkClipboard, ui->clipboard.data);
738 		g_object_unref(ui->clipboard.data);
739 	} else {
740 		gtk_clipboard_set_text(gtkClipboard, ui->clipboard.data, -1);
741 		free(ui->clipboard.data);
742 	}
743 }
744 
remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget * gp,RemminaPluginRdpUiObject * ui)745 void remmina_rdp_cliprdr_set_clipboard_data(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
746 {
747 	TRACE_CALL(__func__);
748 	GtkClipboard *gtkClipboard;
749 	GtkTargetEntry *targets;
750 	gint n_targets;
751 	rfContext *rfi = GET_PLUGIN_DATA(gp);
752 
753 	gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
754 	if (gtkClipboard) {
755 		targets = gtk_target_table_new_from_list(ui->clipboard.targetlist, &n_targets);
756 		if (targets) {
757 			REMMINA_PLUGIN_DEBUG("setting clipboard with owner to owner %p", gp);
758 			gtk_clipboard_set_with_owner(gtkClipboard, targets, n_targets,
759 						     (GtkClipboardGetFunc)remmina_rdp_cliprdr_request_data,
760 						     (GtkClipboardClearFunc)remmina_rdp_cliprdr_empty_clipboard, G_OBJECT(gp));
761 			gtk_target_table_free(targets, n_targets);
762 		}
763 	}
764 }
765 
remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget * gp)766 void remmina_rdp_cliprdr_detach_owner(RemminaProtocolWidget *gp)
767 {
768 	/* When closing a rdp connection, we should check if gp is a clipboard owner.
769 	 * If it’s an owner, detach it from the clipboard */
770 	TRACE_CALL(__func__);
771 	rfContext *rfi = GET_PLUGIN_DATA(gp);
772 	GtkClipboard *gtkClipboard;
773 
774 	if (!rfi || !rfi->drawing_area) return;
775 
776 	gtkClipboard = gtk_widget_get_clipboard(rfi->drawing_area, GDK_SELECTION_CLIPBOARD);
777 	if (gtkClipboard && gtk_clipboard_get_owner(gtkClipboard) == (GObject *)gp)
778 		gtk_clipboard_clear(gtkClipboard);
779 
780 }
781 
remmina_rdp_event_process_clipboard(RemminaProtocolWidget * gp,RemminaPluginRdpUiObject * ui)782 void remmina_rdp_event_process_clipboard(RemminaProtocolWidget *gp, RemminaPluginRdpUiObject *ui)
783 {
784 	TRACE_CALL(__func__);
785 
786 	switch (ui->clipboard.type) {
787 	case REMMINA_RDP_UI_CLIPBOARD_FORMATLIST:
788 		remmina_rdp_cliprdr_mt_get_format_list(gp, ui);
789 		break;
790 
791 	case REMMINA_RDP_UI_CLIPBOARD_GET_DATA:
792 		remmina_rdp_cliprdr_get_clipboard_data(gp, ui);
793 		break;
794 
795 	case REMMINA_RDP_UI_CLIPBOARD_SET_DATA:
796 		remmina_rdp_cliprdr_set_clipboard_data(gp, ui);
797 		break;
798 
799 	case REMMINA_RDP_UI_CLIPBOARD_SET_CONTENT:
800 		remmina_rdp_cliprdr_set_clipboard_content(gp, ui);
801 		break;
802 	}
803 }
804 
remmina_rdp_clipboard_init(rfContext * rfi)805 void remmina_rdp_clipboard_init(rfContext *rfi)
806 {
807 	TRACE_CALL(__func__);
808 	// Future: initialize rfi->clipboard
809 }
remmina_rdp_clipboard_free(rfContext * rfi)810 void remmina_rdp_clipboard_free(rfContext *rfi)
811 {
812 	TRACE_CALL(__func__);
813 
814 	// Future: deinitialize rfi->clipboard
815 }
816 
remmina_rdp_clipboard_abort_transfer(rfContext * rfi)817 void remmina_rdp_clipboard_abort_transfer(rfContext *rfi)
818 {
819 	if (rfi && rfi->clipboard.srv_clip_data_wait == SCDW_BUSY_WAIT) {
820 		REMMINA_PLUGIN_DEBUG("requesting clipboard transfer to abort");
821 		/* Allow clipboard transfer from server to terminate */
822 		rfi->clipboard.srv_clip_data_wait = SCDW_ABORTING;
823 		usleep(100000);
824 	}
825 }
826 
827 
828 
remmina_rdp_cliprdr_init(rfContext * rfi,CliprdrClientContext * cliprdr)829 void remmina_rdp_cliprdr_init(rfContext *rfi, CliprdrClientContext *cliprdr)
830 {
831 	TRACE_CALL(__func__);
832 
833 	rfClipboard *clipboard;
834 	clipboard = &(rfi->clipboard);
835 
836 	rfi->clipboard.rfi = rfi;
837 	cliprdr->custom = (void *)clipboard;
838 
839 	clipboard->context = cliprdr;
840 	pthread_mutex_init(&clipboard->transfer_clip_mutex, NULL);
841 	pthread_cond_init(&clipboard->transfer_clip_cond, NULL);
842 	clipboard->srv_clip_data_wait = SCDW_NONE;
843 
844 	cliprdr->MonitorReady = remmina_rdp_cliprdr_monitor_ready;
845 	cliprdr->ServerCapabilities = remmina_rdp_cliprdr_server_capabilities;
846 	cliprdr->ServerFormatList = remmina_rdp_cliprdr_server_format_list;
847 	cliprdr->ServerFormatListResponse = remmina_rdp_cliprdr_server_format_list_response;
848 	cliprdr->ServerFormatDataRequest = remmina_rdp_cliprdr_server_format_data_request;
849 	cliprdr->ServerFormatDataResponse = remmina_rdp_cliprdr_server_format_data_response;
850 
851 //	cliprdr->ServerFileContentsRequest = remmina_rdp_cliprdr_server_file_contents_request;
852 //	cliprdr->ServerFileContentsResponse = remmina_rdp_cliprdr_server_file_contents_response;
853 }
854