FreeRDP
Loading...
Searching...
No Matches
clipboard.c
1
20#include <winpr/config.h>
21
22#include <winpr/crt.h>
23#include <winpr/collections.h>
24#include <winpr/wlog.h>
25
26#include <winpr/clipboard.h>
27
28#include "clipboard.h"
29
30#include "synthetic_file.h"
31
32#include "../log.h"
33#define TAG WINPR_TAG("clipboard")
34
35const char* const mime_text_plain = "text/plain";
36
45static const char* CF_STANDARD_STRINGS[] = {
46 "CF_RAW", /* 0 */
47 "CF_TEXT", /* 1 */
48 "CF_BITMAP", /* 2 */
49 "CF_METAFILEPICT", /* 3 */
50 "CF_SYLK", /* 4 */
51 "CF_DIF", /* 5 */
52 "CF_TIFF", /* 6 */
53 "CF_OEMTEXT", /* 7 */
54 "CF_DIB", /* 8 */
55 "CF_PALETTE", /* 9 */
56 "CF_PENDATA", /* 10 */
57 "CF_RIFF", /* 11 */
58 "CF_WAVE", /* 12 */
59 "CF_UNICODETEXT", /* 13 */
60 "CF_ENHMETAFILE", /* 14 */
61 "CF_HDROP", /* 15 */
62 "CF_LOCALE", /* 16 */
63 "CF_DIBV5" /* 17 */
64};
65
66const char* ClipboardGetFormatIdString(UINT32 formatId)
67{
68 if (formatId < ARRAYSIZE(CF_STANDARD_STRINGS))
69 return CF_STANDARD_STRINGS[formatId];
70 return "CF_REGISTERED_FORMAT";
71}
72
73static wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId,
74 const char* name)
75{
76 wClipboardFormat* format = nullptr;
77
78 if (!clipboard)
79 return nullptr;
80
81 if (formatId)
82 {
83 for (UINT32 index = 0; index < clipboard->numFormats; index++)
84 {
85 wClipboardFormat* cformat = &clipboard->formats[index];
86 if (formatId == cformat->formatId)
87 {
88 format = cformat;
89 break;
90 }
91 }
92 }
93 else if (name)
94 {
95 for (UINT32 index = 0; index < clipboard->numFormats; index++)
96 {
97 wClipboardFormat* cformat = &clipboard->formats[index];
98 if (!cformat->formatName)
99 continue;
100
101 if (strcmp(name, cformat->formatName) == 0)
102 {
103 format = cformat;
104 break;
105 }
106 }
107 }
108 else
109 {
110 /* special "CF_RAW" case */
111 if (clipboard->numFormats > 0)
112 {
113 format = &clipboard->formats[0];
114
115 if (format->formatId)
116 return nullptr;
117
118 if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0))
119 return format;
120 }
121 }
122
123 return format;
124}
125
126static wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId)
127{
128 if (!format)
129 return nullptr;
130
131 for (UINT32 index = 0; index < format->numSynthesizers; index++)
132 {
133 wClipboardSynthesizer* synthesizer = &(format->synthesizers[index]);
134
135 if (formatId == synthesizer->syntheticId)
136 return synthesizer;
137 }
138
139 return nullptr;
140}
141
142void ClipboardLock(wClipboard* clipboard)
143{
144 if (!clipboard)
145 return;
146
147 EnterCriticalSection(&(clipboard->lock));
148}
149
150void ClipboardUnlock(wClipboard* clipboard)
151{
152 if (!clipboard)
153 return;
154
155 LeaveCriticalSection(&(clipboard->lock));
156}
157
158BOOL ClipboardEmpty(wClipboard* clipboard)
159{
160 if (!clipboard)
161 return FALSE;
162
163 if (clipboard->data)
164 {
165 free(clipboard->data);
166 clipboard->data = nullptr;
167 }
168
169 clipboard->size = 0;
170 clipboard->formatId = 0;
171 clipboard->sequenceNumber++;
172 return TRUE;
173}
174
175UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard)
176{
177 if (!clipboard)
178 return 0;
179
180 return clipboard->numFormats;
181}
182
183UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
184{
185 UINT32* pFormatIds = nullptr;
186 wClipboardFormat* format = nullptr;
187
188 if (!clipboard)
189 return 0;
190
191 if (!ppFormatIds)
192 return 0;
193
194 pFormatIds = *ppFormatIds;
195
196 if (!pFormatIds)
197 {
198 pFormatIds = calloc(clipboard->numFormats, sizeof(UINT32));
199
200 if (!pFormatIds)
201 return 0;
202
203 *ppFormatIds = pFormatIds;
204 }
205
206 for (UINT32 index = 0; index < clipboard->numFormats; index++)
207 {
208 format = &(clipboard->formats[index]);
209 pFormatIds[index] = format->formatId;
210 }
211
212 return clipboard->numFormats;
213}
214
215UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name)
216{
217 wClipboardFormat* format = nullptr;
218
219 if (!clipboard)
220 return 0;
221
222 format = ClipboardFindFormat(clipboard, 0, name);
223
224 if (format)
225 return format->formatId;
226
227 if ((clipboard->numFormats + 1) >= clipboard->maxFormats)
228 {
229 UINT32 numFormats = clipboard->maxFormats * 2;
230 wClipboardFormat* tmpFormat = nullptr;
231 tmpFormat =
232 (wClipboardFormat*)realloc(clipboard->formats, numFormats * sizeof(wClipboardFormat));
233
234 if (!tmpFormat)
235 return 0;
236
237 clipboard->formats = tmpFormat;
238 clipboard->maxFormats = numFormats;
239 }
240
241 format = &(clipboard->formats[clipboard->numFormats]);
242 ZeroMemory(format, sizeof(wClipboardFormat));
243
244 if (name)
245 {
246 format->formatName = _strdup(name);
247
248 if (!format->formatName)
249 return 0;
250 }
251
252 format->formatId = clipboard->nextFormatId++;
253 clipboard->numFormats++;
254 return format->formatId;
255}
256
257BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId,
258 CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
259{
260 UINT32 index = 0;
261 wClipboardFormat* format = nullptr;
262 wClipboardSynthesizer* synthesizer = nullptr;
263
264 if (!clipboard)
265 return FALSE;
266
267 format = ClipboardFindFormat(clipboard, formatId, nullptr);
268
269 if (!format)
270 return FALSE;
271
272 if (format->formatId == syntheticId)
273 return FALSE;
274
275 synthesizer = ClipboardFindSynthesizer(format, formatId);
276
277 if (!synthesizer)
278 {
279 wClipboardSynthesizer* tmpSynthesizer = nullptr;
280 UINT32 numSynthesizers = format->numSynthesizers + 1;
281 tmpSynthesizer = (wClipboardSynthesizer*)realloc(
282 format->synthesizers, numSynthesizers * sizeof(wClipboardSynthesizer));
283
284 if (!tmpSynthesizer)
285 return FALSE;
286
287 format->synthesizers = tmpSynthesizer;
288 format->numSynthesizers = numSynthesizers;
289 index = numSynthesizers - 1;
290 synthesizer = &(format->synthesizers[index]);
291 }
292
293 synthesizer->syntheticId = syntheticId;
294 synthesizer->pfnSynthesize = pfnSynthesize;
295 return TRUE;
296}
297
298UINT32 ClipboardCountFormats(wClipboard* clipboard)
299{
300 UINT32 count = 0;
301 wClipboardFormat* format = nullptr;
302
303 if (!clipboard)
304 return 0;
305
306 format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
307
308 if (!format)
309 return 0;
310
311 count = 1 + format->numSynthesizers;
312 return count;
313}
314
315UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
316{
317 UINT32 count = 0;
318 UINT32* pFormatIds = nullptr;
319 wClipboardFormat* format = nullptr;
320 wClipboardSynthesizer* synthesizer = nullptr;
321
322 if (!clipboard)
323 return 0;
324
325 format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
326
327 if (!format)
328 return 0;
329
330 count = 1 + format->numSynthesizers;
331
332 if (!ppFormatIds)
333 return 0;
334
335 pFormatIds = *ppFormatIds;
336
337 if (!pFormatIds)
338 {
339 pFormatIds = calloc(count, sizeof(UINT32));
340
341 if (!pFormatIds)
342 return 0;
343
344 *ppFormatIds = pFormatIds;
345 }
346
347 pFormatIds[0] = format->formatId;
348
349 for (UINT32 index = 1; index < count; index++)
350 {
351 synthesizer = &(format->synthesizers[index - 1]);
352 pFormatIds[index] = synthesizer->syntheticId;
353 }
354
355 return count;
356}
357
358static void ClipboardUninitFormats(wClipboard* clipboard)
359{
360 WINPR_ASSERT(clipboard);
361 for (UINT32 formatId = 0; formatId < clipboard->numFormats; formatId++)
362 {
363 wClipboardFormat* format = &clipboard->formats[formatId];
364 free(format->formatName);
365 free(format->synthesizers);
366 format->formatName = nullptr;
367 format->synthesizers = nullptr;
368 }
369}
370
371static BOOL ClipboardInitFormats(wClipboard* clipboard)
372{
373 UINT32 formatId = 0;
374 wClipboardFormat* format = nullptr;
375
376 if (!clipboard)
377 return FALSE;
378
379 for (formatId = 0; formatId < CF_MAX; formatId++, clipboard->numFormats++)
380 {
381 format = &(clipboard->formats[clipboard->numFormats]);
382 ZeroMemory(format, sizeof(wClipboardFormat));
383 format->formatId = formatId;
384 format->formatName = _strdup(CF_STANDARD_STRINGS[formatId]);
385
386 if (!format->formatName)
387 goto error;
388 }
389
390 if (!ClipboardInitSynthesizers(clipboard))
391 goto error;
392
393 return TRUE;
394error:
395
396 ClipboardUninitFormats(clipboard);
397 return FALSE;
398}
399
400UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
401{
402 wClipboardFormat* format = nullptr;
403
404 if (!clipboard)
405 return 0;
406
407 format = ClipboardFindFormat(clipboard, 0, name);
408
409 if (!format)
410 return 0;
411
412 return format->formatId;
413}
414
415const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId)
416{
417 wClipboardFormat* format = nullptr;
418
419 if (!clipboard)
420 return nullptr;
421
422 format = ClipboardFindFormat(clipboard, formatId, nullptr);
423
424 if (!format)
425 return nullptr;
426
427 return format->formatName;
428}
429
430void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize)
431{
432 UINT32 SrcSize = 0;
433 UINT32 DstSize = 0;
434 void* pSrcData = nullptr;
435 void* pDstData = nullptr;
436 wClipboardFormat* format = nullptr;
437 wClipboardSynthesizer* synthesizer = nullptr;
438
439 if (!clipboard || !pSize)
440 {
441 WLog_ERR(TAG, "Invalid parameters clipboard=%p, pSize=%p",
442 WINPR_CXX_COMPAT_CAST(const void*, clipboard),
443 WINPR_CXX_COMPAT_CAST(const void*, pSize));
444 return nullptr;
445 }
446
447 *pSize = 0;
448 format = ClipboardFindFormat(clipboard, clipboard->formatId, nullptr);
449
450 if (!format)
451 {
452 WLog_ERR(TAG, "Format [0x%08" PRIx32 "] not found", clipboard->formatId);
453 return nullptr;
454 }
455
456 SrcSize = clipboard->size;
457 pSrcData = clipboard->data;
458
459 if (formatId == format->formatId)
460 {
461 DstSize = SrcSize;
462 pDstData = malloc(DstSize);
463
464 if (!pDstData)
465 return nullptr;
466
467 CopyMemory(pDstData, pSrcData, SrcSize);
468 *pSize = DstSize;
469 }
470 else
471 {
472 synthesizer = ClipboardFindSynthesizer(format, formatId);
473
474 if (!synthesizer || !synthesizer->pfnSynthesize)
475 {
476 WLog_ERR(TAG, "No synthesizer for format %s [0x%08" PRIx32 "] --> %s [0x%08" PRIx32 "]",
477 ClipboardGetFormatName(clipboard, clipboard->formatId), clipboard->formatId,
478 ClipboardGetFormatName(clipboard, formatId), formatId);
479 return nullptr;
480 }
481
482 DstSize = SrcSize;
483 pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize);
484 if (pDstData)
485 *pSize = DstSize;
486 }
487
488 WLog_DBG(TAG, "getting formatId=%s [0x%08" PRIx32 "] data=%p, size=%" PRIu32,
489 ClipboardGetFormatName(clipboard, formatId), formatId, pDstData, *pSize);
490 return pDstData;
491}
492
493BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size)
494{
495 wClipboardFormat* format = nullptr;
496
497 WLog_DBG(TAG, "setting formatId=%s [0x%08" PRIx32 "], size=%" PRIu32,
498 ClipboardGetFormatName(clipboard, formatId), formatId, size);
499 if (!clipboard)
500 return FALSE;
501
502 format = ClipboardFindFormat(clipboard, formatId, nullptr);
503
504 if (!format)
505 return FALSE;
506
507 free(clipboard->data);
508
509 /* append 2 WCHAR zero bytes. Prevent problems with odd input data sizes. */
510 clipboard->data = calloc(size + 2ull * sizeof(WCHAR), sizeof(char));
511
512 if (!clipboard->data)
513 return FALSE;
514
515 memcpy(clipboard->data, data, size);
516
517 /* For string values we don“t know if they are '\0' terminated.
518 * so set the size to the full length in bytes (e.g. string length + 1)
519 */
520 switch (formatId)
521 {
522 case CF_TEXT:
523 case CF_OEMTEXT:
524 clipboard->size = (UINT32)(strnlen(clipboard->data, size) + 1UL);
525 break;
526 case CF_UNICODETEXT:
527 clipboard->size =
528 (UINT32)((_wcsnlen(clipboard->data, size / sizeof(WCHAR)) + 1UL) * sizeof(WCHAR));
529 break;
530 default:
531 clipboard->size = size;
532 break;
533 }
534
535 clipboard->formatId = formatId;
536 clipboard->sequenceNumber++;
537 return TRUE;
538}
539
540UINT64 ClipboardGetOwner(wClipboard* clipboard)
541{
542 if (!clipboard)
543 return 0;
544
545 return clipboard->ownerId;
546}
547
548void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
549{
550 if (!clipboard)
551 return;
552
553 clipboard->ownerId = ownerId;
554}
555
556wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
557{
558 if (!clipboard)
559 return nullptr;
560
561 return &clipboard->delegate;
562}
563
564static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
565{
566 /*
567 * There can be only one local file subsystem active.
568 * Return as soon as initialization succeeds.
569 */
570 if (ClipboardInitSyntheticFileSubsystem(clipboard))
571 {
572 WLog_DBG(TAG, "initialized synthetic local file subsystem");
573 return;
574 }
575 else
576 {
577 WLog_WARN(TAG, "failed to initialize synthetic local file subsystem");
578 }
579
580 WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
581}
582
583wClipboard* ClipboardCreate(void)
584{
585 wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard));
586
587 if (!clipboard)
588 return nullptr;
589
590 clipboard->nextFormatId = 0xC000;
591 clipboard->sequenceNumber = 0;
592
593 if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
594 goto fail;
595
596 clipboard->numFormats = 0;
597 clipboard->maxFormats = 64;
598 clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
599
600 if (!clipboard->formats)
601 goto fail;
602
603 if (!ClipboardInitFormats(clipboard))
604 goto fail;
605
606 clipboard->delegate.clipboard = clipboard;
607 ClipboardInitLocalFileSubsystem(clipboard);
608 return clipboard;
609fail:
610 ClipboardDestroy(clipboard);
611 return nullptr;
612}
613
614void ClipboardDestroy(wClipboard* clipboard)
615{
616 if (!clipboard)
617 return;
618
619 ArrayList_Free(clipboard->localFiles);
620 clipboard->localFiles = nullptr;
621
622 ClipboardUninitFormats(clipboard);
623
624 free(clipboard->data);
625 clipboard->data = nullptr;
626 clipboard->size = 0;
627 clipboard->numFormats = 0;
628 free(clipboard->formats);
629 DeleteCriticalSection(&(clipboard->lock));
630 free(clipboard);
631}
632
633static BOOL is_dos_drive(const char* path, size_t len)
634{
635 if (len < 2)
636 return FALSE;
637
638 WINPR_ASSERT(path);
639 if (path[1] == ':' || path[1] == '|')
640 {
641 if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z')))
642 return TRUE;
643 }
644 return FALSE;
645}
646
647char* parse_uri_to_local_file(const char* uri, size_t uri_len)
648{
649 // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
650 const char prefix[] = "file:";
651 const char prefixTraditional[] = "file://";
652 const char* localName = nullptr;
653 size_t localLen = 0;
654 char* buffer = nullptr;
655 const size_t prefixLen = strnlen(prefix, sizeof(prefix));
656 const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional));
657
658 WINPR_ASSERT(uri || (uri_len == 0));
659
660 WLog_VRB(TAG, "processing URI: %.*s", WINPR_ASSERTING_INT_CAST(int, uri_len), uri);
661
662 if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen) != 0)
663 {
664 WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
665 return nullptr;
666 }
667
668 do
669 {
670 /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F
671 * - The minimal representation of a local file in a DOS- or Windows-
672 * based environment with no authority field and an absolute path
673 * that begins with a drive letter.
674 *
675 * "file:c:/path/to/file"
676 *
677 * - Regular DOS or Windows file URIs with vertical line characters in
678 * the drive letter construct.
679 *
680 * "file:c|/path/to/file"
681 *
682 */
683 if (uri[prefixLen] != '/')
684 {
685
686 if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen))
687 {
688 // Dos and Windows file URI
689 localName = &uri[prefixLen];
690 localLen = uri_len - prefixLen;
691 break;
692 }
693 else
694 {
695 WLog_ERR(TAG, "URI format are not supported: %s", uri);
696 return nullptr;
697 }
698 }
699
700 /*
701 * - The minimal representation of a local file with no authority field
702 * and an absolute path that begins with a slash "/". For example:
703 *
704 * "file:/path/to/file"
705 *
706 */
707 else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/'))
708 {
709 if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1))
710 {
711 // Dos and Windows file URI
712 localName = (uri + prefixLen + 1);
713 localLen = uri_len - prefixLen - 1;
714 }
715 else
716 {
717 localName = &uri[prefixLen];
718 localLen = uri_len - prefixLen;
719 }
720 break;
721 }
722
723 /*
724 * - A traditional file URI for a local file with an empty authority.
725 *
726 * "file:///path/to/file"
727 */
728 if ((uri_len < prefixTraditionalLen) ||
729 strncmp(uri, prefixTraditional, prefixTraditionalLen) != 0)
730 {
731 WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
732 return nullptr;
733 }
734
735 localName = &uri[prefixTraditionalLen];
736 localLen = uri_len - prefixTraditionalLen;
737
738 if (localLen < 1)
739 {
740 WLog_ERR(TAG, "empty 'file:' URI schemes are not supported");
741 return nullptr;
742 }
743
744 /*
745 * "file:///c:/path/to/file"
746 * "file:///c|/path/to/file"
747 */
748 if (localName[0] != '/')
749 {
750 WLog_ERR(TAG, "URI format are not supported: %s", uri);
751 return nullptr;
752 }
753
754 if (is_dos_drive(&localName[1], localLen - 1))
755 {
756 localName++;
757 localLen--;
758 }
759
760 } while (0);
761
762 buffer = winpr_str_url_decode(localName, localLen);
763 if (buffer)
764 {
765 if (buffer[1] == '|' &&
766 ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z')))
767 buffer[1] = ':';
768 return buffer;
769 }
770
771 return nullptr;
772}