Coverage Report

Created: 2025-07-01 06:46

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