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