1 /*
2 * e-mail-request.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include <libsoup/soup.h>
21
22 #include <glib/gi18n.h>
23 #include <camel/camel.h>
24 #include <libedataserver/libedataserver.h>
25
26 #include "shell/e-shell.h"
27
28 #include "em-format/e-mail-formatter.h"
29 #include "em-format/e-mail-formatter-utils.h"
30 #include "em-format/e-mail-formatter-print.h"
31
32 #include "em-utils.h"
33 #include "e-mail-display.h"
34 #include "e-mail-ui-session.h"
35 #include "e-mail-request.h"
36
37 #define d(x)
38
39 struct _EMailRequestPrivate {
40 gint scale_factor;
41 };
42
43 enum {
44 PROP_0,
45 PROP_SCALE_FACTOR
46 };
47
48 static void e_mail_request_content_request_init (EContentRequestInterface *iface);
49
G_DEFINE_TYPE_WITH_CODE(EMailRequest,e_mail_request,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (E_TYPE_CONTENT_REQUEST,e_mail_request_content_request_init))50 G_DEFINE_TYPE_WITH_CODE (EMailRequest, e_mail_request, G_TYPE_OBJECT,
51 G_IMPLEMENT_INTERFACE (E_TYPE_CONTENT_REQUEST, e_mail_request_content_request_init))
52
53 static gboolean
54 e_mail_request_can_process_uri (EContentRequest *request,
55 const gchar *uri)
56 {
57 g_return_val_if_fail (E_IS_MAIL_REQUEST (request), FALSE);
58 g_return_val_if_fail (uri != NULL, FALSE);
59
60 return g_ascii_strncasecmp (uri, "mail:", 5) == 0;
61 }
62
63 static void
save_gicon_to_stream(GIcon * icon,gint size,GOutputStream * output_stream,gchar ** out_mime_type)64 save_gicon_to_stream (GIcon *icon,
65 gint size,
66 GOutputStream *output_stream,
67 gchar **out_mime_type)
68 {
69 GtkIconInfo *icon_info;
70 GdkPixbuf *pixbuf;
71
72 if (size < 16)
73 size = 16;
74
75 icon_info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (), icon, size, GTK_ICON_LOOKUP_FORCE_SIZE);
76 if (!icon_info)
77 return;
78
79 pixbuf = gtk_icon_info_load_icon (icon_info, NULL);
80 if (pixbuf) {
81 if (gdk_pixbuf_save_to_stream (
82 pixbuf, output_stream,
83 "png", NULL, NULL, NULL)) {
84 *out_mime_type = g_strdup ("image/png");
85 }
86 g_object_unref (pixbuf);
87 }
88
89 g_object_unref (icon);
90 }
91
92 static gboolean
mail_request_process_mail_sync(EContentRequest * request,SoupURI * suri,GHashTable * uri_query,GObject * requester,GInputStream ** out_stream,gint64 * out_stream_length,gchar ** out_mime_type,GCancellable * cancellable,GError ** error)93 mail_request_process_mail_sync (EContentRequest *request,
94 SoupURI *suri,
95 GHashTable *uri_query,
96 GObject *requester,
97 GInputStream **out_stream,
98 gint64 *out_stream_length,
99 gchar **out_mime_type,
100 GCancellable *cancellable,
101 GError **error)
102 {
103 EMailFormatter *formatter;
104 EMailPartList *part_list;
105 CamelObjectBag *registry;
106 GOutputStream *output_stream;
107 GBytes *bytes;
108 gchar *tmp, *use_mime_type = NULL;
109 const gchar *val;
110 const gchar *default_charset, *charset;
111 gboolean part_converted_to_utf8 = FALSE;
112
113 EMailFormatterContext context = { 0 };
114
115 if (g_cancellable_set_error_if_cancelled (cancellable, error))
116 return FALSE;
117
118 tmp = g_strdup_printf ("%s://%s%s", suri->scheme, suri->host, suri->path);
119
120 registry = e_mail_part_list_get_registry ();
121 part_list = camel_object_bag_get (registry, tmp);
122
123 g_free (tmp);
124
125 if (!part_list && E_IS_MAIL_DISPLAY (requester) &&
126 e_mail_display_get_mode (E_MAIL_DISPLAY (requester)) == E_MAIL_FORMATTER_MODE_SOURCE) {
127 part_list = e_mail_display_get_part_list (E_MAIL_DISPLAY (requester));
128 if (part_list)
129 g_object_ref (part_list);
130 }
131
132 context.uri = soup_uri_to_string (suri, FALSE);
133
134 if (camel_debug_start ("emformat:requests")) {
135 printf ("%s: found part-list %p for full_uri '%s'\n", G_STRFUNC, part_list, context.uri);
136 camel_debug_end ();
137 }
138
139 if (!part_list) {
140 g_free (context.uri);
141 return FALSE;
142 }
143
144 val = uri_query ? g_hash_table_lookup (uri_query, "headers_collapsed") : NULL;
145 if (val != NULL && atoi (val) == 1)
146 context.flags |= E_MAIL_FORMATTER_HEADER_FLAG_COLLAPSED;
147
148 val = uri_query ? g_hash_table_lookup (uri_query, "headers_collapsable") : NULL;
149 if (val != NULL && atoi (val) == 1)
150 context.flags |= E_MAIL_FORMATTER_HEADER_FLAG_COLLAPSABLE;
151
152 val = uri_query ? g_hash_table_lookup (uri_query, "mode") : NULL;
153 if (val != NULL)
154 context.mode = atoi (val);
155
156 default_charset = uri_query ? g_hash_table_lookup (uri_query, "formatter_default_charset") : NULL;
157 charset = uri_query ? g_hash_table_lookup (uri_query, "formatter_charset") : NULL;
158
159 context.part_list = g_object_ref (part_list);
160
161 if (context.mode == E_MAIL_FORMATTER_MODE_PRINTING)
162 formatter = e_mail_formatter_print_new ();
163 else if (E_IS_MAIL_DISPLAY (requester))
164 formatter = g_object_ref (e_mail_display_get_formatter (E_MAIL_DISPLAY (requester)));
165 else
166 formatter = e_mail_formatter_new ();
167
168 if (default_charset != NULL && *default_charset != '\0')
169 e_mail_formatter_set_default_charset (formatter, default_charset);
170 if (charset != NULL && *charset != '\0')
171 e_mail_formatter_set_charset (formatter, charset);
172
173 output_stream = g_memory_output_stream_new_resizable ();
174
175 val = uri_query ? g_hash_table_lookup (uri_query, "attachment_icon") : NULL;
176 if (val) {
177 gchar *attachment_id;
178
179 attachment_id = soup_uri_decode (val);
180 if (E_IS_MAIL_DISPLAY (requester)) {
181 EMailDisplay *mail_display = E_MAIL_DISPLAY (requester);
182 EAttachmentStore *attachment_store;
183 GList *attachments, *link;
184
185 attachment_store = e_mail_display_get_attachment_store (mail_display);
186 attachments = e_attachment_store_get_attachments (attachment_store);
187 for (link = attachments; link; link = g_list_next (link)) {
188 EAttachment *attachment = link->data;
189 gboolean can_use;
190
191 tmp = g_strdup_printf ("%p", attachment);
192 can_use = g_strcmp0 (tmp, attachment_id) == 0;
193 g_free (tmp);
194
195 if (can_use) {
196 GtkTreeIter iter;
197
198 if (e_attachment_store_find_attachment_iter (attachment_store, attachment, &iter)) {
199 GIcon *icon = NULL;
200
201 gtk_tree_model_get (GTK_TREE_MODEL (attachment_store), &iter,
202 E_ATTACHMENT_STORE_COLUMN_ICON, &icon,
203 -1);
204
205 if (icon) {
206 const gchar *size = g_hash_table_lookup (uri_query, "size");
207 gint scale_factor;
208
209 if (!size)
210 size = "16";
211
212 scale_factor = e_mail_request_get_scale_factor (E_MAIL_REQUEST (request));
213
214 if (scale_factor < 1)
215 scale_factor = 1;
216
217 save_gicon_to_stream (icon, atoi (size) * scale_factor, output_stream, &use_mime_type);
218 }
219 }
220
221 break;
222 }
223 }
224
225 g_list_free_full (attachments, g_object_unref);
226 }
227
228 g_free (attachment_id);
229
230 goto no_part;
231 }
232
233 val = uri_query ? g_hash_table_lookup (uri_query, "part_id") : NULL;
234 if (val != NULL) {
235 EMailPart *part;
236 const gchar *mime_type;
237 gchar *part_id;
238
239 part_id = soup_uri_decode (val);
240 part = e_mail_part_list_ref_part (part_list, part_id);
241 if (!part) {
242 if (camel_debug_start ("emformat:requests")) {
243 printf ("%s: part with id '%s' not found\n", G_STRFUNC, part_id);
244 camel_debug_end ();
245 }
246
247 g_free (part_id);
248 goto no_part;
249 }
250 g_free (part_id);
251
252 mime_type = g_hash_table_lookup (uri_query, "mime_type");
253
254 if (context.mode == E_MAIL_FORMATTER_MODE_SOURCE)
255 mime_type = "application/vnd.evolution.source";
256
257 if (mime_type == NULL)
258 mime_type = e_mail_part_get_mime_type (part);
259
260 e_mail_formatter_format_as (
261 formatter, &context, part,
262 output_stream, mime_type,
263 cancellable);
264
265 part_converted_to_utf8 = e_mail_part_get_converted_to_utf8 (part);
266
267 g_object_unref (part);
268
269 } else {
270 e_mail_formatter_format_sync (
271 formatter, part_list, output_stream,
272 context.flags, context.mode, cancellable);
273 }
274
275 no_part:
276 g_clear_object (&context.part_list);
277
278 g_output_stream_close (output_stream, NULL, NULL);
279
280 bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (output_stream));
281
282 if (g_bytes_get_size (bytes) == 0) {
283 gchar *data;
284
285 g_bytes_unref (bytes);
286
287 data = g_strdup_printf (
288 "<p align='center'>%s</p>",
289 _("The message has no text content."));
290
291 /* Takes ownership of the string. */
292 bytes = g_bytes_new_take (data, strlen (data) + 1);
293 }
294
295 if (!use_mime_type)
296 use_mime_type = g_strdup ("text/html");
297
298 if (part_converted_to_utf8 && g_strcmp0 (use_mime_type, "text/html") == 0) {
299 tmp = g_strconcat (use_mime_type, "; charset=\"UTF-8\"", NULL);
300 g_free (use_mime_type);
301 use_mime_type = tmp;
302 }
303
304 *out_stream = g_memory_input_stream_new_from_bytes (bytes);
305 *out_stream_length = g_bytes_get_size (bytes);
306 *out_mime_type = use_mime_type;
307
308 g_object_unref (output_stream);
309 g_object_unref (part_list);
310 g_object_unref (formatter);
311 g_bytes_unref (bytes);
312 g_free (context.uri);
313
314 return TRUE;
315 }
316
317 static gboolean
mail_request_process_contact_photo_sync(EContentRequest * request,SoupURI * suri,GHashTable * uri_query,GObject * requester,GInputStream ** out_stream,gint64 * out_stream_length,gchar ** out_mime_type,GCancellable * cancellable,GError ** error)318 mail_request_process_contact_photo_sync (EContentRequest *request,
319 SoupURI *suri,
320 GHashTable *uri_query,
321 GObject *requester,
322 GInputStream **out_stream,
323 gint64 *out_stream_length,
324 gchar **out_mime_type,
325 GCancellable *cancellable,
326 GError **error)
327 {
328 EShell *shell;
329 EShellBackend *shell_backend;
330 EMailBackend *mail_backend;
331 EMailSession *mail_session;
332 EPhotoCache *photo_cache;
333 CamelInternetAddress *cia;
334 GInputStream *stream = NULL;
335 const gchar *email_address;
336 const gchar *escaped_string;
337 gchar *unescaped_string;
338 gboolean success = FALSE;
339
340 shell = e_shell_get_default ();
341 shell_backend = e_shell_get_backend_by_name (shell, "mail");
342 mail_backend = E_MAIL_BACKEND (shell_backend);
343 mail_session = e_mail_backend_get_session (mail_backend);
344
345 photo_cache = e_mail_ui_session_get_photo_cache (E_MAIL_UI_SESSION (mail_session));
346
347 escaped_string = uri_query ? g_hash_table_lookup (uri_query, "mailaddr") : NULL;
348 if (escaped_string && *escaped_string) {
349 cia = camel_internet_address_new ();
350
351 unescaped_string = g_uri_unescape_string (escaped_string, NULL);
352 camel_address_decode (CAMEL_ADDRESS (cia), unescaped_string);
353 g_free (unescaped_string);
354
355 if (camel_internet_address_get (cia, 0, NULL, &email_address)) {
356 /* The e_photo_cache_get_photo_sync() can return TRUE even when
357 there is no picture of the found contact, thus check for it. */
358 success = e_photo_cache_get_photo_sync (
359 photo_cache, email_address,
360 cancellable, &stream, error) && stream;
361 }
362
363 g_object_unref (cia);
364
365 if (success) {
366 *out_stream = stream;
367 *out_stream_length = -1;
368 *out_mime_type = g_strdup ("image/*");
369 }
370 }
371
372 if (!success) {
373 GdkPixbuf *pixbuf;
374 gchar *buffer;
375 gsize length;
376
377 g_clear_error (error);
378
379 /* Construct empty image stream, to not show "broken image" icon when no contact photo is found */
380 pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 1, 1);
381 gdk_pixbuf_fill (pixbuf, 0x00000000); /* transparent black */
382 gdk_pixbuf_save_to_buffer (pixbuf, &buffer, &length, "png", NULL, NULL);
383 g_object_unref (pixbuf);
384
385 *out_stream = g_memory_input_stream_new_from_data (buffer, length, g_free);
386 *out_stream_length = length;
387 *out_mime_type = g_strdup ("image/png");
388 }
389
390 return TRUE;
391 }
392
393 typedef struct _MailIdleData
394 {
395 EContentRequest *request;
396 SoupURI *suri;
397 GHashTable *uri_query;
398 GObject *requester;
399 GInputStream **out_stream;
400 gint64 *out_stream_length;
401 gchar **out_mime_type;
402 GCancellable *cancellable;
403 GError **error;
404
405 gboolean success;
406 EFlag *flag;
407 } MailIdleData;
408
409 static gboolean
process_mail_request_idle_cb(gpointer user_data)410 process_mail_request_idle_cb (gpointer user_data)
411 {
412 MailIdleData *mid = user_data;
413
414 g_return_val_if_fail (mid != NULL, FALSE);
415 g_return_val_if_fail (E_IS_MAIL_REQUEST (mid->request), FALSE);
416 g_return_val_if_fail (mid->suri != NULL, FALSE);
417 g_return_val_if_fail (mid->flag != NULL, FALSE);
418
419 mid->success = mail_request_process_mail_sync (mid->request,
420 mid->suri, mid->uri_query, mid->requester, mid->out_stream,
421 mid->out_stream_length, mid->out_mime_type,
422 mid->cancellable, mid->error);
423
424 e_flag_set (mid->flag);
425
426 return FALSE;
427 }
428
429 static gboolean
e_mail_request_process_sync(EContentRequest * request,const gchar * uri,GObject * requester,GInputStream ** out_stream,gint64 * out_stream_length,gchar ** out_mime_type,GCancellable * cancellable,GError ** error)430 e_mail_request_process_sync (EContentRequest *request,
431 const gchar *uri,
432 GObject *requester,
433 GInputStream **out_stream,
434 gint64 *out_stream_length,
435 gchar **out_mime_type,
436 GCancellable *cancellable,
437 GError **error)
438 {
439 SoupURI *suri;
440 GHashTable *uri_query;
441 gboolean success = FALSE;
442
443 g_return_val_if_fail (E_IS_MAIL_REQUEST (request), FALSE);
444 g_return_val_if_fail (uri != NULL, FALSE);
445
446 if (g_cancellable_set_error_if_cancelled (cancellable, error))
447 return FALSE;
448
449 suri = soup_uri_new (uri);
450 g_return_val_if_fail (suri != NULL, FALSE);
451
452 if (suri->query) {
453 uri_query = soup_form_decode (suri->query);
454 } else {
455 uri_query = NULL;
456 }
457
458 if (g_strcmp0 (suri->host, "contact-photo") == 0) {
459 success = mail_request_process_contact_photo_sync (request, suri, uri_query, requester,
460 out_stream, out_stream_length, out_mime_type, cancellable, error);
461 } else {
462 MailIdleData mid;
463
464 mid.request = request;
465 mid.suri = suri;
466 mid.uri_query = uri_query;
467 mid.requester = requester;
468 mid.out_stream = out_stream;
469 mid.out_stream_length = out_stream_length;
470 mid.out_mime_type = out_mime_type;
471 mid.cancellable = cancellable;
472 mid.error = error;
473 mid.flag = e_flag_new ();
474 mid.success = FALSE;
475
476 if (e_util_is_main_thread (NULL)) {
477 process_mail_request_idle_cb (&mid);
478 } else {
479 /* Process e-mail mail requests in the main/UI thread, because
480 * any EMailFormatter can create GtkWidget-s, or manipulate with
481 * them, which should be always done in the main/UI thread. */
482 g_idle_add_full (
483 G_PRIORITY_HIGH_IDLE,
484 process_mail_request_idle_cb,
485 &mid, NULL);
486
487 e_flag_wait (mid.flag);
488 }
489
490 e_flag_free (mid.flag);
491
492 success = mid.success;
493 }
494
495 if (uri_query)
496 g_hash_table_destroy (uri_query);
497 soup_uri_free (suri);
498
499 return success;
500 }
501
502 static void
e_mail_request_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)503 e_mail_request_set_property (GObject *object,
504 guint property_id,
505 const GValue *value,
506 GParamSpec *pspec)
507 {
508 switch (property_id) {
509 case PROP_SCALE_FACTOR:
510 e_mail_request_set_scale_factor (
511 E_MAIL_REQUEST (object),
512 g_value_get_int (value));
513 return;
514 }
515
516 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
517 }
518
519 static void
e_mail_request_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)520 e_mail_request_get_property (GObject *object,
521 guint property_id,
522 GValue *value,
523 GParamSpec *pspec)
524 {
525 switch (property_id) {
526 case PROP_SCALE_FACTOR:
527 g_value_set_int (
528 value,
529 e_mail_request_get_scale_factor (
530 E_MAIL_REQUEST (object)));
531 return;
532 }
533
534 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
535 }
536
537 static void
e_mail_request_content_request_init(EContentRequestInterface * iface)538 e_mail_request_content_request_init (EContentRequestInterface *iface)
539 {
540 iface->can_process_uri = e_mail_request_can_process_uri;
541 iface->process_sync = e_mail_request_process_sync;
542 }
543
544 static void
e_mail_request_class_init(EMailRequestClass * class)545 e_mail_request_class_init (EMailRequestClass *class)
546 {
547 GObjectClass *object_class;
548
549 g_type_class_add_private (class, sizeof (EMailRequestPrivate));
550
551 object_class = G_OBJECT_CLASS (class);
552 object_class->set_property = e_mail_request_set_property;
553 object_class->get_property = e_mail_request_get_property;
554
555 g_object_class_install_property (
556 object_class,
557 PROP_SCALE_FACTOR,
558 g_param_spec_int (
559 "scale-factor",
560 "Scale Factor",
561 NULL,
562 G_MININT, G_MAXINT, 0,
563 G_PARAM_READWRITE |
564 G_PARAM_STATIC_STRINGS));
565 }
566
567 static void
e_mail_request_init(EMailRequest * request)568 e_mail_request_init (EMailRequest *request)
569 {
570 request->priv = G_TYPE_INSTANCE_GET_PRIVATE (request, E_TYPE_MAIL_REQUEST, EMailRequestPrivate);
571 request->priv->scale_factor = 0;
572 }
573
574 EContentRequest *
e_mail_request_new(void)575 e_mail_request_new (void)
576 {
577 return g_object_new (E_TYPE_MAIL_REQUEST, NULL);
578 }
579
580 gint
e_mail_request_get_scale_factor(EMailRequest * mail_request)581 e_mail_request_get_scale_factor (EMailRequest *mail_request)
582 {
583 g_return_val_if_fail (E_IS_MAIL_REQUEST (mail_request), 0);
584
585 return mail_request->priv->scale_factor;
586 }
587
588 void
e_mail_request_set_scale_factor(EMailRequest * mail_request,gint scale_factor)589 e_mail_request_set_scale_factor (EMailRequest *mail_request,
590 gint scale_factor)
591 {
592 g_return_if_fail (E_IS_MAIL_REQUEST (mail_request));
593
594 if (mail_request->priv->scale_factor == scale_factor)
595 return;
596
597 mail_request->priv->scale_factor = scale_factor;
598
599 g_object_notify (G_OBJECT (mail_request), "scale-factor");
600 }
601