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 #define GNU_SOURCE 1
23
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
27
28 #include <glib/gstdio.h>
29 #include <gio/gunixfdlist.h>
30
31 #include "portal-private.h"
32 #include "email.h"
33
34 #ifndef O_PATH
35 #define O_PATH 0
36 #endif
37
38 typedef struct {
39 XdpPortal *portal;
40 XdpParent *parent;
41 char *parent_handle;
42 char **addresses;
43 char **cc;
44 char **bcc;
45 char *subject;
46 char *body;
47 char **attachments;
48 guint signal_id;
49 GTask *task;
50 char *request_path;
51 guint cancelled_id;
52 } EmailCall;
53
54 static void
email_call_free(EmailCall * call)55 email_call_free (EmailCall *call)
56 {
57 if (call->parent)
58 {
59 call->parent->parent_unexport (call->parent);
60 xdp_parent_free (call->parent);
61 }
62 g_free (call->parent_handle);
63
64 if (call->signal_id)
65 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
66
67 if (call->cancelled_id)
68 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
69
70 g_free (call->request_path);
71
72 g_object_unref (call->portal);
73 g_object_unref (call->task);
74
75 g_strfreev (call->addresses);
76 g_strfreev (call->cc);
77 g_strfreev (call->bcc);
78 g_free (call->subject);
79 g_free (call->body);
80 g_strfreev (call->attachments);
81
82 g_free (call);
83 }
84
85
86 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)87 response_received (GDBusConnection *bus,
88 const char *sender_name,
89 const char *object_path,
90 const char *interface_name,
91 const char *signal_name,
92 GVariant *parameters,
93 gpointer data)
94 {
95 EmailCall *call = data;
96 guint32 response;
97 g_autoptr(GVariant) ret = NULL;
98
99 if (call->cancelled_id)
100 {
101 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
102 call->cancelled_id = 0;
103 }
104
105 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
106
107 if (response == 0)
108 g_task_return_boolean (call->task, TRUE);
109 else if (response == 1)
110 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Email canceled");
111 else
112 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Email failed");
113
114 email_call_free (call);
115 }
116
117 static void compose_email (EmailCall *call);
118
119 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)120 parent_exported (XdpParent *parent,
121 const char *handle,
122 gpointer data)
123 {
124 EmailCall *call = data;
125 call->parent_handle = g_strdup (handle);
126 compose_email (call);
127 }
128
129 static void
cancelled_cb(GCancellable * cancellable,gpointer data)130 cancelled_cb (GCancellable *cancellable,
131 gpointer data)
132 {
133 EmailCall *call = data;
134
135 g_debug ("calling Close");
136 g_dbus_connection_call (call->portal->bus,
137 PORTAL_BUS_NAME,
138 call->request_path,
139 REQUEST_INTERFACE,
140 "Close",
141 NULL,
142 NULL,
143 G_DBUS_CALL_FLAGS_NONE,
144 -1,
145 NULL, NULL, NULL);
146
147 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "ComposeEmail call canceled by caller");
148
149 email_call_free (call);
150 }
151
152 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)153 call_returned (GObject *object,
154 GAsyncResult *result,
155 gpointer data)
156 {
157 GDBusConnection *bus = G_DBUS_CONNECTION (object);
158 EmailCall *call = data;
159 GError *error = NULL;
160 g_autoptr(GVariant) ret = NULL;
161
162 ret = g_dbus_connection_call_with_unix_fd_list_finish (bus, NULL, result, &error);
163 if (error)
164 {
165 g_task_return_error (call->task, error);
166 email_call_free (call);
167 }
168 }
169
170 static void
compose_email(EmailCall * call)171 compose_email (EmailCall *call)
172 {
173 GVariantBuilder options;
174 g_autofree char *token = NULL;
175 g_autoptr(GUnixFDList) fd_list = NULL;
176 GCancellable *cancellable;
177 g_autoptr(GVariant) ret = NULL;
178 g_autoptr(GVariant) v = NULL;
179 guint version;
180 g_autoptr(GError) error = NULL;
181
182 if (call->parent_handle == NULL)
183 {
184 call->parent->parent_export (call->parent, parent_exported, call);
185 return;
186 }
187
188 ret = g_dbus_connection_call_sync (call->portal->bus,
189 PORTAL_BUS_NAME,
190 PORTAL_OBJECT_PATH,
191 "org.freedesktop.DBus.Properties",
192 "Get",
193 g_variant_new ("(ss)", "org.freedesktop.portal.Email", "version"),
194 G_VARIANT_TYPE ("(v)"),
195 0,
196 G_MAXINT,
197 NULL,
198 &error);
199 if (!ret)
200 g_warning ("%s", error->message);
201 g_variant_get (ret, "(v)", &v);
202 g_variant_get (v, "u", &version);
203
204 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
205 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
206 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
207 PORTAL_BUS_NAME,
208 REQUEST_INTERFACE,
209 "Response",
210 call->request_path,
211 NULL,
212 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
213 response_received,
214 call,
215 NULL);
216
217 cancellable = g_task_get_cancellable (call->task);
218 if (cancellable)
219 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), call);
220
221 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
222 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
223 if (version >= 3)
224 {
225 if (call->addresses)
226 g_variant_builder_add (&options, "{sv}", "addresses", g_variant_new_strv ((const char *const *)call->addresses, -1));
227 if (call->cc)
228 g_variant_builder_add (&options, "{sv}", "cc", g_variant_new_strv ((const char * const *)call->cc, -1));
229 if (call->bcc)
230 g_variant_builder_add (&options, "{sv}", "bcc", g_variant_new_strv ((const char * const *)call->bcc, -1));
231 }
232 else
233 {
234 if (call->addresses)
235 g_variant_builder_add (&options, "{sv}", "address", g_variant_new_string (call->addresses[0]));
236 }
237
238 if (call->subject)
239 g_variant_builder_add (&options, "{sv}", "subject", g_variant_new_string (call->subject));
240 if (call->body)
241 g_variant_builder_add (&options, "{sv}", "body", g_variant_new_string (call->body));
242 if (call->attachments)
243 {
244 GVariantBuilder attach_fds;
245 int i;
246
247 fd_list = g_unix_fd_list_new ();
248 g_variant_builder_init (&attach_fds, G_VARIANT_TYPE ("ah"));
249
250 for (i = 0; call->attachments[i]; i++)
251 {
252 g_autoptr(GError) error = NULL;
253 int fd;
254 int fd_in;
255
256 fd = g_open (call->attachments[i], O_PATH | O_CLOEXEC);
257 if (fd == -1)
258 {
259 g_warning ("Failed to open %s, skipping", call->attachments[i]);
260 continue;
261 }
262 fd_in = g_unix_fd_list_append (fd_list, fd, &error);
263 if (error)
264 {
265 g_warning ("Failed to add %s to request, skipping: %s", call->attachments[i], error->message);
266 continue;
267 }
268 g_variant_builder_add (&attach_fds, "h", fd_in);
269 }
270
271 g_variant_builder_add (&options, "{sv}", "attachment_fds", g_variant_builder_end (&attach_fds));
272 }
273
274 g_dbus_connection_call_with_unix_fd_list (call->portal->bus,
275 PORTAL_BUS_NAME,
276 PORTAL_OBJECT_PATH,
277 "org.freedesktop.portal.Email",
278 "ComposeEmail",
279 g_variant_new ("(sa{sv})", call->parent_handle, &options),
280 NULL,
281 G_DBUS_CALL_FLAGS_NONE,
282 -1,
283 fd_list,
284 NULL,
285 call_returned,
286 call);
287 }
288
289 /**
290 * xdp_portal_compose_email:
291 * @portal: a [class@Portal]
292 * @parent: (nullable): parent window information
293 * @addresses: (array zero-terminated=1) (nullable): the email addresses to send to
294 * @cc: (array zero-terminated=1) (nullable): the email addresses to cc
295 * @bcc: (array zero-terminated=1) (nullable): the email addresses to bcc
296 * @subject: (nullable): the subject for the email
297 * @body: (nullable): the body for the email
298 * @attachments: (array zero-terminated=1) (nullable): an array of paths for files to attach
299 * @flags: options for this call
300 * @cancellable: (nullable): optional [class@Gio.Cancellable]
301 * @callback: (scope async): a callback to call when the request is done
302 * @data: (closure): data to pass to @callback
303 *
304 * Presents a window that lets the user compose an email,
305 * with some pre-filled information.
306 *
307 * When the request is done, @callback will be called. You can then
308 * call [method@Portal.compose_email_finish] to get the results.
309 */
310 void
xdp_portal_compose_email(XdpPortal * portal,XdpParent * parent,const char * const * addresses,const char * const * cc,const char * const * bcc,const char * subject,const char * body,const char * const * attachments,XdpEmailFlags flags,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)311 xdp_portal_compose_email (XdpPortal *portal,
312 XdpParent *parent,
313 const char *const *addresses,
314 const char *const *cc,
315 const char *const *bcc,
316 const char *subject,
317 const char *body,
318 const char *const *attachments,
319 XdpEmailFlags flags,
320 GCancellable *cancellable,
321 GAsyncReadyCallback callback,
322 gpointer data)
323 {
324 EmailCall *call;
325
326 g_return_if_fail (XDP_IS_PORTAL (portal));
327 g_return_if_fail (flags == XDP_EMAIL_FLAG_NONE);
328
329 call = g_new0 (EmailCall, 1);
330 call->portal = g_object_ref (portal);
331 if (parent)
332 call->parent = xdp_parent_copy (parent);
333 else
334 call->parent_handle = g_strdup ("");
335 call->addresses = g_strdupv ((char**)addresses);
336 call->cc = g_strdupv ((char **)cc);
337 call->bcc = g_strdupv ((char **)bcc);
338 call->subject = g_strdup (subject);
339 call->body = g_strdup (body);
340 call->attachments = g_strdupv ((char **)attachments);
341 call->task = g_task_new (portal, cancellable, callback, data);
342 g_task_set_source_tag (call->task, xdp_portal_compose_email);
343
344 compose_email (call);
345 }
346
347 /**
348 * xdp_portal_compose_email_finish:
349 * @portal: a [class@Portal]
350 * @result: a [iface@Gio.AsyncResult]
351 * @error: return location for an error
352 *
353 * Finishes the compose-email request.
354 *
355 * Returns: `TRUE` if the request was handled successfully
356 */
357 gboolean
xdp_portal_compose_email_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)358 xdp_portal_compose_email_finish (XdpPortal *portal,
359 GAsyncResult *result,
360 GError **error)
361 {
362 g_return_val_if_fail (XDP_IS_PORTAL (portal), FALSE);
363 g_return_val_if_fail (g_task_is_valid (result, portal), FALSE);
364 g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == xdp_portal_compose_email, FALSE);
365
366 return g_task_propagate_boolean (G_TASK (result), error);
367 }
368