1/*
2 * Copyright (C) 2018 Matthias Clasen
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include <errno.h>
21
22#include <sys/types.h>
23#include <sys/stat.h>
24#include <fcntl.h>
25
26#include "gdkcontentformats.h"
27#include "gdkcontentserializer.h"
28#include "gdkcontentdeserializer.h"
29
30#include <gio/gio.h>
31
32#ifdef G_OS_UNIX
33
34#include <gio/gunixfdlist.h>
35
36#ifndef O_PATH
37#define O_PATH 0
38#endif
39
40#ifndef O_CLOEXEC
41#define O_CLOEXEC 0
42#else
43#define HAVE_O_CLOEXEC 1
44#endif
45
46#include "filetransferportalprivate.h"
47
48static GDBusProxy *file_transfer_proxy = NULL;
49
50typedef struct {
51 GTask *task;
52 const char **files;
53 int len;
54 int start;
55} AddFileData;
56
57static void add_files (GDBusProxy *proxy,
58 AddFileData *afd);
59
60static void
61add_files_done (GObject *object,
62 GAsyncResult *result,
63 gpointer data)
64{
65 GDBusProxy *proxy = G_DBUS_PROXY (object);
66 AddFileData *afd = data;
67 GError *error = NULL;
68 GVariant *ret;
69
70 ret = g_dbus_proxy_call_with_unix_fd_list_finish (proxy, NULL, res: result, error: &error);
71 if (ret == NULL)
72 {
73 g_task_return_error (task: afd->task, error);
74 g_object_unref (object: afd->task);
75 g_free (mem: afd);
76 return;
77 }
78
79 g_variant_unref (value: ret);
80
81 if (afd->start >= afd->len)
82 {
83 g_task_return_boolean (task: afd->task, TRUE);
84 g_object_unref (object: afd->task);
85 g_free (mem: afd);
86 return;
87 }
88
89 add_files (proxy, afd);
90}
91
92/* We call AddFiles in chunks of 16 to avoid running into
93 * the per-message fd limit of the bus.
94 */
95static void
96add_files (GDBusProxy *proxy,
97 AddFileData *afd)
98{
99 GUnixFDList *fd_list;
100 GVariantBuilder fds, options;
101 int i;
102 char *key;
103
104 g_variant_builder_init (builder: &fds, G_VARIANT_TYPE ("ah"));
105 fd_list = g_unix_fd_list_new ();
106
107 for (i = 0; afd->files[afd->start + i]; i++)
108 {
109 int fd;
110 int fd_in;
111 GError *error = NULL;
112
113 if (i == 16)
114 break;
115
116 fd = open (file: afd->files[afd->start + i], O_PATH | O_CLOEXEC);
117 if (fd == -1)
118 {
119 g_task_return_new_error (task: afd->task, G_IO_ERROR, code: g_io_error_from_errno (errno),
120 format: "Failed to open %s", afd->files[afd->start + i]);
121 g_object_unref (object: afd->task);
122 g_free (mem: afd);
123 g_object_unref (object: fd_list);
124 return;
125 }
126
127#ifndef HAVE_O_CLOEXEC
128 fcntl (fd, F_SETFD, FD_CLOEXEC);
129#endif
130 fd_in = g_unix_fd_list_append (list: fd_list, fd, error: &error);
131 close (fd: fd);
132
133 if (fd_in == -1)
134 {
135 g_task_return_error (task: afd->task, error);
136 g_object_unref (object: afd->task);
137 g_free (mem: afd);
138 g_object_unref (object: fd_list);
139 return;
140 }
141
142 g_variant_builder_add (builder: &fds, format_string: "h", fd_in);
143 }
144
145 afd->start += 16;
146
147 key = (char *)g_object_get_data (G_OBJECT (afd->task), key: "key");
148
149 g_variant_builder_init (builder: &options, G_VARIANT_TYPE_VARDICT);
150 g_dbus_proxy_call_with_unix_fd_list (proxy,
151 method_name: "AddFiles",
152 parameters: g_variant_new (format_string: "(saha{sv})", key, &fds, &options),
153 flags: 0, timeout_msec: -1,
154 fd_list,
155 NULL,
156 callback: add_files_done, user_data: afd);
157
158 g_object_unref (object: fd_list);
159}
160
161static void
162start_session_done (GObject *object,
163 GAsyncResult *result,
164 gpointer data)
165{
166 GDBusProxy *proxy = G_DBUS_PROXY (object);
167 AddFileData *afd = data;
168 GError *error = NULL;
169 GVariant *ret;
170 const char *key;
171
172 ret = g_dbus_proxy_call_finish (proxy, res: result, error: &error);
173 if (ret == NULL)
174 {
175 g_task_return_error (task: afd->task, error);
176 g_object_unref (object: afd->task);
177 g_free (mem: afd);
178 return;
179 }
180
181 g_variant_get (value: ret, format_string: "(&s)", &key);
182
183 g_object_set_data_full (G_OBJECT (afd->task), key: "key", data: g_strdup (str: key), destroy: g_free);
184
185 g_variant_unref (value: ret);
186
187 add_files (proxy, afd);
188}
189
190void
191file_transfer_portal_register_files (const char **files,
192 gboolean writable,
193 GAsyncReadyCallback callback,
194 gpointer data)
195{
196 GTask *task;
197 AddFileData *afd;
198 GVariantBuilder options;
199
200 task = g_task_new (NULL, NULL, callback, callback_data: data);
201
202 if (file_transfer_proxy == NULL)
203 {
204 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
205 format: "No portal found");
206 g_object_unref (object: task);
207 return;
208 }
209
210 afd = g_new (AddFileData, 1);
211 afd->task = task;
212 afd->files = files;
213 afd->len = g_strv_length (str_array: (char **)files);
214 afd->start = 0;
215
216 g_variant_builder_init (builder: &options, G_VARIANT_TYPE_VARDICT);
217 g_variant_builder_add (builder: &options, format_string: "{sv}", "writable", g_variant_new_boolean (value: writable));
218 g_variant_builder_add (builder: &options, format_string: "{sv}", "autostop", g_variant_new_boolean (TRUE));
219
220 g_dbus_proxy_call (proxy: file_transfer_proxy, method_name: "StartTransfer",
221 parameters: g_variant_new (format_string: "(a{sv})", &options),
222 flags: 0, timeout_msec: -1, NULL, callback: start_session_done, user_data: afd);
223}
224
225gboolean
226file_transfer_portal_register_files_finish (GAsyncResult *result,
227 char **key,
228 GError **error)
229{
230 if (g_task_propagate_boolean (G_TASK (result), error))
231 {
232 *key = g_strdup (str: g_object_get_data (G_OBJECT (result), key: "key"));
233 return TRUE;
234 }
235
236 return FALSE;
237}
238
239static void
240retrieve_files_done (GObject *object,
241 GAsyncResult *result,
242 gpointer data)
243{
244 GDBusProxy *proxy = G_DBUS_PROXY (object);
245 GTask *task = data;
246 GError *error = NULL;
247 GVariant *ret;
248 char **files;
249
250 ret = g_dbus_proxy_call_finish (proxy, res: result, error: &error);
251 if (ret == NULL)
252 {
253 g_task_return_error (task, error);
254 g_object_unref (object: task);
255 return;
256 }
257
258 g_variant_get (value: ret, format_string: "(^a&s)", &files);
259
260 g_object_set_data_full (G_OBJECT (task), key: "files", data: g_strdupv (str_array: files), destroy: (GDestroyNotify)g_strfreev);
261
262 g_variant_unref (value: ret);
263
264 g_task_return_boolean (task, TRUE);
265}
266
267void
268file_transfer_portal_retrieve_files (const char *key,
269 GAsyncReadyCallback callback,
270 gpointer data)
271{
272 GTask *task;
273 GVariantBuilder options;
274
275 task = g_task_new (NULL, NULL, callback, callback_data: data);
276
277 if (file_transfer_proxy == NULL)
278 {
279 g_task_return_new_error (task, G_IO_ERROR, code: G_IO_ERROR_NOT_SUPPORTED,
280 format: "No portal found");
281 g_object_unref (object: task);
282 return;
283 }
284
285 g_variant_builder_init (builder: &options, G_VARIANT_TYPE_VARDICT);
286 g_dbus_proxy_call (proxy: file_transfer_proxy,
287 method_name: "RetrieveFiles",
288 parameters: g_variant_new (format_string: "(sa{sv})", key, &options),
289 flags: 0, timeout_msec: -1, NULL,
290 callback: retrieve_files_done, user_data: task);
291}
292
293gboolean
294file_transfer_portal_retrieve_files_finish (GAsyncResult *result,
295 char ***files,
296 GError **error)
297{
298 if (g_task_propagate_boolean (G_TASK (result), error))
299 {
300 *files = g_strdupv (str_array: g_object_get_data (G_OBJECT (result), key: "files"));
301 return TRUE;
302 }
303
304 return FALSE;
305}
306
307
308/* serializer */
309
310static void
311file_serializer_finish (GObject *source,
312 GAsyncResult *result,
313 gpointer serializer)
314{
315 GOutputStream *stream = G_OUTPUT_STREAM (source);
316 GError *error = NULL;
317
318 if (!g_output_stream_write_all_finish (stream, result, NULL, error: &error))
319 gdk_content_serializer_return_error (serializer, error);
320 else
321 gdk_content_serializer_return_success (serializer);
322}
323
324static void
325portal_ready (GObject *object,
326 GAsyncResult *result,
327 gpointer serializer)
328{
329 GError *error = NULL;
330 char *key;
331
332 if (!file_transfer_portal_register_files_finish (result, key: &key, error: &error))
333 {
334 gdk_content_serializer_return_error (serializer, error);
335 return;
336 }
337
338 g_output_stream_write_all_async (stream: gdk_content_serializer_get_output_stream (serializer),
339 buffer: key,
340 count: strlen (s: key) + 1,
341 io_priority: gdk_content_serializer_get_priority (serializer),
342 cancellable: gdk_content_serializer_get_cancellable (serializer),
343 callback: file_serializer_finish,
344 user_data: serializer);
345 gdk_content_serializer_set_task_data (serializer, data: key, notify: g_free);
346}
347
348static void
349portal_file_serializer (GdkContentSerializer *serializer)
350{
351 GFile *file;
352 const GValue *value;
353 GPtrArray *files;
354
355 files = g_ptr_array_new_with_free_func (element_free_func: g_free);
356
357 value = gdk_content_serializer_get_value (serializer);
358
359 if (G_VALUE_HOLDS (value, G_TYPE_FILE))
360 {
361 file = g_value_get_object (value: gdk_content_serializer_get_value (serializer));
362 if (file)
363 g_ptr_array_add (array: files, data: g_file_get_path (file));
364 g_ptr_array_add (array: files, NULL);
365 }
366 else if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST))
367 {
368 GSList *l;
369
370 for (l = g_value_get_boxed (value); l; l = l->next)
371 g_ptr_array_add (array: files, data: g_file_get_path (file: l->data));
372
373 g_ptr_array_add (array: files, NULL);
374 }
375
376 /* this call doesn't copy the strings, so keep the array around until the registration is done */
377 file_transfer_portal_register_files (files: (const char **)files->pdata, TRUE, callback: portal_ready, data: serializer);
378 gdk_content_serializer_set_task_data (serializer, data: files, notify: (GDestroyNotify)g_ptr_array_unref);
379}
380
381/* deserializer */
382
383static void
384portal_finish (GObject *object,
385 GAsyncResult *result,
386 gpointer deserializer)
387{
388 char **files = NULL;
389 GError *error = NULL;
390 GValue *value;
391
392 if (!file_transfer_portal_retrieve_files_finish (result, files: &files, error: &error))
393 {
394 gdk_content_deserializer_return_error (deserializer, error);
395 return;
396 }
397
398 value = gdk_content_deserializer_get_value (deserializer);
399 if (G_VALUE_HOLDS (value, G_TYPE_FILE))
400 {
401 if (files[0] != NULL)
402 g_value_take_object (value, v_object: g_file_new_for_path (path: files[0]));
403 }
404 else
405 {
406 GSList *l = NULL;
407 gsize i;
408
409 for (i = 0; files[i] != NULL; i++)
410 l = g_slist_prepend (list: l, data: g_file_new_for_path (path: files[i]));
411 g_value_take_boxed (value, v_boxed: g_slist_reverse (list: l));
412 }
413 g_strfreev (str_array: files);
414
415 gdk_content_deserializer_return_success (deserializer);
416}
417
418static void
419portal_file_deserializer_finish (GObject *source,
420 GAsyncResult *result,
421 gpointer deserializer)
422{
423 GOutputStream *stream = G_OUTPUT_STREAM (source);
424 GError *error = NULL;
425 gssize written;
426 char *key;
427
428 written = g_output_stream_splice_finish (stream, result, error: &error);
429 if (written < 0)
430 {
431 gdk_content_deserializer_return_error (deserializer, error);
432 return;
433 }
434
435 /* write terminating NULL */
436 if (!g_output_stream_write (stream, buffer: "", count: 1, NULL, error: &error))
437 {
438 gdk_content_deserializer_return_error (deserializer, error);
439 return;
440 }
441
442 key = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (stream));
443 if (key == NULL)
444 {
445 GError *gerror = g_error_new (G_IO_ERROR,
446 code: G_IO_ERROR_NOT_FOUND,
447 format: "Could not convert data from %s to %s",
448 gdk_content_deserializer_get_mime_type (deserializer),
449 g_type_name (type: gdk_content_deserializer_get_gtype (deserializer)));
450 gdk_content_deserializer_return_error (deserializer, error: gerror);
451 return;
452 }
453
454 file_transfer_portal_retrieve_files (key, callback: portal_finish, data: deserializer);
455 gdk_content_deserializer_set_task_data (deserializer, data: key, notify: g_free);
456}
457
458static void
459portal_file_deserializer (GdkContentDeserializer *deserializer)
460{
461 GOutputStream *output;
462
463 output = g_memory_output_stream_new_resizable ();
464
465 g_output_stream_splice_async (stream: output,
466 source: gdk_content_deserializer_get_input_stream (deserializer),
467 flags: G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
468 io_priority: gdk_content_deserializer_get_priority (deserializer),
469 cancellable: gdk_content_deserializer_get_cancellable (deserializer),
470 callback: portal_file_deserializer_finish,
471 user_data: deserializer);
472 g_object_unref (object: output);
473}
474
475static void
476connection_closed (GDBusConnection *connection,
477 gboolean remote_peer_vanished,
478 GError *error)
479{
480 g_clear_object (&file_transfer_proxy);
481}
482
483static void
484finish_registration (void)
485{
486 gdk_content_register_serializer (G_TYPE_FILE,
487 mime_type: "application/vnd.portal.filetransfer",
488 serialize: portal_file_serializer,
489 NULL,
490 NULL);
491
492 gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
493 mime_type: "application/vnd.portal.filetransfer",
494 serialize: portal_file_serializer,
495 NULL,
496 NULL);
497
498 gdk_content_register_deserializer (mime_type: "application/vnd.portal.filetransfer",
499 GDK_TYPE_FILE_LIST,
500 deserialize: portal_file_deserializer,
501 NULL,
502 NULL);
503
504 gdk_content_register_deserializer (mime_type: "application/vnd.portal.filetransfer",
505 G_TYPE_FILE,
506 deserialize: portal_file_deserializer,
507 NULL,
508 NULL);
509
510 /* FIXME: I missed up and used the wrong mime type here when
511 * I implemented my own protocol. Keep these around for a while
512 * so we can interoperate with existing flatpaks using GTK 4.6
513 */
514 gdk_content_register_serializer (G_TYPE_FILE,
515 mime_type: "application/vnd.portal.files",
516 serialize: portal_file_serializer,
517 NULL,
518 NULL);
519
520 gdk_content_register_serializer (GDK_TYPE_FILE_LIST,
521 mime_type: "application/vnd.portal.files",
522 serialize: portal_file_serializer,
523 NULL,
524 NULL);
525
526 gdk_content_register_deserializer (mime_type: "application/vnd.portal.files",
527 GDK_TYPE_FILE_LIST,
528 deserialize: portal_file_deserializer,
529 NULL,
530 NULL);
531
532 gdk_content_register_deserializer (mime_type: "application/vnd.portal.files",
533 G_TYPE_FILE,
534 deserialize: portal_file_deserializer,
535 NULL,
536 NULL);
537
538 /* Free the singleton when the connection closes, important for test */
539 g_signal_connect (g_dbus_proxy_get_connection (G_DBUS_PROXY (file_transfer_proxy)),
540 "closed", G_CALLBACK (connection_closed), NULL);
541}
542
543static gboolean
544proxy_has_owner (GDBusProxy *proxy)
545{
546 char *owner;
547
548 owner = g_dbus_proxy_get_name_owner (proxy);
549 if (owner)
550 {
551 g_free (mem: owner);
552 return TRUE;
553 }
554
555 return FALSE;
556}
557
558void
559file_transfer_portal_register (void)
560{
561 static gboolean called;
562
563 if (!called)
564 {
565 called = TRUE;
566
567 file_transfer_proxy = g_dbus_proxy_new_for_bus_sync (bus_type: G_BUS_TYPE_SESSION,
568 flags: G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
569 | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS
570 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
571 NULL,
572 name: "org.freedesktop.portal.Documents",
573 object_path: "/org/freedesktop/portal/documents",
574 interface_name: "org.freedesktop.portal.FileTransfer",
575 NULL,
576 NULL);
577
578 if (file_transfer_proxy && !proxy_has_owner (proxy: file_transfer_proxy))
579 g_clear_object (&file_transfer_proxy);
580
581 if (file_transfer_proxy)
582 finish_registration ();
583 }
584}
585
586
587#endif /* G_OS_UNIX */
588

source code of gtk/gdk/filetransferportal.c