FreeRDP
Loading...
Searching...
No Matches
shadow_cliprdr.c
1
21/* One clipboard in the session, one in every client. A subsystem talks to this
22 * file in mime types, the clients in clipboard formats; the conversions between
23 * them come from WinPR's wClipboard.
24 *
25 * session copies subsystem -> session_changed -> Format List to every client
26 * client pastes Format Data Request -> subsystem->ClipboardRead
27 * subsystem -> session_data -> Format Data Response
28 *
29 * client copies Format List -> subsystem->ClipboardOffer
30 * session pastes subsystem -> session_request -> Format Data Request to the owner
31 * Format Data Response -> subsystem->ClipboardProvide
32 *
33 * Nothing is copied before somebody pastes. The last one to copy owns the
34 * clipboard: when a client does, the session offers its data, and the other
35 * clients get an empty list, as their last list no longer holds.
36 *
37 * Data goes only to and from clients that may interact: a viewer sees the screen,
38 * not the clipboard. */
39
40#include <winpr/assert.h>
41#include <winpr/cast.h>
42#include <winpr/clipboard.h>
43#include <winpr/collections.h>
44#include <winpr/image.h>
45#include <winpr/synch.h>
46#include <winpr/sysinfo.h>
47#include <winpr/user.h>
48
49#include <freerdp/log.h>
50
51#include "shadow.h"
52#include "shadow_cliprdr.h"
53
54#define TAG SERVER_TAG("shadow.clipboard")
55
56#if defined(CHANNEL_CLIPRDR_SERVER)
57
58/* How long a client's own Format List may take to follow its capabilities */
59#define SHADOW_CLIPBOARD_LIST_WAIT_MS 1000
60
61/* A client that stopped answering must not block the pastes after it. */
62#define SHADOW_CLIPBOARD_FETCH_TIMEOUT_MS 10000
63
64#define SHADOW_CLIPBOARD_MIME_HTML "text/html"
65#define SHADOW_CLIPBOARD_FORMAT_HTML "HTML Format"
66
67/* the most mime types a client's clipboard turns into */
68#define SHADOW_CLIPBOARD_MAX_OFFER 16
69
70typedef struct
71{
72 rdpShadowClient* client;
73 BOOL ready; /* it sent its first Format List: it may get ours */
74} ShadowClipboardPeer;
75
76/* a client's paste: the session's data, which the subsystem delivers */
77typedef struct
78{
79 rdpShadowClient* client; /* nullptr once it left */
80 UINT32 formatId;
81 char* mime;
82} ShadowClipboardRead;
83
84/* the session's paste: the owner's data, which that client delivers */
85typedef struct
86{
87 rdpShadowClient* client;
88 UINT32 request;
89 UINT32 formatId; /* as the client numbers it */
90 UINT32 sourceId; /* as wClipboard numbers it */
91 UINT32 mimeId;
92 BOOL text;
93 BOOL canceled; /* the answer is on its way, and nobody wants it */
94 UINT64 sent;
95} ShadowClipboardFetch;
96
97struct rdp_shadow_clipboard
98{
99 rdpShadowServer* server;
100 CRITICAL_SECTION lock;
101 wClipboard* convert;
102 UINT32 htmlId;
103
104 wArrayList* peers;
105
106 /* the session's clipboard */
107 char** sessionMimes;
108 size_t nSessionMimes;
109
110 /* the client whose clipboard the session offers */
111 rdpShadowClient* owner;
112 CLIPRDR_FORMAT* ownerFormats;
113 UINT32 nOwnerFormats;
114
115 wArrayList* reads; /* the first is at the subsystem while readBusy */
116 BOOL readBusy;
117 wArrayList* fetches; /* the first is at the client while fetchBusy */
118 BOOL fetchBusy;
119};
120
121static const char* const shadow_clipboard_text_mimes[] = { "text/plain;charset=utf-8",
122 "UTF8_STRING", "text/plain" };
123
124static const UINT32 shadow_clipboard_image_formats[] = { WINPR_IMAGE_PNG, WINPR_IMAGE_BITMAP,
125 WINPR_IMAGE_WEBP, WINPR_IMAGE_JPEG };
126
127static void shadow_clipboard_read_free(void* obj)
128{
129 ShadowClipboardRead* read = obj;
130 if (!read)
131 return;
132 free(read->mime);
133 free(read);
134}
135
136static void shadow_clipboard_formats_free(CLIPRDR_FORMAT* formats, UINT32 count)
137{
138 if (!formats)
139 return;
140 for (UINT32 x = 0; x < count; x++)
141 free(formats[x].formatName);
142 free(formats);
143}
144
145static void shadow_clipboard_mimes_free(char** mimes, size_t count)
146{
147 if (!mimes)
148 return;
149 for (size_t x = 0; x < count; x++)
150 free(mimes[x]);
151 free((void*)mimes);
152}
153
154WINPR_ATTR_NODISCARD
155static rdpShadowSubsystem* shadow_clipboard_subsystem(const rdpShadowClipboard* clipboard)
156{
157 WINPR_ASSERT(clipboard);
158 WINPR_ASSERT(clipboard->server);
159 return clipboard->server->subsystem;
160}
161
162WINPR_ATTR_NODISCARD
163static ShadowClipboardPeer* shadow_clipboard_peer(rdpShadowClipboard* clipboard,
164 const rdpShadowClient* client)
165{
166 WINPR_ASSERT(clipboard);
167
168 const size_t count = ArrayList_Count(clipboard->peers);
169 for (size_t x = 0; x < count; x++)
170 {
171 ShadowClipboardPeer* peer = ArrayList_GetItem(clipboard->peers, x);
172 if (peer && (peer->client == client))
173 return peer;
174 }
175 return nullptr;
176}
177
178WINPR_ATTR_NODISCARD
179static BOOL shadow_clipboard_session_has(const rdpShadowClipboard* clipboard, const char* mime)
180{
181 WINPR_ASSERT(clipboard);
182 WINPR_ASSERT(mime);
183
184 for (size_t x = 0; x < clipboard->nSessionMimes; x++)
185 {
186 if (strcmp(clipboard->sessionMimes[x], mime) == 0)
187 return TRUE;
188 }
189 return FALSE;
190}
191
192/* The mime type to read from the session for a format a client asks for */
193WINPR_ATTR_NODISCARD
194static const char* shadow_clipboard_session_mime(const rdpShadowClipboard* clipboard,
195 UINT32 formatId)
196{
197 WINPR_ASSERT(clipboard);
198
199 if ((formatId == CF_UNICODETEXT) || (formatId == CF_TEXT) || (formatId == CF_OEMTEXT))
200 {
201 for (size_t x = 0; x < ARRAYSIZE(shadow_clipboard_text_mimes); x++)
202 {
203 if (shadow_clipboard_session_has(clipboard, shadow_clipboard_text_mimes[x]))
204 return shadow_clipboard_text_mimes[x];
205 }
206 }
207 else if ((formatId == CF_DIB) || (formatId == CF_DIBV5))
208 {
209 for (size_t x = 0; x < ARRAYSIZE(shadow_clipboard_image_formats); x++)
210 {
211 const UINT32 image = shadow_clipboard_image_formats[x];
212 const char* mime = winpr_image_format_mime(image);
213 if (mime && winpr_image_format_is_supported(image) &&
214 shadow_clipboard_session_has(clipboard, mime))
215 return mime;
216 }
217 }
218 else if ((formatId == clipboard->htmlId) &&
219 shadow_clipboard_session_has(clipboard, SHADOW_CLIPBOARD_MIME_HTML))
220 return SHADOW_CLIPBOARD_MIME_HTML;
221
222 return nullptr;
223}
224
225WINPR_ATTR_NODISCARD
226static UINT shadow_clipboard_send_list(const rdpShadowClipboard* clipboard, rdpShadowClient* client)
227{
228 WINPR_ASSERT(clipboard);
229 WINPR_ASSERT(client);
230 WINPR_ASSERT(client->cliprdr);
231
232 char htmlName[] = SHADOW_CLIPBOARD_FORMAT_HTML;
233 CLIPRDR_FORMAT formats[3] = WINPR_C_ARRAY_INIT;
234 UINT32 count = 0;
235
236 if (shadow_clipboard_session_mime(clipboard, CF_UNICODETEXT))
237 formats[count++].formatId = CF_UNICODETEXT;
238 if (shadow_clipboard_session_mime(clipboard, clipboard->htmlId))
239 {
240 formats[count].formatId = clipboard->htmlId;
241 formats[count++].formatName = htmlName;
242 }
243 if (shadow_clipboard_session_mime(clipboard, CF_DIB))
244 formats[count++].formatId = CF_DIB;
245
246 CLIPRDR_FORMAT_LIST list = WINPR_C_ARRAY_INIT;
247 list.common.msgType = CB_FORMAT_LIST;
248 list.numFormats = count;
249 list.formats = formats;
250 return client->cliprdr->ServerFormatList(client->cliprdr, &list);
251}
252
253/* To every client but one, which may be nullptr: what the session offers now */
254static void shadow_clipboard_announce(rdpShadowClipboard* clipboard, const rdpShadowClient* except)
255{
256 WINPR_ASSERT(clipboard);
257
258 const size_t count = ArrayList_Count(clipboard->peers);
259 for (size_t x = 0; x < count; x++)
260 {
261 const ShadowClipboardPeer* peer = ArrayList_GetItem(clipboard->peers, x);
262 if (!peer || !peer->ready || (peer->client == except) || !peer->client->mayInteract)
263 continue;
264
265 const UINT rc = shadow_clipboard_send_list(clipboard, peer->client);
266 if (rc != CHANNEL_RC_OK)
267 WLog_WARN(TAG, "ServerFormatList failed with %" PRIu32, rc);
268 }
269}
270
271/* @return the data in the other format, to be freed, or nullptr */
272WINPR_ATTR_NODISCARD
273static BYTE* shadow_clipboard_convert(rdpShadowClipboard* clipboard, UINT32 sourceId,
274 UINT32 targetId, const BYTE* data, size_t size,
275 UINT32* pConverted)
276{
277 WINPR_ASSERT(clipboard);
278 WINPR_ASSERT(pConverted);
279
280 *pConverted = 0;
281 if (!data || (size == 0) || (size > SHADOW_CLIPBOARD_MAX_SIZE))
282 return nullptr;
283
284 if (!ClipboardEmpty(clipboard->convert))
285 return nullptr;
286 const BOOL stored = ClipboardSetData(clipboard->convert, sourceId, data,
287 WINPR_ASSERTING_INT_CAST(UINT32, size));
288 if (!stored)
289 return nullptr;
290 return ClipboardGetData(clipboard->convert, targetId, pConverted);
291}
292
293static void shadow_clipboard_respond(CliprdrServerContext* cliprdr, const BYTE* data, UINT32 size)
294{
295 WINPR_ASSERT(cliprdr);
296
297 CLIPRDR_FORMAT_DATA_RESPONSE response = WINPR_C_ARRAY_INIT;
298 response.common.msgType = CB_FORMAT_DATA_RESPONSE;
299 response.common.msgFlags = data ? CB_RESPONSE_OK : CB_RESPONSE_FAIL;
300 response.common.dataLen = data ? size : 0;
301 response.requestedFormatData = data;
302
303 const UINT rc = cliprdr->ServerFormatDataResponse(cliprdr, &response);
304 if (rc != CHANNEL_RC_OK)
305 WLog_WARN(TAG, "ServerFormatDataResponse failed with %" PRIu32, rc);
306}
307
308/* Reads go to the subsystem one at a time, each one's answer starts the next */
309static void shadow_clipboard_read_next(rdpShadowClipboard* clipboard)
310{
311 WINPR_ASSERT(clipboard);
312
313 while (!clipboard->readBusy && (ArrayList_Count(clipboard->reads) > 0))
314 {
315 ShadowClipboardRead* read = ArrayList_GetItem(clipboard->reads, 0);
316 WINPR_ASSERT(read);
317
318 rdpShadowSubsystem* subsystem = shadow_clipboard_subsystem(clipboard);
319 if (read->client && subsystem->ClipboardRead(subsystem, read->mime))
320 {
321 clipboard->readBusy = TRUE;
322 return;
323 }
324
325 if (read->client)
326 shadow_clipboard_respond(read->client->cliprdr, nullptr, 0);
327 if (!ArrayList_RemoveAt(clipboard->reads, 0))
328 return;
329 }
330}
331
332static void shadow_clipboard_provide(rdpShadowClipboard* clipboard, UINT32 request,
333 const BYTE* data, size_t size)
334{
335 WINPR_ASSERT(clipboard);
336
337 rdpShadowSubsystem* subsystem = shadow_clipboard_subsystem(clipboard);
338 if (!subsystem->ClipboardProvide(subsystem, request, data, size))
339 WLog_WARN(TAG, "the subsystem did not take the data of request %" PRIu32, request);
340}
341
342/* Fetches go to the owner one at a time: a Format Data Response names no request */
343static void shadow_clipboard_fetch_next(rdpShadowClipboard* clipboard)
344{
345 WINPR_ASSERT(clipboard);
346
347 while (!clipboard->fetchBusy && (ArrayList_Count(clipboard->fetches) > 0))
348 {
349 ShadowClipboardFetch* fetch = ArrayList_GetItem(clipboard->fetches, 0);
350 WINPR_ASSERT(fetch);
351 WINPR_ASSERT(fetch->client);
352 WINPR_ASSERT(fetch->client->cliprdr);
353
354 CLIPRDR_FORMAT_DATA_REQUEST request = WINPR_C_ARRAY_INIT;
355 request.common.msgType = CB_FORMAT_DATA_REQUEST;
356 request.common.dataLen = 4;
357 request.requestedFormatId = fetch->formatId;
358
359 CliprdrServerContext* cliprdr = fetch->client->cliprdr;
360 const UINT rc = cliprdr->ServerFormatDataRequest(cliprdr, &request);
361 if (rc == CHANNEL_RC_OK)
362 {
363 fetch->sent = GetTickCount64();
364 clipboard->fetchBusy = TRUE;
365 return;
366 }
367
368 WLog_WARN(TAG, "ServerFormatDataRequest failed with %" PRIu32, rc);
369 shadow_clipboard_provide(clipboard, fetch->request, nullptr, 0);
370 if (!ArrayList_RemoveAt(clipboard->fetches, 0))
371 return;
372 }
373}
374
375/* The owner changes: no fetch waiting will be served. A client's answer to the
376 * one it has still arrives (unless the client left): it stays to catch that. */
377static void shadow_clipboard_cancel_fetches(rdpShadowClipboard* clipboard,
378 const rdpShadowClient* gone)
379{
380 WINPR_ASSERT(clipboard);
381
382 for (size_t x = ArrayList_Count(clipboard->fetches); x > 0; x--)
383 {
384 const size_t index = x - 1;
385 ShadowClipboardFetch* fetch = ArrayList_GetItem(clipboard->fetches, index);
386 WINPR_ASSERT(fetch);
387
388 const BOOL atClient = (index == 0) && clipboard->fetchBusy;
389 if (atClient && (fetch->client != gone))
390 {
391 if (!fetch->canceled)
392 shadow_clipboard_provide(clipboard, fetch->request, nullptr, 0);
393 fetch->canceled = TRUE;
394 continue;
395 }
396
397 if (atClient)
398 clipboard->fetchBusy = FALSE;
399 if (!fetch->canceled)
400 shadow_clipboard_provide(clipboard, fetch->request, nullptr, 0);
401 if (!ArrayList_RemoveAt(clipboard->fetches, index))
402 WLog_WARN(TAG, "cannot drop a clipboard request");
403 }
404}
405
406static void shadow_clipboard_clear_owner(rdpShadowClipboard* clipboard)
407{
408 WINPR_ASSERT(clipboard);
409
410 shadow_clipboard_formats_free(clipboard->ownerFormats, clipboard->nOwnerFormats);
411 clipboard->ownerFormats = nullptr;
412 clipboard->nOwnerFormats = 0;
413 clipboard->owner = nullptr;
414}
415
416/* What the session offers is gone with its owner: a paste must not hang on it */
417static void shadow_clipboard_release_owner(rdpShadowClipboard* clipboard)
418{
419 WINPR_ASSERT(clipboard);
420
421 shadow_clipboard_clear_owner(clipboard);
422
423 rdpShadowSubsystem* subsystem = shadow_clipboard_subsystem(clipboard);
424 if (!subsystem->ClipboardOffer(subsystem, nullptr, 0))
425 WLog_WARN(TAG, "the session keeps a clipboard nobody serves");
426}
427
428WINPR_ATTR_NODISCARD
429static const CLIPRDR_FORMAT* shadow_clipboard_find_format(const CLIPRDR_FORMAT* formats,
430 UINT32 count, UINT32 formatId,
431 const char* name)
432{
433 for (UINT32 x = 0; x < count; x++)
434 {
435 const CLIPRDR_FORMAT* format = &formats[x];
436 if (name)
437 {
438 if (format->formatName && (strcmp(format->formatName, name) == 0))
439 return format;
440 }
441 else if (format->formatId == formatId)
442 return format;
443 }
444 return nullptr;
445}
446
447WINPR_ATTR_NODISCARD
448static const CLIPRDR_FORMAT* shadow_clipboard_owner_text(const CLIPRDR_FORMAT* formats,
449 UINT32 count)
450{
451 const UINT32 ids[] = { CF_UNICODETEXT, CF_TEXT, CF_OEMTEXT };
452 for (size_t x = 0; x < ARRAYSIZE(ids); x++)
453 {
454 const CLIPRDR_FORMAT* format =
455 shadow_clipboard_find_format(formats, count, ids[x], nullptr);
456 if (format)
457 return format;
458 }
459 return nullptr;
460}
461
462WINPR_ATTR_NODISCARD
463static const CLIPRDR_FORMAT* shadow_clipboard_owner_image(const CLIPRDR_FORMAT* formats,
464 UINT32 count)
465{
466 const CLIPRDR_FORMAT* format = shadow_clipboard_find_format(formats, count, CF_DIB, nullptr);
467 if (format)
468 return format;
469 return shadow_clipboard_find_format(formats, count, CF_DIBV5, nullptr);
470}
471
472/* What a client's list means in the session. @return the number of mime types */
473WINPR_ATTR_NODISCARD
474static size_t shadow_clipboard_list_mimes(const CLIPRDR_FORMAT* formats, UINT32 count,
475 const char* mimes[SHADOW_CLIPBOARD_MAX_OFFER])
476{
477 size_t offered = 0;
478
479 if (shadow_clipboard_owner_text(formats, count))
480 {
481 for (size_t x = 0; x < ARRAYSIZE(shadow_clipboard_text_mimes); x++)
482 mimes[offered++] = shadow_clipboard_text_mimes[x];
483 }
484
485 if (shadow_clipboard_find_format(formats, count, 0, SHADOW_CLIPBOARD_FORMAT_HTML))
486 mimes[offered++] = SHADOW_CLIPBOARD_MIME_HTML;
487
488 if (shadow_clipboard_owner_image(formats, count))
489 {
490 for (size_t x = 0; x < ARRAYSIZE(shadow_clipboard_image_formats); x++)
491 {
492 const UINT32 image = shadow_clipboard_image_formats[x];
493 const char* mime = winpr_image_format_mime(image);
494 if (mime && winpr_image_format_is_supported(image))
495 mimes[offered++] = mime;
496 }
497 }
498
499 WINPR_ASSERT(offered <= SHADOW_CLIPBOARD_MAX_OFFER);
500 return offered;
501}
502
503WINPR_ATTR_NODISCARD
504static BOOL shadow_clipboard_set_owner(rdpShadowClipboard* clipboard, rdpShadowClient* client,
505 const CLIPRDR_FORMAT_LIST* list, const char* const* mimes,
506 size_t count)
507{
508 WINPR_ASSERT(clipboard);
509 WINPR_ASSERT(client);
510 WINPR_ASSERT(list);
511
512 CLIPRDR_FORMAT* formats = calloc(list->numFormats, sizeof(CLIPRDR_FORMAT));
513 if (!formats)
514 return FALSE;
515
516 for (UINT32 x = 0; x < list->numFormats; x++)
517 {
518 const CLIPRDR_FORMAT* format = &list->formats[x];
519 formats[x].formatId = format->formatId;
520 if (format->formatName)
521 {
522 formats[x].formatName = _strdup(format->formatName);
523 if (!formats[x].formatName)
524 {
525 shadow_clipboard_formats_free(formats, list->numFormats);
526 return FALSE;
527 }
528 }
529 }
530
531 shadow_clipboard_cancel_fetches(clipboard, nullptr);
532 shadow_clipboard_clear_owner(clipboard);
533 clipboard->owner = client;
534 clipboard->ownerFormats = formats;
535 clipboard->nOwnerFormats = list->numFormats;
536
537 shadow_clipboard_mimes_free(clipboard->sessionMimes, clipboard->nSessionMimes);
538 clipboard->sessionMimes = nullptr;
539 clipboard->nSessionMimes = 0;
540
541 shadow_clipboard_announce(clipboard, client);
542
543 rdpShadowSubsystem* subsystem = shadow_clipboard_subsystem(clipboard);
544 return subsystem->ClipboardOffer(subsystem, mimes, count);
545}
546
547WINPR_ATTR_NODISCARD
548static UINT shadow_clipboard_client_format_list(CliprdrServerContext* context,
549 const CLIPRDR_FORMAT_LIST* list)
550{
551 WINPR_ASSERT(context);
552 WINPR_ASSERT(list);
553
554 rdpShadowClient* client = context->custom;
555 WINPR_ASSERT(client);
556
557 CLIPRDR_FORMAT_LIST_RESPONSE response = WINPR_C_ARRAY_INIT;
558 response.common.msgType = CB_FORMAT_LIST_RESPONSE;
559 response.common.msgFlags = CB_RESPONSE_OK;
560 const UINT rc = context->ServerFormatListResponse(context, &response);
561 if (rc != CHANNEL_RC_OK)
562 return rc;
563
564 rdpShadowClipboard* clipboard = client->server->clipboard;
565 WINPR_ASSERT(clipboard);
566
567 EnterCriticalSection(&clipboard->lock);
568 ShadowClipboardPeer* peer = shadow_clipboard_peer(clipboard, client);
569 if (peer && client->mayInteract)
570 {
571 const BOOL first = !peer->ready;
572 peer->ready = TRUE;
573
574 const char* mimes[SHADOW_CLIPBOARD_MAX_OFFER] = WINPR_C_ARRAY_INIT;
575 const size_t count = shadow_clipboard_list_mimes(list->formats, list->numFormats, mimes);
576 if (count > 0)
577 {
578 if (!shadow_clipboard_set_owner(clipboard, client, list, mimes, count))
579 WLog_WARN(TAG, "the session did not take the client's clipboard");
580 }
581 else if (clipboard->owner == client)
582 {
583 /* its clipboard is empty, or holds nothing the session can use */
584 shadow_clipboard_cancel_fetches(clipboard, nullptr);
585 shadow_clipboard_release_owner(clipboard);
586 }
587 else if (first && (clipboard->nSessionMimes > 0))
588 {
589 /* a client that connects with nothing to offer gets the session's */
590 const UINT sent = shadow_clipboard_send_list(clipboard, client);
591 if (sent != CHANNEL_RC_OK)
592 WLog_WARN(TAG, "ServerFormatList failed with %" PRIu32, sent);
593 }
594 }
595 LeaveCriticalSection(&clipboard->lock);
596 return CHANNEL_RC_OK;
597}
598
599/* A client's capabilities follow Monitor Ready. A list of its own usually comes right
600 * after them, and a client with nothing to offer may send none (FreeRDP's own does not).
601 * Our list must not cross one on the wire: it would replace the clipboard the client
602 * just announced. The channel's thread runs this: anything to read wakes it, and the
603 * list then goes out with shadow_clipboard_client_format_list(). */
604WINPR_ATTR_NODISCARD
605static UINT shadow_clipboard_client_capabilities(CliprdrServerContext* context,
606 WINPR_ATTR_UNUSED const CLIPRDR_CAPABILITIES* caps)
607{
608 WINPR_ASSERT(context);
609
610 rdpShadowClient* client = context->custom;
611 WINPR_ASSERT(client);
612 rdpShadowClipboard* clipboard = client->server->clipboard;
613 WINPR_ASSERT(clipboard);
614
615 const DWORD waited =
616 WaitForSingleObject(context->GetEventHandle(context), SHADOW_CLIPBOARD_LIST_WAIT_MS);
617 if (waited != WAIT_TIMEOUT)
618 return CHANNEL_RC_OK;
619
620 EnterCriticalSection(&clipboard->lock);
621 ShadowClipboardPeer* peer = shadow_clipboard_peer(clipboard, client);
622 if (peer && !peer->ready && client->mayInteract)
623 {
624 peer->ready = TRUE;
625 if (clipboard->nSessionMimes > 0)
626 {
627 const UINT sent = shadow_clipboard_send_list(clipboard, client);
628 if (sent != CHANNEL_RC_OK)
629 WLog_WARN(TAG, "ServerFormatList failed with %" PRIu32, sent);
630 }
631 }
632 LeaveCriticalSection(&clipboard->lock);
633 return CHANNEL_RC_OK;
634}
635
636WINPR_ATTR_NODISCARD
637static UINT shadow_clipboard_client_data_request(CliprdrServerContext* context,
638 const CLIPRDR_FORMAT_DATA_REQUEST* request)
639{
640 WINPR_ASSERT(context);
641 WINPR_ASSERT(request);
642
643 rdpShadowClient* client = context->custom;
644 WINPR_ASSERT(client);
645 rdpShadowClipboard* clipboard = client->server->clipboard;
646 WINPR_ASSERT(clipboard);
647
648 BOOL queued = FALSE;
649 EnterCriticalSection(&clipboard->lock);
650 const char* mime = shadow_clipboard_session_mime(clipboard, request->requestedFormatId);
651 if (mime && client->mayInteract && shadow_clipboard_peer(clipboard, client))
652 {
653 ShadowClipboardRead* read = calloc(1, sizeof(ShadowClipboardRead));
654 if (read)
655 {
656 read->client = client;
657 read->formatId = request->requestedFormatId;
658 read->mime = _strdup(mime);
659 queued = read->mime && ArrayList_Append(clipboard->reads, read);
660 if (!queued)
661 shadow_clipboard_read_free(read);
662 }
663 }
664 if (queued)
665 shadow_clipboard_read_next(clipboard);
666 LeaveCriticalSection(&clipboard->lock);
667
668 if (!queued)
669 shadow_clipboard_respond(context, nullptr, 0);
670 return CHANNEL_RC_OK;
671}
672
673WINPR_ATTR_NODISCARD
674static UINT shadow_clipboard_client_data_response(CliprdrServerContext* context,
675 const CLIPRDR_FORMAT_DATA_RESPONSE* response)
676{
677 WINPR_ASSERT(context);
678 WINPR_ASSERT(response);
679
680 rdpShadowClient* client = context->custom;
681 WINPR_ASSERT(client);
682 rdpShadowClipboard* clipboard = client->server->clipboard;
683 WINPR_ASSERT(clipboard);
684
685 EnterCriticalSection(&clipboard->lock);
686 ShadowClipboardFetch* fetch =
687 clipboard->fetchBusy ? ArrayList_GetItem(clipboard->fetches, 0) : nullptr;
688 if (fetch && (fetch->client == client))
689 {
690 clipboard->fetchBusy = FALSE;
691
692 if (!fetch->canceled)
693 {
694 const BOOL ok = (response->common.msgFlags & CB_RESPONSE_OK) != 0;
695 UINT32 size = 0;
696 BYTE* data = nullptr;
697 if (ok)
698 data = shadow_clipboard_convert(clipboard, fetch->sourceId, fetch->mimeId,
699 response->requestedFormatData,
700 response->common.dataLen, &size);
701 WLog_DBG(TAG, "client data: %" PRIu32 " bytes, %" PRIu32 " for the session",
702 response->common.dataLen, size);
703
704 /* a Windows string ends in a NUL, text in a pipe does not */
705 if (data && fetch->text)
706 size = WINPR_ASSERTING_INT_CAST(UINT32, strnlen((const char*)data, size));
707 shadow_clipboard_provide(clipboard, fetch->request, data, size);
708 free(data);
709 }
710
711 if (!ArrayList_RemoveAt(clipboard->fetches, 0))
712 WLog_WARN(TAG, "cannot drop a clipboard request");
713 shadow_clipboard_fetch_next(clipboard);
714 }
715 LeaveCriticalSection(&clipboard->lock);
716 return CHANNEL_RC_OK;
717}
718
719#endif /* CHANNEL_CLIPRDR_SERVER */
720
721BOOL shadow_clipboard_session_changed(rdpShadowServer* server, const char* const* mimeTypes,
722 size_t count)
723{
724 WINPR_ASSERT(server);
725 WINPR_ASSERT(mimeTypes || (count == 0));
726
727#if defined(CHANNEL_CLIPRDR_SERVER)
728 rdpShadowClipboard* clipboard = server->clipboard;
729 if (!clipboard)
730 return FALSE;
731
732 char** mimes = nullptr;
733 if (count > 0)
734 {
735 mimes = (char**)calloc(count, sizeof(char*));
736 if (!mimes)
737 return FALSE;
738 for (size_t x = 0; x < count; x++)
739 {
740 mimes[x] = _strdup(mimeTypes[x]);
741 if (!mimes[x])
742 {
743 shadow_clipboard_mimes_free(mimes, count);
744 return FALSE;
745 }
746 }
747 }
748
749 EnterCriticalSection(&clipboard->lock);
750 shadow_clipboard_cancel_fetches(clipboard, nullptr);
751 shadow_clipboard_clear_owner(clipboard);
752 shadow_clipboard_mimes_free(clipboard->sessionMimes, clipboard->nSessionMimes);
753 clipboard->sessionMimes = mimes;
754 clipboard->nSessionMimes = count;
755 shadow_clipboard_announce(clipboard, nullptr);
756 LeaveCriticalSection(&clipboard->lock);
757 return TRUE;
758#else
759 return FALSE;
760#endif
761}
762
763BOOL shadow_clipboard_session_data(rdpShadowServer* server, const BYTE* data, size_t size)
764{
765 WINPR_ASSERT(server);
766
767#if defined(CHANNEL_CLIPRDR_SERVER)
768 rdpShadowClipboard* clipboard = server->clipboard;
769 if (!clipboard)
770 return FALSE;
771
772 BOOL rc = FALSE;
773 EnterCriticalSection(&clipboard->lock);
774 const ShadowClipboardRead* read =
775 clipboard->readBusy ? ArrayList_GetItem(clipboard->reads, 0) : nullptr;
776 if (read)
777 {
778 rc = TRUE;
779 clipboard->readBusy = FALSE;
780
781 if (read->client)
782 {
783 const UINT32 mimeId = ClipboardRegisterFormat(clipboard->convert, read->mime);
784 UINT32 converted = 0;
785 BYTE* payload =
786 shadow_clipboard_convert(clipboard, mimeId, read->formatId, data, size, &converted);
787 WLog_DBG(TAG, "session data: %" PRIuz " bytes of %s, %" PRIu32 " for the client", size,
788 read->mime, converted);
789 shadow_clipboard_respond(read->client->cliprdr, payload, converted);
790 free(payload);
791 }
792
793 if (!ArrayList_RemoveAt(clipboard->reads, 0))
794 WLog_WARN(TAG, "cannot drop a clipboard read");
795 shadow_clipboard_read_next(clipboard);
796 }
797 LeaveCriticalSection(&clipboard->lock);
798 return rc;
799#else
800 WINPR_UNUSED(data);
801 WINPR_UNUSED(size);
802 return FALSE;
803#endif
804}
805
806BOOL shadow_clipboard_session_request(rdpShadowServer* server, UINT32 request, const char* mimeType)
807{
808 WINPR_ASSERT(server);
809 WINPR_ASSERT(mimeType);
810
811#if defined(CHANNEL_CLIPRDR_SERVER)
812 rdpShadowClipboard* clipboard = server->clipboard;
813 if (!clipboard)
814 return FALSE;
815
816 EnterCriticalSection(&clipboard->lock);
817
818 /* an owner that stopped answering */
819 ShadowClipboardFetch* first =
820 clipboard->fetchBusy ? ArrayList_GetItem(clipboard->fetches, 0) : nullptr;
821 if (first && (GetTickCount64() - first->sent >= SHADOW_CLIPBOARD_FETCH_TIMEOUT_MS))
822 {
823 WLog_WARN(TAG, "no clipboard data from the client");
824 clipboard->fetchBusy = FALSE;
825 if (!first->canceled)
826 shadow_clipboard_provide(clipboard, first->request, nullptr, 0);
827 if (!ArrayList_RemoveAt(clipboard->fetches, 0))
828 WLog_WARN(TAG, "cannot drop a clipboard request");
829 }
830
831 const CLIPRDR_FORMAT* source = nullptr;
832 if (clipboard->owner)
833 {
834 const CLIPRDR_FORMAT* formats = clipboard->ownerFormats;
835 const UINT32 count = clipboard->nOwnerFormats;
836 if (strcmp(mimeType, SHADOW_CLIPBOARD_MIME_HTML) == 0)
837 source = shadow_clipboard_find_format(formats, count, 0, SHADOW_CLIPBOARD_FORMAT_HTML);
838 else if (strncmp(mimeType, "image/", 6) == 0)
839 source = shadow_clipboard_owner_image(formats, count);
840 else
841 source = shadow_clipboard_owner_text(formats, count);
842 }
843
844 BOOL queued = FALSE;
845 if (source)
846 {
847 ShadowClipboardFetch* fetch = calloc(1, sizeof(ShadowClipboardFetch));
848 if (fetch)
849 {
850 fetch->client = clipboard->owner;
851 fetch->request = request;
852 fetch->formatId = source->formatId;
853 fetch->sourceId = source->formatName
854 ? ClipboardRegisterFormat(clipboard->convert, source->formatName)
855 : source->formatId;
856 fetch->mimeId = ClipboardRegisterFormat(clipboard->convert, mimeType);
857 fetch->text = strncmp(mimeType, "image/", 6) != 0;
858 queued = ArrayList_Append(clipboard->fetches, fetch);
859 if (!queued)
860 free(fetch);
861 }
862 }
863
864 if (queued)
865 shadow_clipboard_fetch_next(clipboard);
866 else
867 shadow_clipboard_provide(clipboard, request, nullptr, 0);
868 LeaveCriticalSection(&clipboard->lock);
869 return TRUE;
870#else
871 WINPR_UNUSED(request);
872 return FALSE;
873#endif
874}
875
876#if defined(CHANNEL_CLIPRDR_SERVER)
877
878rdpShadowClipboard* shadow_clipboard_new(rdpShadowServer* server)
879{
880 WINPR_ASSERT(server);
881
882 rdpShadowClipboard* clipboard = calloc(1, sizeof(rdpShadowClipboard));
883 if (!clipboard)
884 return nullptr;
885
886 clipboard->server = server;
887 if (!InitializeCriticalSectionAndSpinCount(&clipboard->lock, 4000))
888 {
889 free(clipboard);
890 return nullptr;
891 }
892
893 clipboard->convert = ClipboardCreate();
894 clipboard->peers = ArrayList_New(FALSE);
895 clipboard->reads = ArrayList_New(FALSE);
896 clipboard->fetches = ArrayList_New(FALSE);
897 if (!clipboard->convert || !clipboard->peers || !clipboard->reads || !clipboard->fetches)
898 goto fail;
899
900 wObject* peers = ArrayList_Object(clipboard->peers);
901 WINPR_ASSERT(peers);
902 peers->fnObjectFree = free;
903 wObject* reads = ArrayList_Object(clipboard->reads);
904 WINPR_ASSERT(reads);
905 reads->fnObjectFree = shadow_clipboard_read_free;
906 wObject* fetches = ArrayList_Object(clipboard->fetches);
907 WINPR_ASSERT(fetches);
908 fetches->fnObjectFree = free;
909
910 clipboard->htmlId = ClipboardRegisterFormat(clipboard->convert, SHADOW_CLIPBOARD_FORMAT_HTML);
911 if (clipboard->htmlId == 0)
912 goto fail;
913 return clipboard;
914
915fail:
916 shadow_clipboard_free(clipboard);
917 return nullptr;
918}
919
920void shadow_clipboard_free(rdpShadowClipboard* clipboard)
921{
922 if (!clipboard)
923 return;
924
925 ArrayList_Free(clipboard->fetches);
926 ArrayList_Free(clipboard->reads);
927 ArrayList_Free(clipboard->peers);
928 shadow_clipboard_formats_free(clipboard->ownerFormats, clipboard->nOwnerFormats);
929 shadow_clipboard_mimes_free(clipboard->sessionMimes, clipboard->nSessionMimes);
930 ClipboardDestroy(clipboard->convert);
931 DeleteCriticalSection(&clipboard->lock);
932 free(clipboard);
933}
934
935int shadow_client_cliprdr_init(rdpShadowClient* client)
936{
937 WINPR_ASSERT(client);
938 WINPR_ASSERT(client->server);
939
940 rdpShadowClipboard* clipboard = client->server->clipboard;
941 const rdpShadowSubsystem* subsystem = client->subsystem;
942 WINPR_ASSERT(subsystem);
943
944 if (!clipboard || !subsystem->ClipboardOffer || !subsystem->ClipboardRead ||
945 !subsystem->ClipboardProvide)
946 return 0;
947
948 CliprdrServerContext* cliprdr = cliprdr_server_context_new(client->vcm);
949 if (!cliprdr)
950 return -1;
951
952 cliprdr->custom = client;
953 cliprdr->rdpcontext = &client->context;
954 cliprdr->autoInitializationSequence = TRUE;
955 cliprdr->useLongFormatNames = TRUE;
956 cliprdr->ClientCapabilities = shadow_clipboard_client_capabilities;
957 cliprdr->ClientFormatList = shadow_clipboard_client_format_list;
958 cliprdr->ClientFormatDataRequest = shadow_clipboard_client_data_request;
959 cliprdr->ClientFormatDataResponse = shadow_clipboard_client_data_response;
960
961 ShadowClipboardPeer* peer = calloc(1, sizeof(ShadowClipboardPeer));
962 if (!peer)
963 {
964 cliprdr_server_context_free(cliprdr);
965 return -1;
966 }
967 peer->client = client;
968 client->cliprdr = cliprdr;
969
970 EnterCriticalSection(&clipboard->lock);
971 const BOOL added = ArrayList_Append(clipboard->peers, peer);
972 LeaveCriticalSection(&clipboard->lock);
973 if (!added)
974 {
975 free(peer);
976 client->cliprdr = nullptr;
977 cliprdr_server_context_free(cliprdr);
978 return -1;
979 }
980
981 const UINT rc = cliprdr->Start(cliprdr);
982 if (rc != CHANNEL_RC_OK)
983 {
984 WLog_ERR(TAG, "cannot start the clipboard channel: %" PRIu32, rc);
985 return -1; /* shadow_client_cliprdr_uninit() cleans up */
986 }
987 return 1;
988}
989
990void shadow_client_cliprdr_uninit(rdpShadowClient* client)
991{
992 WINPR_ASSERT(client);
993 WINPR_ASSERT(client->server);
994
995 CliprdrServerContext* cliprdr = client->cliprdr;
996 if (!cliprdr)
997 return;
998
999 rdpShadowClipboard* clipboard = client->server->clipboard;
1000 WINPR_ASSERT(clipboard);
1001
1002 EnterCriticalSection(&clipboard->lock);
1003 ShadowClipboardPeer* peer = shadow_clipboard_peer(clipboard, client);
1004 if (peer && !ArrayList_Remove(clipboard->peers, peer))
1005 WLog_WARN(TAG, "cannot drop a client of the clipboard");
1006
1007 for (size_t x = ArrayList_Count(clipboard->reads); x > 0; x--)
1008 {
1009 const size_t index = x - 1;
1010 ShadowClipboardRead* read = ArrayList_GetItem(clipboard->reads, index);
1011 WINPR_ASSERT(read);
1012 if (read->client != client)
1013 continue;
1014
1015 /* the one at the subsystem stays until its answer */
1016 if ((index == 0) && clipboard->readBusy)
1017 read->client = nullptr;
1018 else if (!ArrayList_RemoveAt(clipboard->reads, index))
1019 WLog_WARN(TAG, "cannot drop a clipboard read");
1020 }
1021
1022 if (clipboard->owner == client)
1023 {
1024 shadow_clipboard_cancel_fetches(clipboard, client);
1025 shadow_clipboard_release_owner(clipboard);
1026 }
1027 else if (clipboard->fetchBusy)
1028 {
1029 /* an answer it still owed from when it was the owner */
1030 const ShadowClipboardFetch* fetch = ArrayList_GetItem(clipboard->fetches, 0);
1031 if (fetch && (fetch->client == client))
1032 {
1033 clipboard->fetchBusy = FALSE;
1034 if (!ArrayList_RemoveAt(clipboard->fetches, 0))
1035 WLog_WARN(TAG, "cannot drop a clipboard request");
1036 shadow_clipboard_fetch_next(clipboard);
1037 }
1038 }
1039 LeaveCriticalSection(&clipboard->lock);
1040
1041 /* outside the lock: its thread may wait for it */
1042 const UINT rc = cliprdr->Stop(cliprdr);
1043 if (rc != CHANNEL_RC_OK)
1044 WLog_WARN(TAG, "cannot stop the clipboard channel: %" PRIu32, rc);
1045 cliprdr_server_context_free(cliprdr);
1046 client->cliprdr = nullptr;
1047}
1048
1049#else /* CHANNEL_CLIPRDR_SERVER */
1050
1051rdpShadowClipboard* shadow_clipboard_new(WINPR_ATTR_UNUSED rdpShadowServer* server)
1052{
1053 return nullptr;
1054}
1055
1056void shadow_clipboard_free(WINPR_ATTR_UNUSED rdpShadowClipboard* clipboard)
1057{
1058}
1059
1060int shadow_client_cliprdr_init(WINPR_ATTR_UNUSED rdpShadowClient* client)
1061{
1062 return 0;
1063}
1064
1065void shadow_client_cliprdr_uninit(WINPR_ATTR_UNUSED rdpShadowClient* client)
1066{
1067}
1068
1069#endif /* CHANNEL_CLIPRDR_SERVER */
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59