Coverage Report

Created: 2026-10-28 06:52

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