FreeRDP
Loading...
Searching...
No Matches
client_cliprdr_file.c
1
24#include <freerdp/config.h>
25
26#include <stdlib.h>
27#include <errno.h>
28
29#ifdef WITH_FUSE
30#define FUSE_USE_VERSION 30
31#include <fuse_lowlevel.h>
32#endif
33
34#if defined(WITH_FUSE)
35#include <sys/mount.h>
36#include <sys/stat.h>
37#include <errno.h>
38#include <time.h>
39#endif
40
41#include <winpr/crt.h>
42#include <winpr/string.h>
43#include <winpr/assert.h>
44#include <winpr/image.h>
45#include <winpr/stream.h>
46#include <winpr/clipboard.h>
47#include <winpr/path.h>
48
49#include <freerdp/utils/signal.h>
50#include <freerdp/log.h>
51#include <freerdp/client/cliprdr.h>
52#include <freerdp/channels/channels.h>
53#include <freerdp/channels/cliprdr.h>
54
55#include <freerdp/client/client_cliprdr_file.h>
56
57#if defined(WITH_CLIPRDR_FILE_APPLE)
58#include <winpr/file.h>
59
60#include <freerdp/freerdp.h>
61#include <freerdp/settings.h>
62#include <freerdp/utils/cliprdr_utils.h>
63
64#include "client_cliprdr_file_apple.h"
65#endif
66
67#define NO_CLIP_DATA_ID (UINT64_C(1) << 32)
68#define WIN32_FILETIME_TO_UNIX_EPOCH INT64_C(11644473600)
69
70#ifdef WITH_DEBUG_CLIPRDR
71#define DEBUG_CLIPRDR(log, ...) WLog_Print(log, WLOG_DEBUG, __VA_ARGS__)
72#else
73#define DEBUG_CLIPRDR(log, ...) \
74 do \
75 { \
76 } while (0)
77#endif
78
79#if defined(WITH_FUSE)
80typedef enum eFuseLowlevelOperationType
81{
82 FUSE_LL_OPERATION_NONE,
83 FUSE_LL_OPERATION_LOOKUP,
84 FUSE_LL_OPERATION_GETATTR,
85 FUSE_LL_OPERATION_READ,
86} FuseLowlevelOperationType;
87
88typedef struct sCliprdrFuseFile CliprdrFuseFile;
89
90struct sCliprdrFuseFile
91{
92 CliprdrFuseFile* parent;
93 wArrayList* children;
94
95 size_t filename_len;
96 const char* filename;
97
98 size_t filename_with_root_len;
99 char* filename_with_root;
100 UINT32 list_idx;
101 fuse_ino_t ino;
102
103 BOOL is_directory;
104 BOOL is_readonly;
105
106 BOOL has_size;
107 UINT64 size;
108
109 BOOL has_last_write_time;
110 INT64 last_write_time_unix;
111
112 BOOL has_clip_data_id;
113 UINT32 clip_data_id;
114};
115
116typedef struct
117{
118 CliprdrFileContext* file_context;
119
120 CliprdrFuseFile* clip_data_dir;
121
122 BOOL has_clip_data_id;
123 UINT32 clip_data_id;
124} CliprdrFuseClipDataEntry;
125
126typedef struct
127{
128 CliprdrFileContext* file_context;
129
130 wArrayList* fuse_files;
131
132 BOOL all_files;
133 BOOL has_clip_data_id;
134 UINT32 clip_data_id;
135} FuseFileClearContext;
136
137typedef struct
138{
139 FuseLowlevelOperationType operation_type;
140 CliprdrFuseFile* fuse_file;
141 fuse_req_t fuse_req;
142 UINT32 stream_id;
143} CliprdrFuseRequest;
144#endif
145
146typedef struct
147{
148 char* name;
149 FILE* fp;
150 INT64 size;
151 CliprdrFileContext* context;
152} CliprdrLocalFile;
153
154typedef struct
155{
156 UINT32 lockId;
157 BOOL locked;
158 size_t count;
159 CliprdrLocalFile* files;
160 CliprdrFileContext* context;
161} CliprdrLocalStream;
162
163struct cliprdr_file_context
164{
165#if defined(WITH_FUSE)
166 /* FUSE related**/
167 HANDLE fuse_start_sync;
168 HANDLE fuse_stop_sync;
169 HANDLE fuse_thread;
170 struct fuse_session* fuse_sess;
171#if FUSE_USE_VERSION < 30
172 struct fuse_chan* ch;
173#endif
174
175 wHashTable* inode_table;
176 wHashTable* dir_table;
177 wHashTable* clip_data_table;
178 wHashTable* request_table;
179
180 CliprdrFuseClipDataEntry* clip_data_entry_without_id;
181 UINT32 current_clip_data_id;
182
183 fuse_ino_t next_ino;
184 UINT32 next_clip_data_id;
185 UINT32 next_stream_id;
186#endif
187
188#if defined(WITH_CLIPRDR_FILE_APPLE)
189 /* Server files on the pasteboard, accessed on the main thread only */
190 FILEDESCRIPTORW* apple_files;
191 UINT32 apple_files_count;
192 size_t* apple_top_level;
193 size_t apple_top_level_count;
194 char* apple_download_dir;
195
196 /* The pending FILECONTENTS request, answered on the channel thread */
197 CRITICAL_SECTION apple_lock;
198 HANDLE apple_contents_event;
199 UINT32 apple_stream_id;
200 BOOL apple_contents_success;
201 BYTE* apple_contents_data;
202 UINT32 apple_contents_size;
203#endif
204
205 /* File clipping */
206 BOOL file_formats_registered;
207 UINT32 file_capability_flags;
208
209 UINT32 local_lock_id;
210
211 wHashTable* local_streams;
212 wLog* log;
213 void* clipboard;
214 CliprdrClientContext* context;
215 char* path;
216 char* exposed_path;
217 BYTE server_data_hash[WINPR_SHA256_DIGEST_LENGTH];
218 BYTE client_data_hash[WINPR_SHA256_DIGEST_LENGTH];
219};
220
221#if defined(WITH_FUSE)
222static CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino);
223
224static void fuse_file_free(void* data)
225{
226 CliprdrFuseFile* fuse_file = data;
227
228 if (!fuse_file)
229 return;
230
231 ArrayList_Free(fuse_file->children);
232 free(fuse_file->filename_with_root);
233
234 free(fuse_file);
235}
236
237WINPR_ATTR_FORMAT_ARG(1, 2)
238WINPR_ATTR_MALLOC(fuse_file_free, 1)
239WINPR_ATTR_NODISCARD
240static CliprdrFuseFile* fuse_file_new(WINPR_FORMAT_ARG const char* fmt, ...)
241{
242 CliprdrFuseFile* file = calloc(1, sizeof(CliprdrFuseFile));
243 if (!file)
244 return nullptr;
245
246 file->children = ArrayList_New(FALSE);
247 if (!file->children)
248 goto fail;
249
250 WINPR_ASSERT(fmt);
251
252 {
253 va_list ap = WINPR_C_ARRAY_INIT;
254 va_start(ap, fmt);
255 const int rc =
256 winpr_vasprintf(&file->filename_with_root, &file->filename_with_root_len, fmt, ap);
257 va_end(ap);
258
259 if (rc < 0)
260 goto fail;
261 }
262
263 if (file->filename_with_root && (file->filename_with_root_len > 0))
264 {
265 file->filename_len = 0;
266 file->filename = strrchr(file->filename_with_root, '/') + 1;
267 if (file->filename)
268 file->filename_len = strnlen(file->filename, file->filename_with_root_len);
269 }
270 return file;
271fail:
272 fuse_file_free(file);
273 return nullptr;
274}
275
276static void clip_data_entry_free(void* data)
277{
278 CliprdrFuseClipDataEntry* clip_data_entry = data;
279
280 if (!clip_data_entry)
281 return;
282
283 if (clip_data_entry->has_clip_data_id)
284 {
285 CliprdrFileContext* file_context = clip_data_entry->file_context;
286 CLIPRDR_UNLOCK_CLIPBOARD_DATA unlock_clipboard_data = WINPR_C_ARRAY_INIT;
287
288 WINPR_ASSERT(file_context);
289
290 unlock_clipboard_data.common.msgType = CB_UNLOCK_CLIPDATA;
291 unlock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
292
293 const UINT rc = file_context->context->ClientUnlockClipboardData(file_context->context,
294 &unlock_clipboard_data);
295 if (rc != CHANNEL_RC_OK)
296 WLog_Print(file_context->log, WLOG_DEBUG,
297 "ClientUnlockClipboardData failed with %" PRIu32, rc);
298
299 clip_data_entry->has_clip_data_id = FALSE;
300
301 WLog_Print(file_context->log, WLOG_DEBUG, "Destroyed ClipDataEntry with id %u",
302 clip_data_entry->clip_data_id);
303 }
304
305 free(clip_data_entry);
306}
307
308static BOOL does_server_support_clipdata_locking(CliprdrFileContext* file_context)
309{
310 WINPR_ASSERT(file_context);
311
312 return (cliprdr_file_context_remote_get_flags(file_context) & CB_CAN_LOCK_CLIPDATA) != 0;
313}
314
315static UINT32 get_next_free_clip_data_id(CliprdrFileContext* file_context)
316{
317 UINT32 clip_data_id = 0;
318
319 WINPR_ASSERT(file_context);
320
321 HashTable_Lock(file_context->inode_table);
322 clip_data_id = file_context->next_clip_data_id;
323 while (clip_data_id == 0 ||
324 HashTable_GetItemValue(file_context->clip_data_table, (void*)(uintptr_t)clip_data_id))
325 ++clip_data_id;
326
327 file_context->next_clip_data_id = clip_data_id + 1;
328 HashTable_Unlock(file_context->inode_table);
329
330 return clip_data_id;
331}
332
333static CliprdrFuseClipDataEntry* clip_data_entry_new(CliprdrFileContext* file_context,
334 BOOL needs_clip_data_id)
335{
336 CliprdrFuseClipDataEntry* clip_data_entry = nullptr;
337 CLIPRDR_LOCK_CLIPBOARD_DATA lock_clipboard_data = WINPR_C_ARRAY_INIT;
338
339 WINPR_ASSERT(file_context);
340
341 clip_data_entry = calloc(1, sizeof(CliprdrFuseClipDataEntry));
342 if (!clip_data_entry)
343 return nullptr;
344
345 clip_data_entry->file_context = file_context;
346 clip_data_entry->clip_data_id = get_next_free_clip_data_id(file_context);
347
348 if (!needs_clip_data_id)
349 return clip_data_entry;
350
351 lock_clipboard_data.common.msgType = CB_LOCK_CLIPDATA;
352 lock_clipboard_data.clipDataId = clip_data_entry->clip_data_id;
353
354 if (file_context->context->ClientLockClipboardData(file_context->context, &lock_clipboard_data))
355 {
356 HashTable_Lock(file_context->inode_table);
357 clip_data_entry_free(clip_data_entry);
358 HashTable_Unlock(file_context->inode_table);
359 return nullptr;
360 }
361 clip_data_entry->has_clip_data_id = TRUE;
362
363 WLog_Print(file_context->log, WLOG_DEBUG, "Created ClipDataEntry with id %u",
364 clip_data_entry->clip_data_id);
365
366 return clip_data_entry;
367}
368
369static BOOL should_remove_fuse_file(CliprdrFuseFile* fuse_file, BOOL all_files,
370 BOOL has_clip_data_id, UINT32 clip_data_id)
371{
372 if (all_files)
373 return TRUE;
374
375 if (fuse_file->ino == FUSE_ROOT_ID)
376 return FALSE;
377 if (!fuse_file->has_clip_data_id && !has_clip_data_id)
378 return TRUE;
379 if (fuse_file->has_clip_data_id && has_clip_data_id &&
380 (fuse_file->clip_data_id == clip_data_id))
381 return TRUE;
382
383 return FALSE;
384}
385
386static BOOL maybe_clear_fuse_request(const void* key, void* value, void* arg)
387{
388 CliprdrFuseRequest* fuse_request = value;
389 FuseFileClearContext* clear_context = arg;
390 CliprdrFileContext* file_context = clear_context->file_context;
391 CliprdrFuseFile* fuse_file = fuse_request->fuse_file;
392
393 WINPR_ASSERT(file_context);
394
395 if (!should_remove_fuse_file(fuse_file, clear_context->all_files,
396 clear_context->has_clip_data_id, clear_context->clip_data_id))
397 return TRUE;
398
399 DEBUG_CLIPRDR(file_context->log, "Clearing FileContentsRequest for file \"%s\"",
400 fuse_file->filename_with_root);
401
402 fuse_reply_err(fuse_request->fuse_req, EIO);
403 HashTable_Remove(file_context->request_table, key);
404
405 return TRUE;
406}
407
408static BOOL maybe_steal_inode(const void* key, void* value, void* arg)
409{
410 CliprdrFuseFile* fuse_file = value;
411 FuseFileClearContext* clear_context = arg;
412 CliprdrFileContext* file_context = clear_context->file_context;
413
414 WINPR_ASSERT(file_context);
415
416 if (should_remove_fuse_file(fuse_file, clear_context->all_files,
417 clear_context->has_clip_data_id, clear_context->clip_data_id))
418 {
419 if (!ArrayList_Append(clear_context->fuse_files, fuse_file))
420 WLog_Print(file_context->log, WLOG_ERROR,
421 "Failed to append FUSE file to list for deletion");
422
423 if (!clear_context->all_files && fuse_file->is_directory)
424 {
425 HashTable_Remove(file_context->dir_table, fuse_file->filename_with_root);
426 }
427 HashTable_Remove(file_context->inode_table, key);
428 }
429
430 return TRUE;
431}
432
433static BOOL notify_delete_child(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
434{
435 CliprdrFuseFile* child = data;
436
437 WINPR_ASSERT(child);
438
439 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
440 CliprdrFuseFile* parent = va_arg(ap, CliprdrFuseFile*);
441
442 WINPR_ASSERT(file_context);
443 WINPR_ASSERT(parent);
444
445 WINPR_ASSERT(file_context->fuse_sess);
446 fuse_lowlevel_notify_delete(file_context->fuse_sess, parent->ino, child->ino, child->filename,
447 child->filename_len);
448
449 return TRUE;
450}
451
452static BOOL invalidate_inode(void* data, WINPR_ATTR_UNUSED size_t index, va_list ap)
453{
454 CliprdrFuseFile* fuse_file = data;
455
456 WINPR_ASSERT(fuse_file);
457
458 CliprdrFileContext* file_context = va_arg(ap, CliprdrFileContext*);
459 WINPR_ASSERT(file_context);
460 WINPR_ASSERT(file_context->fuse_sess);
461
462 const BOOL res =
463 ArrayList_ForEach(fuse_file->children, notify_delete_child, file_context, fuse_file);
464
465 DEBUG_CLIPRDR(file_context->log, "Invalidating inode %lu for file \"%s\"", fuse_file->ino,
466 fuse_file->filename);
467 fuse_lowlevel_notify_inval_inode(file_context->fuse_sess, fuse_file->ino, 0, 0);
468 WLog_Print(file_context->log, WLOG_DEBUG, "Inode %lu invalidated", fuse_file->ino);
469
470 return res;
471}
472
473WINPR_ATTR_NODISCARD
474static bool clear_selection(CliprdrFileContext* file_context, BOOL all_selections,
475 CliprdrFuseClipDataEntry* clip_data_entry)
476{
477 bool res = true;
478 FuseFileClearContext clear_context = WINPR_C_ARRAY_INIT;
479 CliprdrFuseFile* clip_data_dir = nullptr;
480
481 WINPR_ASSERT(file_context);
482
483 clear_context.file_context = file_context;
484 clear_context.fuse_files = ArrayList_New(FALSE);
485 WINPR_ASSERT(clear_context.fuse_files);
486
487 wObject* aobj = ArrayList_Object(clear_context.fuse_files);
488 WINPR_ASSERT(aobj);
489 aobj->fnObjectFree = fuse_file_free;
490
491 if (clip_data_entry)
492 {
493 clip_data_dir = clip_data_entry->clip_data_dir;
494 clip_data_entry->clip_data_dir = nullptr;
495
496 WINPR_ASSERT(clip_data_dir);
497
498 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
499 if (root_dir)
500 ArrayList_Remove(root_dir->children, clip_data_dir);
501
502 clear_context.has_clip_data_id = clip_data_dir->has_clip_data_id;
503 clear_context.clip_data_id = clip_data_dir->clip_data_id;
504 }
505 clear_context.all_files = all_selections;
506
507 if (clip_data_entry && clip_data_entry->has_clip_data_id)
508 WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection for clipDataId %u",
509 clip_data_entry->clip_data_id);
510 else
511 WLog_Print(file_context->log, WLOG_DEBUG, "Clearing selection%s",
512 all_selections ? "s" : "");
513
514 if (!HashTable_Foreach(file_context->request_table, maybe_clear_fuse_request, &clear_context))
515 res = false;
516 if (all_selections)
517 {
518 HashTable_Clear(file_context->dir_table);
519 }
520 if (!HashTable_Foreach(file_context->inode_table, maybe_steal_inode, &clear_context))
521 res = false;
522 HashTable_Unlock(file_context->inode_table);
523
524 if (file_context->fuse_sess)
525 {
526 /*
527 * fuse_lowlevel_notify_inval_inode() is a blocking operation. If we receive a
528 * FUSE request (e.g. read()), then FUSE would block in read(), since the
529 * mutex of the inode_table would still be locked, if we wouldn't unlock it
530 * here.
531 * So, to avoid a deadlock here, unlock the mutex and reply all incoming
532 * operations with -ENOENT until the invalidation process is complete.
533 */
534 if (!ArrayList_ForEach(clear_context.fuse_files, invalidate_inode, file_context))
535 res = false;
536 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
537 if (clip_data_dir && root_dir)
538 {
539 fuse_lowlevel_notify_delete(file_context->fuse_sess, root_dir->ino, clip_data_dir->ino,
540 clip_data_dir->filename, clip_data_dir->filename_len);
541 }
542 }
543 ArrayList_Free(clear_context.fuse_files);
544
545 HashTable_Lock(file_context->inode_table);
546 if (clip_data_entry && clip_data_entry->has_clip_data_id)
547 WLog_Print(file_context->log, WLOG_DEBUG, "Selection cleared for clipDataId %u",
548 clip_data_entry->clip_data_id);
549 else
550 WLog_Print(file_context->log, WLOG_DEBUG, "Selection%s cleared", all_selections ? "s" : "");
551 return res;
552}
553
554WINPR_ATTR_NODISCARD
555static bool clear_entry_selection(CliprdrFuseClipDataEntry* clip_data_entry)
556{
557 WINPR_ASSERT(clip_data_entry);
558
559 if (!clip_data_entry->clip_data_dir)
560 return true;
561
562 return clear_selection(clip_data_entry->file_context, FALSE, clip_data_entry);
563}
564
565WINPR_ATTR_NODISCARD
566static bool clear_no_cdi_entry(CliprdrFileContext* file_context)
567{
568 BOOL res = true;
569 WINPR_ASSERT(file_context);
570
571 WINPR_ASSERT(file_context->inode_table);
572 HashTable_Lock(file_context->inode_table);
573 if (file_context->clip_data_entry_without_id)
574 {
575 res = clear_entry_selection(file_context->clip_data_entry_without_id);
576
577 clip_data_entry_free(file_context->clip_data_entry_without_id);
578 file_context->clip_data_entry_without_id = nullptr;
579 }
580 HashTable_Unlock(file_context->inode_table);
581 return res;
582}
583
584WINPR_ATTR_NODISCARD
585static BOOL clear_clip_data_entries(WINPR_ATTR_UNUSED const void* key, void* value,
586 WINPR_ATTR_UNUSED void* arg)
587{
588 return clear_entry_selection(value);
589}
590
591WINPR_ATTR_NODISCARD
592static UINT clear_cdi_entries(CliprdrFileContext* file_context)
593{
594 UINT res = CHANNEL_RC_OK;
595 WINPR_ASSERT(file_context);
596
597 HashTable_Lock(file_context->inode_table);
598 if (!HashTable_Foreach(file_context->clip_data_table, clear_clip_data_entries, nullptr))
599 res = ERROR_INTERNAL_ERROR;
600
601 HashTable_Clear(file_context->clip_data_table);
602 HashTable_Unlock(file_context->inode_table);
603
604 return res;
605}
606
607static UINT prepare_clip_data_entry_with_id(CliprdrFileContext* file_context)
608{
609 CliprdrFuseClipDataEntry* clip_data_entry = nullptr;
610
611 WINPR_ASSERT(file_context);
612
613 clip_data_entry = clip_data_entry_new(file_context, TRUE);
614 if (!clip_data_entry)
615 {
616 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
617 return ERROR_INTERNAL_ERROR;
618 }
619
620 HashTable_Lock(file_context->inode_table);
621 if (!HashTable_Insert(file_context->clip_data_table,
622 (void*)(uintptr_t)clip_data_entry->clip_data_id, clip_data_entry))
623 {
624 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert clipDataEntry");
625 clip_data_entry_free(clip_data_entry);
626 HashTable_Unlock(file_context->inode_table);
627 return ERROR_INTERNAL_ERROR;
628 }
629 HashTable_Unlock(file_context->inode_table);
630
631 // HashTable_Insert owns clip_data_entry
632 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
633 file_context->current_clip_data_id = clip_data_entry->clip_data_id;
634
635 return CHANNEL_RC_OK;
636}
637
638static UINT prepare_clip_data_entry_without_id(CliprdrFileContext* file_context)
639{
640 WINPR_ASSERT(file_context);
641 WINPR_ASSERT(!file_context->clip_data_entry_without_id);
642
643 file_context->clip_data_entry_without_id = clip_data_entry_new(file_context, FALSE);
644 if (!file_context->clip_data_entry_without_id)
645 {
646 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create clipDataEntry");
647 return ERROR_INTERNAL_ERROR;
648 }
649
650 return CHANNEL_RC_OK;
651}
652#endif
653
654UINT cliprdr_file_context_notify_new_server_format_list(CliprdrFileContext* file_context)
655{
656 UINT rc = CHANNEL_RC_OK;
657 WINPR_ASSERT(file_context);
658 WINPR_ASSERT(file_context->context);
659
660#if defined(WITH_FUSE)
661 if (!clear_no_cdi_entry(file_context))
662 return ERROR_INTERNAL_ERROR;
663 /* TODO: assign timeouts to old locks instead */
664 rc = clear_cdi_entries(file_context);
665 if (rc != CHANNEL_RC_OK)
666 return rc;
667
668 if (does_server_support_clipdata_locking(file_context))
669 rc = prepare_clip_data_entry_with_id(file_context);
670 else
671 rc = prepare_clip_data_entry_without_id(file_context);
672#endif
673 return rc;
674}
675
676UINT cliprdr_file_context_notify_new_client_format_list(CliprdrFileContext* file_context)
677{
678 WINPR_ASSERT(file_context);
679 WINPR_ASSERT(file_context->context);
680
681#if defined(WITH_FUSE)
682 if (!clear_no_cdi_entry(file_context))
683 return ERROR_INTERNAL_ERROR;
684 /* TODO: assign timeouts to old locks instead */
685 return clear_cdi_entries(file_context);
686#endif
687
688 return CHANNEL_RC_OK;
689}
690
691static CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
692 const char* data, size_t size);
693static void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread);
694static BOOL local_stream_discard(const void* key, void* value, void* arg);
695
696WINPR_ATTR_FORMAT_ARG(6, 7)
697static void writelog(wLog* log, DWORD level, const char* fname, const char* fkt, size_t line,
698 WINPR_FORMAT_ARG const char* fmt, ...)
699{
700 if (!WLog_IsLevelActive(log, level))
701 return;
702
703 va_list ap = WINPR_C_ARRAY_INIT;
704 va_start(ap, fmt);
705 WLog_PrintTextMessageVA(log, level, line, fname, fkt, fmt, ap);
706 va_end(ap);
707}
708
709#if defined(WITH_FUSE)
710static CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context);
711static void cliprdr_file_fuse_lookup(fuse_req_t req, fuse_ino_t parent, const char* name);
712static void cliprdr_file_fuse_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
713static void cliprdr_file_fuse_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
714 struct fuse_file_info* fi);
715static void cliprdr_file_fuse_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
716 struct fuse_file_info* fi);
717static void cliprdr_file_fuse_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
718static void cliprdr_file_fuse_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi);
719
720static const struct fuse_lowlevel_ops cliprdr_file_fuse_oper = {
721 .lookup = cliprdr_file_fuse_lookup,
722 .getattr = cliprdr_file_fuse_getattr,
723 .readdir = cliprdr_file_fuse_readdir,
724 .open = cliprdr_file_fuse_open,
725 .read = cliprdr_file_fuse_read,
726 .opendir = cliprdr_file_fuse_opendir,
727};
728
729CliprdrFuseFile* get_fuse_file_by_ino(CliprdrFileContext* file_context, fuse_ino_t fuse_ino)
730{
731 WINPR_ASSERT(file_context);
732
733 return HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)fuse_ino);
734}
735
736static CliprdrFuseFile*
737get_fuse_file_by_name_from_parent(WINPR_ATTR_UNUSED CliprdrFileContext* file_context,
738 CliprdrFuseFile* parent, const char* name)
739{
740 WINPR_ASSERT(file_context);
741 WINPR_ASSERT(parent);
742
743 for (size_t i = 0; i < ArrayList_Count(parent->children); ++i)
744 {
745 CliprdrFuseFile* child = ArrayList_GetItem(parent->children, i);
746
747 WINPR_ASSERT(child);
748
749 if (strncmp(name, child->filename, child->filename_len + 1) == 0)
750 return child;
751 }
752
753 DEBUG_CLIPRDR(file_context->log, "Requested file \"%s\" in directory \"%s\" does not exist",
754 name, parent->filename);
755
756 return nullptr;
757}
758
759static CliprdrFuseRequest* cliprdr_fuse_request_new(CliprdrFileContext* file_context,
760 CliprdrFuseFile* fuse_file, fuse_req_t fuse_req,
761 FuseLowlevelOperationType operation_type)
762{
763 CliprdrFuseRequest* fuse_request = nullptr;
764 UINT32 stream_id = file_context->next_stream_id;
765
766 WINPR_ASSERT(file_context);
767 WINPR_ASSERT(fuse_file);
768
769 fuse_request = calloc(1, sizeof(CliprdrFuseRequest));
770 if (!fuse_request)
771 {
772 WLog_Print(file_context->log, WLOG_ERROR, "Failed to allocate FUSE request for file \"%s\"",
773 fuse_file->filename_with_root);
774 return nullptr;
775 }
776
777 fuse_request->fuse_file = fuse_file;
778 fuse_request->fuse_req = fuse_req;
779 fuse_request->operation_type = operation_type;
780
781 while (stream_id == 0 ||
782 HashTable_GetItemValue(file_context->request_table, (void*)(uintptr_t)stream_id))
783 ++stream_id;
784 fuse_request->stream_id = stream_id;
785
786 file_context->next_stream_id = stream_id + 1;
787
788 if (!HashTable_Insert(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id,
789 fuse_request))
790 {
791 WLog_Print(file_context->log, WLOG_ERROR, "Failed to track FUSE request for file \"%s\"",
792 fuse_file->filename_with_root);
793 free(fuse_request);
794 return nullptr;
795 }
796
797 return fuse_request;
798}
799
800static BOOL request_file_size_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
801 fuse_req_t fuse_req, FuseLowlevelOperationType operation_type)
802{
803 CliprdrFuseRequest* fuse_request = nullptr;
804 CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = WINPR_C_ARRAY_INIT;
805
806 WINPR_ASSERT(file_context);
807 WINPR_ASSERT(fuse_file);
808
809 fuse_request = cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, operation_type);
810 if (!fuse_request)
811 return FALSE;
812
813 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
814 file_contents_request.streamId = fuse_request->stream_id;
815 file_contents_request.listIndex = fuse_file->list_idx;
816 file_contents_request.dwFlags = FILECONTENTS_SIZE;
817 file_contents_request.cbRequested = 0x8;
818 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
819 file_contents_request.clipDataId = fuse_file->clip_data_id;
820
821 if (file_context->context->ClientFileContentsRequest(file_context->context,
822 &file_contents_request))
823 {
824 WLog_Print(file_context->log, WLOG_ERROR,
825 "Failed to send FileContentsRequest for file \"%s\"",
826 fuse_file->filename_with_root);
827 HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id);
828 return FALSE;
829 }
830 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
831 DEBUG_CLIPRDR(file_context->log, "Requested file size for file \"%s\" with stream id %u",
832 fuse_file->filename, fuse_request->stream_id);
833
834 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
835 return TRUE;
836}
837
838static void write_file_attributes(CliprdrFuseFile* fuse_file, struct stat* attr)
839{
840 memset(attr, 0, sizeof(struct stat));
841
842 if (!fuse_file)
843 return;
844
845 attr->st_ino = fuse_file->ino;
846 if (fuse_file->is_directory)
847 {
848 attr->st_mode = S_IFDIR | (fuse_file->is_readonly ? 0555 : 0755);
849 attr->st_nlink = 2;
850 }
851 else
852 {
853 attr->st_mode = S_IFREG | (fuse_file->is_readonly ? 0444 : 0644);
854 attr->st_nlink = 1;
855 attr->st_size = WINPR_ASSERTING_INT_CAST(off_t, fuse_file->size);
856 }
857 attr->st_uid = getuid();
858 attr->st_gid = getgid();
859 attr->st_atime = attr->st_mtime = attr->st_ctime =
860 (fuse_file->has_last_write_time ? fuse_file->last_write_time_unix : time(nullptr));
861}
862
863static void cliprdr_file_fuse_lookup(fuse_req_t fuse_req, fuse_ino_t parent_ino, const char* name)
864{
865 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
866 CliprdrFuseFile* parent = nullptr;
867 CliprdrFuseFile* fuse_file = nullptr;
868 struct fuse_entry_param entry = WINPR_C_ARRAY_INIT;
869
870 WINPR_ASSERT(file_context);
871
872 HashTable_Lock(file_context->inode_table);
873 if (!(parent = get_fuse_file_by_ino(file_context, parent_ino)) || !parent->is_directory)
874 {
875 HashTable_Unlock(file_context->inode_table);
876 fuse_reply_err(fuse_req, ENOENT);
877 return;
878 }
879 if (!(fuse_file = get_fuse_file_by_name_from_parent(file_context, parent, name)))
880 {
881 HashTable_Unlock(file_context->inode_table);
882 fuse_reply_err(fuse_req, ENOENT);
883 return;
884 }
885
886 DEBUG_CLIPRDR(file_context->log, "lookup() has been called for \"%s\"", name);
887 DEBUG_CLIPRDR(file_context->log, "Parent is \"%s\", child is \"%s\"",
888 parent->filename_with_root, fuse_file->filename_with_root);
889
890 if (!fuse_file->is_directory && !fuse_file->has_size)
891 {
892 BOOL result = 0;
893
894 result =
895 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_LOOKUP);
896 HashTable_Unlock(file_context->inode_table);
897
898 if (!result)
899 fuse_reply_err(fuse_req, EIO);
900
901 return;
902 }
903
904 entry.ino = fuse_file->ino;
905 write_file_attributes(fuse_file, &entry.attr);
906 entry.attr_timeout = 1.0;
907 entry.entry_timeout = 1.0;
908 HashTable_Unlock(file_context->inode_table);
909
910 fuse_reply_entry(fuse_req, &entry);
911}
912
913static void cliprdr_file_fuse_getattr(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
914 WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
915{
916 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
917 CliprdrFuseFile* fuse_file = nullptr;
918 struct stat attr = WINPR_C_ARRAY_INIT;
919
920 WINPR_ASSERT(file_context);
921
922 HashTable_Lock(file_context->inode_table);
923 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
924 {
925 HashTable_Unlock(file_context->inode_table);
926 fuse_reply_err(fuse_req, ENOENT);
927 return;
928 }
929
930 DEBUG_CLIPRDR(file_context->log, "getattr() has been called for file \"%s\"",
931 fuse_file->filename_with_root);
932
933 if (!fuse_file->is_directory && !fuse_file->has_size)
934 {
935 BOOL result = 0;
936
937 result =
938 request_file_size_async(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_GETATTR);
939 HashTable_Unlock(file_context->inode_table);
940
941 if (!result)
942 fuse_reply_err(fuse_req, EIO);
943
944 return;
945 }
946
947 write_file_attributes(fuse_file, &attr);
948 HashTable_Unlock(file_context->inode_table);
949
950 fuse_reply_attr(fuse_req, &attr, 1.0);
951}
952
953static void cliprdr_file_fuse_open(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
954 struct fuse_file_info* file_info)
955{
956 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
957 CliprdrFuseFile* fuse_file = nullptr;
958
959 WINPR_ASSERT(file_context);
960
961 HashTable_Lock(file_context->inode_table);
962 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
963 {
964 HashTable_Unlock(file_context->inode_table);
965 fuse_reply_err(fuse_req, ENOENT);
966 return;
967 }
968 if (fuse_file->is_directory)
969 {
970 HashTable_Unlock(file_context->inode_table);
971 fuse_reply_err(fuse_req, EISDIR);
972 return;
973 }
974 HashTable_Unlock(file_context->inode_table);
975
976 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
977 {
978 fuse_reply_err(fuse_req, EACCES);
979 return;
980 }
981
982 /* Important for KDE to get file correctly */
983 file_info->direct_io = 1;
984
985 fuse_reply_open(fuse_req, file_info);
986}
987
988static BOOL request_file_range_async(CliprdrFileContext* file_context, CliprdrFuseFile* fuse_file,
989 fuse_req_t fuse_req, off_t offset, size_t requested_size)
990{
991 CLIPRDR_FILE_CONTENTS_REQUEST file_contents_request = WINPR_C_ARRAY_INIT;
992
993 WINPR_ASSERT(file_context);
994 WINPR_ASSERT(fuse_file);
995
996 if (requested_size > UINT32_MAX)
997 return FALSE;
998
999 CliprdrFuseRequest* fuse_request =
1000 cliprdr_fuse_request_new(file_context, fuse_file, fuse_req, FUSE_LL_OPERATION_READ);
1001 if (!fuse_request)
1002 return FALSE;
1003
1004 file_contents_request.common.msgType = CB_FILECONTENTS_REQUEST;
1005 file_contents_request.streamId = fuse_request->stream_id;
1006 file_contents_request.listIndex = fuse_file->list_idx;
1007 file_contents_request.dwFlags = FILECONTENTS_RANGE;
1008 file_contents_request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF);
1009 file_contents_request.nPositionHigh = (UINT32)((offset >> 32) & 0xFFFFFFFF);
1010 file_contents_request.cbRequested = (UINT32)requested_size;
1011 file_contents_request.haveClipDataId = fuse_file->has_clip_data_id;
1012 file_contents_request.clipDataId = fuse_file->clip_data_id;
1013
1014 if (file_context->context->ClientFileContentsRequest(file_context->context,
1015 &file_contents_request))
1016 {
1017 WLog_Print(file_context->log, WLOG_ERROR,
1018 "Failed to send FileContentsRequest for file \"%s\"",
1019 fuse_file->filename_with_root);
1020 HashTable_Remove(file_context->request_table, (void*)(uintptr_t)fuse_request->stream_id);
1021 return FALSE;
1022 }
1023
1024 // file_context->request_table owns fuse_request
1025 // NOLINTBEGIN(clang-analyzer-unix.Malloc)
1026 DEBUG_CLIPRDR(
1027 file_context->log,
1028 "Requested file range (%zu Bytes at offset %ld) for file \"%s\" with stream id %u",
1029 requested_size, offset, fuse_file->filename, fuse_request->stream_id);
1030
1031 return TRUE;
1032 // NOLINTEND(clang-analyzer-unix.Malloc)
1033}
1034
1035static void cliprdr_file_fuse_read(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t size,
1036 off_t offset, WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
1037{
1038 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1039 CliprdrFuseFile* fuse_file = nullptr;
1040 BOOL result = 0;
1041
1042 WINPR_ASSERT(file_context);
1043
1044 HashTable_Lock(file_context->inode_table);
1045 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1046 {
1047 HashTable_Unlock(file_context->inode_table);
1048 fuse_reply_err(fuse_req, ENOENT);
1049 return;
1050 }
1051 if (fuse_file->is_directory)
1052 {
1053 HashTable_Unlock(file_context->inode_table);
1054 fuse_reply_err(fuse_req, EISDIR);
1055 return;
1056 }
1057 if (!fuse_file->has_size || (offset < 0) || ((size_t)offset > fuse_file->size))
1058 {
1059 HashTable_Unlock(file_context->inode_table);
1060 fuse_reply_err(fuse_req, EINVAL);
1061 return;
1062 }
1063
1064 size = MIN(size, 8ULL * 1024ULL * 1024ULL);
1065
1066 result = request_file_range_async(file_context, fuse_file, fuse_req, offset, size);
1067 HashTable_Unlock(file_context->inode_table);
1068
1069 if (!result)
1070 fuse_reply_err(fuse_req, EIO);
1071}
1072
1073static void cliprdr_file_fuse_opendir(fuse_req_t fuse_req, fuse_ino_t fuse_ino,
1074 struct fuse_file_info* file_info)
1075{
1076 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1077 CliprdrFuseFile* fuse_file = nullptr;
1078
1079 WINPR_ASSERT(file_context);
1080
1081 HashTable_Lock(file_context->inode_table);
1082 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1083 {
1084 HashTable_Unlock(file_context->inode_table);
1085 fuse_reply_err(fuse_req, ENOENT);
1086 return;
1087 }
1088 if (!fuse_file->is_directory)
1089 {
1090 HashTable_Unlock(file_context->inode_table);
1091 fuse_reply_err(fuse_req, ENOTDIR);
1092 return;
1093 }
1094 HashTable_Unlock(file_context->inode_table);
1095
1096 if ((file_info->flags & O_ACCMODE) != O_RDONLY)
1097 {
1098 fuse_reply_err(fuse_req, EACCES);
1099 return;
1100 }
1101
1102 fuse_reply_open(fuse_req, file_info);
1103}
1104
1105static void cliprdr_file_fuse_readdir(fuse_req_t fuse_req, fuse_ino_t fuse_ino, size_t max_size,
1106 off_t offset,
1107 WINPR_ATTR_UNUSED struct fuse_file_info* file_info)
1108{
1109 CliprdrFileContext* file_context = fuse_req_userdata(fuse_req);
1110 CliprdrFuseFile* fuse_file = nullptr;
1111 CliprdrFuseFile* child = nullptr;
1112 struct stat attr = WINPR_C_ARRAY_INIT;
1113 size_t written_size = 0;
1114 size_t entry_size = 0;
1115 char* filename = nullptr;
1116 char* buf = nullptr;
1117
1118 WINPR_ASSERT(file_context);
1119
1120 HashTable_Lock(file_context->inode_table);
1121 if (!(fuse_file = get_fuse_file_by_ino(file_context, fuse_ino)))
1122 {
1123 HashTable_Unlock(file_context->inode_table);
1124 fuse_reply_err(fuse_req, ENOENT);
1125 return;
1126 }
1127 if (!fuse_file->is_directory)
1128 {
1129 HashTable_Unlock(file_context->inode_table);
1130 fuse_reply_err(fuse_req, ENOTDIR);
1131 return;
1132 }
1133
1134 DEBUG_CLIPRDR(file_context->log, "Reading directory \"%s\" at offset %ld",
1135 fuse_file->filename_with_root, offset);
1136
1137 if ((offset < 0) || ((size_t)offset >= ArrayList_Count(fuse_file->children)))
1138 {
1139 HashTable_Unlock(file_context->inode_table);
1140 fuse_reply_buf(fuse_req, nullptr, 0);
1141 return;
1142 }
1143
1144 buf = calloc(max_size, sizeof(char));
1145 if (!buf)
1146 {
1147 HashTable_Unlock(file_context->inode_table);
1148 fuse_reply_err(fuse_req, ENOMEM);
1149 return;
1150 }
1151 written_size = 0;
1152
1153 for (off_t i = offset; i < 2; ++i)
1154 {
1155 if (i == 0)
1156 {
1157 write_file_attributes(fuse_file, &attr);
1158 filename = ".";
1159 }
1160 else if (i == 1)
1161 {
1162 write_file_attributes(fuse_file->parent, &attr);
1163 attr.st_ino = fuse_file->parent ? attr.st_ino : FUSE_ROOT_ID;
1164 attr.st_mode = fuse_file->parent ? attr.st_mode : 0555;
1165 filename = "..";
1166 }
1167 else
1168 {
1169 WINPR_ASSERT(FALSE);
1170 }
1171
1176 entry_size = fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1177 filename, &attr, i + 1);
1178 if (entry_size > max_size - written_size)
1179 break;
1180
1181 written_size += entry_size;
1182 }
1183
1184 for (size_t j = 0, i = 2; j < ArrayList_Count(fuse_file->children); ++j, ++i)
1185 {
1186 if (i < (size_t)offset)
1187 continue;
1188
1189 child = ArrayList_GetItem(fuse_file->children, j);
1190
1191 write_file_attributes(child, &attr);
1192 entry_size =
1193 fuse_add_direntry(fuse_req, buf + written_size, max_size - written_size,
1194 child->filename, &attr, WINPR_ASSERTING_INT_CAST(off_t, i + 1));
1195 if (entry_size > max_size - written_size)
1196 break;
1197
1198 written_size += entry_size;
1199 }
1200 HashTable_Unlock(file_context->inode_table);
1201
1202 fuse_reply_buf(fuse_req, buf, written_size);
1203 free(buf);
1204}
1205
1206static void fuse_abort(int sig, const char* signame, void* context)
1207{
1208 CliprdrFileContext* file = (CliprdrFileContext*)context;
1209
1210 if (file)
1211 {
1212 WLog_Print(file->log, WLOG_INFO, "signal %s [%d] aborting session", signame, sig);
1213 cliprdr_file_session_terminate(file, FALSE);
1214 }
1215}
1216
1217static DWORD WINAPI cliprdr_file_fuse_thread(LPVOID arg)
1218{
1219 CliprdrFileContext* file = (CliprdrFileContext*)arg;
1220
1221 WINPR_ASSERT(file);
1222
1223 DEBUG_CLIPRDR(file->log, "Starting fuse with mountpoint '%s'", file->path);
1224
1225 struct fuse_args args = FUSE_ARGS_INIT(0, nullptr);
1226 fuse_opt_add_arg(&args, file->path);
1227 file->fuse_sess = fuse_session_new(&args, &cliprdr_file_fuse_oper,
1228 sizeof(cliprdr_file_fuse_oper), (void*)file);
1229 (void)SetEvent(file->fuse_start_sync);
1230
1231 if (file->fuse_sess != nullptr)
1232 {
1233 if (freerdp_add_signal_cleanup_handler(file, fuse_abort))
1234 {
1235 if (0 == fuse_session_mount(file->fuse_sess, file->path))
1236 {
1237 fuse_session_loop(file->fuse_sess);
1238 fuse_session_unmount(file->fuse_sess);
1239 }
1240 freerdp_del_signal_cleanup_handler(file, fuse_abort);
1241 }
1242
1243 WLog_Print(file->log, WLOG_DEBUG, "Waiting for FUSE stop sync");
1244 if (WaitForSingleObject(file->fuse_stop_sync, INFINITE) == WAIT_FAILED)
1245 WLog_Print(file->log, WLOG_ERROR, "Failed to wait for stop sync");
1246 fuse_session_destroy(file->fuse_sess);
1247 }
1248 fuse_opt_free_args(&args);
1249
1250 DEBUG_CLIPRDR(file->log, "Quitting fuse with mountpoint '%s'", file->path);
1251
1252 ExitThread(0);
1253 return 0;
1254}
1255
1256static UINT cliprdr_file_context_server_file_contents_response(
1257 CliprdrClientContext* cliprdr_context,
1258 const CLIPRDR_FILE_CONTENTS_RESPONSE* file_contents_response)
1259{
1260 CliprdrFileContext* file_context = nullptr;
1261 CliprdrFuseRequest* fuse_request = nullptr;
1262 struct fuse_entry_param entry = WINPR_C_ARRAY_INIT;
1263
1264 WINPR_ASSERT(cliprdr_context);
1265 WINPR_ASSERT(file_contents_response);
1266
1267 file_context = cliprdr_context->custom;
1268 WINPR_ASSERT(file_context);
1269
1270 HashTable_Lock(file_context->inode_table);
1271 fuse_request = HashTable_GetItemValue(file_context->request_table,
1272 (void*)(uintptr_t)file_contents_response->streamId);
1273 if (!fuse_request)
1274 {
1275 HashTable_Unlock(file_context->inode_table);
1276 return CHANNEL_RC_OK;
1277 }
1278
1279 if (!(file_contents_response->common.msgFlags & CB_RESPONSE_OK))
1280 {
1281 WLog_Print(file_context->log, WLOG_WARN,
1282 "FileContentsRequests for file \"%s\" was unsuccessful",
1283 fuse_request->fuse_file->filename);
1284
1285 fuse_reply_err(fuse_request->fuse_req, EIO);
1286 HashTable_Remove(file_context->request_table,
1287 (void*)(uintptr_t)file_contents_response->streamId);
1288 HashTable_Unlock(file_context->inode_table);
1289 return CHANNEL_RC_OK;
1290 }
1291
1292 if ((fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1293 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR) &&
1294 file_contents_response->cbRequested != sizeof(UINT64))
1295 {
1296 WLog_Print(file_context->log, WLOG_WARN,
1297 "Received invalid file size for file \"%s\" from the client",
1298 fuse_request->fuse_file->filename);
1299 fuse_reply_err(fuse_request->fuse_req, EIO);
1300 HashTable_Remove(file_context->request_table,
1301 (void*)(uintptr_t)file_contents_response->streamId);
1302 HashTable_Unlock(file_context->inode_table);
1303 return CHANNEL_RC_OK;
1304 }
1305
1306 if (fuse_request->operation_type == FUSE_LL_OPERATION_LOOKUP ||
1307 fuse_request->operation_type == FUSE_LL_OPERATION_GETATTR)
1308 {
1309 DEBUG_CLIPRDR(file_context->log, "Received file size for file \"%s\" with stream id %u",
1310 fuse_request->fuse_file->filename, file_contents_response->streamId);
1311
1312 fuse_request->fuse_file->size =
1313 *(WINPR_PACKED_ALIGN_CAST(const UINT64*, file_contents_response->requestedData));
1314 fuse_request->fuse_file->has_size = TRUE;
1315
1316 entry.ino = fuse_request->fuse_file->ino;
1317 write_file_attributes(fuse_request->fuse_file, &entry.attr);
1318 entry.attr_timeout = 1.0;
1319 entry.entry_timeout = 1.0;
1320 }
1321 else if (fuse_request->operation_type == FUSE_LL_OPERATION_READ)
1322 {
1323 DEBUG_CLIPRDR(file_context->log, "Received file range for file \"%s\" with stream id %u",
1324 fuse_request->fuse_file->filename, file_contents_response->streamId);
1325 }
1326 HashTable_Unlock(file_context->inode_table);
1327
1328 switch (fuse_request->operation_type)
1329 {
1330 case FUSE_LL_OPERATION_NONE:
1331 break;
1332 case FUSE_LL_OPERATION_LOOKUP:
1333 fuse_reply_entry(fuse_request->fuse_req, &entry);
1334 break;
1335 case FUSE_LL_OPERATION_GETATTR:
1336 fuse_reply_attr(fuse_request->fuse_req, &entry.attr, entry.attr_timeout);
1337 break;
1338 case FUSE_LL_OPERATION_READ:
1339 fuse_reply_buf(fuse_request->fuse_req,
1340 (const char*)file_contents_response->requestedData,
1341 file_contents_response->cbRequested);
1342 break;
1343 default:
1344 break;
1345 }
1346
1347 HashTable_Remove(file_context->request_table,
1348 (void*)(uintptr_t)file_contents_response->streamId);
1349
1350 return CHANNEL_RC_OK;
1351}
1352#endif
1353
1354#if defined(WITH_CLIPRDR_FILE_APPLE)
1355static char* apple_descriptor_name(const FILEDESCRIPTORW* descriptor)
1356{
1357 WINPR_ASSERT(descriptor);
1358 return ConvertWCharNToUtf8Alloc(descriptor->cFileName, ARRAYSIZE(descriptor->cFileName),
1359 nullptr);
1360}
1361
1362/* Map a server relative path (dir\file) to a local relative path (dir/file) */
1363static char* apple_local_relative_path(const char* name)
1364{
1365 WINPR_ASSERT(name);
1366
1367 const size_t len = strlen(name);
1368 if (len == 0)
1369 return nullptr;
1370
1371 char* path = _strdup(name);
1372 if (!path)
1373 return nullptr;
1374
1375 size_t start = 0;
1376 for (size_t x = 0; x <= len; x++)
1377 {
1378 if ((x < len) && (name[x] == '/'))
1379 goto fail;
1380 if ((x < len) && (name[x] != '\\'))
1381 continue;
1382
1383 const char* component = &name[start];
1384 const size_t clen = x - start;
1385 if ((clen == 0) || ((clen == 1) && (component[0] == '.')) ||
1386 ((clen == 2) && (strncmp(component, "..", 2) == 0)))
1387 goto fail;
1388 if (x < len)
1389 path[x] = '/';
1390 start = x + 1;
1391 }
1392 return path;
1393
1394fail:
1395 free(path);
1396 return nullptr;
1397}
1398
1399static void apple_clear_server_files(CliprdrFileContext* file)
1400{
1401 WINPR_ASSERT(file);
1402
1403 free(file->apple_files);
1404 file->apple_files = nullptr;
1405 file->apple_files_count = 0;
1406 free(file->apple_top_level);
1407 file->apple_top_level = nullptr;
1408 file->apple_top_level_count = 0;
1409 free(file->apple_download_dir);
1410 file->apple_download_dir = nullptr;
1411}
1412
1413static BOOL apple_request_file_contents(CliprdrFileContext* file, UINT32 listIndex, UINT32 flags,
1414 UINT64 offset, UINT32 size, BYTE** pdata, UINT32* plen)
1415{
1416 WINPR_ASSERT(file);
1417 WINPR_ASSERT(pdata);
1418 WINPR_ASSERT(plen);
1419
1420 *pdata = nullptr;
1421 *plen = 0;
1422
1423 CliprdrClientContext* cliprdr = file->context;
1424 if (!cliprdr || !cliprdr->ClientFileContentsRequest)
1425 return FALSE;
1426
1427 CLIPRDR_FILE_CONTENTS_REQUEST request = WINPR_C_ARRAY_INIT;
1428 request.common.msgType = CB_FILECONTENTS_REQUEST;
1429 request.listIndex = listIndex;
1430 request.dwFlags = flags;
1431 request.nPositionLow = (UINT32)(offset & 0xFFFFFFFF);
1432 request.nPositionHigh = (UINT32)(offset >> 32);
1433 request.cbRequested = size;
1434
1435 EnterCriticalSection(&file->apple_lock);
1436 request.streamId = ++file->apple_stream_id;
1437 file->apple_contents_success = FALSE;
1438 free(file->apple_contents_data);
1439 file->apple_contents_data = nullptr;
1440 file->apple_contents_size = 0;
1441 (void)ResetEvent(file->apple_contents_event);
1442 LeaveCriticalSection(&file->apple_lock);
1443
1444 if (cliprdr->ClientFileContentsRequest(cliprdr, &request) != CHANNEL_RC_OK)
1445 return FALSE;
1446
1447 rdpContext* context = cliprdr->rdpcontext;
1448 WINPR_ASSERT(context);
1449
1450 HANDLE hdl[2] = { freerdp_abort_event(context), file->apple_contents_event };
1451 const UINT32 timeout = freerdp_settings_get_uint32(context->settings, FreeRDP_TcpAckTimeout);
1452 if (WaitForMultipleObjects(ARRAYSIZE(hdl), hdl, FALSE, timeout) != WAIT_OBJECT_0 + 1)
1453 {
1454 WLog_Print(file->log, WLOG_ERROR, "no file contents reply for stream %" PRIu32,
1455 request.streamId);
1456 return FALSE;
1457 }
1458
1459 EnterCriticalSection(&file->apple_lock);
1460 const BOOL rc = file->apple_contents_success;
1461 if (rc)
1462 {
1463 *pdata = file->apple_contents_data;
1464 *plen = file->apple_contents_size;
1465 file->apple_contents_data = nullptr;
1466 file->apple_contents_size = 0;
1467 }
1468 LeaveCriticalSection(&file->apple_lock);
1469 return rc;
1470}
1471
1472static UINT
1473cliprdr_file_context_apple_contents_response(CliprdrClientContext* cliprdr_context,
1474 const CLIPRDR_FILE_CONTENTS_RESPONSE* response)
1475{
1476 WINPR_ASSERT(cliprdr_context);
1477 WINPR_ASSERT(response);
1478
1479 CliprdrFileContext* file = cliprdr_context->custom;
1480 WINPR_ASSERT(file);
1481
1482 EnterCriticalSection(&file->apple_lock);
1483 if (response->streamId != file->apple_stream_id)
1484 {
1485 /* A late reply to a request that already timed out */
1486 WLog_Print(file->log, WLOG_WARN,
1487 "file contents response for stale stream %" PRIu32 ", ignoring",
1488 response->streamId);
1489 LeaveCriticalSection(&file->apple_lock);
1490 return CHANNEL_RC_OK;
1491 }
1492
1493 BOOL success = (response->common.msgFlags & CB_RESPONSE_OK) &&
1494 !(response->common.msgFlags & CB_RESPONSE_FAIL);
1495 if (success && response->requestedData && (response->cbRequested > 0))
1496 {
1497 file->apple_contents_data = malloc(response->cbRequested);
1498 if (file->apple_contents_data)
1499 {
1500 memcpy(file->apple_contents_data, response->requestedData, response->cbRequested);
1501 file->apple_contents_size = response->cbRequested;
1502 }
1503 else
1504 success = FALSE;
1505 }
1506 file->apple_contents_success = success;
1507 (void)SetEvent(file->apple_contents_event);
1508 LeaveCriticalSection(&file->apple_lock);
1509 return CHANNEL_RC_OK;
1510}
1511
1512static BOOL apple_download_file(CliprdrFileContext* file, UINT32 listIndex,
1513 const FILEDESCRIPTORW* descriptor, const char* path)
1514{
1515 WINPR_ASSERT(file);
1516 WINPR_ASSERT(descriptor);
1517 WINPR_ASSERT(path);
1518
1519 UINT64 size = 0;
1520 if (descriptor->dwFlags & FD_FILESIZE)
1521 size = ((UINT64)descriptor->nFileSizeHigh << 32) | descriptor->nFileSizeLow;
1522 else
1523 {
1524 BYTE* data = nullptr;
1525 UINT32 len = 0;
1526 const BOOL rc = apple_request_file_contents(file, listIndex, FILECONTENTS_SIZE, 0,
1527 sizeof(UINT64), &data, &len);
1528 if (rc && (len >= sizeof(UINT64)))
1529 memcpy(&size, data, sizeof(size));
1530 free(data);
1531 if (!rc || (len < sizeof(UINT64)))
1532 return FALSE;
1533 }
1534
1535 FILE* fp = winpr_fopen(path, "wb");
1536 if (!fp)
1537 return FALSE;
1538
1539 BOOL rc = TRUE;
1540 const UINT32 chunk = 1024 * 1024;
1541 for (UINT64 offset = 0; offset < size;)
1542 {
1543 const UINT32 requested = (size - offset < chunk) ? (UINT32)(size - offset) : chunk;
1544 BYTE* data = nullptr;
1545 UINT32 len = 0;
1546 if (!apple_request_file_contents(file, listIndex, FILECONTENTS_RANGE, offset, requested,
1547 &data, &len) ||
1548 (len == 0) || (fwrite(data, 1, len, fp) != len))
1549 {
1550 free(data);
1551 rc = FALSE;
1552 break;
1553 }
1554 free(data);
1555 offset += len;
1556 }
1557
1558 if (fclose(fp) != 0)
1559 rc = FALSE;
1560 if (!rc)
1561 (void)winpr_DeleteFile(path);
1562 return rc;
1563}
1564
1565/* Download the file, or the directory with everything below it */
1566static BOOL apple_download_entry(CliprdrFileContext* file, UINT32 listIndex, const char* name)
1567{
1568 WINPR_ASSERT(file);
1569 WINPR_ASSERT(name);
1570
1571 char* relative = apple_local_relative_path(name);
1572 if (!relative)
1573 {
1574 WLog_Print(file->log, WLOG_WARN, "skipping invalid server file name %s", name);
1575 return TRUE;
1576 }
1577
1578 BOOL rc = FALSE;
1579 const FILEDESCRIPTORW* descriptor = &file->apple_files[listIndex];
1580 char* path = nullptr;
1581 size_t pathlen = 0;
1582 if (winpr_asprintf(&path, &pathlen, "%s/%s", file->apple_download_dir, relative) < 0)
1583 goto fail;
1584
1585 if ((descriptor->dwFlags & FD_ATTRIBUTES) &&
1586 (descriptor->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1587 rc = winpr_PathMakePath(path, nullptr);
1588 else
1589 {
1590 char* slash = strrchr(path, '/');
1591 WINPR_ASSERT(slash);
1592 *slash = '\0';
1593 const BOOL parent = winpr_PathMakePath(path, nullptr);
1594 *slash = '/';
1595 if (parent)
1596 {
1597 rc = apple_download_file(file, listIndex, descriptor, path);
1598 if (!rc)
1599 WLog_Print(file->log, WLOG_ERROR, "failed to download %s", name);
1600 }
1601 }
1602
1603fail:
1604 free(path);
1605 free(relative);
1606 return rc;
1607}
1608
1609/* Called on the main thread when an application pastes top level item index */
1610static char* apple_provide_file(void* userdata, size_t index)
1611{
1612 CliprdrFileContext* file = userdata;
1613 WINPR_ASSERT(file);
1614
1615 if (!file->context || (index >= file->apple_top_level_count))
1616 return nullptr;
1617
1618 char* top = apple_descriptor_name(&file->apple_files[file->apple_top_level[index]]);
1619 if (!top)
1620 return nullptr;
1621 const size_t toplen = strlen(top);
1622
1623 char* result = nullptr;
1624 for (UINT32 x = 0; x < file->apple_files_count; x++)
1625 {
1626 char* name = apple_descriptor_name(&file->apple_files[x]);
1627 if (!name)
1628 continue;
1629
1630 const BOOL below =
1631 (strncmp(name, top, toplen) == 0) && ((name[toplen] == '\0') || (name[toplen] == '\\'));
1632 const BOOL rc = !below || apple_download_entry(file, x, name);
1633 free(name);
1634 if (!rc)
1635 goto fail;
1636 }
1637
1638 {
1639 size_t len = 0;
1640 if (winpr_asprintf(&result, &len, "%s/%s", file->apple_download_dir, top) < 0)
1641 result = nullptr;
1642 }
1643
1644fail:
1645 free(top);
1646 return result;
1647}
1648
1649static BOOL apple_set_server_files(CliprdrFileContext* file, const void* data, size_t size)
1650{
1651 WINPR_ASSERT(file);
1652
1653 if (size > UINT32_MAX)
1654 return FALSE;
1655
1656 FILEDESCRIPTORW* files = nullptr;
1657 UINT32 count = 0;
1658 if (cliprdr_parse_file_list(data, (UINT32)size, &files, &count) != 0)
1659 return FALSE;
1660
1661 size_t* top_level = (count > 0) ? calloc(count, sizeof(size_t)) : nullptr;
1662 size_t top_level_count = 0;
1663 for (UINT32 x = 0; top_level && (x < count); x++)
1664 {
1665 char* name = apple_descriptor_name(&files[x]);
1666 if (name && (name[0] != '\0') && !strchr(name, '\\'))
1667 top_level[top_level_count++] = x;
1668 free(name);
1669 }
1670
1671 char* dir = (top_level_count > 0) ? cliprdr_file_apple_new_download_dir() : nullptr;
1672 if (!dir)
1673 {
1674 free(top_level);
1675 free(files);
1676 return FALSE;
1677 }
1678
1679 apple_clear_server_files(file);
1680 file->apple_files = files;
1681 file->apple_files_count = count;
1682 file->apple_top_level = top_level;
1683 file->apple_top_level_count = top_level_count;
1684 file->apple_download_dir = dir;
1685 return TRUE;
1686}
1687#endif
1688
1689static UINT cliprdr_file_context_send_file_contents_failure(
1690 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1691{
1692 CLIPRDR_FILE_CONTENTS_RESPONSE response = WINPR_C_ARRAY_INIT;
1693
1694 WINPR_ASSERT(file);
1695 WINPR_ASSERT(fileContentsRequest);
1696
1697 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1698 ((UINT64)fileContentsRequest->nPositionLow);
1699 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1700 "server file contents request [lockID %" PRIu32 ", streamID %" PRIu32
1701 ", index %" PRIu32 "] offset %" PRIu64 ", size %" PRIu32 " failed",
1702 fileContentsRequest->clipDataId, fileContentsRequest->streamId,
1703 fileContentsRequest->listIndex, offset, fileContentsRequest->cbRequested);
1704
1705 response.common.msgFlags = CB_RESPONSE_FAIL;
1706 response.streamId = fileContentsRequest->streamId;
1707
1708 WINPR_ASSERT(file->context);
1709 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1710 return file->context->ClientFileContentsResponse(file->context, &response);
1711}
1712
1713static UINT
1714cliprdr_file_context_send_contents_response(CliprdrFileContext* file,
1715 const CLIPRDR_FILE_CONTENTS_REQUEST* request,
1716 const void* data, size_t size)
1717{
1718 if (size > UINT32_MAX)
1719 return ERROR_INVALID_PARAMETER;
1720
1721 CLIPRDR_FILE_CONTENTS_RESPONSE response = { .streamId = request->streamId,
1722 .requestedData = data,
1723 .cbRequested = (UINT32)size,
1724 .common.msgFlags = CB_RESPONSE_OK };
1725
1726 WINPR_ASSERT(request);
1727 WINPR_ASSERT(file);
1728
1729 WLog_Print(file->log, WLOG_DEBUG, "send contents response streamID=%" PRIu32 ", size=%" PRIu32,
1730 response.streamId, response.cbRequested);
1731 WINPR_ASSERT(file->context);
1732 WINPR_ASSERT(file->context->ClientFileContentsResponse);
1733 return file->context->ClientFileContentsResponse(file->context, &response);
1734}
1735
1736static BOOL dump_streams(const void* key, void* value, WINPR_ATTR_UNUSED void* arg)
1737{
1738 const UINT32* ukey = key;
1739 CliprdrLocalStream* cur = value;
1740
1741 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1742 "[key %" PRIu32 "] lockID %" PRIu32 ", count %" PRIuz ", locked %d", *ukey,
1743 cur->lockId, cur->count, cur->locked);
1744 for (size_t x = 0; x < cur->count; x++)
1745 {
1746 const CliprdrLocalFile* file = &cur->files[x];
1747 writelog(cur->context->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1748 "file [%" PRIuz "] %s: %" PRId64, x, file->name, file->size);
1749 }
1750 return TRUE;
1751}
1752
1753static CliprdrLocalFile* file_info_for_request(CliprdrFileContext* file, UINT32 lockId,
1754 UINT32 listIndex)
1755{
1756 WINPR_ASSERT(file);
1757
1758 CliprdrLocalStream* cur = HashTable_GetItemValue(file->local_streams, &lockId);
1759 if (cur)
1760 {
1761 if (listIndex < cur->count)
1762 {
1763 CliprdrLocalFile* f = &cur->files[listIndex];
1764 return f;
1765 }
1766 else
1767 {
1768 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1769 "invalid entry index for lockID %" PRIu32 ", index %" PRIu32 " [count %" PRIuz
1770 "] [locked %d]",
1771 lockId, listIndex, cur->count, cur->locked);
1772 }
1773 }
1774 else
1775 {
1776 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1777 "missing entry for lockID %" PRIu32 ", index %" PRIu32, lockId, listIndex);
1778 if (!HashTable_Foreach(file->local_streams, dump_streams, file))
1779 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1780 "HashTable_Foreach failed");
1781 }
1782
1783 return nullptr;
1784}
1785
1786static CliprdrLocalFile* file_for_request(CliprdrFileContext* file, UINT32 lockId, UINT32 listIndex)
1787{
1788 CliprdrLocalFile* f = file_info_for_request(file, lockId, listIndex);
1789 if (f)
1790 {
1791 if (!f->fp)
1792 {
1793 const char* name = f->name;
1794 f->fp = winpr_fopen(name, "rb");
1795 }
1796 if (!f->fp)
1797 {
1798 char ebuffer[256] = WINPR_C_ARRAY_INIT;
1799 writelog(file->log, WLOG_WARN, __FILE__, __func__, __LINE__,
1800 "[lockID %" PRIu32 ", index %" PRIu32
1801 "] failed to open file '%s' [size %" PRId64 "] %s [%d]",
1802 lockId, listIndex, f->name, f->size,
1803 winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1804 return nullptr;
1805 }
1806 }
1807
1808 return f;
1809}
1810
1811static void cliprdr_local_file_try_close(CliprdrLocalFile* file, UINT res, UINT64 offset,
1812 UINT64 size)
1813{
1814 WINPR_ASSERT(file);
1815
1816 if (res != 0)
1817 {
1818 WINPR_ASSERT(file->context);
1819 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after error %" PRIu32,
1820 file->name, res);
1821 }
1822 else if (((file->size > 0) && (offset + size >= (UINT64)file->size)))
1823 {
1824 WINPR_ASSERT(file->context);
1825 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s after read", file->name);
1826 }
1827 else
1828 {
1829 // TODO: we need to keep track of open files to avoid running out of file descriptors
1830 // TODO: for the time being just close again.
1831 }
1832 if (file->fp)
1833 (void)fclose(file->fp);
1834 file->fp = nullptr;
1835}
1836
1837static UINT cliprdr_file_context_server_file_size_request(
1838 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1839{
1840 WINPR_ASSERT(fileContentsRequest);
1841
1842 if (fileContentsRequest->cbRequested != sizeof(UINT64))
1843 {
1844 WLog_Print(file->log, WLOG_WARN, "unexpected FILECONTENTS_SIZE request: %" PRIu32 " bytes",
1845 fileContentsRequest->cbRequested);
1846 }
1847
1848 HashTable_Lock(file->local_streams);
1849
1850 UINT res = CHANNEL_RC_OK;
1851 CliprdrLocalFile* rfile =
1852 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1853 if (!rfile)
1854 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1855 else
1856 {
1857 if (_fseeki64(rfile->fp, 0, SEEK_END) < 0)
1858 res = cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1859 else
1860 {
1861 const INT64 size = _ftelli64(rfile->fp);
1862 rfile->size = size;
1863 cliprdr_local_file_try_close(rfile, res, 0, 0);
1864
1865 res = cliprdr_file_context_send_contents_response(file, fileContentsRequest, &size,
1866 sizeof(size));
1867 }
1868 }
1869
1870 HashTable_Unlock(file->local_streams);
1871 return res;
1872}
1873
1874static UINT cliprdr_file_context_server_file_range_request(
1875 CliprdrFileContext* file, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1876{
1877 BYTE* data = nullptr;
1878
1879 WINPR_ASSERT(fileContentsRequest);
1880
1881 HashTable_Lock(file->local_streams);
1882 const UINT64 offset = (((UINT64)fileContentsRequest->nPositionHigh) << 32) |
1883 ((UINT64)fileContentsRequest->nPositionLow);
1884
1885 CliprdrLocalFile* rfile =
1886 file_for_request(file, fileContentsRequest->clipDataId, fileContentsRequest->listIndex);
1887 if (!rfile)
1888 goto fail;
1889
1890 if (_fseeki64(rfile->fp, WINPR_ASSERTING_INT_CAST(int64_t, offset), SEEK_SET) < 0)
1891 goto fail;
1892
1893 data = malloc(fileContentsRequest->cbRequested);
1894 if (!data)
1895 goto fail;
1896
1897 {
1898 const size_t r = fread(data, 1, fileContentsRequest->cbRequested, rfile->fp);
1899 const UINT rc =
1900 cliprdr_file_context_send_contents_response(file, fileContentsRequest, data, r);
1901 free(data);
1902
1903 cliprdr_local_file_try_close(rfile, rc, offset, fileContentsRequest->cbRequested);
1904 HashTable_Unlock(file->local_streams);
1905 return rc;
1906 }
1907fail:
1908 if (rfile)
1909 cliprdr_local_file_try_close(rfile, ERROR_INTERNAL_ERROR, offset,
1910 fileContentsRequest->cbRequested);
1911 free(data);
1912 HashTable_Unlock(file->local_streams);
1913 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1914}
1915
1916static void cliprdr_local_stream_free(void* obj);
1917
1918static UINT change_lock(CliprdrFileContext* file, UINT32 lockId, BOOL lock)
1919{
1920 UINT rc = CHANNEL_RC_OK;
1921
1922 WINPR_ASSERT(file);
1923
1924 HashTable_Lock(file->local_streams);
1925
1926 {
1927 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
1928 if (lock && !stream)
1929 {
1930 stream = cliprdr_local_stream_new(file, lockId, nullptr, 0);
1931 if (!HashTable_Insert(file->local_streams, &lockId, stream))
1932 {
1933 rc = ERROR_INTERNAL_ERROR;
1934 cliprdr_local_stream_free(stream);
1935 stream = nullptr;
1936 }
1937 file->local_lock_id = lockId;
1938 }
1939 if (stream)
1940 {
1941 stream->locked = lock;
1942 stream->lockId = lockId;
1943 }
1944 }
1945
1946 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert ownership stream
1947 if (!lock)
1948 {
1949 if (!HashTable_Foreach(file->local_streams, local_stream_discard, file))
1950 rc = ERROR_INTERNAL_ERROR;
1951 }
1952
1953 HashTable_Unlock(file->local_streams);
1954 return rc;
1955}
1956
1957static UINT cliprdr_file_context_lock(CliprdrClientContext* context,
1958 const CLIPRDR_LOCK_CLIPBOARD_DATA* lockClipboardData)
1959{
1960 WINPR_ASSERT(context);
1961 WINPR_ASSERT(lockClipboardData);
1962 CliprdrFileContext* file = (context->custom);
1963 return change_lock(file, lockClipboardData->clipDataId, TRUE);
1964}
1965
1966static UINT cliprdr_file_context_unlock(CliprdrClientContext* context,
1967 const CLIPRDR_UNLOCK_CLIPBOARD_DATA* unlockClipboardData)
1968{
1969 WINPR_ASSERT(context);
1970 WINPR_ASSERT(unlockClipboardData);
1971 CliprdrFileContext* file = (context->custom);
1972 return change_lock(file, unlockClipboardData->clipDataId, FALSE);
1973}
1974
1975static UINT cliprdr_file_context_server_file_contents_request(
1976 CliprdrClientContext* context, const CLIPRDR_FILE_CONTENTS_REQUEST* fileContentsRequest)
1977{
1978 UINT error = NO_ERROR;
1979
1980 WINPR_ASSERT(context);
1981 WINPR_ASSERT(fileContentsRequest);
1982
1983 CliprdrFileContext* file = (context->custom);
1984 WINPR_ASSERT(file);
1985
1986 /*
1987 * MS-RDPECLIP 2.2.5.3 File Contents Request PDU (CLIPRDR_FILECONTENTS_REQUEST):
1988 * The FILECONTENTS_SIZE and FILECONTENTS_RANGE flags MUST NOT be set at the same time.
1989 */
1990 if ((fileContentsRequest->dwFlags & (FILECONTENTS_SIZE | FILECONTENTS_RANGE)) ==
1991 (FILECONTENTS_SIZE | FILECONTENTS_RANGE))
1992 {
1993 WLog_Print(file->log, WLOG_ERROR, "invalid CLIPRDR_FILECONTENTS_REQUEST.dwFlags");
1994 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
1995 }
1996
1997 if (fileContentsRequest->dwFlags & FILECONTENTS_SIZE)
1998 error = cliprdr_file_context_server_file_size_request(file, fileContentsRequest);
1999
2000 if (fileContentsRequest->dwFlags & FILECONTENTS_RANGE)
2001 error = cliprdr_file_context_server_file_range_request(file, fileContentsRequest);
2002
2003 if (error)
2004 {
2005 WLog_Print(file->log, WLOG_ERROR, "failed to handle CLIPRDR_FILECONTENTS_REQUEST: 0x%08X",
2006 error);
2007 return cliprdr_file_context_send_file_contents_failure(file, fileContentsRequest);
2008 }
2009
2010 return CHANNEL_RC_OK;
2011}
2012
2013BOOL cliprdr_file_context_init(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
2014{
2015 WINPR_ASSERT(file);
2016 WINPR_ASSERT(cliprdr);
2017
2018 cliprdr->custom = file;
2019 file->context = cliprdr;
2020
2021 cliprdr->ServerLockClipboardData = cliprdr_file_context_lock;
2022 cliprdr->ServerUnlockClipboardData = cliprdr_file_context_unlock;
2023 cliprdr->ServerFileContentsRequest = cliprdr_file_context_server_file_contents_request;
2024#if defined(WITH_FUSE)
2025 cliprdr->ServerFileContentsResponse = cliprdr_file_context_server_file_contents_response;
2026
2027 CliprdrFuseFile* root_dir = fuse_file_new_root(file);
2028 return root_dir != nullptr;
2029#elif defined(WITH_CLIPRDR_FILE_APPLE)
2030 cliprdr->ServerFileContentsResponse = cliprdr_file_context_apple_contents_response;
2031 return TRUE;
2032#else
2033 return TRUE;
2034#endif
2035}
2036
2037#if defined(WITH_FUSE)
2038WINPR_ATTR_NODISCARD
2039static bool clear_all_selections(CliprdrFileContext* file_context)
2040{
2041 WINPR_ASSERT(file_context);
2042 WINPR_ASSERT(file_context->inode_table);
2043
2044 HashTable_Lock(file_context->inode_table);
2045 const bool rc = clear_selection(file_context, TRUE, nullptr);
2046
2047 HashTable_Clear(file_context->clip_data_table);
2048 HashTable_Unlock(file_context->inode_table);
2049 return rc;
2050}
2051#endif
2052
2053BOOL cliprdr_file_context_uninit(CliprdrFileContext* file, CliprdrClientContext* cliprdr)
2054{
2055
2056 // Clear all data before the channel is closed
2057 // the cleanup handlers are dependent on a working channel.
2058 if (file)
2059 {
2060#if defined(WITH_FUSE)
2061 if (file->inode_table)
2062 {
2063 if (!clear_no_cdi_entry(file))
2064 return FALSE;
2065 if (!clear_all_selections(file))
2066 return FALSE;
2067 }
2068#endif
2069#if defined(WITH_CLIPRDR_FILE_APPLE)
2070 /* The file list stays until cliprdr_file_context_free(), a paste might still read it */
2071 cliprdr_file_apple_detach();
2072#endif
2073
2074 HashTable_Clear(file->local_streams);
2075
2076 file->context = nullptr;
2077 }
2078
2079 if (cliprdr)
2080 {
2081#if defined(WITH_FUSE) || defined(WITH_CLIPRDR_FILE_APPLE)
2082 cliprdr->ServerFileContentsResponse = nullptr;
2083#endif
2084 }
2085
2086 return TRUE;
2087}
2088
2089static BOOL cliprdr_file_content_changed_and_update(void* ihash, size_t hsize, const void* data,
2090 size_t size)
2091{
2092
2093 BYTE hash[WINPR_SHA256_DIGEST_LENGTH] = WINPR_C_ARRAY_INIT;
2094
2095 if (hsize < sizeof(hash))
2096 return FALSE;
2097
2098 if (!winpr_Digest(WINPR_MD_SHA256, data, size, hash, sizeof(hash)))
2099 return FALSE;
2100
2101 const BOOL changed = memcmp(hash, ihash, sizeof(hash)) != 0;
2102 if (changed)
2103 memcpy(ihash, hash, sizeof(hash));
2104 return changed;
2105}
2106
2107static BOOL cliprdr_file_client_content_changed_and_update(CliprdrFileContext* file,
2108 const void* data, size_t size)
2109{
2110 WINPR_ASSERT(file);
2111 return cliprdr_file_content_changed_and_update(file->client_data_hash,
2112 sizeof(file->client_data_hash), data, size);
2113}
2114
2115#if defined(WITH_FUSE)
2116static fuse_ino_t get_next_free_inode(CliprdrFileContext* file_context)
2117{
2118 fuse_ino_t ino = 0;
2119
2120 WINPR_ASSERT(file_context);
2121
2122 ino = file_context->next_ino;
2123 while (ino == 0 || ino == FUSE_ROOT_ID ||
2124 HashTable_GetItemValue(file_context->inode_table, (void*)(uintptr_t)ino))
2125 ++ino;
2126
2127 file_context->next_ino = ino + 1;
2128
2129 return ino;
2130}
2131
2132static CliprdrFuseFile* clip_data_dir_new(CliprdrFileContext* file_context, BOOL has_clip_data_id,
2133 UINT32 clip_data_id)
2134{
2135 WINPR_ASSERT(file_context);
2136
2137 UINT64 data_id = clip_data_id;
2138 if (!has_clip_data_id)
2139 data_id = NO_CLIP_DATA_ID;
2140
2141 CliprdrFuseFile* clip_data_dir = fuse_file_new("/%" PRIu64, data_id);
2142 if (!clip_data_dir)
2143 return nullptr;
2144
2145 clip_data_dir->ino = get_next_free_inode(file_context);
2146 clip_data_dir->is_directory = TRUE;
2147 clip_data_dir->is_readonly = TRUE;
2148 clip_data_dir->has_clip_data_id = has_clip_data_id;
2149 clip_data_dir->clip_data_id = clip_data_id;
2150
2151 CliprdrFuseFile* root_dir = get_fuse_file_by_ino(file_context, FUSE_ROOT_ID);
2152 if (!root_dir)
2153 {
2154 WLog_Print(file_context->log, WLOG_ERROR, "FUSE root directory missing");
2155 fuse_file_free(clip_data_dir);
2156 return nullptr;
2157 }
2158 if (!ArrayList_Append(root_dir->children, clip_data_dir))
2159 {
2160 WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
2161 fuse_file_free(clip_data_dir);
2162 return nullptr;
2163 }
2164 clip_data_dir->parent = root_dir;
2165
2166 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)clip_data_dir->ino,
2167 clip_data_dir))
2168 {
2169 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
2170 ArrayList_Remove(root_dir->children, clip_data_dir);
2171 fuse_file_free(clip_data_dir);
2172 return nullptr;
2173 }
2174
2175 if (!HashTable_Insert(file_context->dir_table, clip_data_dir->filename_with_root,
2176 clip_data_dir))
2177 {
2178 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into dir table");
2179 ArrayList_Remove(root_dir->children, clip_data_dir);
2180 fuse_file_free(clip_data_dir);
2181 return nullptr;
2182 }
2183
2184 return clip_data_dir;
2185}
2186
2187static char* get_parent_path(const char* filepath)
2188{
2189 const char* base = strrchr(filepath, '/');
2190 WINPR_ASSERT(base);
2191
2192 while ((base > filepath) && (*base == '/'))
2193 --base;
2194
2195 WINPR_ASSERT(base >= filepath);
2196 const size_t parent_path_length = 1ULL + (size_t)(base - filepath);
2197 char* parent_path = calloc(parent_path_length + 1, sizeof(char));
2198 if (!parent_path)
2199 return nullptr;
2200
2201 memcpy(parent_path, filepath, parent_path_length);
2202
2203 return parent_path;
2204}
2205
2206static CliprdrFuseFile* get_parent_directory(CliprdrFileContext* file_context, const char* path)
2207{
2208
2209 WINPR_ASSERT(file_context);
2210 WINPR_ASSERT(path);
2211
2212 char* parent_path = get_parent_path(path);
2213 if (!parent_path)
2214 return nullptr;
2215
2216 CliprdrFuseFile* parent = HashTable_GetItemValue(file_context->dir_table, parent_path);
2217 free(parent_path);
2218 return parent;
2219}
2220
2221// NOLINTBEGIN(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
2222static BOOL selection_handle_file(CliprdrFileContext* file_context,
2223 CliprdrFuseClipDataEntry* clip_data_entry, uint32_t index,
2224 const FILEDESCRIPTORW* file)
2225{
2226
2227 WINPR_ASSERT(file_context);
2228 WINPR_ASSERT(clip_data_entry);
2229 WINPR_ASSERT(file);
2230
2231 CliprdrFuseFile* clip_data_dir = clip_data_entry->clip_data_dir;
2232 WINPR_ASSERT(clip_data_dir);
2233
2234 char filename[ARRAYSIZE(file->cFileName) * 8] = WINPR_C_ARRAY_INIT;
2235 const SSIZE_T filenamelen = ConvertWCharNToUtf8(file->cFileName, ARRAYSIZE(file->cFileName),
2236 filename, ARRAYSIZE(filename) - 1);
2237 if (filenamelen < 0)
2238 {
2239 WLog_Print(file_context->log, WLOG_ERROR, "Failed to convert filename");
2240 return FALSE;
2241 }
2242
2243 for (size_t j = 0; filename[j]; ++j)
2244 {
2245 if (filename[j] == '\\')
2246 filename[j] = '/';
2247 }
2248
2249 BOOL crc = FALSE;
2250 CliprdrFuseFile* fuse_file = fuse_file_new(
2251 "%.*s/%s", WINPR_ASSERTING_INT_CAST(int, clip_data_dir->filename_with_root_len),
2252 clip_data_dir->filename_with_root, filename);
2253 if (!fuse_file)
2254 {
2255 WLog_Print(file_context->log, WLOG_ERROR, "Failed to create FUSE file");
2256 goto end;
2257 }
2258
2259 fuse_file->parent = get_parent_directory(file_context, fuse_file->filename_with_root);
2260 if (!fuse_file->parent)
2261 {
2262 WLog_Print(file_context->log, WLOG_ERROR, "Found no parent for FUSE file");
2263 goto end;
2264 }
2265
2266 if (!ArrayList_Append(fuse_file->parent->children, fuse_file))
2267 {
2268 WLog_Print(file_context->log, WLOG_ERROR, "Failed to append FUSE file");
2269 goto end;
2270 }
2271
2272 fuse_file->list_idx = index;
2273 fuse_file->ino = get_next_free_inode(file_context);
2274 fuse_file->has_clip_data_id = clip_data_entry->has_clip_data_id;
2275 fuse_file->clip_data_id = clip_data_entry->clip_data_id;
2276 if (file->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
2277 fuse_file->is_directory = TRUE;
2278 if (file->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
2279 fuse_file->is_readonly = TRUE;
2280 if (file->dwFlags & FD_FILESIZE)
2281 {
2282 fuse_file->size = ((UINT64)file->nFileSizeHigh << 32) + file->nFileSizeLow;
2283 fuse_file->has_size = TRUE;
2284 }
2285 if (file->dwFlags & FD_WRITESTIME)
2286 {
2287 INT64 filetime = 0;
2288
2289 filetime = file->ftLastWriteTime.dwHighDateTime;
2290 filetime <<= 32;
2291 filetime += file->ftLastWriteTime.dwLowDateTime;
2292
2293 fuse_file->last_write_time_unix =
2294 1LL * filetime / (10LL * 1000LL * 1000LL) - WIN32_FILETIME_TO_UNIX_EPOCH;
2295 fuse_file->has_last_write_time = TRUE;
2296 }
2297
2298 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)fuse_file->ino, fuse_file))
2299 {
2300 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into inode table");
2301 goto end;
2302 }
2303
2304 if (fuse_file->is_directory)
2305 {
2306
2307 if (!HashTable_Insert(file_context->dir_table, fuse_file->filename_with_root, fuse_file))
2308 {
2309 WLog_Print(file_context->log, WLOG_ERROR, "Failed to insert inode into dir table");
2310 goto end;
2311 }
2312 }
2313
2314 crc = TRUE;
2315
2316end:
2317 if (!crc)
2318 {
2319 fuse_file_free(fuse_file);
2320 return clear_entry_selection(clip_data_entry);
2321 }
2322 return TRUE;
2323}
2324// NOLINTEND(clang-analyzer-unix.Malloc) HashTable_Insert owns fuse_file
2325
2326static BOOL set_selection_for_clip_data_entry(CliprdrFileContext* file_context,
2327 CliprdrFuseClipDataEntry* clip_data_entry,
2328 const FILEDESCRIPTORW* files, UINT32 n_files)
2329{
2330 WINPR_ASSERT(file_context);
2331 WINPR_ASSERT(clip_data_entry);
2332 WINPR_ASSERT(files);
2333
2334 if (clip_data_entry->has_clip_data_id)
2335 WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection for clipDataId %u",
2336 clip_data_entry->clip_data_id);
2337 else
2338 WLog_Print(file_context->log, WLOG_DEBUG, "Setting selection");
2339
2340 for (UINT32 i = 0; i < n_files; ++i)
2341 {
2342 const FILEDESCRIPTORW* file = &files[i];
2343 if (!selection_handle_file(file_context, clip_data_entry, i, file))
2344 return FALSE;
2345 }
2346
2347 if (clip_data_entry->has_clip_data_id)
2348 WLog_Print(file_context->log, WLOG_DEBUG, "Selection set for clipDataId %u",
2349 clip_data_entry->clip_data_id);
2350 else
2351 WLog_Print(file_context->log, WLOG_DEBUG, "Selection set");
2352
2353 return TRUE;
2354}
2355
2356static BOOL update_exposed_path(CliprdrFileContext* file_context, wClipboard* clip,
2357 CliprdrFuseClipDataEntry* clip_data_entry)
2358{
2359 wClipboardDelegate* delegate = nullptr;
2360 CliprdrFuseFile* clip_data_dir = nullptr;
2361
2362 WINPR_ASSERT(file_context);
2363 WINPR_ASSERT(clip);
2364 WINPR_ASSERT(clip_data_entry);
2365
2366 delegate = ClipboardGetDelegate(clip);
2367 WINPR_ASSERT(delegate);
2368
2369 clip_data_dir = clip_data_entry->clip_data_dir;
2370 WINPR_ASSERT(clip_data_dir);
2371
2372 free(file_context->exposed_path);
2373 file_context->exposed_path = GetCombinedPath(file_context->path, clip_data_dir->filename);
2374 if (file_context->exposed_path)
2375 WLog_Print(file_context->log, WLOG_DEBUG, "Updated exposed path to \"%s\"",
2376 file_context->exposed_path);
2377
2378 delegate->basePath = file_context->exposed_path;
2379
2380 return delegate->basePath != nullptr;
2381}
2382#endif
2383
2384BOOL cliprdr_file_context_update_server_data(CliprdrFileContext* file_context, wClipboard* clip,
2385 const void* data, size_t size)
2386{
2387#if defined(WITH_FUSE)
2388 CliprdrFuseClipDataEntry* clip_data_entry = nullptr;
2389 FILEDESCRIPTORW* files = nullptr;
2390 UINT32 n_files = 0;
2391 BOOL rc = FALSE;
2392
2393 WINPR_ASSERT(file_context);
2394 WINPR_ASSERT(clip);
2395 if (size > UINT32_MAX)
2396 return FALSE;
2397
2398 if (cliprdr_parse_file_list(data, (UINT32)size, &files, &n_files))
2399 {
2400 WLog_Print(file_context->log, WLOG_ERROR, "Failed to parse file list");
2401 return FALSE;
2402 }
2403
2404 HashTable_Lock(file_context->inode_table);
2405 HashTable_Lock(file_context->clip_data_table);
2406 if (does_server_support_clipdata_locking(file_context))
2407 clip_data_entry = HashTable_GetItemValue(
2408 file_context->clip_data_table, (void*)(uintptr_t)file_context->current_clip_data_id);
2409 else
2410 clip_data_entry = file_context->clip_data_entry_without_id;
2411
2412 WINPR_ASSERT(clip_data_entry);
2413
2414 if (!clear_entry_selection(clip_data_entry))
2415 goto fail;
2416 WINPR_ASSERT(!clip_data_entry->clip_data_dir);
2417
2418 clip_data_entry->clip_data_dir =
2419 clip_data_dir_new(file_context, does_server_support_clipdata_locking(file_context),
2420 file_context->current_clip_data_id);
2421 if (!clip_data_entry->clip_data_dir)
2422 goto fail;
2423 if (!update_exposed_path(file_context, clip, clip_data_entry))
2424 goto fail;
2425 if (!set_selection_for_clip_data_entry(file_context, clip_data_entry, files, n_files))
2426 goto fail;
2427
2428 rc = TRUE;
2429
2430fail:
2431 HashTable_Unlock(file_context->clip_data_table);
2432 HashTable_Unlock(file_context->inode_table);
2433 free(files);
2434 return rc;
2435#else
2436 return FALSE;
2437#endif
2438}
2439
2440void* cliprdr_file_context_get_context(CliprdrFileContext* file)
2441{
2442 WINPR_ASSERT(file);
2443 return file->clipboard;
2444}
2445
2446void cliprdr_file_session_terminate(CliprdrFileContext* file, BOOL stop_thread)
2447{
2448 if (!file)
2449 return;
2450
2451#if defined(WITH_FUSE)
2452 WINPR_ASSERT(file->fuse_stop_sync);
2453
2454 WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE exit flag");
2455 if (file->fuse_sess)
2456 fuse_session_exit(file->fuse_sess);
2457
2458 if (stop_thread)
2459 {
2460 WLog_Print(file->log, WLOG_DEBUG, "Setting FUSE stop event");
2461 (void)SetEvent(file->fuse_stop_sync);
2462 }
2463#endif
2464 /* not elegant but works for umounting FUSE
2465 fuse_chan must receive an oper buf to unblock fuse_session_receive_buf function.
2466 */
2467#if defined(WITH_FUSE)
2468 WLog_Print(file->log, WLOG_DEBUG, "Forcing FUSE to check exit flag");
2469#endif
2470 if (winpr_PathFileExists(file->path))
2471 WLog_Print(file->log, WLOG_WARN, "winpr_PathFileExists(%s) failed", file->path);
2472}
2473
2474void cliprdr_file_context_free(CliprdrFileContext* file)
2475{
2476 if (!file)
2477 return;
2478
2479#if defined(WITH_FUSE)
2480 if (file->fuse_thread)
2481 {
2482 WINPR_ASSERT(file->fuse_stop_sync);
2483
2484 WLog_Print(file->log, WLOG_DEBUG, "Stopping FUSE thread");
2485 cliprdr_file_session_terminate(file, TRUE);
2486
2487 WLog_Print(file->log, WLOG_DEBUG, "Waiting on FUSE thread");
2488 (void)WaitForSingleObject(file->fuse_thread, INFINITE);
2489 (void)CloseHandle(file->fuse_thread);
2490 }
2491 if (file->fuse_stop_sync)
2492 (void)CloseHandle(file->fuse_stop_sync);
2493 if (file->fuse_start_sync)
2494 (void)CloseHandle(file->fuse_start_sync);
2495
2496 HashTable_Free(file->request_table);
2497 HashTable_Free(file->clip_data_table);
2498 HashTable_Free(file->dir_table);
2499 HashTable_Free(file->inode_table);
2500
2501#endif
2502#if defined(WITH_CLIPRDR_FILE_APPLE)
2503 apple_clear_server_files(file);
2504 free(file->apple_contents_data);
2505 if (file->apple_contents_event)
2506 {
2507 (void)CloseHandle(file->apple_contents_event);
2508 DeleteCriticalSection(&file->apple_lock);
2509 }
2510#endif
2511 HashTable_Free(file->local_streams);
2512 winpr_RemoveDirectory(file->path);
2513 free(file->path);
2514 free(file->exposed_path);
2515 free(file);
2516}
2517
2518static BOOL create_base_path(CliprdrFileContext* file)
2519{
2520 WINPR_ASSERT(file);
2521
2522 char base[64] = WINPR_C_ARRAY_INIT;
2523 (void)_snprintf(base, sizeof(base), "com.freerdp.client.cliprdr.%" PRIu32,
2524 GetCurrentProcessId());
2525
2526 file->path = GetKnownSubPath(KNOWN_PATH_TEMP, base);
2527 if (!file->path)
2528 return FALSE;
2529
2530 if (!winpr_PathFileExists(file->path) && !winpr_PathMakePath(file->path, nullptr))
2531 {
2532 WLog_Print(file->log, WLOG_ERROR, "Failed to create directory '%s'", file->path);
2533 return FALSE;
2534 }
2535 return TRUE;
2536}
2537
2538static void cliprdr_local_file_free(CliprdrLocalFile* file)
2539{
2540 const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT;
2541 if (!file)
2542 return;
2543 if (file->fp)
2544 {
2545 WLog_Print(file->context->log, WLOG_DEBUG, "closing file %s, discarding entry", file->name);
2546 (void)fclose(file->fp);
2547 }
2548 free(file->name);
2549 *file = empty;
2550}
2551
2552static BOOL cliprdr_local_file_new(CliprdrFileContext* context, CliprdrLocalFile* f,
2553 const char* path)
2554{
2555 const CliprdrLocalFile empty = WINPR_C_ARRAY_INIT;
2556 WINPR_ASSERT(f);
2557 WINPR_ASSERT(context);
2558 WINPR_ASSERT(path);
2559
2560 *f = empty;
2561 f->context = context;
2562 f->name = winpr_str_url_decode(path, strlen(path));
2563 if (!f->name)
2564 goto fail;
2565
2566 return TRUE;
2567fail:
2568 cliprdr_local_file_free(f);
2569 return FALSE;
2570}
2571
2572static void cliprdr_local_files_free(CliprdrLocalStream* stream)
2573{
2574 WINPR_ASSERT(stream);
2575
2576 for (size_t x = 0; x < stream->count; x++)
2577 cliprdr_local_file_free(&stream->files[x]);
2578 free(stream->files);
2579
2580 stream->files = nullptr;
2581 stream->count = 0;
2582}
2583
2584static void cliprdr_local_stream_free(void* obj)
2585{
2586 CliprdrLocalStream* stream = (CliprdrLocalStream*)obj;
2587 if (stream)
2588 cliprdr_local_files_free(stream);
2589
2590 free(stream);
2591}
2592
2593static BOOL append_entry(CliprdrLocalStream* stream, const char* path)
2594{
2595 CliprdrLocalFile* tmp = realloc(stream->files, sizeof(CliprdrLocalFile) * (stream->count + 1));
2596 if (!tmp)
2597 return FALSE;
2598 stream->files = tmp;
2599 CliprdrLocalFile* f = &stream->files[stream->count++];
2600
2601 return cliprdr_local_file_new(stream->context, f, path);
2602}
2603
2604static BOOL is_directory(const char* path)
2605{
2606 WCHAR* wpath = ConvertUtf8ToWCharAlloc(path, nullptr);
2607 if (!wpath)
2608 return FALSE;
2609
2610 HANDLE hFile = CreateFileW(wpath, 0, FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
2611 FILE_ATTRIBUTE_NORMAL, nullptr);
2612 free(wpath);
2613
2614 if (hFile == INVALID_HANDLE_VALUE)
2615 return FALSE;
2616
2617 BY_HANDLE_FILE_INFORMATION fileInformation = WINPR_C_ARRAY_INIT;
2618 const BOOL status = GetFileInformationByHandle(hFile, &fileInformation);
2619 (void)CloseHandle(hFile);
2620 if (!status)
2621 return FALSE;
2622
2623 return (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
2624}
2625
2626static BOOL add_directory(CliprdrLocalStream* stream, const char* path)
2627{
2628 char* wildcardpath = GetCombinedPath(path, "*");
2629 if (!wildcardpath)
2630 return FALSE;
2631 WCHAR* wpath = ConvertUtf8ToWCharAlloc(wildcardpath, nullptr);
2632 free(wildcardpath);
2633 if (!wpath)
2634 return FALSE;
2635
2636 WIN32_FIND_DATAW FindFileData = WINPR_C_ARRAY_INIT;
2637 HANDLE hFind = FindFirstFileW(wpath, &FindFileData);
2638 free(wpath);
2639
2640 if (hFind == INVALID_HANDLE_VALUE)
2641 return FALSE;
2642
2643 BOOL rc = FALSE;
2644 char* next = nullptr;
2645
2646 WCHAR dotbuffer[6] = WINPR_C_ARRAY_INIT;
2647 WCHAR dotdotbuffer[6] = WINPR_C_ARRAY_INIT;
2648 const WCHAR* dot = InitializeConstWCharFromUtf8(".", dotbuffer, ARRAYSIZE(dotbuffer));
2649 const WCHAR* dotdot = InitializeConstWCharFromUtf8("..", dotdotbuffer, ARRAYSIZE(dotdotbuffer));
2650 do
2651 {
2652 if (_wcscmp(FindFileData.cFileName, dot) == 0)
2653 continue;
2654 if (_wcscmp(FindFileData.cFileName, dotdot) == 0)
2655 continue;
2656
2657 char cFileName[MAX_PATH] = WINPR_C_ARRAY_INIT;
2658 (void)ConvertWCharNToUtf8(FindFileData.cFileName, ARRAYSIZE(FindFileData.cFileName),
2659 cFileName, ARRAYSIZE(cFileName));
2660
2661 free(next);
2662 next = GetCombinedPath(path, cFileName);
2663 if (!next)
2664 goto fail;
2665
2666 if (!append_entry(stream, next))
2667 goto fail;
2668 if (is_directory(next))
2669 {
2670 if (!add_directory(stream, next))
2671 goto fail;
2672 }
2673 } while (FindNextFileW(hFind, &FindFileData));
2674
2675 rc = TRUE;
2676fail:
2677 free(next);
2678 FindClose(hFind);
2679
2680 return rc;
2681}
2682
2683static BOOL cliprdr_local_stream_update(CliprdrLocalStream* stream, const char* data, size_t size)
2684{
2685 BOOL rc = FALSE;
2686 WINPR_ASSERT(stream);
2687 if (size == 0)
2688 return TRUE;
2689
2690 cliprdr_local_files_free(stream);
2691
2692 stream->files = calloc(size, sizeof(CliprdrLocalFile));
2693 if (!stream->files)
2694 return FALSE;
2695
2696 char* copy = strndup(data, size);
2697 if (!copy)
2698 return FALSE;
2699
2700 char* dname = nullptr;
2701 char* saveptr = nullptr;
2702 char* ptr = strtok_s(copy, "\r\n", &saveptr);
2703 while (ptr)
2704 {
2705 const char* cname = ptr;
2706 if (strncmp("file:///", ptr, 8) == 0)
2707 cname = &ptr[7];
2708 else if (strncmp("file:/", ptr, 6) == 0)
2709 cname = &ptr[5];
2710
2711 const size_t nlen = strnlen(cname, size);
2712 free(dname);
2713 dname = winpr_str_url_decode(cname, nlen);
2714 if (!dname)
2715 goto fail;
2716 if (!append_entry(stream, dname))
2717 goto fail;
2718
2719 if (is_directory(dname))
2720 {
2721 const BOOL res = add_directory(stream, dname);
2722 if (!res)
2723 goto fail;
2724 }
2725 ptr = strtok_s(nullptr, "\r\n", &saveptr);
2726 }
2727
2728 rc = TRUE;
2729fail:
2730 free(dname);
2731 free(copy);
2732 return rc;
2733}
2734
2735CliprdrLocalStream* cliprdr_local_stream_new(CliprdrFileContext* context, UINT32 streamID,
2736 const char* data, size_t size)
2737{
2738 WINPR_ASSERT(context);
2739 CliprdrLocalStream* stream = calloc(1, sizeof(CliprdrLocalStream));
2740 if (!stream)
2741 return nullptr;
2742
2743 stream->context = context;
2744 if (!cliprdr_local_stream_update(stream, data, size))
2745 goto fail;
2746
2747 stream->lockId = streamID;
2748 return stream;
2749
2750fail:
2751 cliprdr_local_stream_free(stream);
2752 return nullptr;
2753}
2754
2755static UINT32 UINTPointerHash(const void* id)
2756{
2757 WINPR_ASSERT(id);
2758 return *((const UINT32*)id);
2759}
2760
2761static BOOL UINTPointerCompare(const void* pointer1, const void* pointer2)
2762{
2763 if (!pointer1 || !pointer2)
2764 return pointer1 == pointer2;
2765
2766 const UINT32* a = pointer1;
2767 const UINT32* b = pointer2;
2768 return *a == *b;
2769}
2770
2771static void* UINTPointerClone(const void* other)
2772{
2773 const UINT32* src = other;
2774 if (!src)
2775 return nullptr;
2776
2777 UINT32* copy = calloc(1, sizeof(UINT32));
2778 if (!copy)
2779 return nullptr;
2780
2781 *copy = *src;
2782 return copy;
2783}
2784
2785#if defined(WITH_FUSE)
2786CliprdrFuseFile* fuse_file_new_root(CliprdrFileContext* file_context)
2787{
2788 CliprdrFuseFile* root_dir = fuse_file_new("/");
2789 if (!root_dir)
2790 return nullptr;
2791
2792 root_dir->ino = FUSE_ROOT_ID;
2793 root_dir->is_directory = TRUE;
2794 root_dir->is_readonly = TRUE;
2795
2796 if (!HashTable_Insert(file_context->inode_table, (void*)(uintptr_t)root_dir->ino, root_dir))
2797 {
2798 fuse_file_free(root_dir);
2799 return nullptr;
2800 }
2801
2802 if (!HashTable_Insert(file_context->dir_table, root_dir->filename_with_root, root_dir))
2803 {
2804 fuse_file_free(root_dir);
2805 return nullptr;
2806 }
2807
2808 return root_dir;
2809}
2810#endif
2811
2812CliprdrFileContext* cliprdr_file_context_new(void* context)
2813{
2814 CliprdrFileContext* file = calloc(1, sizeof(CliprdrFileContext));
2815 if (!file)
2816 return nullptr;
2817
2818 file->log = WLog_Get(CLIENT_TAG("common.cliprdr.file"));
2819 file->clipboard = context;
2820
2821 file->local_streams = HashTable_New(FALSE);
2822 if (!file->local_streams)
2823 goto fail;
2824
2825 if (!HashTable_SetHashFunction(file->local_streams, UINTPointerHash))
2826 goto fail;
2827
2828 {
2829 wObject* hkobj = HashTable_KeyObject(file->local_streams);
2830 WINPR_ASSERT(hkobj);
2831 hkobj->fnObjectEquals = UINTPointerCompare;
2832 hkobj->fnObjectFree = free;
2833 hkobj->fnObjectNew = UINTPointerClone;
2834 }
2835
2836 {
2837 wObject* hobj = HashTable_ValueObject(file->local_streams);
2838 WINPR_ASSERT(hobj);
2839 hobj->fnObjectFree = cliprdr_local_stream_free;
2840 }
2841
2842#if defined(WITH_FUSE)
2843 file->inode_table = HashTable_New(FALSE);
2844 file->dir_table = HashTable_New(FALSE);
2845 file->clip_data_table = HashTable_New(FALSE);
2846 file->request_table = HashTable_New(FALSE);
2847 if (!file->inode_table || !file->dir_table || !file->clip_data_table || !file->request_table)
2848 goto fail;
2849
2850 {
2851 wObject* ctobj = HashTable_ValueObject(file->request_table);
2852 WINPR_ASSERT(ctobj);
2853 ctobj->fnObjectFree = free;
2854 }
2855 {
2856 wObject* ctobj = HashTable_ValueObject(file->clip_data_table);
2857 WINPR_ASSERT(ctobj);
2858 ctobj->fnObjectFree = clip_data_entry_free;
2859 }
2860 {
2861 if (!HashTable_SetHashFunction(file->dir_table, HashTable_StringHash))
2862 goto fail;
2863 wObject* kobj = HashTable_KeyObject(file->dir_table);
2864 WINPR_ASSERT(kobj);
2865 kobj->fnObjectEquals = HashTable_StringCompare;
2866 }
2867#endif
2868
2869 if (!create_base_path(file))
2870 goto fail;
2871
2872#if defined(WITH_CLIPRDR_FILE_APPLE)
2873 if (!InitializeCriticalSectionAndSpinCount(&file->apple_lock, 4000))
2874 goto fail;
2875 if (!(file->apple_contents_event = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
2876 {
2877 DeleteCriticalSection(&file->apple_lock);
2878 goto fail;
2879 }
2880#endif
2881
2882#if defined(WITH_FUSE)
2883 if (!(file->fuse_start_sync = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
2884 goto fail;
2885 if (!(file->fuse_stop_sync = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
2886 goto fail;
2887 if (!(file->fuse_thread = CreateThread(nullptr, 0, cliprdr_file_fuse_thread, file, 0, nullptr)))
2888 goto fail;
2889
2890 if (WaitForSingleObject(file->fuse_start_sync, INFINITE) == WAIT_FAILED)
2891 WLog_Print(file->log, WLOG_ERROR, "Failed to wait for start sync");
2892#endif
2893 return file;
2894
2895fail:
2896 WINPR_PRAGMA_DIAG_PUSH
2897 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
2898 cliprdr_file_context_free(file);
2899 WINPR_PRAGMA_DIAG_POP
2900 return nullptr;
2901}
2902
2903BOOL local_stream_discard(const void* key, void* value, void* arg)
2904{
2905 CliprdrFileContext* file = arg;
2906 CliprdrLocalStream* stream = value;
2907 WINPR_ASSERT(file);
2908 WINPR_ASSERT(stream);
2909
2910 if (!stream->locked)
2911 HashTable_Remove(file->local_streams, key);
2912 return TRUE;
2913}
2914
2915BOOL cliprdr_file_context_clear(CliprdrFileContext* file)
2916{
2917 WINPR_ASSERT(file);
2918
2919 WLog_Print(file->log, WLOG_DEBUG, "clear file clipboard...");
2920
2921 HashTable_Lock(file->local_streams);
2922 const BOOL res = HashTable_Foreach(file->local_streams, local_stream_discard, file);
2923 HashTable_Unlock(file->local_streams);
2924
2925 memset(file->server_data_hash, 0, sizeof(file->server_data_hash));
2926 memset(file->client_data_hash, 0, sizeof(file->client_data_hash));
2927 return res;
2928}
2929
2930BOOL cliprdr_file_context_update_client_data(CliprdrFileContext* file, const char* data,
2931 size_t size)
2932{
2933 BOOL rc = FALSE;
2934
2935 WINPR_ASSERT(file);
2936 if (!cliprdr_file_client_content_changed_and_update(file, data, size))
2937 return TRUE;
2938
2939 if (!cliprdr_file_context_clear(file))
2940 return FALSE;
2941
2942 UINT32 lockId = file->local_lock_id;
2943
2944 HashTable_Lock(file->local_streams);
2945 CliprdrLocalStream* stream = HashTable_GetItemValue(file->local_streams, &lockId);
2946
2947 WLog_Print(file->log, WLOG_DEBUG, "update client file list (stream=%p)...", (void*)stream);
2948 if (stream)
2949 rc = cliprdr_local_stream_update(stream, data, size);
2950 else
2951 {
2952 stream = cliprdr_local_stream_new(file, lockId, data, size);
2953 rc = HashTable_Insert(file->local_streams, &stream->lockId, stream);
2954 if (!rc)
2955 cliprdr_local_stream_free(stream);
2956 }
2957 // HashTable_Insert owns stream
2958 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc)
2959 HashTable_Unlock(file->local_streams);
2960 return rc;
2961}
2962
2963UINT32 cliprdr_file_context_current_flags(CliprdrFileContext* file)
2964{
2965 WINPR_ASSERT(file);
2966
2967 if ((file->file_capability_flags & CB_STREAM_FILECLIP_ENABLED) == 0)
2968 return 0;
2969
2970 if (!file->file_formats_registered)
2971 return 0;
2972
2973 return CB_STREAM_FILECLIP_ENABLED | CB_FILECLIP_NO_FILE_PATHS |
2974 CB_HUGE_FILE_SUPPORT_ENABLED; // | CB_CAN_LOCK_CLIPDATA;
2975}
2976
2977BOOL cliprdr_file_context_set_locally_available(CliprdrFileContext* file, BOOL available)
2978{
2979 WINPR_ASSERT(file);
2980 file->file_formats_registered = available;
2981 return TRUE;
2982}
2983
2984BOOL cliprdr_file_context_remote_set_flags(CliprdrFileContext* file, UINT32 flags)
2985{
2986 WINPR_ASSERT(file);
2987 file->file_capability_flags = flags;
2988 return TRUE;
2989}
2990
2991UINT32 cliprdr_file_context_remote_get_flags(CliprdrFileContext* file)
2992{
2993 WINPR_ASSERT(file);
2994 return file->file_capability_flags;
2995}
2996
2997BOOL cliprdr_file_context_has_local_support(CliprdrFileContext* file)
2998{
2999 WINPR_UNUSED(file);
3000
3001#if defined(WITH_FUSE)
3002 return TRUE;
3003#else
3004 return FALSE;
3005#endif
3006}
3007
3008BOOL cliprdr_file_context_set_local_clipboard_data(CliprdrFileContext* file,
3009 cliprdr_file_context_data_fkt fkt,
3010 void* userdata, const char** mime_types,
3011 size_t count)
3012{
3013 WINPR_ASSERT(file);
3014 WINPR_ASSERT(fkt);
3015 WINPR_ASSERT(mime_types || (count == 0));
3016
3017#if defined(WITH_CLIPRDR_FILE_APPLE)
3018 if (!file->context)
3019 return FALSE;
3020
3021 BOOL files = FALSE;
3022 for (size_t x = 0; x < count; x++)
3023 {
3024 if (mime_types[x] && (strcmp(mime_types[x], "text/uri-list") == 0))
3025 files = TRUE;
3026 }
3027 if (!files)
3028 return FALSE;
3029
3030 /* Only the file list is fetched now, the contents follow on paste */
3031 size_t size = 0;
3032 const void* data = fkt(userdata, "FileGroupDescriptorW", &size);
3033 if (!data || !apple_set_server_files(file, data, size))
3034 return FALSE;
3035
3036 WLog_Print(file->log, WLOG_DEBUG, "offering %" PRIuz " server files (%" PRIu32 " entries)",
3037 file->apple_top_level_count, file->apple_files_count);
3038 return cliprdr_file_apple_offer(file->apple_top_level_count, apple_provide_file, file,
3039 mime_types, count, fkt, userdata);
3040#else
3041 WINPR_UNUSED(file);
3042 WINPR_UNUSED(fkt);
3043 WINPR_UNUSED(userdata);
3044 WINPR_UNUSED(mime_types);
3045 WINPR_UNUSED(count);
3046 return FALSE;
3047#endif
3048}
3049
3050char* cliprdr_file_context_get_local_uri_list(CliprdrFileContext* file, size_t* size)
3051{
3052 WINPR_ASSERT(file);
3053 WINPR_ASSERT(size);
3054
3055 *size = 0;
3056#if defined(WITH_CLIPRDR_FILE_APPLE)
3057 return cliprdr_file_apple_get_uri_list(size);
3058#else
3059 WINPR_UNUSED(file);
3060 return nullptr;
3061#endif
3062}
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:61
WINPR_ATTR_NODISCARD OBJECT_NEW_FN fnObjectNew
Definition collections.h:54