FreeRDP
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 
35 const char* mime_text_plain = "text/plain";
36 
45 static 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 
66 const 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 
73 static wClipboardFormat* ClipboardFindFormat(wClipboard* clipboard, UINT32 formatId,
74  const char* name)
75 {
76  wClipboardFormat* format = NULL;
77 
78  if (!clipboard)
79  return NULL;
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 NULL;
117 
118  if (!format->formatName || (strcmp(format->formatName, CF_STANDARD_STRINGS[0]) == 0))
119  return format;
120  }
121  }
122 
123  return format;
124 }
125 
126 static wClipboardSynthesizer* ClipboardFindSynthesizer(wClipboardFormat* format, UINT32 formatId)
127 {
128  if (!format)
129  return NULL;
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 NULL;
140 }
141 
142 void ClipboardLock(wClipboard* clipboard)
143 {
144  if (!clipboard)
145  return;
146 
147  EnterCriticalSection(&(clipboard->lock));
148 }
149 
150 void ClipboardUnlock(wClipboard* clipboard)
151 {
152  if (!clipboard)
153  return;
154 
155  LeaveCriticalSection(&(clipboard->lock));
156 }
157 
158 BOOL ClipboardEmpty(wClipboard* clipboard)
159 {
160  if (!clipboard)
161  return FALSE;
162 
163  if (clipboard->data)
164  {
165  free(clipboard->data);
166  clipboard->data = NULL;
167  }
168 
169  clipboard->size = 0;
170  clipboard->formatId = 0;
171  clipboard->sequenceNumber++;
172  return TRUE;
173 }
174 
175 UINT32 ClipboardCountRegisteredFormats(wClipboard* clipboard)
176 {
177  if (!clipboard)
178  return 0;
179 
180  return clipboard->numFormats;
181 }
182 
183 UINT32 ClipboardGetRegisteredFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
184 {
185  UINT32* pFormatIds = NULL;
186  wClipboardFormat* format = NULL;
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 
215 UINT32 ClipboardRegisterFormat(wClipboard* clipboard, const char* name)
216 {
217  wClipboardFormat* format = NULL;
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 = NULL;
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 
257 BOOL ClipboardRegisterSynthesizer(wClipboard* clipboard, UINT32 formatId, UINT32 syntheticId,
258  CLIPBOARD_SYNTHESIZE_FN pfnSynthesize)
259 {
260  UINT32 index = 0;
261  wClipboardFormat* format = NULL;
262  wClipboardSynthesizer* synthesizer = NULL;
263 
264  if (!clipboard)
265  return FALSE;
266 
267  format = ClipboardFindFormat(clipboard, formatId, NULL);
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 = NULL;
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 
298 UINT32 ClipboardCountFormats(wClipboard* clipboard)
299 {
300  UINT32 count = 0;
301  wClipboardFormat* format = NULL;
302 
303  if (!clipboard)
304  return 0;
305 
306  format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
307 
308  if (!format)
309  return 0;
310 
311  count = 1 + format->numSynthesizers;
312  return count;
313 }
314 
315 UINT32 ClipboardGetFormatIds(wClipboard* clipboard, UINT32** ppFormatIds)
316 {
317  UINT32 count = 0;
318  UINT32* pFormatIds = NULL;
319  wClipboardFormat* format = NULL;
320  wClipboardSynthesizer* synthesizer = NULL;
321 
322  if (!clipboard)
323  return 0;
324 
325  format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
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 
358 static 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 = NULL;
367  format->synthesizers = NULL;
368  }
369 }
370 
371 static BOOL ClipboardInitFormats(wClipboard* clipboard)
372 {
373  UINT32 formatId = 0;
374  wClipboardFormat* format = NULL;
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;
394 error:
395 
396  ClipboardUninitFormats(clipboard);
397  return FALSE;
398 }
399 
400 UINT32 ClipboardGetFormatId(wClipboard* clipboard, const char* name)
401 {
402  wClipboardFormat* format = NULL;
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 
415 const char* ClipboardGetFormatName(wClipboard* clipboard, UINT32 formatId)
416 {
417  wClipboardFormat* format = NULL;
418 
419  if (!clipboard)
420  return NULL;
421 
422  format = ClipboardFindFormat(clipboard, formatId, NULL);
423 
424  if (!format)
425  return NULL;
426 
427  return format->formatName;
428 }
429 
430 void* ClipboardGetData(wClipboard* clipboard, UINT32 formatId, UINT32* pSize)
431 {
432  UINT32 SrcSize = 0;
433  UINT32 DstSize = 0;
434  void* pSrcData = NULL;
435  void* pDstData = NULL;
436  wClipboardFormat* format = NULL;
437  wClipboardSynthesizer* synthesizer = NULL;
438 
439  if (!clipboard)
440  return NULL;
441 
442  if (!pSize)
443  return NULL;
444 
445  *pSize = 0;
446  format = ClipboardFindFormat(clipboard, clipboard->formatId, NULL);
447 
448  if (!format)
449  return NULL;
450 
451  SrcSize = clipboard->size;
452  pSrcData = clipboard->data;
453 
454  if (formatId == format->formatId)
455  {
456  DstSize = SrcSize;
457  pDstData = malloc(DstSize);
458 
459  if (!pDstData)
460  return NULL;
461 
462  CopyMemory(pDstData, pSrcData, SrcSize);
463  *pSize = DstSize;
464  }
465  else
466  {
467  synthesizer = ClipboardFindSynthesizer(format, formatId);
468 
469  if (!synthesizer || !synthesizer->pfnSynthesize)
470  return NULL;
471 
472  DstSize = SrcSize;
473  pDstData = synthesizer->pfnSynthesize(clipboard, format->formatId, pSrcData, &DstSize);
474  if (pDstData)
475  *pSize = DstSize;
476  }
477 
478  return pDstData;
479 }
480 
481 BOOL ClipboardSetData(wClipboard* clipboard, UINT32 formatId, const void* data, UINT32 size)
482 {
483  wClipboardFormat* format = NULL;
484 
485  if (!clipboard)
486  return FALSE;
487 
488  format = ClipboardFindFormat(clipboard, formatId, NULL);
489 
490  if (!format)
491  return FALSE;
492 
493  free(clipboard->data);
494  clipboard->data = malloc(size);
495 
496  if (!clipboard->data)
497  return FALSE;
498 
499  memcpy(clipboard->data, data, size);
500  clipboard->size = size;
501  clipboard->formatId = formatId;
502  clipboard->sequenceNumber++;
503  return TRUE;
504 }
505 
506 UINT64 ClipboardGetOwner(wClipboard* clipboard)
507 {
508  if (!clipboard)
509  return 0;
510 
511  return clipboard->ownerId;
512 }
513 
514 void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
515 {
516  if (!clipboard)
517  return;
518 
519  clipboard->ownerId = ownerId;
520 }
521 
522 wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
523 {
524  if (!clipboard)
525  return NULL;
526 
527  return &clipboard->delegate;
528 }
529 
530 static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
531 {
532  /*
533  * There can be only one local file subsystem active.
534  * Return as soon as initialization succeeds.
535  */
536  if (ClipboardInitSyntheticFileSubsystem(clipboard))
537  {
538  WLog_DBG(TAG, "initialized synthetic local file subsystem");
539  return;
540  }
541  else
542  {
543  WLog_WARN(TAG, "failed to initialize synthetic local file subsystem");
544  }
545 
546  WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
547 }
548 
549 wClipboard* ClipboardCreate(void)
550 {
551  wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard));
552 
553  if (!clipboard)
554  return NULL;
555 
556  clipboard->nextFormatId = 0xC000;
557  clipboard->sequenceNumber = 0;
558 
559  if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
560  goto fail;
561 
562  clipboard->numFormats = 0;
563  clipboard->maxFormats = 64;
564  clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
565 
566  if (!clipboard->formats)
567  goto fail;
568 
569  if (!ClipboardInitFormats(clipboard))
570  goto fail;
571 
572  clipboard->delegate.clipboard = clipboard;
573  ClipboardInitLocalFileSubsystem(clipboard);
574  return clipboard;
575 fail:
576  ClipboardDestroy(clipboard);
577  return NULL;
578 }
579 
580 void ClipboardDestroy(wClipboard* clipboard)
581 {
582  if (!clipboard)
583  return;
584 
585  ArrayList_Free(clipboard->localFiles);
586  clipboard->localFiles = NULL;
587 
588  ClipboardUninitFormats(clipboard);
589 
590  free(clipboard->data);
591  clipboard->data = NULL;
592  clipboard->size = 0;
593  clipboard->numFormats = 0;
594  free(clipboard->formats);
595  DeleteCriticalSection(&(clipboard->lock));
596  free(clipboard);
597 }
598 
599 static BOOL is_dos_drive(const char* path, size_t len)
600 {
601  if (len < 2)
602  return FALSE;
603 
604  WINPR_ASSERT(path);
605  if (path[1] == ':' || path[1] == '|')
606  {
607  if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z')))
608  return TRUE;
609  }
610  return FALSE;
611 }
612 
613 char* parse_uri_to_local_file(const char* uri, size_t uri_len)
614 {
615  // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
616  const char prefix[] = "file:";
617  const char prefixTraditional[] = "file://";
618  const char* localName = NULL;
619  size_t localLen = 0;
620  char* buffer = NULL;
621  const size_t prefixLen = strnlen(prefix, sizeof(prefix));
622  const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional));
623 
624  WINPR_ASSERT(uri || (uri_len == 0));
625 
626  WLog_VRB(TAG, "processing URI: %.*s", uri_len, uri);
627 
628  if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen) != 0)
629  {
630  WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
631  return NULL;
632  }
633 
634  do
635  {
636  /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F
637  * - The minimal representation of a local file in a DOS- or Windows-
638  * based environment with no authority field and an absolute path
639  * that begins with a drive letter.
640  *
641  * "file:c:/path/to/file"
642  *
643  * - Regular DOS or Windows file URIs with vertical line characters in
644  * the drive letter construct.
645  *
646  * "file:c|/path/to/file"
647  *
648  */
649  if (uri[prefixLen] != '/')
650  {
651 
652  if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen))
653  {
654  // Dos and Windows file URI
655  localName = &uri[prefixLen];
656  localLen = uri_len - prefixLen;
657  break;
658  }
659  else
660  {
661  WLog_ERR(TAG, "URI format are not supported: %s", uri);
662  return NULL;
663  }
664  }
665 
666  /*
667  * - The minimal representation of a local file with no authority field
668  * and an absolute path that begins with a slash "/". For example:
669  *
670  * "file:/path/to/file"
671  *
672  */
673  else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/'))
674  {
675  if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1))
676  {
677  // Dos and Windows file URI
678  localName = (uri + prefixLen + 1);
679  localLen = uri_len - prefixLen - 1;
680  }
681  else
682  {
683  localName = &uri[prefixLen];
684  localLen = uri_len - prefixLen;
685  }
686  break;
687  }
688 
689  /*
690  * - A traditional file URI for a local file with an empty authority.
691  *
692  * "file:///path/to/file"
693  */
694  if ((uri_len < prefixTraditionalLen) ||
695  strncmp(uri, prefixTraditional, prefixTraditionalLen) != 0)
696  {
697  WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
698  return NULL;
699  }
700 
701  localName = &uri[prefixTraditionalLen];
702  localLen = uri_len - prefixTraditionalLen;
703 
704  if (localLen < 1)
705  {
706  WLog_ERR(TAG, "empty 'file:' URI schemes are not supported");
707  return NULL;
708  }
709 
710  /*
711  * "file:///c:/path/to/file"
712  * "file:///c|/path/to/file"
713  */
714  if (localName[0] != '/')
715  {
716  WLog_ERR(TAG, "URI format are not supported: %s", uri);
717  return NULL;
718  }
719 
720  if (is_dos_drive(&localName[1], localLen - 1))
721  {
722  localName++;
723  localLen--;
724  }
725 
726  } while (0);
727 
728  buffer = winpr_str_url_decode(localName, localLen);
729  if (buffer)
730  {
731  if (buffer[1] == '|' &&
732  ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z')))
733  buffer[1] = ':';
734  return buffer;
735  }
736 
737  return NULL;
738 }