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