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