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