FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
synthetic_file.c
1
20#include <winpr/config.h>
21#include <winpr/platform.h>
22
23WINPR_PRAGMA_DIAG_PUSH
24WINPR_PRAGMA_DIAG_IGNORED_RESERVED_ID_MACRO
25WINPR_PRAGMA_DIAG_IGNORED_UNUSED_MACRO
26
27#define _FILE_OFFSET_BITS 64 // NOLINT(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp)
28
29WINPR_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
51static const char* mime_uri_list = "text/uri-list";
52static const char* mime_FileGroupDescriptorW = "FileGroupDescriptorW";
53static const char* mime_gnome_copied_files = "x-special/gnome-copied-files";
54static const char* mime_mate_copied_files = "x-special/mate-copied-files";
55
56struct 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
72void free_synthetic_file(struct synthetic_file* file);
73
74static 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;
116fail:
117 free_synthetic_file(file);
118 return NULL;
119}
120
121static UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force);
122
123void 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 */
139static 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;
164error:
165 free(remote_name);
166 return NULL;
167}
168
169static 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
192static BOOL add_file_to_list(wClipboard* clipboard, const WCHAR* local_name,
193 const WCHAR* remote_name, wArrayList* files);
194
195static 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
237static 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
276static 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
308static 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
342static 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
359static 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
385static 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
415static 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
476static 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
513static 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;
534error:
535 free(descriptors);
536 return NULL;
537}
538
539static void* convert_any_uri_list_to_filedescriptors(wClipboard* clipboard,
540 WINPR_ATTR_UNUSED UINT32 formatId,
541 UINT32* pSize)
542{
543 FILEDESCRIPTORW* descriptors = NULL;
544
545 WINPR_ASSERT(clipboard);
546 WINPR_ASSERT(pSize);
547
548 descriptors = convert_local_file_list_to_filedescriptors(clipboard->localFiles);
549 *pSize = 0;
550 if (!descriptors)
551 return NULL;
552
553 *pSize = (UINT32)ArrayList_Count(clipboard->localFiles) * sizeof(FILEDESCRIPTORW);
554 clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
555 return descriptors;
556}
557
558static void* convert_uri_list_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
559 const void* data, UINT32* pSize)
560{
561 const UINT32 expected = ClipboardGetFormatId(clipboard, mime_uri_list);
562 if (formatId != expected)
563 return NULL;
564 if (!process_uri_list(clipboard, (const char*)data, *pSize))
565 return NULL;
566 return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
567}
568
569static BOOL process_files(wClipboard* clipboard, const char* data, UINT32 pSize, const char* prefix)
570{
571 WINPR_ASSERT(prefix);
572
573 const size_t prefix_len = strlen(prefix);
574
575 WINPR_ASSERT(clipboard);
576
577 ArrayList_Clear(clipboard->localFiles);
578
579 if (!data || (pSize < prefix_len))
580 return FALSE;
581 if (strncmp(data, prefix, prefix_len) != 0)
582 return FALSE;
583 data += prefix_len;
584 if (pSize < prefix_len)
585 return FALSE;
586 pSize -= WINPR_ASSERTING_INT_CAST(uint32_t, prefix_len);
587
588 BOOL rc = FALSE;
589 char* copy = strndup(data, pSize);
590 if (!copy)
591 goto fail;
592
593 char* endptr = NULL;
594 char* tok = strtok_s(copy, "\n", &endptr);
595 while (tok)
596 {
597 const size_t tok_len = strnlen(tok, pSize);
598 if (!process_uri(clipboard, tok, tok_len))
599 goto fail;
600 if (pSize < tok_len)
601 goto fail;
602 pSize -= WINPR_ASSERTING_INT_CAST(uint32_t, tok_len);
603 tok = strtok_s(NULL, "\n", &endptr);
604 }
605 rc = TRUE;
606
607fail:
608 free(copy);
609 return rc;
610}
611
612static BOOL process_gnome_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
613{
614 return process_files(clipboard, data, pSize, "copy\n");
615}
616
617static BOOL process_mate_copied_files(wClipboard* clipboard, const char* data, UINT32 pSize)
618{
619 return process_files(clipboard, data, pSize, "copy\n");
620}
621
622static void* convert_gnome_copied_files_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_gnome_copied_files(clipboard, (const char*)data, *pSize))
629 return NULL;
630 return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
631}
632
633static void* convert_mate_copied_files_to_filedescriptors(wClipboard* clipboard, UINT32 formatId,
634 const void* data, UINT32* pSize)
635{
636 const UINT32 expected = ClipboardGetFormatId(clipboard, mime_mate_copied_files);
637 if (formatId != expected)
638 return NULL;
639
640 if (!process_mate_copied_files(clipboard, (const char*)data, *pSize))
641 return NULL;
642
643 return convert_any_uri_list_to_filedescriptors(clipboard, formatId, pSize);
644}
645
646static size_t count_special_chars(const WCHAR* str)
647{
648 size_t count = 0;
649 const WCHAR* start = str;
650
651 WINPR_ASSERT(str);
652 while (*start)
653 {
654 const WCHAR sharp = '#';
655 const WCHAR questionmark = '?';
656 const WCHAR star = '*';
657 const WCHAR exclamationmark = '!';
658 const WCHAR percent = '%';
659
660 if ((*start == sharp) || (*start == questionmark) || (*start == star) ||
661 (*start == exclamationmark) || (*start == percent))
662 {
663 count++;
664 }
665 start++;
666 }
667 return count;
668}
669
670static const char* stop_at_special_chars(const char* str)
671{
672 const char* start = str;
673 WINPR_ASSERT(str);
674
675 while (*start)
676 {
677 if (*start == '#' || *start == '?' || *start == '*' || *start == '!' || *start == '%')
678 {
679 return start;
680 }
681 start++;
682 }
683 return NULL;
684}
685
686/* The universal converter from filedescriptors to different file lists */
687static void* convert_filedescriptors_to_file_list(wClipboard* clipboard, UINT32 formatId,
688 const void* data, UINT32* pSize,
689 const char* header, const char* lineprefix,
690 const char* lineending, BOOL skip_last_lineending)
691{
692 union
693 {
694 char c[2];
695 WCHAR w;
696 } backslash;
697 backslash.c[0] = '\\';
698 backslash.c[1] = '\0';
699
700 const FILEDESCRIPTORW* descriptors = NULL;
701 UINT32 nrDescriptors = 0;
702 size_t count = 0;
703 size_t alloc = 0;
704 size_t pos = 0;
705 size_t baseLength = 0;
706 char* dst = NULL;
707 size_t header_len = strlen(header);
708 size_t lineprefix_len = strlen(lineprefix);
709 size_t lineending_len = strlen(lineending);
710 size_t decoration_len = 0;
711
712 if (!clipboard || !data || !pSize)
713 return NULL;
714
715 if (*pSize < sizeof(UINT32))
716 return NULL;
717
718 if (clipboard->delegate.basePath)
719 baseLength = strnlen(clipboard->delegate.basePath, MAX_PATH);
720
721 if (baseLength < 1)
722 return NULL;
723
724 wStream sbuffer = { 0 };
725 wStream* s = Stream_StaticConstInit(&sbuffer, data, *pSize);
726 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
727 return NULL;
728
729 Stream_Read_UINT32(s, nrDescriptors);
730
731 count = (*pSize - 4) / sizeof(FILEDESCRIPTORW);
732
733 if ((count < 1) || (count != nrDescriptors))
734 return NULL;
735
736 descriptors = Stream_ConstPointer(s);
737
738 if (formatId != ClipboardGetFormatId(clipboard, mime_FileGroupDescriptorW))
739 return NULL;
740
741 /* Plus 1 for '/' between basepath and filename*/
742 decoration_len = lineprefix_len + lineending_len + baseLength + 1;
743 alloc = header_len;
744
745 /* Get total size of file/folder names under first level folder only */
746 for (size_t x = 0; x < count; x++)
747 {
748 const FILEDESCRIPTORW* dsc = &descriptors[x];
749
750 if (_wcschr(dsc->cFileName, backslash.w) == NULL)
751 {
752 alloc += ARRAYSIZE(dsc->cFileName) *
753 8; /* Overallocate, just take the biggest value the result path can have */
754 /* # (1 char) -> %23 (3 chars) , the first char is replaced inplace */
755 alloc += count_special_chars(dsc->cFileName) * 2;
756 alloc += decoration_len;
757 }
758 }
759
760 /* Append a prefix file:// and postfix \n for each file */
761 /* We need to keep last \n since snprintf is null terminated!! */
762 alloc++;
763 dst = calloc(alloc, sizeof(char));
764
765 if (!dst)
766 return NULL;
767
768 (void)_snprintf(&dst[0], alloc, "%s", header);
769
770 pos = header_len;
771
772 for (size_t x = 0; x < count; x++)
773 {
774 const FILEDESCRIPTORW* dsc = &descriptors[x];
775 BOOL fail = TRUE;
776 if (_wcschr(dsc->cFileName, backslash.w) != NULL)
777 {
778 continue;
779 }
780 int rc = -1;
781 char curName[520] = { 0 };
782 const char* stop_at = NULL;
783 const char* previous_at = NULL;
784
785 if (ConvertWCharNToUtf8(dsc->cFileName, ARRAYSIZE(dsc->cFileName), curName,
786 ARRAYSIZE(curName)) < 0)
787 goto loop_fail;
788
789 rc = _snprintf(&dst[pos], alloc - pos, "%s%s/", lineprefix, clipboard->delegate.basePath);
790
791 if (rc < 0)
792 goto loop_fail;
793
794 pos += (size_t)rc;
795
796 previous_at = curName;
797 while ((stop_at = stop_at_special_chars(previous_at)) != NULL)
798 {
799 const intptr_t diff = stop_at - previous_at;
800 if (diff < 0)
801 goto loop_fail;
802 char* tmp = strndup(previous_at, WINPR_ASSERTING_INT_CAST(size_t, diff));
803 if (!tmp)
804 goto loop_fail;
805
806 rc = _snprintf(&dst[pos], WINPR_ASSERTING_INT_CAST(size_t, diff + 1), "%s", tmp);
807 free(tmp);
808 if (rc < 0)
809 goto loop_fail;
810
811 pos += (size_t)rc;
812 rc = _snprintf(&dst[pos], 4, "%%%x", *stop_at);
813 if (rc < 0)
814 goto loop_fail;
815
816 pos += (size_t)rc;
817 previous_at = stop_at + 1;
818 }
819
820 rc = _snprintf(&dst[pos], alloc - pos, "%s%s", previous_at, lineending);
821
822 fail = FALSE;
823 loop_fail:
824 if ((rc < 0) || fail)
825 {
826 free(dst);
827 return NULL;
828 }
829
830 pos += (size_t)rc;
831 }
832
833 if (skip_last_lineending)
834 {
835 const size_t endlen = strlen(lineending);
836 if (alloc > endlen)
837 {
838 const size_t len = strnlen(dst, alloc);
839 if (len < endlen)
840 {
841 free(dst);
842 return NULL;
843 }
844
845 if (memcmp(&dst[len - endlen], lineending, endlen) == 0)
846 {
847 memset(&dst[len - endlen], 0, endlen);
848 alloc -= endlen;
849 }
850 }
851 }
852
853 alloc = strnlen(dst, alloc) + 1;
854 *pSize = (UINT32)alloc;
855 clipboard->fileListSequenceNumber = clipboard->sequenceNumber;
856 return dst;
857}
858
859/* Prepend header of kde dolphin format to file list
860 * See:
861 * GTK: https://docs.gtk.org/glib/struct.Uri.html
862 * uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
863 * uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
864 */
865static void* convert_filedescriptors_to_uri_list(wClipboard* clipboard, UINT32 formatId,
866 const void* data, UINT32* pSize)
867{
868 return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "", "file://",
869 "\r\n", FALSE);
870}
871
872/* Prepend header of common gnome format to file list*/
873static void* convert_filedescriptors_to_gnome_copied_files(wClipboard* clipboard, UINT32 formatId,
874 const void* data, UINT32* pSize)
875{
876 return convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize, "copy\n",
877 "file://", "\n", TRUE);
878}
879
880static void* convert_filedescriptors_to_mate_copied_files(wClipboard* clipboard, UINT32 formatId,
881 const void* data, UINT32* pSize)
882{
883
884 char* pDstData = convert_filedescriptors_to_file_list(clipboard, formatId, data, pSize,
885 "copy\n", "file://", "\n", TRUE);
886 if (!pDstData)
887 {
888 return pDstData;
889 }
890 /* Replace last \n with \0
891 see
892 mate-desktop/caja/libcaja-private/caja-clipboard.c:caja_clipboard_get_uri_list_from_selection_data
893 */
894
895 pDstData[*pSize - 1] = '\0';
896 *pSize = *pSize - 1;
897 return pDstData;
898}
899
900static void array_free_synthetic_file(void* the_file)
901{
902 struct synthetic_file* file = the_file;
903 free_synthetic_file(file);
904}
905
906static BOOL register_file_formats_and_synthesizers(wClipboard* clipboard)
907{
908 wObject* obj = NULL;
909
910 /*
911 1. Gnome Nautilus based file manager (Nautilus only with version >= 3.30 AND < 40):
912 TARGET: UTF8_STRING
913 format: x-special/nautilus-clipboard\copy\n\file://path\n\0
914 2. Kde Dolpin and Qt:
915 TARGET: text/uri-list
916 format: file:path\r\n\0
917 See:
918 GTK: https://docs.gtk.org/glib/struct.Uri.html
919 uri syntax: https://www.rfc-editor.org/rfc/rfc3986#section-3
920 uri-lists format: https://www.rfc-editor.org/rfc/rfc2483#section-5
921 3. Gnome and others (Unity/XFCE/Nautilus < 3.30/Nautilus >= 40):
922 TARGET: x-special/gnome-copied-files
923 format: copy\nfile://path\n\0
924 4. Mate Caja:
925 TARGET: x-special/mate-copied-files
926 format: copy\nfile://path\n
927
928 TODO: other file managers do not use previous targets and formats.
929 */
930
931 const UINT32 local_gnome_file_format_id =
932 ClipboardRegisterFormat(clipboard, mime_gnome_copied_files);
933 const UINT32 local_mate_file_format_id =
934 ClipboardRegisterFormat(clipboard, mime_mate_copied_files);
935 const UINT32 file_group_format_id =
936 ClipboardRegisterFormat(clipboard, mime_FileGroupDescriptorW);
937 const UINT32 local_file_format_id = ClipboardRegisterFormat(clipboard, mime_uri_list);
938
939 if (!file_group_format_id || !local_file_format_id || !local_gnome_file_format_id ||
940 !local_mate_file_format_id)
941 goto error;
942
943 clipboard->localFiles = ArrayList_New(FALSE);
944
945 if (!clipboard->localFiles)
946 goto error;
947
948 obj = ArrayList_Object(clipboard->localFiles);
949 obj->fnObjectFree = array_free_synthetic_file;
950
951 if (!ClipboardRegisterSynthesizer(clipboard, local_file_format_id, file_group_format_id,
952 convert_uri_list_to_filedescriptors))
953 goto error_free_local_files;
954
955 if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_file_format_id,
956 convert_filedescriptors_to_uri_list))
957 goto error_free_local_files;
958
959 if (!ClipboardRegisterSynthesizer(clipboard, local_gnome_file_format_id, file_group_format_id,
960 convert_gnome_copied_files_to_filedescriptors))
961 goto error_free_local_files;
962
963 if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_gnome_file_format_id,
964 convert_filedescriptors_to_gnome_copied_files))
965 goto error_free_local_files;
966
967 if (!ClipboardRegisterSynthesizer(clipboard, local_mate_file_format_id, file_group_format_id,
968 convert_mate_copied_files_to_filedescriptors))
969 goto error_free_local_files;
970
971 if (!ClipboardRegisterSynthesizer(clipboard, file_group_format_id, local_mate_file_format_id,
972 convert_filedescriptors_to_mate_copied_files))
973 goto error_free_local_files;
974
975 return TRUE;
976error_free_local_files:
977 ArrayList_Free(clipboard->localFiles);
978 clipboard->localFiles = NULL;
979error:
980 return FALSE;
981}
982
983static int32_t file_get_size(const struct synthetic_file* file, UINT64* size)
984{
985 UINT64 s = 0;
986
987 if (!file || !size)
988 return E_INVALIDARG;
989
990 s = file->nFileSizeHigh;
991 s <<= 32;
992 s |= file->nFileSizeLow;
993 *size = s;
994 return NO_ERROR;
995}
996
997static UINT delegate_file_request_size(wClipboardDelegate* delegate,
998 const wClipboardFileSizeRequest* request)
999{
1000 UINT64 size = 0;
1001
1002 if (!delegate || !delegate->clipboard || !request)
1003 return ERROR_BAD_ARGUMENTS;
1004
1005 if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1006 return ERROR_INVALID_STATE;
1007
1008 struct synthetic_file* file =
1009 ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1010
1011 if (!file)
1012 return ERROR_INDEX_ABSENT;
1013
1014 const int32_t s = file_get_size(file, &size);
1015 uint32_t error = 0;
1016 if (error)
1017 error = delegate->ClipboardFileSizeFailure(delegate, request, (UINT)s);
1018 else
1019 error = delegate->ClipboardFileSizeSuccess(delegate, request, size);
1020
1021 if (error)
1022 WLog_WARN(TAG, "failed to report file size result: 0x%08X", error);
1023
1024 return NO_ERROR;
1025}
1026
1027UINT synthetic_file_read_close(struct synthetic_file* file, BOOL force)
1028{
1029 if (!file || INVALID_HANDLE_VALUE == file->fd)
1030 return NO_ERROR;
1031
1032 /* Always force close the file. Clipboard might open hundreds of files
1033 * so avoid caching to prevent running out of available file descriptors */
1034 UINT64 size = 0;
1035 file_get_size(file, &size);
1036 if ((file->offset < 0) || ((UINT64)file->offset >= size) || force)
1037 {
1038 WLog_VRB(TAG, "close file %d", file->fd);
1039 if (!CloseHandle(file->fd))
1040 {
1041 WLog_WARN(TAG, "failed to close fd %d: %" PRIu32, file->fd, GetLastError());
1042 }
1043
1044 file->fd = INVALID_HANDLE_VALUE;
1045 }
1046
1047 return NO_ERROR;
1048}
1049
1050static UINT file_get_range(struct synthetic_file* file, UINT64 offset, UINT32 size,
1051 BYTE** actual_data, UINT32* actual_size)
1052{
1053 UINT error = NO_ERROR;
1054 DWORD dwLow = 0;
1055 DWORD dwHigh = 0;
1056
1057 WINPR_ASSERT(file);
1058 WINPR_ASSERT(actual_data);
1059 WINPR_ASSERT(actual_size);
1060
1061 if (INVALID_HANDLE_VALUE == file->fd)
1062 {
1063 BY_HANDLE_FILE_INFORMATION FileInfo = { 0 };
1064
1065 file->fd = CreateFileW(file->local_name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
1066 FILE_ATTRIBUTE_NORMAL, NULL);
1067 if (INVALID_HANDLE_VALUE == file->fd)
1068 {
1069 error = GetLastError();
1070 WLog_ERR(TAG, "failed to open file %s: 0x%08" PRIx32, file->local_name, error);
1071 return error;
1072 }
1073
1074 if (!GetFileInformationByHandle(file->fd, &FileInfo))
1075 {
1076 (void)CloseHandle(file->fd);
1077 file->fd = INVALID_HANDLE_VALUE;
1078 error = GetLastError();
1079 WLog_ERR(TAG, "Get file [%s] information fail: 0x%08" PRIx32, file->local_name, error);
1080 return error;
1081 }
1082
1083 file->offset = 0;
1084 file->nFileSizeHigh = FileInfo.nFileSizeHigh;
1085 file->nFileSizeLow = FileInfo.nFileSizeLow;
1086
1087 /*
1088 {
1089 UINT64 s = 0;
1090 file_get_size(file, &s);
1091 WLog_DBG(TAG, "open file %d -> %s", file->fd, file->local_name);
1092 WLog_DBG(TAG, "file %d size: %" PRIu64 " bytes", file->fd, s);
1093 } //*/
1094 }
1095
1096 do
1097 {
1098 /*
1099 * We should avoid seeking when possible as some filesystems (e.g.,
1100 * an FTP server mapped via FUSE) may not support seeking. We keep
1101 * an accurate account of the current file offset and do not call
1102 * lseek() if the client requests file content sequentially.
1103 */
1104 if (offset > INT64_MAX)
1105 {
1106 WLog_ERR(TAG, "offset [%" PRIu64 "] > INT64_MAX", offset);
1107 error = ERROR_SEEK;
1108 break;
1109 }
1110
1111 if (file->offset != (INT64)offset)
1112 {
1113 WLog_DBG(TAG, "file %d force seeking to %" PRIu64 ", current %" PRIu64, file->fd,
1114 offset, file->offset);
1115
1116 dwHigh = offset >> 32;
1117 dwLow = offset & 0xFFFFFFFF;
1118 if (INVALID_SET_FILE_POINTER == SetFilePointer(file->fd,
1119 WINPR_ASSERTING_INT_CAST(LONG, dwLow),
1120 (PLONG)&dwHigh, FILE_BEGIN))
1121 {
1122 error = GetLastError();
1123 break;
1124 }
1125 }
1126
1127 BYTE* buffer = malloc(size);
1128 if (!buffer)
1129 {
1130 error = ERROR_NOT_ENOUGH_MEMORY;
1131 break;
1132 }
1133 if (!ReadFile(file->fd, buffer, size, (LPDWORD)actual_size, NULL))
1134 {
1135 free(buffer);
1136 error = GetLastError();
1137 break;
1138 }
1139
1140 *actual_data = buffer;
1141 file->offset += *actual_size;
1142 WLog_VRB(TAG, "file %d actual read %" PRIu32 " bytes (offset %" PRIu64 ")", file->fd,
1143 *actual_size, file->offset);
1144 } while (0);
1145
1146 synthetic_file_read_close(file, TRUE /* (error != NO_ERROR) && (size > 0) */);
1147 return error;
1148}
1149
1150static UINT delegate_file_request_range(wClipboardDelegate* delegate,
1151 const wClipboardFileRangeRequest* request)
1152{
1153 UINT error = 0;
1154 BYTE* data = NULL;
1155 UINT32 size = 0;
1156 UINT64 offset = 0;
1157 struct synthetic_file* file = NULL;
1158
1159 if (!delegate || !delegate->clipboard || !request)
1160 return ERROR_BAD_ARGUMENTS;
1161
1162 if (delegate->clipboard->sequenceNumber != delegate->clipboard->fileListSequenceNumber)
1163 return ERROR_INVALID_STATE;
1164
1165 file = ArrayList_GetItem(delegate->clipboard->localFiles, request->listIndex);
1166
1167 if (!file)
1168 return ERROR_INDEX_ABSENT;
1169
1170 offset = (((UINT64)request->nPositionHigh) << 32) | ((UINT64)request->nPositionLow);
1171 error = file_get_range(file, offset, request->cbRequested, &data, &size);
1172
1173 if (error)
1174 error = delegate->ClipboardFileRangeFailure(delegate, request, error);
1175 else
1176 error = delegate->ClipboardFileRangeSuccess(delegate, request, data, size);
1177
1178 if (error)
1179 WLog_WARN(TAG, "failed to report file range result: 0x%08X", error);
1180
1181 free(data);
1182 return NO_ERROR;
1183}
1184
1185static UINT dummy_file_size_success(WINPR_ATTR_UNUSED wClipboardDelegate* delegate,
1186 WINPR_ATTR_UNUSED const wClipboardFileSizeRequest* request,
1187 WINPR_ATTR_UNUSED UINT64 fileSize)
1188{
1189 return ERROR_NOT_SUPPORTED;
1190}
1191
1192static UINT dummy_file_size_failure(WINPR_ATTR_UNUSED wClipboardDelegate* delegate,
1193 WINPR_ATTR_UNUSED const wClipboardFileSizeRequest* request,
1194 WINPR_ATTR_UNUSED UINT errorCode)
1195{
1196 return ERROR_NOT_SUPPORTED;
1197}
1198
1199static UINT dummy_file_range_success(WINPR_ATTR_UNUSED wClipboardDelegate* delegate,
1200 WINPR_ATTR_UNUSED const wClipboardFileRangeRequest* request,
1201 WINPR_ATTR_UNUSED const BYTE* data,
1202 WINPR_ATTR_UNUSED UINT32 size)
1203{
1204 return ERROR_NOT_SUPPORTED;
1205}
1206
1207static UINT dummy_file_range_failure(WINPR_ATTR_UNUSED wClipboardDelegate* delegate,
1208 WINPR_ATTR_UNUSED const wClipboardFileRangeRequest* request,
1209 WINPR_ATTR_UNUSED UINT errorCode)
1210{
1211 return ERROR_NOT_SUPPORTED;
1212}
1213
1214static void setup_delegate(wClipboardDelegate* delegate)
1215{
1216 WINPR_ASSERT(delegate);
1217
1218 delegate->ClientRequestFileSize = delegate_file_request_size;
1219 delegate->ClipboardFileSizeSuccess = dummy_file_size_success;
1220 delegate->ClipboardFileSizeFailure = dummy_file_size_failure;
1221 delegate->ClientRequestFileRange = delegate_file_request_range;
1222 delegate->ClipboardFileRangeSuccess = dummy_file_range_success;
1223 delegate->ClipboardFileRangeFailure = dummy_file_range_failure;
1224 delegate->IsFileNameComponentValid = ValidFileNameComponent;
1225}
1226
1227BOOL ClipboardInitSyntheticFileSubsystem(wClipboard* clipboard)
1228{
1229 if (!clipboard)
1230 return FALSE;
1231
1232 if (!register_file_formats_and_synthesizers(clipboard))
1233 return FALSE;
1234
1235 setup_delegate(&clipboard->delegate);
1236 return TRUE;
1237}
This struct contains function pointer to initialize/free objects.
Definition collections.h:57