1 /*
2 * Copyright (C) 2018, Matthias Clasen
3 *
4 * This file is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation, version 3.0 of the
7 * License.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * 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 program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-3.0-only
18 */
19
20 #include "config.h"
21
22 #include "openuri.h"
23
24 #define GNU_SOURCE 1
25
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include <glib/gstdio.h>
31 #include <gio/gunixfdlist.h>
32
33 #include "portal-private.h"
34
35 typedef struct {
36 XdpPortal *portal;
37 XdpParent *parent;
38 char *parent_handle;
39 char *uri;
40 gboolean ask;
41 gboolean writable;
42 gboolean open_dir;
43 guint signal_id;
44 GTask *task;
45 char *request_path;
46 guint cancelled_id;
47 } OpenCall;
48
49 static void
open_call_free(OpenCall * call)50 open_call_free (OpenCall *call)
51 {
52 if (call->parent)
53 {
54 call->parent->parent_unexport (call->parent);
55 xdp_parent_free (call->parent);
56 }
57 g_free (call->parent_handle);
58
59 if (call->signal_id)
60 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
61
62 if (call->cancelled_id)
63 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
64
65 g_free (call->request_path);
66
67 g_object_unref (call->portal);
68 g_object_unref (call->task);
69 g_free (call->uri);
70
71 g_free (call);
72 }
73
74 static void
response_received(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)75 response_received (GDBusConnection *bus,
76 const char *sender_name,
77 const char *object_path,
78 const char *interface_name,
79 const char *signal_name,
80 GVariant *parameters,
81 gpointer data)
82 {
83 OpenCall *call = data;
84 guint32 response;
85 g_autoptr(GVariant) ret = NULL;
86
87 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
88
89 if (response == 0)
90 g_task_return_boolean (call->task, TRUE);
91 else if (response == 1)
92 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "OpenURI canceled");
93 else
94 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "OpenURI failed");
95
96 open_call_free (call);
97 }
98
99 static void do_open (OpenCall *call);
100
101 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)102 parent_exported (XdpParent *parent,
103 const char *handle,
104 gpointer data)
105 {
106 OpenCall *call = data;
107 call->parent_handle = g_strdup (handle);
108 do_open (call);
109 }
110
111 static void
cancelled_cb(GCancellable * cancellable,gpointer data)112 cancelled_cb (GCancellable *cancellable,
113 gpointer data)
114 {
115 OpenCall *call = data;
116
117 g_dbus_connection_call (call->portal->bus,
118 PORTAL_BUS_NAME,
119 call->request_path,
120 REQUEST_INTERFACE,
121 "Close",
122 NULL,
123 NULL,
124 G_DBUS_CALL_FLAGS_NONE,
125 -1,
126 NULL, NULL, NULL);
127
128 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "OpenURI call canceled by caller");
129
130 open_call_free (call);
131 }
132
133 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)134 call_returned (GObject *object,
135 GAsyncResult *result,
136 gpointer data)
137 {
138 OpenCall *call = data;
139 GError *error = NULL;
140 g_autoptr(GVariant) ret = NULL;
141 g_autoptr(GFile) file = NULL;
142
143 file = g_file_new_for_uri (call->uri);
144 if (g_file_is_native (file))
145 ret = g_dbus_connection_call_with_unix_fd_list_finish (G_DBUS_CONNECTION (object), NULL, result, &error);
146 else
147 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
148
149 if (error)
150 {
151 g_task_return_error (call->task, error);
152 open_call_free (call);
153 }
154 }
155
156 #ifndef O_PATH
157 #define O_PATH 0
158 #endif
159
160 static void
do_open(OpenCall * call)161 do_open (OpenCall *call)
162 {
163 GVariantBuilder options;
164 g_autofree char *token = NULL;
165 g_autoptr(GFile) file = NULL;
166 GCancellable *cancellable;
167
168 if (call->parent_handle == NULL)
169 {
170 call->parent->parent_export (call->parent, parent_exported, call);
171 return;
172 }
173
174 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
175 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
176 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
177 PORTAL_BUS_NAME,
178 REQUEST_INTERFACE,
179 "Response",
180 call->request_path,
181 NULL,
182 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
183 response_received,
184 call,
185 NULL);
186
187 cancellable = g_task_get_cancellable (call->task);
188 if (cancellable)
189 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
190
191 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
192 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
193 g_variant_builder_add (&options, "{sv}", "writable", g_variant_new_boolean (call->writable));
194 g_variant_builder_add (&options, "{sv}", "ask", g_variant_new_boolean (call->ask));
195
196 file = g_file_new_for_uri (call->uri);
197
198 if (g_file_is_native (file))
199 {
200 g_autoptr(GUnixFDList) fd_list = NULL;
201 g_autofree char *path = NULL;
202 int fd, fd_in, flags;
203
204 path = g_file_get_path (file);
205
206 if (call->writable)
207 flags = O_RDWR | O_CLOEXEC;
208 else
209 flags = O_RDONLY | O_CLOEXEC;
210
211 fd = g_open (path, flags);
212 if (fd == -1)
213 {
214 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to open '%s'", call->uri);
215 open_call_free (call);
216
217 return;
218 }
219
220 fd_list = g_unix_fd_list_new_from_array (&fd, 1);
221 fd = -1;
222 fd_in = 0;
223
224 g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
225 PORTAL_BUS_NAME,
226 PORTAL_OBJECT_PATH,
227 "org.freedesktop.portal.OpenURI",
228 call->open_dir ? "OpenDirectory" : "OpenFile",
229 g_variant_new ("(sha{sv})", call->parent_handle, fd_in, &options),
230 NULL,
231 G_DBUS_CALL_FLAGS_NONE,
232 -1,
233 fd_list,
234 NULL,
235 call_returned,
236 call);
237 }
238 else
239 {
240 g_dbus_connection_call (call->portal->bus,
241 PORTAL_BUS_NAME,
242 PORTAL_OBJECT_PATH,
243 "org.freedesktop.portal.OpenURI",
244 "OpenURI",
245 g_variant_new ("(ssa{sv})", call->parent_handle, call->uri, &options),
246 NULL,
247 G_DBUS_CALL_FLAGS_NONE,
248 -1,
249 NULL,
250 call_returned,
251 call);
252 }
253 }
254
255 /**
256 * xdp_portal_open_uri:
257 * @portal: a [class@Portal]
258 * @parent: parent window information
259 * @uri: the URI to open
260 * @flags: options for this call
261 * @cancellable: (nullable): optional [class@Gio.Cancellable]
262 * @callback: (scope async): a callback to call when the request is done
263 * @data: (closure): data to pass to @callback
264 *
265 * Opens @uri with an external hamdler.
266 */
267 void
xdp_portal_open_uri(XdpPortal * portal,XdpParent * parent,const char * uri,XdpOpenUriFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)268 xdp_portal_open_uri (XdpPortal *portal,
269 XdpParent *parent,
270 const char *uri,
271 XdpOpenUriFlags flags,
272 GCancellable *cancellable,
273 GAsyncReadyCallback callback,
274 gpointer data)
275 {
276 OpenCall *call = NULL;
277
278 g_return_if_fail (XDP_IS_PORTAL (portal));
279 g_return_if_fail ((flags & ~(XDP_OPEN_URI_FLAG_ASK |
280 XDP_OPEN_URI_FLAG_WRITABLE)) == 0);
281
282 call = g_new0 (OpenCall, 1);
283 call->portal = g_object_ref (portal);
284 if (parent)
285 call->parent = xdp_parent_copy (parent);
286 else
287 call->parent_handle = g_strdup ("");
288 call->uri = g_strdup (uri);
289 call->ask = (flags & XDP_OPEN_URI_FLAG_ASK) != 0;
290 call->writable = (flags & XDP_OPEN_URI_FLAG_WRITABLE) != 0;
291 call->open_dir = FALSE;
292 call->task = g_task_new (portal, cancellable, callback, data);
293 g_task_set_source_tag (call->task, xdp_portal_open_uri);
294
295 do_open (call);
296 }
297
298 /**
299 * xdp_portal_open_uri_finish:
300 * @portal: a [class@Portal]
301 * @result: a [iface@Gio.AsyncResult]
302 * @error: return location for an error
303 *
304 * Finishes the open-uri request, and returns
305 * the result in the form of a boolean.
306 *
307 * Returns: `TRUE` if the call succeeded
308 */
309 gboolean
xdp_portal_open_uri_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)310 xdp_portal_open_uri_finish (XdpPortal *portal,
311 GAsyncResult *result,
312 GError **error)
313 {
314 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
315 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
316 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_open_uri, FALSE);
317
318 return g_task_propagate_boolean (G_TASK (result), error);
319 }
320
321 /**
322 * xdp_portal_open_directory:
323 * @portal: a [class@Portal]
324 * @parent: parent window information
325 * @uri: the URI to open
326 * @flags: options for this call
327 * @cancellable: (nullable): optional [class@Gio.Cancellable]
328 * @callback: (scope async): a callback to call when the request is done
329 * @data: (closure): data to pass to @callback
330 *
331 * Opens the directory containing the file specified by the @uri. which
332 * must be a file: uri pointing to a file that the application has access
333 * to.
334 */
335 void
xdp_portal_open_directory(XdpPortal * portal,XdpParent * parent,const char * uri,XdpOpenUriFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)336 xdp_portal_open_directory (XdpPortal *portal,
337 XdpParent *parent,
338 const char *uri,
339 XdpOpenUriFlags flags,
340 GCancellable *cancellable,
341 GAsyncReadyCallback callback,
342 gpointer data)
343 {
344 OpenCall *call = NULL;
345
346 g_return_if_fail (XDP_IS_PORTAL (portal));
347 g_return_if_fail ((flags & ~(XDP_OPEN_URI_FLAG_ASK)) == 0);
348
349 call = g_new0 (OpenCall, 1);
350 call->portal = g_object_ref (portal);
351 if (parent)
352 call->parent = xdp_parent_copy (parent);
353 else
354 call->parent_handle = g_strdup ("");
355 call->uri = g_strdup (uri);
356 call->ask = (flags & XDP_OPEN_URI_FLAG_ASK) != 0;
357 call->writable = FALSE;
358 call->open_dir = TRUE;
359 call->task = g_task_new (portal, cancellable, callback, data);
360 g_task_set_source_tag (call->task, xdp_portal_open_directory);
361
362 do_open (call);
363 }
364
365 /**
366 * xdp_portal_open_directory_finish:
367 * @portal: a [class@Portal]
368 * @result: a [iface@Gio.AsyncResult]
369 * @error: return location for an error
370 *
371 * Finishes the open-directory request, and returns
372 * the result in the form of a boolean.
373 *
374 * Returns: `TRUE` if the call succeeded
375 */
376 gboolean
xdp_portal_open_directory_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)377 xdp_portal_open_directory_finish (XdpPortal *portal,
378 GAsyncResult *result,
379 GError **error)
380 {
381 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
382 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
383 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_open_directory, FALSE);
384
385 return g_task_propagate_boolean (G_TASK (result), error);
386 }
387