FreeRDP
synthetic_file.c
1 
20 #include <winpr/config.h>
21 #include <winpr/platform.h>
22 
23 WINPR_PRAGMA_DIAG_PUSH
24 WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
25 WINPR_PRAGMA_DIAG_IGNORED_UNUSED_MACRO
26 
27 #define _FILE_OFFSET_BITS 64 // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
28 
29 WINPR_PRAGMA_DIAG_POP
30 
31 #include <errno.h>
32 
33 #include <winpr/wtypes.h>
34 
35 #include <winpr/crt.h>
36 #include <winpr/clipboard.h>
37 #include <winpr/collections.h>
38 #include <winpr/file.h>
39 #include <winpr/shell.h>
40 #include <winpr/string.h>
41 #include <winpr/wlog.h>
42 #include <winpr/path.h>
43 #include <winpr/print.h>
44 
45 #include "clipboard.h"
46 #include "synthetic_file.h"
47 
48 #include "../log.h"
49 #define TAG WINPR_TAG("clipboard.synthetic.file")
50 
51 static const char* mime_uri_list = "text/uri-list";
52 static const char* mime_FileGroupDescriptorW = "FileGroupDescriptorW";
53 static const char* mime_gnome_copied_files = "x-special/gnome-copied-files";
54 static const char* mime_mate_copied_files = "x-special/mate-copied-files";
55 
56 struct synthetic_file
57 {
58  WCHAR* local_name;
59  WCHAR* remote_name;
60 
61  HANDLE fd;
62  INT64 offset;
63 
64  DWORD dwFileAttributes;
65  FILETIME ftCreationTime;
66  FILETIME ftLastAccessTime;
67  FILETIME ftLastWriteTime;
68  DWORD nFileSizeHigh;
69  DWORD nFileSizeLow;
70 };
71 
72 void free_synthetic_file(struct synthetic_file* file);
73 
74 static struct synthetic_file* make_synthetic_file(const WCHAR* local_name, const WCHAR* remote_name)
75 {
76  struct synthetic_file* file = NULL;
77  WIN32_FIND_DATAW fd = { 0 };
78  HANDLE hFind = NULL;
79 
80  WINPR_ASSERT(local_name);
81  WINPR_ASSERT(remote_name);
82 
83  hFind = FindFirstFileW(local_name, &fd);
84  if (INVALID_HANDLE_VALUE == hFind)
85  {
86  WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError());
87  return NULL;
88  }
89  FindClose(hFind);
90 
91  file = calloc(1, sizeof(*file));
92  if (!file)
93  return NULL;
94 
95  file->fd = INVALID_HANDLE_VALUE;
96  file->offset = 0;
97  file->local_name = _wcsdup(local_name);
98  if (!file->local_name)
99  goto fail;
100 
101  file->remote_name = _wcsdup(remote_name);
102  if (!file->remote_name)
103  goto fail;
104 
105  const size_t len = _wcslen(file->remote_name);
106  PathCchConvertStyleW(file->remote_name, len, PATH_STYLE_WINDOWS);
107 
108  file->dwFileAttributes = fd.dwFileAttributes;
109  file->ftCreationTime = fd.ftCreationTime;
110  file->ftLastWriteTime = fd.ftLastWriteTime;
111  file->ftLastAccessTime = fd.ftLastAccessTime;
112  file->nFileSizeHigh = fd.nFileSizeHigh;
113  file->nFileSizeLow = fd.nFileSizeLow;
114 
115  return file;
116 fail:
117  free_synthetic_file(file);
118  return NULL;
119 }
120 
121 static UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force);
122 
123 void free_synthetic_file(struct synthetic_file* file)
124 {
125  if (!file)
126  return;
127 
128  synthetic_file_read_close(file, TRUE);
129 
130  free(file->local_name);
131  free(file->remote_name);
132  free(file);
133 }
134 
135 /*
136  * Note that the function converts a single file name component,
137  * it does not take care of component separators.
138  */
139 static WCHAR* convert_local_name_component_to_remote(wClipboard* clipboard, const WCHAR* local_name)
140 {
141  wClipboardDelegate* delegate = ClipboardGetDelegate(clipboard);
142  WCHAR* remote_name = NULL;
143 
144  WINPR_ASSERT(delegate);
145 
146  remote_name = _wcsdup(local_name);
147 
148  /*
149  * Some file names are not valid on Windows. Check for these now
150  * so that we won't get ourselves into a trouble later as such names
151  * are known to crash some Windows shells when pasted via clipboard.
152  *
153  * The IsFileNameComponentValid callback can be overridden by the API
154  * user, if it is known, that the connected peer is not on the
155  * Windows platform.
156  */
157  if (!delegate->IsFileNameComponentValid(remote_name))
158  {
159  WLog_ERR(TAG, "invalid file name component: %s", local_name);
160  goto error;
161  }
162 
163  return remote_name;
164 error:
165  free(remote_name);
166  return NULL;
167 }
168 
169 static WCHAR* concat_file_name(const WCHAR* dir, const WCHAR* file)
170 {
171  size_t len_dir = 0;
172  size_t len_file = 0;
173  const WCHAR slash = '/';
174  WCHAR* buffer = NULL;
175 
176  WINPR_ASSERT(dir);
177  WINPR_ASSERT(file);
178 
179  len_dir = _wcslen(dir);
180  len_file = _wcslen(file);
181  buffer = calloc(len_dir + 1 + len_file + 2, sizeof(WCHAR));
182 
183  if (!buffer)
184  return NULL;
185 
186  memcpy(buffer, dir, len_dir * sizeof(WCHAR));
187  buffer[len_dir] = slash;
188  memcpy(buffer + len_dir + 1, file, len_file * sizeof(WCHAR));
189  return buffer;
190 }
191 
192 static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name,
193  const WCHAR* remote_name, wArrayList* files);
194 
195 static BOOL add_directory_entry_to_list(wClipboard* clipboard, const WCHAR* local_dir_name,
196  const WCHAR* remote_dir_name,
197  const LPWIN32_FIND_DATAW pFileData, wArrayList* files)
198 {
199  BOOL result = FALSE;
200  WCHAR* local_name = NULL;
201  WCHAR* remote_name = NULL;
202  WCHAR* remote_base_name = NULL;
203 
204  WCHAR dotbuffer[6] = { 0 };
205  WCHAR dotdotbuffer[6] = { 0 };
206  const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
207  const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
208 
209  WINPR_ASSERT(clipboard);
210  WINPR_ASSERT(local_dir_name);
211  WINPR_ASSERT(remote_dir_name);
212  WINPR_ASSERT(pFileData);
213  WINPR_ASSERT(files);
214 
215  /* Skip special directory entries. */
216 
217  if ((_wcscmp(pFileData->cFileName, dot) == 0) || (_wcscmp(pFileData->cFileName, dotdot) == 0))
218  return TRUE;
219 
220  remote_base_name = convert_local_name_component_to_remote(clipboard, pFileData->cFileName);
221 
222  if (!remote_base_name)
223  return FALSE;
224 
225  local_name = concat_file_name(local_dir_name, pFileData->cFileName);
226  remote_name = concat_file_name(remote_dir_name, remote_base_name);
227 
228  if (local_name && remote_name)
229  result = add_file_to_list(clipboard, local_name, remote_name, files);
230 
231  free(remote_base_name);
232  free(remote_name);
233  free(local_name);
234  return result;
235 }
236 
237 static BOOL do_add_directory_contents_to_list(wClipboard* clipboard, const WCHAR* local_name,
238  const WCHAR* remote_name, WCHAR* namebuf,
239  wArrayList* files)
240 {
241  WINPR_ASSERT(clipboard);
242  WINPR_ASSERT(local_name);
243  WINPR_ASSERT(remote_name);
244  WINPR_ASSERT(files);
245  WINPR_ASSERT(namebuf);
246 
247  WIN32_FIND_DATAW FindData = { 0 };
248  HANDLE hFind = FindFirstFileW(namebuf, &FindData);
249  if (INVALID_HANDLE_VALUE == hFind)
250  {
251  WLog_ERR(TAG, "FindFirstFile failed (%" PRIu32 ")", GetLastError());
252  return FALSE;
253  }
254  while (TRUE)
255  {
256  if (!add_directory_entry_to_list(clipboard, local_name, remote_name, &FindData, files))
257  {
258  FindClose(hFind);
259  return FALSE;
260  }
261 
262  BOOL bRet = FindNextFileW(hFind, &FindData);
263  if (!bRet)
264  {
265  FindClose(hFind);
266  if (ERROR_NO_MORE_FILES == GetLastError())
267  return TRUE;
268  WLog_WARN(TAG, "FindNextFile failed (%" PRIu32 ")", GetLastError());
269  return FALSE;
270  }
271  }
272 
273  return TRUE;
274 }
275 
276 static BOOL add_directory_contents_to_list(wClipboard* clipboard, const WCHAR* local_name,
277  const WCHAR* remote_name, wArrayList* files)
278 {
279  BOOL result = FALSE;
280  union
281  {
282  const char* c;
283  const WCHAR* w;
284  } wildcard;
285  const char buffer[6] = "/\0*\0\0\0";
286  wildcard.c = buffer;
287  const size_t wildcardLen = ARRAYSIZE(buffer) / sizeof(WCHAR);
288 
289  WINPR_ASSERT(clipboard);
290  WINPR_ASSERT(local_name);
291  WINPR_ASSERT(remote_name);
292  WINPR_ASSERT(files);
293 
294  size_t len = _wcslen(local_name);
295  WCHAR* namebuf = calloc(len + wildcardLen, sizeof(WCHAR));
296  if (!namebuf)
297  return FALSE;
298 
299  _wcsncat(namebuf, local_name, len);
300  _wcsncat(namebuf, wildcard.w, wildcardLen);
301 
302  result = do_add_directory_contents_to_list(clipboard, local_name, remote_name, namebuf, files);
303 
304  free(namebuf);
305  return result;
306 }
307 
308 static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name,
309  const WCHAR* remote_name, wArrayList* files)
310 {
311  struct synthetic_file* file = NULL;
312 
313  WINPR_ASSERT(clipboard);
314  WINPR_ASSERT(local_name);
315  WINPR_ASSERT(remote_name);
316  WINPR_ASSERT(files);
317 
318  file = make_synthetic_file(local_name, remote_name);
319 
320  if (!file)
321  return FALSE;
322 
323  if (!ArrayList_Append(files, file))
324  {
325  free_synthetic_file(file);
326  return FALSE;
327  }
328 
329  if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
330  {
331  /*
332  * This is effectively a recursive call, but we do not track
333  * recursion depth, thus filesystem loops can cause a crash.
334  */
335  if (!add_directory_contents_to_list(clipboard, local_name, remote_name, files))
336  return FALSE;
337  }
338 
339  return TRUE;
340 }
341 
342 static const WCHAR* get_basename(const WCHAR* name)
343 {
344  const WCHAR* c = name;
345  const WCHAR* last_name = name;
346  const WCHAR slash = '/';
347 
348  WINPR_ASSERT(name);
349 
350  while (*c++)
351  {
352  if (*c == slash)
353  last_name = c + 1;
354  }
355 
356  return last_name;
357 }
358 
359 static BOOL process_file_name(wClipboard* clipboard, const WCHAR* local_name, wArrayList* files)
360 {
361  BOOL result = FALSE;
362  const WCHAR* base_name = NULL;
363  WCHAR* remote_name = NULL;
364 
365  WINPR_ASSERT(clipboard);
366  WINPR_ASSERT(local_name);
367  WINPR_ASSERT(files);
368 
369  /*
370  * Start with the base name of the file. text/uri-list contains the
371  * exact files selected by the user, and we want the remote files
372  * to have names relative to that selection.
373  */
374  base_name = get_basename(local_name);
375  remote_name = convert_local_name_component_to_remote(clipboard, base_name);
376 
377  if (!remote_name)
378  return FALSE;
379 
380  result = add_file_to_list(clipboard, local_name, remote_name, files);
381  free(remote_name);
382  return result;
383 }
384 
385 static BOOL process_uri(wClipboard* clipboard, const char* uri, size_t uri_len)
386 {
387  // URI is specified by RFC 8089: https://datatracker.ietf.org/doc/html/rfc8089
388  BOOL result = FALSE;
389  char* name = NULL;
390 
391  WINPR_ASSERT(clipboard);
392 
393  name = parse_uri_to_local_file(uri, uri_len);
394  if (name)
395  {
396  WCHAR* wname = NULL;
397  /*
398  * Note that local file names are not actually guaranteed to be
399  * encoded in UTF-8. Filesystems and users can use whatever they
400  * want. The OS does not care, aside from special treatment of
401  * '\0' and '/' bytes. But we need to make some decision here.
402  * Assuming UTF-8 is currently the most sane thing.
403  */
404  wname = ConvertUtf8ToWCharAlloc(name, NULL);
405  if (wname)
406  result = process_file_name(clipboard, wname, clipboard->localFiles);
407 
408  free(name);
409  free(wname);
410  }
411 
412  return result;
413 }
414 
415 static BOOL process_uri_list(wClipboard* clipboard, const char* data, size_t length)
416 {
417  const char* cur = data;
418  const char* lim = data + length;
419 
420  WINPR_ASSERT(clipboard);
421  WINPR_ASSERT(data);
422 
423  WLog_VRB(TAG, "processing URI list:\n%.*s", length, data);
424  ArrayList_Clear(clipboard->localFiles);
425 
426  /*
427  * The "text/uri-list" Internet Media Type is specified by RFC 2483.
428  *
429  * While the RFCs 2046 and 2483 require the lines of text/... formats
430  * to be terminated by CRLF sequence, be prepared for those who don't
431  * read the spec, use plain LFs, and don't leave the trailing CRLF.
432  */
433 
434  while (cur < lim)
435  {
436  BOOL comment = (*cur == '#');
437  const char* start = cur;
438  const char* stop = cur;
439 
440  for (; stop < lim; stop++)
441  {
442  if (*stop == '\r')
443  {
444  if ((stop + 1 < lim) && (*(stop + 1) == '\n'))
445  cur = stop + 2;
446  else
447  cur = stop + 1;
448 
449  break;
450  }
451 
452  if (*stop == '\n')
453  {
454  cur = stop + 1;
455  break;
456  }
457  }
458 
459  if (stop == lim)
460  {
461  if (strnlen(start, WINPR_ASSERTING_INT_CAST(size_t, stop - start)) < 1)
462  return TRUE;
463  cur = lim;
464  }
465 
466  if (comment)
467  continue;
468 
469  if (!process_uri(clipboard, start, WINPR_ASSERTING_INT_CAST(size_t, stop - start)))
470  return FALSE;
471  }
472 
473  return TRUE;
474 }
475 
476 static BOOL convert_local_file_to_filedescriptor(const struct synthetic_file* file,
477  FILEDESCRIPTORW* descriptor)
478 {
479  size_t remote_len = 0;
480 
481  WINPR_ASSERT(file);
482  WINPR_ASSERT(descriptor);
483 
484  descriptor->dwFlags = FD_ATTRIBUTES | FD_FILESIZE | FD_WRITESTIME | FD_PROGRESSUI;
485 
486  if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
487  {
488  descriptor->dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
489  descriptor->nFileSizeLow = 0;
490  descriptor->nFileSizeHigh = 0;
491  }
492  else
493  {
494  descriptor->dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
495  descriptor->nFileSizeLow = file->nFileSizeLow;
496  descriptor->nFileSizeHigh = file->nFileSizeHigh;
497  }
498 
499  descriptor->ftLastWriteTime = file->ftLastWriteTime;
500 
501  remote_len = _wcsnlen(file->remote_name, ARRAYSIZE(descriptor->cFileName));
502 
503  if (remote_len >= ARRAYSIZE(descriptor->cFileName))
504  {
505  WLog_ERR(TAG, "file name too long (%" PRIuz " characters)", remote_len);
506  return FALSE;
507  }
508 
509  memcpy(descriptor->cFileName, file->remote_name, remote_len * sizeof(WCHAR));
510  return TRUE;
511 }
512 
513 static FILEDESCRIPTORW* convert_local_file_list_to_filedescriptors(wArrayList* files)
514 {
515  size_t count = 0;
516  FILEDESCRIPTORW* descriptors = NULL;
517 
518  count = ArrayList_Count(files);
519 
520  descriptors = calloc(count, sizeof(FILEDESCRIPTORW));
521 
522  if (!descriptors)
523  goto error;
524 
525  for (size_t i = 0; i < count; i++)
526  {
527  const struct synthetic_file* file = ArrayList_GetItem(files, i);
528 
529  if (!convert_local_file_to_filedescriptor(file, &descriptors[i]))
530  goto error;
531  }
532 
533  return descriptors;
534 error:
535  free(descriptors);
536  return NULL;
537 }
538 
539 static void* convert_any_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
540  UINT32* pSize)
541 {
542  FILEDESCRIPTORW* descriptors = NULL;
543 
544  WINPR_ASSERT(clipboard);
545  WINPR_ASSERT(pSize);
546 
547  descriptors = convert_local_file_list_to_filedescriptors(clipboard->localFiles);
548  *pSize = 0;
549  if (!descriptors)
550  return NULL;
551 
552  *pSize = (UINT32)ArrayList_Count(clipboard->localFiles) * sizeof(FILEDESCRIPTORW);
553  clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
554  return descriptors;
555 }
556 
557 static void* convert_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
558  const void* data, UINT32* pSize)
559 {
560  const UINT32 expected = ClipboardGetFormatId(clipboard, mime_uri_list);
561  if (formatId != expected)
562  return NULL;
563  if (!process_uri_list(clipboard, (const char*)data, *pSize))
564  return NULL;
565  return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
566 }
567 
568 static BOOL process_files(wClipboard* clipboard, const char* data, UINT32 pSize, const char* prefix)
569 {
570  WINPR_ASSERT(prefix);
571 
572  const size_t prefix_len = strlen(prefix);
573 
574  WINPR_ASSERT(clipboard);
575 
576  ArrayList_Clear(clipboard->localFiles);
577 
578  if (!data || (pSize < prefix_len))
579  return FALSE;
580  if (strncmp(data, prefix, prefix_len) != 0)
581  return FALSE;
582  data += prefix_len;
583  pSize -= prefix_len;
584 
585  BOOL rc = FALSE;
586  char* copy = strndup(data, pSize);
587  if (!copy)
588  goto fail;
589 
590  char* endptr = NULL;
591  char* tok = strtok_s(copy, "\n", &endptr);
592  while (tok)
593  {
594  size_t tok_len = strnlen(tok, pSize);
595  if (!process_uri(clipboard, tok, tok_len))
596  goto fail;
597  pSize -= tok_len;
598  tok = strtok_s(NULL, "\n", &endptr);
599  }
600  rc = TRUE;
601 
602 fail:
603  free(copy);
604  return rc;
605 }
606 
607 static BOOL process_gnome_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
608 {
609  return process_files(clipboard, data, pSize, "copy\n");
610 }
611 
612 static BOOL process_mate_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
613 {
614  return process_files(clipboard, data, pSize, "copy\n");
615 }
616 
617 static BOOL process_nautilus_clipboard(wClipboard* clipboard, const char* data, UINT32 pSize)
618 {
619  return process_files(clipboard, data, pSize, "x-special/nautilus-clipboard\ncopy\n");
620 }
621 
622 static void* convert_nautilus_clipboard_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
623  const void* data, UINT32* pSize)
624 {
625  const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files);
626  if (formatId != expected)
627  return NULL;
628  if (!process_nautilus_clipboard(clipboard, (const char*)data, *pSize))
629  return NULL;
630  return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
631 }
632 
633 static void* convert_gnome_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
634  const void* data, UINT32* pSize)
635 {
636  const UINT32 expected = ClipboardGetFormatId(clipboard, mime_gnome_copied_files);
637  if (formatId != expected)
638  return NULL;
639  if (!process_gnome_copied_files(clipboard, (const char*)data, *pSize))
640  return NULL;
641  return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
642 }
643 
644 static void* convert_mate_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
645  const void* data, UINT32* pSize)
646 {
647  const UINT32 expected = ClipboardGetFormatId(clipboard, mime_mate_copied_files);
648  if (formatId != expected)
649  return NULL;
650 
651  if (!process_mate_copied_files(clipboard, (const char*)data, *pSize))
652  return NULL;
653 
654  return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
655 }
656 
657 static size_t count_special_chars(const WCHAR* str)
658 {
659  size_t count = 0;
660  const WCHAR* start = str;
661 
662  WINPR_ASSERT(str);
663  while (*start)
664  {
665  const WCHAR sharp = '#';
666  const WCHAR questionmark = '?';
667  const WCHAR star = '*';
668  const WCHAR exclamationmark = '!';
669  const WCHAR percent = '%';
670 
671  if ((*start == sharp) || (*start == questionmark) || (*start == star) ||
672  (*start == exclamationmark) || (*start == percent))
673  {
674  count++;
675  }
676  start++;
677  }
678  return count;
679 }
680 
681 static const char* stop_at_special_chars(const char* str)
682 {
683  const char* start = str;
684  WINPR_ASSERT(str);
685 
686  while (*start)
687  {
688  if (*start == '#' || *start == '?' || *start == '*' || *start == '!' || *start == '%')
689  {
690  return start;
691  }
692  start++;
693  }
694  return NULL;
695 }
696 
697 /* The universal converter from filedescriptors to different file lists */
698 static void* convert_filedescriptors_to_file_list(wClipboard* clipboard, UINT32 formatId,
699  const void* data, UINT32* pSize,
700  const char* header, const char* lineprefix,
701  const char* lineending, BOOL skip_last_lineending)
702 {
703  union
704  {
705  char c[2];
706  WCHAR w;
707  } backslash;
708  backslash.c[0] = '\\';
709  backslash.c[1] = '\0';
710 
711  const FILEDESCRIPTORW* descriptors = NULL;
712  UINT32 nrDescriptors = 0;
713  size_t count = 0;
714  size_t alloc = 0;
715  size_t pos = 0;
716  size_t baseLength = 0;
717  char* dst = NULL;
718  size_t header_len = strlen(header);
719  size_t lineprefix_len = strlen(lineprefix);
720  size_t lineending_len = strlen(lineending);
721  size_t decoration_len = 0;
722 
723  if (!clipboard || !data || !pSize)
724  return NULL;
725 
726  if (*pSize < sizeof(UINT32))
727  return NULL;
728 
729  if (clipboard->delegate.basePath)
730  baseLength = strnlen(clipboard->delegate.basePath, MAX_PATH);
731 
732  if (baseLength < 1)
733  return NULL;
734 
735  wStream sbuffer = { 0 };
736  wStream* s = Stream_StaticConstInit(&sbuffer, data, *pSize);
737  if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
738  return NULL;
739 
740  Stream_Read_UINT32(s, nrDescriptors);
741 
742  count = (*pSize - 4) / sizeof(FILEDESCRIPTORW);
743 
744  if ((count < 1) || (count != nrDescriptors))
745  return NULL;
746 
747  descriptors = Stream_ConstPointer(s);
748 
749  if (formatId != ClipboardGetFormatId(clipboard, mime_FileGroupDescriptorW))
750  return NULL;
751 
752  /* Plus 1 for '/' between basepath and filename*/
753  decoration_len = lineprefix_len + lineending_len + baseLength + 1;
754  alloc = header_len;
755 
756  /* Get total size of file/folder names under first level folder only */
757  for (size_t x = 0; x < count; x++)
758  {
759  const FILEDESCRIPTORW* dsc = &descriptors[x];
760 
761  if (_wcschr(dsc->cFileName, backslash.w) == NULL)
762  {
763  alloc += ARRAYSIZE(dsc->cFileName) *
764  8; /* Overallocate, just take the biggest value the result path can have */
765  /* # (1 char) -> %23 (3 chars) , the first char is replaced inplace */
766  alloc += count_special_chars(dsc->cFileName) * 2;
767  alloc += decoration_len;
768  }
769  }
770 
771  /* Append a prefix file:// and postfix \n for each file */
772  /* We need to keep last \n since snprintf is null terminated!! */
773  alloc++;
774  dst = calloc(alloc, sizeof(char));
775 
776  if (!dst)
777  return NULL;
778 
779  (void)_snprintf(&dst[0], alloc, "%s", header);
780 
781  pos = header_len;
782 
783  for (size_t x = 0; x < count; x++)
784  {
785  const FILEDESCRIPTORW* dsc = &descriptors[x];
786  BOOL fail = TRUE;
787  if (_wcschr(dsc->cFileName, backslash.w) != NULL)
788  {
789  continue;
790  }
791  int rc = -1;
792  char curName[520] = { 0 };
793  const char* stop_at = NULL;
794  const char* previous_at = NULL;
795 
796  if (ConvertWCharNToUtf8(dsc->cFileName, ARRAYSIZE(dsc->cFileName), curName,
797  ARRAYSIZE(curName)) < 0)
798  goto loop_fail;
799 
800  rc = _snprintf(&dst[pos], alloc - pos, "%s%s/", lineprefix, clipboard->delegate.basePath);
801 
802  if (rc < 0)
803  goto loop_fail;
804 
805  pos += (size_t)rc;
806 
807  previous_at = curName;
808  while ((stop_at = stop_at_special_chars(previous_at)) != NULL)
809  {
810  char* tmp =
811  strndup(previous_at, WINPR_ASSERTING_INT_CAST(size_t, stop_at - previous_at));
812  if (!tmp)
813  goto loop_fail;
814 
815  rc = _snprintf(&dst[pos], WINPR_ASSERTING_INT_CAST(size_t, stop_at - previous_at + 1),
816  "%s", tmp);
817  free(tmp);
818  if (rc < 0)
819  goto loop_fail;
820 
821  pos += (size_t)rc;
822  rc = _snprintf(&dst[pos], 4, "%%%x", *stop_at);
823  if (rc < 0)
824  goto loop_fail;
825 
826  pos += (size_t)rc;
827  previous_at = stop_at + 1;
828  }
829 
830  rc = _snprintf(&dst[pos], alloc - pos, "%s%s", previous_at, lineending);
831 
832  fail = FALSE;
833  loop_fail:
834  if ((rc < 0) || fail)
835  {
836  free(dst);
837  return NULL;
838  }
839 
840  pos += (size_t)rc;
841  }
842 
843  if (skip_last_lineending)
844  {
845  const size_t endlen = strlen(lineending);
846  if (alloc > endlen)
847  {
848  const size_t len = strnlen(dst, alloc);
849  if (len < endlen)
850  {
851  free(dst);
852  return NULL;
853  }
854 
855  if (memcmp(&dst[len - endlen], lineending, endlen) == 0)
856  {
857  memset(&dst[len - endlen], 0, endlen);
858  alloc -= endlen;
859  }
860  }
861  }
862 
863  alloc = strnlen(dst, alloc) + 1;
864  *pSize = (UINT32)alloc;
865  clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
866  return dst;
867 }
868 
869 /* Prepend header of kde dolphin format to file list
870  * See:
871  * GTK: https://docs.gtk.org/glib/struct.Uri.html
872  * uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
873  * uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
874  */
875 static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 formatId,
876  const void* data, UINT32* pSize)
877 {
878  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "", "file://",
879  "\r\n", FALSE);
880 }
881 
882 /* Prepend header of common gnome format to file list*/
883 static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId,
884  const void* data, UINT32* pSize)
885 {
886  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "copy\n",
887  "file://", "\n", TRUE);
888 }
889 
890 /* Prepend header of nautilus based filemanager's format to file list*/
891 static void* convert_filedescriptors_to_nautilus_clipboard(wClipboard* clipboard, UINT32 formatId,
892  const void* data, UINT32* pSize)
893 {
894  /* Here Nemo (and Caja) have different behavior. They encounter error with the last \n . but
895  nautilus needs it. So user have to skip Nemo's error dialog to continue. Caja has different
896  TARGET , so it's easy to fix. see convert_filedescriptors_to_mate_copied_files
897 
898  The text based "x-special/nautilus-clipboard" type was introduced with GNOME 3.30 and
899  was necessary for the desktop icons extension, as gnome-shell at that time only
900  supported text based mime types for gnome extensions. With GNOME 3.38, gnome-shell got
901  support for non-text based mime types for gnome extensions. With GNOME 40, nautilus reverted
902  the mime type change to "x-special/gnome-copied-files" and removed support for the text based
903  mime type. So, in the near future, change this behaviour in favor for Nemo and Caja.
904  */
905  /* see nautilus/src/nautilus-clipboard.c:convert_selection_data_to_str_list
906  see nemo/libnemo-private/nemo-clipboard.c:nemo_clipboard_get_uri_list_from_selection_data
907  */
908 
909  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
910  "x-special/nautilus-clipboard\ncopy\n", "file://",
911  "\n", FALSE);
912 }
913 
914 static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId,
915  const void* data, UINT32* pSize)
916 {
917 
918  char* pDstData = convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
919  "copy\n", "file://", "\n", TRUE);
920  if (!pDstData)
921  {
922  return pDstData;
923  }
924  /* Replace last \n with \0
925  see
926  mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data
927  */
928 
929  pDstData[*pSize - 1] = '\0';
930  *pSize = *pSize - 1;
931  return pDstData;
932 }
933 
934 static void array_free_synthetic_file(void* the_file)
935 {
936  struct synthetic_file* file = the_file;
937  free_synthetic_file(file);
938 }
939 
940 static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard)
941 {
942  wObject* obj = NULL;
943 
944  /*
945  1. Gnome Nautilus based file manager (Nautilus only with version >= 3.30 AND < 40):
946  TARGET: UTF8_STRING
947  format: x-special/nautilus-clipboard\copy\n\file://path\n\0
948  2. Kde Dolpin and Qt:
949  TARGET: text/uri-list
950  format: file:path\r\n\0
951  See:
952  GTK: https://docs.gtk.org/glib/struct.Uri.html
953  uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
954  uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
955  3. Gnome and others (Unity/XFCE/Nautilus < 3.30/Nautilus >= 40):
956  TARGET: x-special/gnome-copied-files
957  format: copy\nfile://path\n\0
958  4. Mate Caja:
959  TARGET: x-special/mate-copied-files
960  format: copy\nfile://path\n
961 
962  TODO: other file managers do not use previous targets and formats.
963  */
964 
965  const UINT32 local_gnome_file_format_id =
966  ClipboardRegisterFormat(clipboard, mime_gnome_copied_files);
967  const UINT32 local_mate_file_format_id =
968  ClipboardRegisterFormat(clipboard, mime_mate_copied_files);
969  const UINT32 file_group_format_id =
970  ClipboardRegisterFormat(clipboard, mime_FileGroupDescriptorW);
971  const UINT32 local_file_format_id = ClipboardRegisterFormat(clipboard, mime_uri_list);
972 
973  if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id ||
974  !local_mate_file_format_id)
975  goto error;
976 
977  clipboard->localFiles = ArrayList_New(FALSE);
978 
979  if (!clipboard->localFiles)
980  goto error;
981 
982  obj = ArrayList_Object(clipboard->localFiles);
983  obj->fnObjectFree = array_free_synthetic_file;
984 
985  if (!ClipboardRegisterSynthesizer(clipboard, local_file_format_id, file_group_format_id,
986  convert_uri_list_to_filedescriptors))
987  goto error_free_local_files;
988 
989  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_file_format_id,
990  convert_filedescriptors_to_uri_list))
991  goto error_free_local_files;
992 
993  if (!ClipboardRegisterSynthesizer(clipboard, local_gnome_file_format_id, file_group_format_id,
994  convert_gnome_copied_files_to_filedescriptors))
995  goto error_free_local_files;
996 
997  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id,
998  convert_filedescriptors_to_gnome_copied_files))
999  goto error_free_local_files;
1000 
1001  if (!ClipboardRegisterSynthesizer(clipboard, local_mate_file_format_id, file_group_format_id,
1002  convert_mate_copied_files_to_filedescriptors))
1003  goto error_free_local_files;
1004 
1005  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id,
1006  convert_filedescriptors_to_mate_copied_files))
1007  goto error_free_local_files;
1008 
1009  return TRUE;
1010 error_free_local_files:
1011  ArrayList_Free(clipboard->localFiles);
1012  clipboard->localFiles = NULL;
1013 error:
1014  return FALSE;
1015 }
1016 
1017 static int32_t file_get_size(const struct synthetic_file* file, UINT64* size)
1018 {
1019  UINT64 s = 0;
1020 
1021  if (!file || !size)
1022  return E_INVALIDARG;
1023 
1024  s = file->nFileSizeHigh;
1025  s <<= 32;
1026  s |= file->nFileSizeLow;
1027  *size = s;
1028  return NO_ERROR;
1029 }
1030 
1031 static UINT delegate_file_request_size(wClipboardDelegate* delegate,
1032  const wClipboardFileSizeRequest* request)
1033 {
1034  UINT error = NO_ERROR;
1035  UINT64 size = 0;
1036  struct synthetic_file* file = NULL;
1037 
1038  if (!delegate || !delegate->clipboard || !request)
1039  return ERROR_BAD_ARGUMENTS;
1040 
1041  if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1042  return ERROR_INVALID_STATE;
1043 
1044  file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1045 
1046  if (!file)
1047  return ERROR_INDEX_ABSENT;
1048 
1049  error = file_get_size(file, &size);
1050 
1051  if (error)
1052  error = delegate->ClipboardFileSizeFailure(delegate, request, error);
1053  else
1054  error = delegate->ClipboardFileSizeSuccess(delegate, request, size);
1055 
1056  if (error)
1057  WLog_WARN(TAG, "failed to report file size result: 0x%08X", error);
1058 
1059  return NO_ERROR;
1060 }
1061 
1062 UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force)
1063 {
1064  if (!file || INVALID_HANDLE_VALUE == file->fd)
1065  return NO_ERROR;
1066 
1067  /* Always force close the file. Clipboard might open hundreds of files
1068  * so avoid caching to prevent running out of available file descriptors */
1069  UINT64 size = 0;
1070  file_get_size(file, &size);
1071  if ((file->offset < 0) || ((UINT64)file->offset >= size) || force)
1072  {
1073  WLog_VRB(TAG, "close file %d", file->fd);
1074  if (!CloseHandle(file->fd))
1075  {
1076  WLog_WARN(TAG, "failed to close fd %d: %" PRIu32, file->fd, GetLastError());
1077  }
1078 
1079  file->fd = INVALID_HANDLE_VALUE;
1080  }
1081 
1082  return NO_ERROR;
1083 }
1084 
1085 static UINT file_get_range(struct synthetic_file* file, UINT64 offset, UINT32 size,
1086  BYTE** actual_data, UINT32* actual_size)
1087 {
1088  UINT error = NO_ERROR;
1089  DWORD dwLow = 0;
1090  DWORD dwHigh = 0;
1091 
1092  WINPR_ASSERT(file);
1093  WINPR_ASSERT(actual_data);
1094  WINPR_ASSERT(actual_size);
1095 
1096  if (INVALID_HANDLE_VALUE == file->fd)
1097  {
1098  BY_HANDLE_FILE_INFORMATION FileInfo = { 0 };
1099 
1100  file->fd = CreateFileW(file->local_name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
1101  FILE_ATTRIBUTE_NORMAL, NULL);
1102  if (INVALID_HANDLE_VALUE == file->fd)
1103  {
1104  error = GetLastError();
1105  WLog_ERR(TAG, "failed to open file %s: 0x%08" PRIx32, file->local_name, error);
1106  return error;
1107  }
1108 
1109  if (!GetFileInformationByHandle(file->fd, &FileInfo))
1110  {
1111  (void)CloseHandle(file->fd);
1112  file->fd = INVALID_HANDLE_VALUE;
1113  error = GetLastError();
1114  WLog_ERR(TAG, "Get file [%s] information fail: 0x%08" PRIx32, file->local_name, error);
1115  return error;
1116  }
1117 
1118  file->offset = 0;
1119  file->nFileSizeHigh = FileInfo.nFileSizeHigh;
1120  file->nFileSizeLow = FileInfo.nFileSizeLow;
1121 
1122  /*
1123  {
1124  UINT64 s = 0;
1125  file_get_size(file, &s);
1126  WLog_DBG(TAG, "open file %d -> %s", file->fd, file->local_name);
1127  WLog_DBG(TAG, "file %d size: %" PRIu64 " bytes", file->fd, s);
1128  } //*/
1129  }
1130 
1131  do
1132  {
1133  /*
1134  * We should avoid seeking when possible as some filesystems (e.g.,
1135  * an FTP server mapped via FUSE) may not support seeking. We keep
1136  * an accurate account of the current file offset and do not call
1137  * lseek() if the client requests file content sequentially.
1138  */
1139  if (offset > INT64_MAX)
1140  {
1141  WLog_ERR(TAG, "offset [%" PRIu64 "] > INT64_MAX", offset);
1142  error = ERROR_SEEK;
1143  break;
1144  }
1145 
1146  if (file->offset != (INT64)offset)
1147  {
1148  WLog_DBG(TAG, "file %d force seeking to %" PRIu64 ", current %" PRIu64, file->fd,
1149  offset, file->offset);
1150 
1151  dwHigh = offset >> 32;
1152  dwLow = offset & 0xFFFFFFFF;
1153  if (INVALID_SET_FILE_POINTER == SetFilePointer(file->fd,
1154  WINPR_ASSERTING_INT_CAST(LONG, dwLow),
1155  (PLONG)&dwHigh, FILE_BEGIN))
1156  {
1157  error = GetLastError();
1158  break;
1159  }
1160  }
1161 
1162  BYTE* buffer = malloc(size);
1163  if (!buffer)
1164  {
1165  error = ERROR_NOT_ENOUGH_MEMORY;
1166  break;
1167  }
1168  if (!ReadFile(file->fd, buffer, size, (LPDWORD)actual_size, NULL))
1169  {
1170  free(buffer);
1171  error = GetLastError();
1172  break;
1173  }
1174 
1175  *actual_data = buffer;
1176  file->offset += *actual_size;
1177  WLog_VRB(TAG, "file %d actual read %" PRIu32 " bytes (offset %" PRIu64 ")", file->fd,
1178  *actual_size, file->offset);
1179  } while (0);
1180 
1181  synthetic_file_read_close(file, TRUE /* (error != NO_ERROR) && (size > 0) */);
1182  return error;
1183 }
1184 
1185 static UINT delegate_file_request_range(wClipboardDelegate* delegate,
1186  const wClipboardFileRangeRequest* request)
1187 {
1188  UINT error = 0;
1189  BYTE* data = NULL;
1190  UINT32 size = 0;
1191  UINT64 offset = 0;
1192  struct synthetic_file* file = NULL;
1193 
1194  if (!delegate || !delegate->clipboard || !request)
1195  return ERROR_BAD_ARGUMENTS;
1196 
1197  if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1198  return ERROR_INVALID_STATE;
1199 
1200  file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1201 
1202  if (!file)
1203  return ERROR_INDEX_ABSENT;
1204 
1205  offset = (((UINT64)request->nPositionHigh) << 32) | ((UINT64)request->nPositionLow);
1206  error = file_get_range(file, offset, request->cbRequested, &data, &size);
1207 
1208  if (error)
1209  error = delegate->ClipboardFileRangeFailure(delegate, request, error);
1210  else
1211  error = delegate->ClipboardFileRangeSuccess(delegate, request, data, size);
1212 
1213  if (error)
1214  WLog_WARN(TAG, "failed to report file range result: 0x%08X", error);
1215 
1216  free(data);
1217  return NO_ERROR;
1218 }
1219 
1220 static UINT dummy_file_size_success(wClipboardDelegate* delegate,
1221  const wClipboardFileSizeRequest* request, UINT64 fileSize)
1222 {
1223  return ERROR_NOT_SUPPORTED;
1224 }
1225 
1226 static UINT dummy_file_size_failure(wClipboardDelegate* delegate,
1227  const wClipboardFileSizeRequest* request, UINT errorCode)
1228 {
1229  return ERROR_NOT_SUPPORTED;
1230 }
1231 
1232 static UINT dummy_file_range_success(wClipboardDelegate* delegate,
1233  const wClipboardFileRangeRequest* request, const BYTE* data,
1234  UINT32 size)
1235 {
1236  return ERROR_NOT_SUPPORTED;
1237 }
1238 
1239 static UINT dummy_file_range_failure(wClipboardDelegate* delegate,
1240  const wClipboardFileRangeRequest* request, UINT errorCode)
1241 {
1242  return ERROR_NOT_SUPPORTED;
1243 }
1244 
1245 static void setup_delegate(wClipboardDelegate* delegate)
1246 {
1247  WINPR_ASSERT(delegate);
1248 
1249  delegate->ClientRequestFileSize = delegate_file_request_size;
1250  delegate->ClipboardFileSizeSuccess = dummy_file_size_success;
1251  delegate->ClipboardFileSizeFailure = dummy_file_size_failure;
1252  delegate->ClientRequestFileRange = delegate_file_request_range;
1253  delegate->ClipboardFileRangeSuccess = dummy_file_range_success;
1254  delegate->ClipboardFileRangeFailure = dummy_file_range_failure;
1255  delegate->IsFileNameComponentValid = ValidFileNameComponent;
1256 }
1257 
1258 BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard)
1259 {
1260  if (!clipboard)
1261  return FALSE;
1262 
1263  if (!register_file_formats_and_synthesizers(clipboard))
1264  return FALSE;
1265 
1266  setup_delegate(&clipboard->delegate);
1267  return TRUE;
1268 }
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57