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