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 
495  clipboard->data = calloc(size + sizeof(WCHAR), sizeof(char));
496 
497  if (!clipboard->data)
498  return FALSE;
499 
500  memcpy(clipboard->data, data, size);
501 
502  /* For string values we donĀ“t know if they are '\0' terminated.
503  * so set the size to the full length in bytes (e.g. string length + 1)
504  */
505  switch (formatId)
506  {
507  case CF_TEXT:
508  case CF_OEMTEXT:
509  clipboard->size = (UINT32)(strnlen(clipboard->data, size) + 1UL);
510  break;
511  case CF_UNICODETEXT:
512  clipboard->size =
513  (UINT32)((_wcsnlen(clipboard->data, size / sizeof(WCHAR)) + 1UL) * sizeof(WCHAR));
514  break;
515  default:
516  clipboard->size = size;
517  break;
518  }
519 
520  clipboard->formatId = formatId;
521  clipboard->sequenceNumber++;
522  return TRUE;
523 }
524 
525 UINT64 ClipboardGetOwner(wClipboard* clipboard)
526 {
527  if (!clipboard)
528  return 0;
529 
530  return clipboard->ownerId;
531 }
532 
533 void ClipboardSetOwner(wClipboard* clipboard, UINT64 ownerId)
534 {
535  if (!clipboard)
536  return;
537 
538  clipboard->ownerId = ownerId;
539 }
540 
541 wClipboardDelegate* ClipboardGetDelegate(wClipboard* clipboard)
542 {
543  if (!clipboard)
544  return NULL;
545 
546  return &clipboard->delegate;
547 }
548 
549 static void ClipboardInitLocalFileSubsystem(wClipboard* clipboard)
550 {
551  /*
552  * There can be only one local file subsystem active.
553  * Return as soon as initialization succeeds.
554  */
555  if (ClipboardInitSyntheticFileSubsystem(clipboard))
556  {
557  WLog_DBG(TAG, "initialized synthetic local file subsystem");
558  return;
559  }
560  else
561  {
562  WLog_WARN(TAG, "failed to initialize synthetic local file subsystem");
563  }
564 
565  WLog_INFO(TAG, "failed to initialize local file subsystem, file transfer not available");
566 }
567 
568 wClipboard* ClipboardCreate(void)
569 {
570  wClipboard* clipboard = (wClipboard*)calloc(1, sizeof(wClipboard));
571 
572  if (!clipboard)
573  return NULL;
574 
575  clipboard->nextFormatId = 0xC000;
576  clipboard->sequenceNumber = 0;
577 
578  if (!InitializeCriticalSectionAndSpinCount(&(clipboard->lock), 4000))
579  goto fail;
580 
581  clipboard->numFormats = 0;
582  clipboard->maxFormats = 64;
583  clipboard->formats = (wClipboardFormat*)calloc(clipboard->maxFormats, sizeof(wClipboardFormat));
584 
585  if (!clipboard->formats)
586  goto fail;
587 
588  if (!ClipboardInitFormats(clipboard))
589  goto fail;
590 
591  clipboard->delegate.clipboard = clipboard;
592  ClipboardInitLocalFileSubsystem(clipboard);
593  return clipboard;
594 fail:
595  ClipboardDestroy(clipboard);
596  return NULL;
597 }
598 
599 void ClipboardDestroy(wClipboard* clipboard)
600 {
601  if (!clipboard)
602  return;
603 
604  ArrayList_Free(clipboard->localFiles);
605  clipboard->localFiles = NULL;
606 
607  ClipboardUninitFormats(clipboard);
608 
609  free(clipboard->data);
610  clipboard->data = NULL;
611  clipboard->size = 0;
612  clipboard->numFormats = 0;
613  free(clipboard->formats);
614  DeleteCriticalSection(&(clipboard->lock));
615  free(clipboard);
616 }
617 
618 static BOOL is_dos_drive(const char* path, size_t len)
619 {
620  if (len < 2)
621  return FALSE;
622 
623  WINPR_ASSERT(path);
624  if (path[1] == ':' || path[1] == '|')
625  {
626  if (((path[0] >= 'A') && (path[0] <= 'Z')) || ((path[0] >= 'a') && (path[0] <= 'z')))
627  return TRUE;
628  }
629  return FALSE;
630 }
631 
632 char* parse_uri_to_local_file(const char* uri, size_t uri_len)
633 {
634  // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
635  const char prefix[] = "file:";
636  const char prefixTraditional[] = "file://";
637  const char* localName = NULL;
638  size_t localLen = 0;
639  char* buffer = NULL;
640  const size_t prefixLen = strnlen(prefix, sizeof(prefix));
641  const size_t prefixTraditionalLen = strnlen(prefixTraditional, sizeof(prefixTraditional));
642 
643  WINPR_ASSERT(uri || (uri_len == 0));
644 
645  WLog_VRB(TAG, "processing URI: %.*s", uri_len, uri);
646 
647  if ((uri_len <= prefixLen) || strncmp(uri, prefix, prefixLen) != 0)
648  {
649  WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
650  return NULL;
651  }
652 
653  do
654  {
655  /* https://datatracker.ietf.org/doc/html/rfc8089#appendix-F
656  * - The minimal representation of a local file in a DOS- or Windows-
657  * based environment with no authority field and an absolute path
658  * that begins with a drive letter.
659  *
660  * "file:c:/path/to/file"
661  *
662  * - Regular DOS or Windows file URIs with vertical line characters in
663  * the drive letter construct.
664  *
665  * "file:c|/path/to/file"
666  *
667  */
668  if (uri[prefixLen] != '/')
669  {
670 
671  if (is_dos_drive(&uri[prefixLen], uri_len - prefixLen))
672  {
673  // Dos and Windows file URI
674  localName = &uri[prefixLen];
675  localLen = uri_len - prefixLen;
676  break;
677  }
678  else
679  {
680  WLog_ERR(TAG, "URI format are not supported: %s", uri);
681  return NULL;
682  }
683  }
684 
685  /*
686  * - The minimal representation of a local file with no authority field
687  * and an absolute path that begins with a slash "/". For example:
688  *
689  * "file:/path/to/file"
690  *
691  */
692  else if ((uri_len > prefixLen + 1) && (uri[prefixLen + 1] != '/'))
693  {
694  if (is_dos_drive(&uri[prefixLen + 1], uri_len - prefixLen - 1))
695  {
696  // Dos and Windows file URI
697  localName = (uri + prefixLen + 1);
698  localLen = uri_len - prefixLen - 1;
699  }
700  else
701  {
702  localName = &uri[prefixLen];
703  localLen = uri_len - prefixLen;
704  }
705  break;
706  }
707 
708  /*
709  * - A traditional file URI for a local file with an empty authority.
710  *
711  * "file:///path/to/file"
712  */
713  if ((uri_len < prefixTraditionalLen) ||
714  strncmp(uri, prefixTraditional, prefixTraditionalLen) != 0)
715  {
716  WLog_ERR(TAG, "non-'file:' URI schemes are not supported");
717  return NULL;
718  }
719 
720  localName = &uri[prefixTraditionalLen];
721  localLen = uri_len - prefixTraditionalLen;
722 
723  if (localLen < 1)
724  {
725  WLog_ERR(TAG, "empty 'file:' URI schemes are not supported");
726  return NULL;
727  }
728 
729  /*
730  * "file:///c:/path/to/file"
731  * "file:///c|/path/to/file"
732  */
733  if (localName[0] != '/')
734  {
735  WLog_ERR(TAG, "URI format are not supported: %s", uri);
736  return NULL;
737  }
738 
739  if (is_dos_drive(&localName[1], localLen - 1))
740  {
741  localName++;
742  localLen--;
743  }
744 
745  } while (0);
746 
747  buffer = winpr_str_url_decode(localName, localLen);
748  if (buffer)
749  {
750  if (buffer[1] == '|' &&
751  ((buffer[0] >= 'A' && buffer[0] <= 'Z') || (buffer[0] >= 'a' && buffer[0] <= 'z')))
752  buffer[1] = ':';
753  return buffer;
754  }
755 
756  return NULL;
757 }