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, stop - start) < 1)
462  return TRUE;
463  cur = lim;
464  }
465 
466  if (comment)
467  continue;
468 
469  if (!process_uri(clipboard, start, 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 = strndup(previous_at, stop_at - previous_at);
811  if (!tmp)
812  goto loop_fail;
813 
814  rc = _snprintf(&dst[pos], stop_at - previous_at + 1, "%s", tmp);
815  free(tmp);
816  if (rc < 0)
817  goto loop_fail;
818 
819  pos += (size_t)rc;
820  rc = _snprintf(&dst[pos], 4, "%%%x", *stop_at);
821  if (rc < 0)
822  goto loop_fail;
823 
824  pos += (size_t)rc;
825  previous_at = stop_at + 1;
826  }
827 
828  rc = _snprintf(&dst[pos], alloc - pos, "%s%s", previous_at, lineending);
829 
830  fail = FALSE;
831  loop_fail:
832  if ((rc < 0) || fail)
833  {
834  free(dst);
835  return NULL;
836  }
837 
838  pos += (size_t)rc;
839  }
840 
841  if (skip_last_lineending)
842  {
843  const size_t endlen = strlen(lineending);
844  if (alloc > endlen)
845  {
846  const size_t len = strnlen(dst, alloc);
847  if (len < endlen)
848  {
849  free(dst);
850  return NULL;
851  }
852 
853  if (memcmp(&dst[len - endlen], lineending, endlen) == 0)
854  {
855  memset(&dst[len - endlen], 0, endlen);
856  alloc -= endlen;
857  }
858  }
859  }
860 
861  alloc = strnlen(dst, alloc) + 1;
862  *pSize = (UINT32)alloc;
863  clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
864  return dst;
865 }
866 
867 /* Prepend header of kde dolphin format to file list
868  * See:
869  * GTK: https://docs.gtk.org/glib/struct.Uri.html
870  * uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
871  * uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
872  */
873 static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 formatId,
874  const void* data, UINT32* pSize)
875 {
876  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "", "file://",
877  "\r\n", FALSE);
878 }
879 
880 /* Prepend header of common gnome format to file list*/
881 static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId,
882  const void* data, UINT32* pSize)
883 {
884  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "copy\n",
885  "file://", "\n", TRUE);
886 }
887 
888 /* Prepend header of nautilus based filemanager's format to file list*/
889 static void* convert_filedescriptors_to_nautilus_clipboard(wClipboard* clipboard, UINT32 formatId,
890  const void* data, UINT32* pSize)
891 {
892  /* Here Nemo (and Caja) have different behavior. They encounter error with the last \n . but
893  nautilus needs it. So user have to skip Nemo's error dialog to continue. Caja has different
894  TARGET , so it's easy to fix. see convert_filedescriptors_to_mate_copied_files
895 
896  The text based "x-special/nautilus-clipboard" type was introduced with GNOME 3.30 and
897  was necessary for the desktop icons extension, as gnome-shell at that time only
898  supported text based mime types for gnome extensions. With GNOME 3.38, gnome-shell got
899  support for non-text based mime types for gnome extensions. With GNOME 40, nautilus reverted
900  the mime type change to "x-special/gnome-copied-files" and removed support for the text based
901  mime type. So, in the near future, change this behaviour in favor for Nemo and Caja.
902  */
903  /* see nautilus/src/nautilus-clipboard.c:convert_selection_data_to_str_list
904  see nemo/libnemo-private/nemo-clipboard.c:nemo_clipboard_get_uri_list_from_selection_data
905  */
906 
907  return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
908  "x-special/nautilus-clipboard\ncopy\n", "file://",
909  "\n", FALSE);
910 }
911 
912 static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId,
913  const void* data, UINT32* pSize)
914 {
915 
916  char* pDstData = convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
917  "copy\n", "file://", "\n", TRUE);
918  if (!pDstData)
919  {
920  return pDstData;
921  }
922  /* Replace last \n with \0
923  see
924  mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data
925  */
926 
927  pDstData[*pSize - 1] = '\0';
928  *pSize = *pSize - 1;
929  return pDstData;
930 }
931 
932 static void array_free_synthetic_file(void* the_file)
933 {
934  struct synthetic_file* file = the_file;
935  free_synthetic_file(file);
936 }
937 
938 static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard)
939 {
940  wObject* obj = NULL;
941 
942  /*
943  1. Gnome Nautilus based file manager (Nautilus only with version >= 3.30 AND < 40):
944  TARGET: UTF8_STRING
945  format: x-special/nautilus-clipboard\copy\n\file://path\n\0
946  2. Kde Dolpin and Qt:
947  TARGET: text/uri-list
948  format: file:path\r\n\0
949  See:
950  GTK: https://docs.gtk.org/glib/struct.Uri.html
951  uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
952  uri-lists fomat: https://www.rfc-editor.org/rfc/rfc2483#section-5
953  3. Gnome and others (Unity/XFCE/Nautilus < 3.30/Nautilus >= 40):
954  TARGET: x-special/gnome-copied-files
955  format: copy\nfile://path\n\0
956  4. Mate Caja:
957  TARGET: x-special/mate-copied-files
958  format: copy\nfile://path\n
959 
960  TODO: other file managers do not use previous targets and formats.
961  */
962 
963  const UINT32 local_gnome_file_format_id =
964  ClipboardRegisterFormat(clipboard, mime_gnome_copied_files);
965  const UINT32 local_mate_file_format_id =
966  ClipboardRegisterFormat(clipboard, mime_mate_copied_files);
967  const UINT32 file_group_format_id =
968  ClipboardRegisterFormat(clipboard, mime_FileGroupDescriptorW);
969  const UINT32 local_file_format_id = ClipboardRegisterFormat(clipboard, mime_uri_list);
970 
971  if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id ||
972  !local_mate_file_format_id)
973  goto error;
974 
975  clipboard->localFiles = ArrayList_New(FALSE);
976 
977  if (!clipboard->localFiles)
978  goto error;
979 
980  obj = ArrayList_Object(clipboard->localFiles);
981  obj->fnObjectFree = array_free_synthetic_file;
982 
983  if (!ClipboardRegisterSynthesizer(clipboard, local_file_format_id, file_group_format_id,
984  convert_uri_list_to_filedescriptors))
985  goto error_free_local_files;
986 
987  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_file_format_id,
988  convert_filedescriptors_to_uri_list))
989  goto error_free_local_files;
990 
991  if (!ClipboardRegisterSynthesizer(clipboard, local_gnome_file_format_id, file_group_format_id,
992  convert_gnome_copied_files_to_filedescriptors))
993  goto error_free_local_files;
994 
995  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id,
996  convert_filedescriptors_to_gnome_copied_files))
997  goto error_free_local_files;
998 
999  if (!ClipboardRegisterSynthesizer(clipboard, local_mate_file_format_id, file_group_format_id,
1000  convert_mate_copied_files_to_filedescriptors))
1001  goto error_free_local_files;
1002 
1003  if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id,
1004  convert_filedescriptors_to_mate_copied_files))
1005  goto error_free_local_files;
1006 
1007  return TRUE;
1008 error_free_local_files:
1009  ArrayList_Free(clipboard->localFiles);
1010  clipboard->localFiles = NULL;
1011 error:
1012  return FALSE;
1013 }
1014 
1015 static UINT file_get_size(const struct synthetic_file* file, UINT64* size)
1016 {
1017  UINT64 s = 0;
1018 
1019  if (!file || !size)
1020  return E_INVALIDARG;
1021 
1022  s = file->nFileSizeHigh;
1023  s <<= 32;
1024  s |= file->nFileSizeLow;
1025  *size = s;
1026  return NO_ERROR;
1027 }
1028 
1029 static UINT delegate_file_request_size(wClipboardDelegate* delegate,
1030  const wClipboardFileSizeRequest* request)
1031 {
1032  UINT error = NO_ERROR;
1033  UINT64 size = 0;
1034  struct synthetic_file* file = NULL;
1035 
1036  if (!delegate || !delegate->clipboard || !request)
1037  return ERROR_BAD_ARGUMENTS;
1038 
1039  if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1040  return ERROR_INVALID_STATE;
1041 
1042  file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1043 
1044  if (!file)
1045  return ERROR_INDEX_ABSENT;
1046 
1047  error = file_get_size(file, &size);
1048 
1049  if (error)
1050  error = delegate->ClipboardFileSizeFailure(delegate, request, error);
1051  else
1052  error = delegate->ClipboardFileSizeSuccess(delegate, request, size);
1053 
1054  if (error)
1055  WLog_WARN(TAG, "failed to report file size result: 0x%08X", error);
1056 
1057  return NO_ERROR;
1058 }
1059 
1060 UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force)
1061 {
1062  if (!file || INVALID_HANDLE_VALUE == file->fd)
1063  return NO_ERROR;
1064 
1065  /* Always force close the file. Clipboard might open hundreds of files
1066  * so avoid caching to prevent running out of available file descriptors */
1067  UINT64 size = 0;
1068  file_get_size(file, &size);
1069  if ((file->offset < 0) || ((UINT64)file->offset >= size) || force)
1070  {
1071  WLog_VRB(TAG, "close file %d", file->fd);
1072  if (!CloseHandle(file->fd))
1073  {
1074  WLog_WARN(TAG, "failed to close fd %d: %" PRIu32, file->fd, GetLastError());
1075  }
1076 
1077  file->fd = INVALID_HANDLE_VALUE;
1078  }
1079 
1080  return NO_ERROR;
1081 }
1082 
1083 static UINT file_get_range(struct synthetic_file* file, UINT64 offset, UINT32 size,
1084  BYTE** actual_data, UINT32* actual_size)
1085 {
1086  UINT error = NO_ERROR;
1087  DWORD dwLow = 0;
1088  DWORD dwHigh = 0;
1089 
1090  WINPR_ASSERT(file);
1091  WINPR_ASSERT(actual_data);
1092  WINPR_ASSERT(actual_size);
1093 
1094  if (INVALID_HANDLE_VALUE == file->fd)
1095  {
1096  BY_HANDLE_FILE_INFORMATION FileInfo = { 0 };
1097 
1098  file->fd = CreateFileW(file->local_name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
1099  FILE_ATTRIBUTE_NORMAL, NULL);
1100  if (INVALID_HANDLE_VALUE == file->fd)
1101  {
1102  error = GetLastError();
1103  WLog_ERR(TAG, "failed to open file %s: 0x%08" PRIx32, file->local_name, error);
1104  return error;
1105  }
1106 
1107  if (!GetFileInformationByHandle(file->fd, &FileInfo))
1108  {
1109  (void)CloseHandle(file->fd);
1110  file->fd = INVALID_HANDLE_VALUE;
1111  error = GetLastError();
1112  WLog_ERR(TAG, "Get file [%s] information fail: 0x%08" PRIx32, file->local_name, error);
1113  return error;
1114  }
1115 
1116  file->offset = 0;
1117  file->nFileSizeHigh = FileInfo.nFileSizeHigh;
1118  file->nFileSizeLow = FileInfo.nFileSizeLow;
1119 
1120  /*
1121  {
1122  UINT64 s = 0;
1123  file_get_size(file, &s);
1124  WLog_DBG(TAG, "open file %d -> %s", file->fd, file->local_name);
1125  WLog_DBG(TAG, "file %d size: %" PRIu64 " bytes", file->fd, s);
1126  } //*/
1127  }
1128 
1129  do
1130  {
1131  /*
1132  * We should avoid seeking when possible as some filesystems (e.g.,
1133  * an FTP server mapped via FUSE) may not support seeking. We keep
1134  * an accurate account of the current file offset and do not call
1135  * lseek() if the client requests file content sequentially.
1136  */
1137  if (offset > INT64_MAX)
1138  {
1139  WLog_ERR(TAG, "offset [%" PRIu64 "] > INT64_MAX", offset);
1140  error = ERROR_SEEK;
1141  break;
1142  }
1143 
1144  if (file->offset != (INT64)offset)
1145  {
1146  WLog_DBG(TAG, "file %d force seeking to %" PRIu64 ", current %" PRIu64, file->fd,
1147  offset, file->offset);
1148 
1149  dwHigh = offset >> 32;
1150  dwLow = offset & 0xFFFFFFFF;
1151  if (INVALID_SET_FILE_POINTER ==
1152  SetFilePointer(file->fd, dwLow, (PLONG)&dwHigh, FILE_BEGIN))
1153  {
1154  error = GetLastError();
1155  break;
1156  }
1157  }
1158 
1159  BYTE* buffer = malloc(size);
1160  if (!buffer)
1161  {
1162  error = ERROR_NOT_ENOUGH_MEMORY;
1163  break;
1164  }
1165  if (!ReadFile(file->fd, buffer, size, (LPDWORD)actual_size, NULL))
1166  {
1167  free(buffer);
1168  error = GetLastError();
1169  break;
1170  }
1171 
1172  *actual_data = buffer;
1173  file->offset += *actual_size;
1174  WLog_VRB(TAG, "file %d actual read %" PRIu32 " bytes (offset %" PRIu64 ")", file->fd,
1175  *actual_size, file->offset);
1176  } while (0);
1177 
1178  synthetic_file_read_close(file, TRUE /* (error != NO_ERROR) && (size > 0) */);
1179  return error;
1180 }
1181 
1182 static UINT delegate_file_request_range(wClipboardDelegate* delegate,
1183  const wClipboardFileRangeRequest* request)
1184 {
1185  UINT error = 0;
1186  BYTE* data = NULL;
1187  UINT32 size = 0;
1188  UINT64 offset = 0;
1189  struct synthetic_file* file = NULL;
1190 
1191  if (!delegate || !delegate->clipboard || !request)
1192  return ERROR_BAD_ARGUMENTS;
1193 
1194  if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1195  return ERROR_INVALID_STATE;
1196 
1197  file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1198 
1199  if (!file)
1200  return ERROR_INDEX_ABSENT;
1201 
1202  offset = (((UINT64)request->nPositionHigh) << 32) | ((UINT64)request->nPositionLow);
1203  error = file_get_range(file, offset, request->cbRequested, &data, &size);
1204 
1205  if (error)
1206  error = delegate->ClipboardFileRangeFailure(delegate, request, error);
1207  else
1208  error = delegate->ClipboardFileRangeSuccess(delegate, request, data, size);
1209 
1210  if (error)
1211  WLog_WARN(TAG, "failed to report file range result: 0x%08X", error);
1212 
1213  free(data);
1214  return NO_ERROR;
1215 }
1216 
1217 static UINT dummy_file_size_success(wClipboardDelegate* delegate,
1218  const wClipboardFileSizeRequest* request, UINT64 fileSize)
1219 {
1220  return ERROR_NOT_SUPPORTED;
1221 }
1222 
1223 static UINT dummy_file_size_failure(wClipboardDelegate* delegate,
1224  const wClipboardFileSizeRequest* request, UINT errorCode)
1225 {
1226  return ERROR_NOT_SUPPORTED;
1227 }
1228 
1229 static UINT dummy_file_range_success(wClipboardDelegate* delegate,
1230  const wClipboardFileRangeRequest* request, const BYTE* data,
1231  UINT32 size)
1232 {
1233  return ERROR_NOT_SUPPORTED;
1234 }
1235 
1236 static UINT dummy_file_range_failure(wClipboardDelegate* delegate,
1237  const wClipboardFileRangeRequest* request, UINT errorCode)
1238 {
1239  return ERROR_NOT_SUPPORTED;
1240 }
1241 
1242 static void setup_delegate(wClipboardDelegate* delegate)
1243 {
1244  WINPR_ASSERT(delegate);
1245 
1246  delegate->ClientRequestFileSize = delegate_file_request_size;
1247  delegate->ClipboardFileSizeSuccess = dummy_file_size_success;
1248  delegate->ClipboardFileSizeFailure = dummy_file_size_failure;
1249  delegate->ClientRequestFileRange = delegate_file_request_range;
1250  delegate->ClipboardFileRangeSuccess = dummy_file_range_success;
1251  delegate->ClipboardFileRangeFailure = dummy_file_range_failure;
1252  delegate->IsFileNameComponentValid = ValidFileNameComponent;
1253 }
1254 
1255 BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard)
1256 {
1257  if (!clipboard)
1258  return FALSE;
1259 
1260  if (!register_file_formats_and_synthesizers(clipboard))
1261  return FALSE;
1262 
1263  setup_delegate(&clipboard->delegate);
1264  return TRUE;
1265 }
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57