1 /*
2  * e-mail-parser.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 "e-mail-parser.h"
21 
22 #include <string.h>
23 
24 #include <libebackend/libebackend.h>
25 
26 #include <shell/e-shell.h>
27 #include <shell/e-shell-window.h>
28 
29 #include "e-mail-parser-extension.h"
30 #include "e-mail-part-attachment.h"
31 #include "e-mail-part-utils.h"
32 
33 #define E_MAIL_PARSER_GET_PRIVATE(obj) \
34 	(G_TYPE_INSTANCE_GET_PRIVATE \
35 	((obj), E_TYPE_MAIL_PARSER, EMailParserPrivate))
36 
37 #define d(x)
38 
39 struct _EMailParserPrivate {
40 	GMutex mutex;
41 
42 	gint last_error;
43 
44 	CamelSession *session;
45 	GHashTable *ongoing_part_lists; /* GCancellable * ~> EMailPartList * */
46 };
47 
48 enum {
49 	PROP_0,
50 	PROP_SESSION
51 };
52 
53 /* internal parser extensions */
54 GType e_mail_parser_application_mbox_get_type (void);
55 GType e_mail_parser_audio_get_type (void);
56 GType e_mail_parser_headers_get_type (void);
57 GType e_mail_parser_message_get_type (void);
58 GType e_mail_parser_secure_button_get_type (void);
59 GType e_mail_parser_source_get_type (void);
60 GType e_mail_parser_image_get_type (void);
61 GType e_mail_parser_inline_pgp_encrypted_get_type (void);
62 GType e_mail_parser_inline_pgp_signed_get_type (void);
63 GType e_mail_parser_message_delivery_status_get_type (void);
64 GType e_mail_parser_message_external_get_type (void);
65 GType e_mail_parser_message_rfc822_get_type (void);
66 GType e_mail_parser_multipart_alternative_get_type (void);
67 GType e_mail_parser_multipart_apple_double_get_type (void);
68 GType e_mail_parser_multipart_digest_get_type (void);
69 GType e_mail_parser_multipart_encrypted_get_type (void);
70 GType e_mail_parser_multipart_mixed_get_type (void);
71 GType e_mail_parser_multipart_related_get_type (void);
72 GType e_mail_parser_multipart_signed_get_type (void);
73 GType e_mail_parser_text_enriched_get_type (void);
74 GType e_mail_parser_text_html_get_type (void);
75 GType e_mail_parser_text_plain_get_type (void);
76 #ifdef ENABLE_SMIME
77 GType e_mail_parser_application_smime_get_type (void);
78 #endif
79 
80 static gpointer parent_class;
81 
82 static void
mail_parser_move_security_before_headers(GQueue * part_queue)83 mail_parser_move_security_before_headers (GQueue *part_queue)
84 {
85 	GList *link, *last_headers = NULL;
86 	GSList *headers_stack = NULL;
87 
88 	link = g_queue_peek_head_link (part_queue);
89 	while (link) {
90 		EMailPart *part = link->data;
91 		const gchar *id;
92 
93 		if (!part) {
94 			link = g_list_next (link);
95 			continue;
96 		}
97 
98 		id = e_mail_part_get_id (part);
99 		if (!id) {
100 			link = g_list_next (link);
101 			continue;
102 		}
103 
104 		if (g_str_has_suffix (id, ".rfc822")) {
105 			headers_stack = g_slist_prepend (headers_stack, last_headers);
106 			last_headers = NULL;
107 		} else if (g_str_has_suffix (id, ".rfc822.end")) {
108 			g_warn_if_fail (headers_stack != NULL);
109 
110 			if (headers_stack) {
111 				last_headers = headers_stack->data;
112 				headers_stack = g_slist_remove (headers_stack, last_headers);
113 			} else {
114 				last_headers = NULL;
115 			}
116 		}
117 
118 		if (g_strcmp0 (e_mail_part_get_mime_type (part), "application/vnd.evolution.headers") == 0) {
119 			last_headers = link;
120 			link = g_list_next (link);
121 		} else if (g_strcmp0 (e_mail_part_get_mime_type (part), "application/vnd.evolution.secure-button") == 0) {
122 			g_warn_if_fail (last_headers != NULL);
123 
124 			if (last_headers) {
125 				GList *next = g_list_next (link);
126 
127 				g_warn_if_fail (g_queue_remove (part_queue, part));
128 				g_queue_insert_before (part_queue, last_headers, part);
129 
130 				link = next;
131 			} else {
132 				link = g_list_next (link);
133 			}
134 		} else {
135 			link = g_list_next (link);
136 		}
137 	}
138 
139 	g_warn_if_fail (headers_stack == NULL);
140 	g_slist_free (headers_stack);
141 }
142 
143 static void
mail_parser_run(EMailParser * parser,EMailPartList * part_list,GCancellable * cancellable)144 mail_parser_run (EMailParser *parser,
145                  EMailPartList *part_list,
146                  GCancellable *cancellable)
147 {
148 	EMailExtensionRegistry *reg;
149 	CamelMimeMessage *message;
150 	EMailPart *mail_part;
151 	GQueue *parsers;
152 	GQueue mail_part_queue = G_QUEUE_INIT;
153 	GList *iter;
154 	GString *part_id;
155 
156 	if (cancellable)
157 		g_object_ref (cancellable);
158 	else
159 		cancellable = g_cancellable_new ();
160 
161 	g_mutex_lock (&parser->priv->mutex);
162 	g_hash_table_insert (parser->priv->ongoing_part_lists, cancellable, part_list);
163 	g_mutex_unlock (&parser->priv->mutex);
164 
165 	message = e_mail_part_list_get_message (part_list);
166 
167 	reg = e_mail_parser_get_extension_registry (parser);
168 
169 	parsers = e_mail_extension_registry_get_for_mime_type (
170 		reg, "application/vnd.evolution.message");
171 
172 	if (parsers == NULL)
173 		parsers = e_mail_extension_registry_get_for_mime_type (
174 			reg, "message/*");
175 
176 	/* No parsers means the internal Evolution parser
177 	 * extensions were not loaded. Something is terribly wrong! */
178 	g_return_if_fail (parsers != NULL);
179 
180 	part_id = g_string_new (".message");
181 
182 	mail_part = e_mail_part_new (CAMEL_MIME_PART (message), ".message");
183 	e_mail_part_list_add_part (part_list, mail_part);
184 	g_object_unref (mail_part);
185 
186 	for (iter = parsers->head; iter; iter = iter->next) {
187 		EMailParserExtension *extension;
188 		gboolean message_handled;
189 
190 		if (g_cancellable_is_cancelled (cancellable))
191 			break;
192 
193 		extension = iter->data;
194 		if (!extension)
195 			continue;
196 
197 		message_handled = e_mail_parser_extension_parse (
198 			extension, parser,
199 			CAMEL_MIME_PART (message),
200 			part_id, cancellable, &mail_part_queue);
201 
202 		if (message_handled)
203 			break;
204 	}
205 
206 	mail_parser_move_security_before_headers (&mail_part_queue);
207 
208 	while (!g_queue_is_empty (&mail_part_queue)) {
209 		mail_part = g_queue_pop_head (&mail_part_queue);
210 		e_mail_part_list_add_part (part_list, mail_part);
211 		g_object_unref (mail_part);
212 	}
213 
214 	g_mutex_lock (&parser->priv->mutex);
215 	g_hash_table_remove (parser->priv->ongoing_part_lists, cancellable);
216 	g_mutex_unlock (&parser->priv->mutex);
217 
218 	g_clear_object (&cancellable);
219 	g_string_free (part_id, TRUE);
220 }
221 
222 static void
shell_gone_cb(gpointer user_data,GObject * gone_extension_registry)223 shell_gone_cb (gpointer user_data,
224 	       GObject *gone_extension_registry)
225 {
226 	EMailParserClass *class = user_data;
227 
228 	g_return_if_fail (class != NULL);
229 
230 	g_clear_object (&class->extension_registry);
231 }
232 
233 static void
mail_parser_set_session(EMailParser * parser,CamelSession * session)234 mail_parser_set_session (EMailParser *parser,
235                          CamelSession *session)
236 {
237 	g_return_if_fail (CAMEL_IS_SESSION (session));
238 	g_return_if_fail (parser->priv->session == NULL);
239 
240 	parser->priv->session = g_object_ref (session);
241 }
242 
243 static void
e_mail_parser_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)244 e_mail_parser_set_property (GObject *object,
245                           guint property_id,
246                           const GValue *value,
247                           GParamSpec *pspec)
248 {
249 	switch (property_id) {
250 		case PROP_SESSION:
251 			mail_parser_set_session (
252 				E_MAIL_PARSER (object),
253 				g_value_get_object (value));
254 			return;
255 	}
256 
257 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
258 }
259 
260 static void
e_mail_parser_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)261 e_mail_parser_get_property (GObject *object,
262                           guint property_id,
263                           GValue *value,
264                           GParamSpec *pspec)
265 {
266 	switch (property_id) {
267 		case PROP_SESSION:
268 			g_value_set_object (
269 				value,
270 				e_mail_parser_get_session (
271 				E_MAIL_PARSER (object)));
272 			return;
273 	}
274 
275 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
276 }
277 
278 static void
e_mail_parser_finalize(GObject * object)279 e_mail_parser_finalize (GObject *object)
280 {
281 	EMailParserPrivate *priv;
282 
283 	priv = E_MAIL_PARSER_GET_PRIVATE (object);
284 
285 	g_clear_object (&priv->session);
286 	g_hash_table_destroy (priv->ongoing_part_lists);
287 	g_mutex_clear (&priv->mutex);
288 
289 	/* Chain up to parent's finalize() method. */
290 	G_OBJECT_CLASS (parent_class)->finalize (object);
291 }
292 
293 static void
e_mail_parser_base_init(EMailParserClass * class)294 e_mail_parser_base_init (EMailParserClass *class)
295 {
296 	EShell *shell;
297 
298 	/* Register internal extensions. */
299 	g_type_ensure (e_mail_parser_application_mbox_get_type ());
300 	/* This is currently disabled, because the WebKit player requires javascript,
301 	   which is disabled in Evolution. */
302 	/* g_type_ensure (e_mail_parser_audio_get_type ()); */
303 	g_type_ensure (e_mail_parser_headers_get_type ());
304 	g_type_ensure (e_mail_parser_message_get_type ());
305 	g_type_ensure (e_mail_parser_secure_button_get_type ());
306 	g_type_ensure (e_mail_parser_source_get_type ());
307 	g_type_ensure (e_mail_parser_image_get_type ());
308 	g_type_ensure (e_mail_parser_inline_pgp_encrypted_get_type ());
309 	g_type_ensure (e_mail_parser_inline_pgp_signed_get_type ());
310 	g_type_ensure (e_mail_parser_message_delivery_status_get_type ());
311 	g_type_ensure (e_mail_parser_message_external_get_type ());
312 	g_type_ensure (e_mail_parser_message_rfc822_get_type ());
313 	g_type_ensure (e_mail_parser_multipart_alternative_get_type ());
314 	g_type_ensure (e_mail_parser_multipart_apple_double_get_type ());
315 	g_type_ensure (e_mail_parser_multipart_digest_get_type ());
316 	g_type_ensure (e_mail_parser_multipart_encrypted_get_type ());
317 	g_type_ensure (e_mail_parser_multipart_mixed_get_type ());
318 	g_type_ensure (e_mail_parser_multipart_related_get_type ());
319 	g_type_ensure (e_mail_parser_multipart_signed_get_type ());
320 	g_type_ensure (e_mail_parser_text_enriched_get_type ());
321 	g_type_ensure (e_mail_parser_text_html_get_type ());
322 	g_type_ensure (e_mail_parser_text_plain_get_type ());
323 #ifdef ENABLE_SMIME
324 	g_type_ensure (e_mail_parser_application_smime_get_type ());
325 #endif
326 
327 	class->extension_registry = g_object_new (
328 		E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY, NULL);
329 
330 	e_mail_parser_extension_registry_load (class->extension_registry);
331 
332 	e_extensible_load_extensions (E_EXTENSIBLE (class->extension_registry));
333 
334 	shell = e_shell_get_default ();
335 	/* It can be NULL when creating developer documentation */
336 	if (shell)
337 		g_object_weak_ref (G_OBJECT (shell), shell_gone_cb, class);
338 }
339 
340 static void
e_mail_parser_class_init(EMailParserClass * class)341 e_mail_parser_class_init (EMailParserClass *class)
342 {
343 	GObjectClass *object_class;
344 
345 	parent_class = g_type_class_peek_parent (class);
346 	g_type_class_add_private (class, sizeof (EMailParserPrivate));
347 
348 	object_class = G_OBJECT_CLASS (class);
349 	object_class->finalize = e_mail_parser_finalize;
350 	object_class->set_property = e_mail_parser_set_property;
351 	object_class->get_property = e_mail_parser_get_property;
352 
353 	g_object_class_install_property (
354 		object_class,
355 		PROP_SESSION,
356 		g_param_spec_object (
357 			"session",
358 			"Camel Session",
359 			NULL,
360 			CAMEL_TYPE_SESSION,
361 			G_PARAM_READWRITE |
362 			G_PARAM_CONSTRUCT_ONLY));
363 }
364 
365 static void
e_mail_parser_init(EMailParser * parser)366 e_mail_parser_init (EMailParser *parser)
367 {
368 	parser->priv = E_MAIL_PARSER_GET_PRIVATE (parser);
369 	parser->priv->ongoing_part_lists = g_hash_table_new (g_direct_hash, g_direct_equal);
370 
371 	g_mutex_init (&parser->priv->mutex);
372 }
373 
374 GType
e_mail_parser_get_type(void)375 e_mail_parser_get_type (void)
376 {
377 	static GType type = 0;
378 
379 	if (G_UNLIKELY (type == 0)) {
380 		static const GTypeInfo type_info = {
381 			sizeof (EMailParserClass),
382 			(GBaseInitFunc) e_mail_parser_base_init,
383 			(GBaseFinalizeFunc) NULL,
384 			(GClassInitFunc) e_mail_parser_class_init,
385 			(GClassFinalizeFunc) NULL,
386 			NULL,  /* class_data */
387 			sizeof (EMailParser),
388 			0,     /* n_preallocs */
389 			(GInstanceInitFunc) e_mail_parser_init,
390 			NULL   /* value_table */
391 		};
392 
393 		type = g_type_register_static (
394 			G_TYPE_OBJECT, "EMailParser",
395 			&type_info, 0);
396 	}
397 
398 	return type;
399 }
400 
401 EMailParser *
e_mail_parser_new(CamelSession * session)402 e_mail_parser_new (CamelSession *session)
403 {
404 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
405 
406 	return g_object_new (
407 		E_TYPE_MAIL_PARSER,
408 		"session", session, NULL);
409 }
410 
411 /**
412  * e_mail_parser_parse_sync:
413  * @parser: an #EMailParser
414  * @folder: (allow none) a #CamelFolder containing the @message or %NULL
415  * @message_uid: (allow none) UID of the @message within the @folder or %NULL
416  * @message: a #CamelMimeMessage
417  * @cancellable: (allow-none) a #GCancellable
418  *
419  * Parses the @message synchronously. Returns a list of #EMailPart<!-- -->s which
420  * represents structure of the message and additional properties of each part.
421  *
422  * Note that this function can block for a while, so it's not a good idea to call
423  * it from main thread.
424  *
425  * Return Value: An #EMailPartsList
426  */
427 EMailPartList *
e_mail_parser_parse_sync(EMailParser * parser,CamelFolder * folder,const gchar * message_uid,CamelMimeMessage * message,GCancellable * cancellable)428 e_mail_parser_parse_sync (EMailParser *parser,
429                           CamelFolder *folder,
430                           const gchar *message_uid,
431                           CamelMimeMessage *message,
432                           GCancellable *cancellable)
433 {
434 	EMailPartList *part_list;
435 
436 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
437 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
438 
439 	part_list = e_mail_part_list_new (message, message_uid, folder);
440 
441 	mail_parser_run (parser, part_list, cancellable);
442 
443 	if (camel_debug_start ("emformat:parser")) {
444 		GQueue queue = G_QUEUE_INIT;
445 
446 		printf (
447 			"%s finished with EMailPartList:\n",
448 			G_OBJECT_TYPE_NAME (parser));
449 
450 		e_mail_part_list_queue_parts (part_list, NULL, &queue);
451 
452 		while (!g_queue_is_empty (&queue)) {
453 			EMailPart *part;
454 
455 			part = g_queue_pop_head (&queue);
456 
457 			printf (
458 				"	id: %s | cid: %s | mime_type: %s | "
459 				"is_hidden: %d | is_attachment: %d | is_printable: %d\n",
460 				e_mail_part_get_id (part),
461 				e_mail_part_get_cid (part),
462 				e_mail_part_get_mime_type (part),
463 				part->is_hidden ? 1 : 0,
464 				e_mail_part_get_is_attachment (part) ? 1 : 0,
465 				e_mail_part_get_is_printable (part) ? 1 : 0);
466 
467 			g_object_unref (part);
468 		}
469 
470 		camel_debug_end ();
471 	}
472 
473 	return part_list;
474 }
475 
476 static void
mail_parser_parse_thread(GSimpleAsyncResult * simple,GObject * source_object,GCancellable * cancellable)477 mail_parser_parse_thread (GSimpleAsyncResult *simple,
478                           GObject *source_object,
479                           GCancellable *cancellable)
480 {
481 	EMailPartList *part_list;
482 
483 	part_list = g_simple_async_result_get_op_res_gpointer (simple);
484 
485 	mail_parser_run (
486 		E_MAIL_PARSER (source_object),
487 		part_list, cancellable);
488 }
489 
490 /**
491  * e_mail_parser_parse:
492  * @parser: an #EMailParser
493  * @message: a #CamelMimeMessage
494  * @callback: a #GAsyncReadyCallback
495  * @cancellable: (allow-none) a #GCancellable
496  * @user_data: (allow-none) user data passed to the callback
497  *
498  * Asynchronous version of e_mail_parser_parse_sync().
499  */
500 void
e_mail_parser_parse(EMailParser * parser,CamelFolder * folder,const gchar * message_uid,CamelMimeMessage * message,GAsyncReadyCallback callback,GCancellable * cancellable,gpointer user_data)501 e_mail_parser_parse (EMailParser *parser,
502                      CamelFolder *folder,
503                      const gchar *message_uid,
504                      CamelMimeMessage *message,
505                      GAsyncReadyCallback callback,
506                      GCancellable *cancellable,
507                      gpointer user_data)
508 {
509 	GSimpleAsyncResult *simple;
510 	EMailPartList *part_list;
511 
512 	g_return_if_fail (E_IS_MAIL_PARSER (parser));
513 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
514 
515 	part_list = e_mail_part_list_new (message, message_uid, folder);
516 
517 	simple = g_simple_async_result_new (
518 		G_OBJECT (parser), callback,
519 		user_data, e_mail_parser_parse);
520 
521 	g_simple_async_result_set_check_cancellable (simple, cancellable);
522 
523 	g_simple_async_result_set_op_res_gpointer (
524 		simple, part_list, (GDestroyNotify) g_object_unref);
525 
526 	g_simple_async_result_run_in_thread (
527 		simple, mail_parser_parse_thread,
528 		G_PRIORITY_DEFAULT, cancellable);
529 
530 	g_object_unref (simple);
531 }
532 
533 EMailPartList *
e_mail_parser_parse_finish(EMailParser * parser,GAsyncResult * result,GError ** error)534 e_mail_parser_parse_finish (EMailParser *parser,
535                             GAsyncResult *result,
536                             GError **error)
537 {
538 	GSimpleAsyncResult *simple;
539 	EMailPartList *part_list;
540 
541 	g_return_val_if_fail (
542 		g_simple_async_result_is_valid (
543 		result, G_OBJECT (parser), e_mail_parser_parse), NULL);
544 
545 	simple = G_SIMPLE_ASYNC_RESULT (result);
546 	part_list = g_simple_async_result_get_op_res_gpointer (simple);
547 
548 	if (camel_debug_start ("emformat:parser")) {
549 		GQueue queue = G_QUEUE_INIT;
550 
551 		printf (
552 			"%s finished with EMailPartList:\n",
553 			G_OBJECT_TYPE_NAME (parser));
554 
555 		e_mail_part_list_queue_parts (part_list, NULL, &queue);
556 
557 		while (!g_queue_is_empty (&queue)) {
558 			EMailPart *part;
559 
560 			part = g_queue_pop_head (&queue);
561 
562 			printf (
563 				"	id: %s | cid: %s | mime_type: %s | "
564 				"is_hidden: %d | is_attachment: %d | is_printable: %d\n",
565 				e_mail_part_get_id (part),
566 				e_mail_part_get_cid (part),
567 				e_mail_part_get_mime_type (part),
568 				part->is_hidden ? 1 : 0,
569 				e_mail_part_get_is_attachment (part) ? 1 : 0,
570 				e_mail_part_get_is_printable (part) ? 1 : 0);
571 
572 			g_object_unref (part);
573 		}
574 
575 		camel_debug_end ();
576 	}
577 
578 	return g_object_ref (part_list);
579 }
580 
581 GQueue *
e_mail_parser_get_parsers_for_part(EMailParser * parser,CamelMimePart * part)582 e_mail_parser_get_parsers_for_part (EMailParser *parser,
583 				    CamelMimePart *part)
584 {
585 	CamelContentType *ct;
586 	gchar *mime_type;
587 	GQueue *parsers;
588 
589 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
590 	g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL);
591 
592 	ct = camel_mime_part_get_content_type (part);
593 	if (!ct) {
594 		mime_type = (gchar *) "application/vnd.evolution.error";
595 	} else {
596 		gchar *tmp;
597 		tmp = camel_content_type_simple (ct);
598 		mime_type = g_ascii_strdown (tmp, -1);
599 		g_free (tmp);
600 	}
601 
602 	parsers = e_mail_parser_get_parsers (parser, mime_type);
603 
604 	if (ct)
605 		g_free (mime_type);
606 
607 	return parsers;
608 }
609 
610 GQueue *
e_mail_parser_get_parsers(EMailParser * parser,const gchar * mime_type)611 e_mail_parser_get_parsers (EMailParser *parser,
612 			   const gchar *mime_type)
613 {
614 	EMailExtensionRegistry *reg;
615 	EMailParserClass *parser_class;
616 	gchar *as_mime_type;
617 	GQueue *parsers;
618 
619 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
620 
621 	parser_class = E_MAIL_PARSER_GET_CLASS (parser);
622 	g_return_val_if_fail (parser_class != NULL, NULL);
623 
624 	if (mime_type)
625 		as_mime_type = g_ascii_strdown (mime_type, -1);
626 	else
627 		as_mime_type = NULL;
628 
629 	reg = E_MAIL_EXTENSION_REGISTRY (parser_class->extension_registry);
630 
631 	parsers = e_mail_extension_registry_get_for_mime_type (reg, as_mime_type);
632 	if (!parsers)
633 		parsers = e_mail_extension_registry_get_fallback (reg, as_mime_type);
634 
635 	g_free (as_mime_type);
636 
637 	return parsers;
638 }
639 
640 gboolean
e_mail_parser_parse_part(EMailParser * parser,CamelMimePart * part,GString * part_id,GCancellable * cancellable,GQueue * out_mail_parts)641 e_mail_parser_parse_part (EMailParser *parser,
642                           CamelMimePart *part,
643                           GString *part_id,
644                           GCancellable *cancellable,
645                           GQueue *out_mail_parts)
646 {
647 	CamelContentType *ct;
648 	gchar *mime_type;
649 	gint handled;
650 
651 	ct = camel_mime_part_get_content_type (part);
652 	if (!ct) {
653 		mime_type = (gchar *) "application/vnd.evolution.error";
654 	} else {
655 		gchar *tmp;
656 		tmp = camel_content_type_simple (ct);
657 		mime_type = g_ascii_strdown (tmp, -1);
658 		g_free (tmp);
659 	}
660 
661 	handled = e_mail_parser_parse_part_as (
662 		parser, part, part_id, mime_type,
663 		cancellable, out_mail_parts);
664 
665 	if (ct) {
666 		g_free (mime_type);
667 	}
668 
669 	return handled;
670 }
671 
672 gboolean
e_mail_parser_parse_part_as(EMailParser * parser,CamelMimePart * part,GString * part_id,const gchar * mime_type,GCancellable * cancellable,GQueue * out_mail_parts)673 e_mail_parser_parse_part_as (EMailParser *parser,
674                              CamelMimePart *part,
675                              GString *part_id,
676                              const gchar *mime_type,
677                              GCancellable *cancellable,
678                              GQueue *out_mail_parts)
679 {
680 	GQueue *parsers;
681 	GList *iter;
682 	gboolean mime_part_handled = FALSE;
683 
684 	parsers = e_mail_parser_get_parsers (parser, mime_type);
685 
686 	if (parsers == NULL) {
687 		e_mail_parser_wrap_as_attachment (
688 			parser, part, part_id, out_mail_parts);
689 		return TRUE;
690 	}
691 
692 	for (iter = parsers->head; iter; iter = iter->next) {
693 		EMailParserExtension *extension;
694 
695 		extension = iter->data;
696 		if (!extension)
697 			continue;
698 
699 		mime_part_handled = e_mail_parser_extension_parse (
700 			extension, parser, part, part_id,
701 			cancellable, out_mail_parts);
702 
703 		if (mime_part_handled)
704 			break;
705 	}
706 
707 	return mime_part_handled;
708 }
709 
710 void
e_mail_parser_error(EMailParser * parser,GQueue * out_mail_parts,const gchar * format,...)711 e_mail_parser_error (EMailParser *parser,
712                      GQueue *out_mail_parts,
713                      const gchar *format,
714                      ...)
715 {
716 	const gchar *mime_type = "application/vnd.evolution.error";
717 	EMailPart *mail_part;
718 	CamelMimePart *part;
719 	gchar *errmsg;
720 	gchar *uri;
721 	va_list ap;
722 
723 	g_return_if_fail (E_IS_MAIL_PARSER (parser));
724 	g_return_if_fail (out_mail_parts != NULL);
725 	g_return_if_fail (format != NULL);
726 
727 	va_start (ap, format);
728 	errmsg = g_strdup_vprintf (format, ap);
729 
730 	part = camel_mime_part_new ();
731 	camel_mime_part_set_content (
732 		part, errmsg, strlen (errmsg), mime_type);
733 	g_free (errmsg);
734 	va_end (ap);
735 
736 	g_mutex_lock (&parser->priv->mutex);
737 	parser->priv->last_error++;
738 	uri = g_strdup_printf (".error.%d", parser->priv->last_error);
739 	g_mutex_unlock (&parser->priv->mutex);
740 
741 	mail_part = e_mail_part_new (part, uri);
742 	e_mail_part_set_mime_type (mail_part, mime_type);
743 	e_mail_part_set_is_printable (mail_part, FALSE);
744 	mail_part->is_error = TRUE;
745 
746 	g_free (uri);
747 	g_object_unref (part);
748 
749 	g_queue_push_tail (out_mail_parts, mail_part);
750 }
751 
752 static void
attachment_loaded(EAttachment * attachment,GAsyncResult * res,gpointer user_data)753 attachment_loaded (EAttachment *attachment,
754                    GAsyncResult *res,
755                    gpointer user_data)
756 {
757 	EShell *shell;
758 	GtkWindow *window;
759 
760 	shell = e_shell_get_default ();
761 	window = e_shell_get_active_window (shell);
762 
763 	e_attachment_load_handle_error (attachment, res, window);
764 
765 	g_object_unref (attachment);
766 }
767 
768 /* Idle callback */
769 static gboolean
load_attachment_idle(EAttachment * attachment)770 load_attachment_idle (EAttachment *attachment)
771 {
772 	e_attachment_load_async (
773 		attachment,
774 		(GAsyncReadyCallback) attachment_loaded, NULL);
775 
776 	return FALSE;
777 }
778 
779 void
e_mail_parser_wrap_as_attachment(EMailParser * parser,CamelMimePart * part,GString * part_id,GQueue * parts_queue)780 e_mail_parser_wrap_as_attachment (EMailParser *parser,
781                                   CamelMimePart *part,
782                                   GString *part_id,
783                                   GQueue *parts_queue)
784 {
785 	EMailPartAttachment *empa;
786 	EAttachment *attachment;
787 	EMailPart *first_part;
788 	const gchar *snoop_mime_type;
789 	GQueue *extensions;
790 	CamelContentType *ct;
791 	gchar *mime_type;
792 	CamelDataWrapper *dw;
793 	GByteArray *ba;
794 	gsize size;
795 	gint part_id_len;
796 
797 	ct = camel_mime_part_get_content_type (part);
798 	extensions = NULL;
799 	snoop_mime_type = NULL;
800 	if (ct) {
801 		EMailExtensionRegistry *reg;
802 		mime_type = camel_content_type_simple (ct);
803 
804 		reg = e_mail_parser_get_extension_registry (parser);
805 		extensions = e_mail_extension_registry_get_for_mime_type (
806 			reg, mime_type);
807 
808 		if (camel_content_type_is (ct, "text", "*") ||
809 		    camel_content_type_is (ct, "message", "*"))
810 			snoop_mime_type = mime_type;
811 		else
812 			g_free (mime_type);
813 	}
814 
815 	if (!snoop_mime_type)
816 		snoop_mime_type = e_mail_part_snoop_type (part);
817 
818 	if (!extensions) {
819 		EMailExtensionRegistry *reg;
820 
821 		reg = e_mail_parser_get_extension_registry (parser);
822 		extensions = e_mail_extension_registry_get_for_mime_type (
823 			reg, snoop_mime_type);
824 
825 		if (!extensions) {
826 			extensions = e_mail_extension_registry_get_fallback (
827 				reg, snoop_mime_type);
828 		}
829 	}
830 
831 	part_id_len = part_id->len;
832 	g_string_append (part_id, ".attachment");
833 
834 	empa = e_mail_part_attachment_new (part, part_id->str);
835 	empa->shown = extensions && (!g_queue_is_empty (extensions) &&
836 		e_mail_part_is_inline (part, extensions));
837 	empa->snoop_mime_type = snoop_mime_type;
838 
839 	first_part = g_queue_peek_head (parts_queue);
840 	if (first_part != NULL && !E_IS_MAIL_PART_ATTACHMENT (first_part)) {
841 		const gchar *id = e_mail_part_get_id (first_part);
842 		empa->part_id_with_attachment = g_strdup (id);
843 		first_part->is_hidden = TRUE;
844 	}
845 
846 	attachment = e_mail_part_attachment_ref_attachment (empa);
847 
848 	e_attachment_set_initially_shown (attachment, empa->shown);
849 	e_attachment_set_can_show (
850 		attachment,
851 		extensions && !g_queue_is_empty (extensions));
852 
853 	/* Try to guess size of the attachments */
854 	dw = camel_medium_get_content (CAMEL_MEDIUM (part));
855 	ba = camel_data_wrapper_get_byte_array (dw);
856 	if (ba) {
857 		size = ba->len;
858 
859 		if (camel_mime_part_get_encoding (part) == CAMEL_TRANSFER_ENCODING_BASE64)
860 			size = size / 1.37;
861 	} else {
862 		size = 0;
863 	}
864 
865 	/* e_attachment_load_async must be called from main thread */
866 	/* Prioritize ahead of GTK+ redraws. */
867 	g_idle_add_full (
868 		G_PRIORITY_HIGH_IDLE,
869 		(GSourceFunc) load_attachment_idle,
870 		g_object_ref (attachment),
871 		NULL);
872 
873 	if (size != 0) {
874 		GFileInfo *file_info;
875 
876 		file_info = e_attachment_ref_file_info (attachment);
877 
878 		if (file_info == NULL) {
879 			file_info = g_file_info_new ();
880 			g_file_info_set_content_type (
881 				file_info, empa->snoop_mime_type);
882 		}
883 
884 		g_file_info_set_size (file_info, size);
885 		e_attachment_set_file_info (attachment, file_info);
886 
887 		g_object_unref (file_info);
888 	}
889 
890 	g_object_unref (attachment);
891 
892 	g_string_truncate (part_id, part_id_len);
893 
894 	/* Push to head, not tail. */
895 	g_queue_push_head (parts_queue, empa);
896 }
897 
898 void
e_mail_parser_wrap_as_non_expandable_attachment(EMailParser * parser,CamelMimePart * part,GString * part_id,GQueue * out_parts_queue)899 e_mail_parser_wrap_as_non_expandable_attachment (EMailParser *parser,
900 						 CamelMimePart *part,
901 						 GString *part_id,
902 						 GQueue *out_parts_queue)
903 {
904 	GQueue work_queue = G_QUEUE_INIT;
905 	GList *head, *link;
906 
907 	g_return_if_fail (E_IS_MAIL_PARSER (parser));
908 	g_return_if_fail (CAMEL_IS_MIME_PART (part));
909 	g_return_if_fail (part_id != NULL);
910 	g_return_if_fail (out_parts_queue != NULL);
911 
912 	e_mail_parser_wrap_as_attachment (parser, part, part_id, &work_queue);
913 
914 	head = g_queue_peek_head_link (&work_queue);
915 
916 	for (link = head; link; link = g_list_next (link)) {
917 		EMailPartAttachment *empa = link->data;
918 
919 		if (E_IS_MAIL_PART_ATTACHMENT (empa)) {
920 			EAttachment *attachment;
921 			CamelMimePart *att_part;
922 
923 			empa->shown = FALSE;
924 			e_mail_part_attachment_set_expandable (empa, FALSE);
925 
926 			attachment = e_mail_part_attachment_ref_attachment (empa);
927 			e_attachment_set_initially_shown (attachment, FALSE);
928 			e_attachment_set_can_show (attachment, FALSE);
929 
930 			att_part = e_attachment_ref_mime_part (attachment);
931 			if (att_part)
932 				camel_mime_part_set_disposition (att_part, NULL);
933 
934 			g_clear_object (&att_part);
935 			g_clear_object (&attachment);
936 		}
937 	}
938 
939 	e_queue_transfer (&work_queue, out_parts_queue);
940 }
941 
942 CamelSession *
e_mail_parser_get_session(EMailParser * parser)943 e_mail_parser_get_session (EMailParser *parser)
944 {
945 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
946 
947 	return parser->priv->session;
948 }
949 
950 /* The 'operation' is not used as a GCancellable, but as an identificator
951    of an ongoing operation for which the part list is requested. */
952 EMailPartList *
e_mail_parser_ref_part_list_for_operation(EMailParser * parser,GCancellable * operation)953 e_mail_parser_ref_part_list_for_operation (EMailParser *parser,
954 					   GCancellable *operation)
955 {
956 	EMailPartList *part_list;
957 
958 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
959 
960 	g_mutex_lock (&parser->priv->mutex);
961 	part_list = g_hash_table_lookup (parser->priv->ongoing_part_lists, operation);
962 	if (part_list)
963 		g_object_ref (part_list);
964 	g_mutex_unlock (&parser->priv->mutex);
965 
966 	return part_list;
967 }
968 
969 EMailExtensionRegistry *
e_mail_parser_get_extension_registry(EMailParser * parser)970 e_mail_parser_get_extension_registry (EMailParser *parser)
971 {
972 	EMailParserClass *parser_class;
973 
974 	g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL);
975 
976 	parser_class = E_MAIL_PARSER_GET_CLASS (parser);
977 	g_return_val_if_fail (parser_class != NULL, NULL);
978 
979 	return E_MAIL_EXTENSION_REGISTRY (parser_class->extension_registry);
980 }
981