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