1 /*
2  * e-mail-part-utils.h
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 <glib/gi18n-lib.h>
21 
22 #include "e-mail-part-utils.h"
23 #include "e-mail-parser-extension.h"
24 
25 #include <e-util/e-util.h>
26 #include <gdk/gdk.h>
27 
28 #include <libsoup/soup.h>
29 
30 #include <string.h>
31 
32 #define d(x)
33 
34 /**
35  * e_mail_part_is_secured:
36  * @part: a #CamelMimePart
37  *
38  * Whether @part is signed or encrypted or not.
39  *
40  * Return Value: TRUE/FALSE
41  */
42 gboolean
e_mail_part_is_secured(CamelMimePart * part)43 e_mail_part_is_secured (CamelMimePart *part)
44 {
45 	CamelContentType *ct = camel_mime_part_get_content_type (part);
46 
47 	return (camel_content_type_is (ct, "multipart", "signed") ||
48 		camel_content_type_is (ct, "multipart", "encrypted") ||
49 		camel_content_type_is (ct, "application", "x-inlinepgp-signed") ||
50 		camel_content_type_is (ct, "application", "x-inlinepgp-encrypted") ||
51 		camel_content_type_is (ct, "application", "xpkcs7mime") ||
52 		camel_content_type_is (ct, "application", "xpkcs7-mime") ||
53 		camel_content_type_is (ct, "application", "x-pkcs7-mime") ||
54 		camel_content_type_is (ct, "application", "pkcs7-mime"));
55 }
56 
57 /*
58  * Returns: one of -e-mail-formatter-frame-security-* style classes
59  */
60 const gchar *
e_mail_part_get_frame_security_style(EMailPart * part)61 e_mail_part_get_frame_security_style (EMailPart *part)
62 {
63 	const gchar *frame_style = NULL;
64 	guint32 flags;
65 
66 	g_return_val_if_fail (part != NULL, "-e-mail-formatter-frame-security-none");
67 
68 	flags = e_mail_part_get_validity_flags (part);
69 
70 	if (flags == E_MAIL_PART_VALIDITY_NONE) {
71 		EMailPartList *part_list;
72 
73 		part_list = e_mail_part_ref_part_list (part);
74 
75 		if (part_list) {
76 			GQueue queue = G_QUEUE_INIT;
77 			GList *link;
78 			GSList *stack = NULL;
79 			gchar *end_partid = NULL;
80 			gboolean any_secure = FALSE;
81 
82 			e_mail_part_list_queue_parts (part_list, NULL, &queue);
83 
84 			for (link = g_queue_peek_head_link (&queue); link; link = g_list_next (link)) {
85 				EMailPart *lpart = link->data;
86 
87 				if (lpart == part) {
88 					GList *start = link;
89 
90 					/* Find which message this part belongs to */
91 					while (start = g_list_previous (start), start) {
92 						lpart = start->data;
93 						if (e_mail_part_id_has_suffix (lpart, ".rfc822") ||
94 						    e_mail_part_id_has_suffix (lpart, ".headers")) {
95 							end_partid = g_strconcat (e_mail_part_get_id (lpart), ".end", NULL);
96 							break;
97 						}
98 					}
99 
100 					link = start ? start : link;
101 					break;
102 				}
103 			}
104 
105 			for (; link && !any_secure && end_partid; link = g_list_next (link)) {
106 				EMailPart *lpart = link->data;
107 
108 				if (!lpart)
109 					continue;
110 
111 				if (g_strcmp0 (end_partid, e_mail_part_get_id (lpart)) == 0) {
112 					g_free (end_partid);
113 					end_partid = NULL;
114 
115 					if (stack) {
116 						end_partid = stack->data;
117 						stack = g_slist_remove (stack, end_partid);
118 					}
119 
120 					continue;
121 				}
122 
123 				if (e_mail_part_id_has_suffix (lpart, ".rfc822")) {
124 					stack = g_slist_prepend (stack, end_partid);
125 					end_partid = g_strconcat (e_mail_part_get_id (lpart), ".end", NULL);
126 				}
127 
128 				if (!stack)
129 					any_secure = e_mail_part_get_validity_flags (lpart) != E_MAIL_PART_VALIDITY_NONE;
130 			}
131 
132 			while (!g_queue_is_empty (&queue))
133 				g_object_unref (g_queue_pop_head (&queue));
134 
135 			g_slist_free_full (stack, g_free);
136 			g_object_unref (part_list);
137 			g_free (end_partid);
138 
139 			/* This part is neither signed, nor encrypted, but other parts
140 			   are signed or encrypted, thus mark this one as with bad security. */
141 			if (any_secure)
142 				return "-e-mail-formatter-frame-security-bad";
143 		}
144 
145 		return "-e-mail-formatter-frame-security-none";
146 	} else {
147 		GList *head, *link;
148 
149 		head = g_queue_peek_head_link (&part->validities);
150 
151 		for (link = head; link != NULL; link = g_list_next (link)) {
152 			EMailPartValidityPair *pair = link->data;
153 			if (pair->validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_BAD) {
154 				return "-e-mail-formatter-frame-security-bad";
155 			} else if (pair->validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN) {
156 				frame_style = "-e-mail-formatter-frame-security-unknown";
157 			} else if (frame_style == NULL && (
158 				pair->validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY || (
159 				pair->validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_GOOD &&
160 				(flags & E_MAIL_PART_VALIDITY_SENDER_SIGNER_MISMATCH) != 0))) {
161 				frame_style = "-e-mail-formatter-frame-security-need-key";
162 			} else if (frame_style == NULL &&
163 				pair->validity->sign.status == CAMEL_CIPHER_VALIDITY_SIGN_GOOD) {
164 				frame_style = "-e-mail-formatter-frame-security-good";
165 			}
166 		}
167 	}
168 
169 	if (frame_style == NULL)
170 		frame_style = "-e-mail-formatter-frame-security-none";
171 
172 	return frame_style;
173 }
174 
175 /**
176  * e_mail_part_snoop_type:
177  * @part: a #CamelMimePart
178  *
179  * Tries to snoop the mime type of a part.
180  *
181  * Return value: %NULL if unknown (more likely application/octet-stream).
182  **/
183 const gchar *
e_mail_part_snoop_type(CamelMimePart * part)184 e_mail_part_snoop_type (CamelMimePart *part)
185 {
186 	/* cache is here only to be able still return const gchar * */
187 	static GHashTable *types_cache = NULL;
188 
189 	const gchar *filename;
190 	gchar *name_type = NULL, *magic_type = NULL, *res, *tmp;
191 	CamelDataWrapper *dw;
192 
193 	filename = camel_mime_part_get_filename (part);
194 	if (filename != NULL)
195 		name_type = e_util_guess_mime_type (filename, FALSE);
196 
197 	dw = camel_medium_get_content ((CamelMedium *) part);
198 	if (!camel_data_wrapper_is_offline (dw)) {
199 		GByteArray *byte_array;
200 		CamelStream *stream;
201 
202 		byte_array = g_byte_array_new ();
203 		stream = camel_stream_mem_new_with_byte_array (byte_array);
204 
205 		if (camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL) > 0) {
206 			gchar *content_type;
207 
208 			content_type = g_content_type_guess (
209 				filename, byte_array->data,
210 				byte_array->len, NULL);
211 
212 			if (content_type != NULL)
213 				magic_type = g_content_type_get_mime_type (content_type);
214 
215 			g_free (content_type);
216 		}
217 
218 		g_object_unref (stream);
219 	}
220 
221 	/* If gvfs doesn't recognize the data by magic, but it
222 	 * contains English words, it will call it text/plain. If the
223 	 * filename-based check came up with something different, use
224 	 * that instead and if it returns "application/octet-stream"
225 	 * try to do better with the filename check.
226 	 */
227 
228 	if (magic_type) {
229 		if (name_type
230 		    && (!strcmp (magic_type, "text/plain")
231 			|| !strcmp (magic_type, "application/octet-stream")))
232 			res = name_type;
233 		else
234 			res = magic_type;
235 	} else
236 		res = name_type;
237 
238 	if (res != name_type)
239 		g_free (name_type);
240 
241 	if (res != magic_type)
242 		g_free (magic_type);
243 
244 	if (!types_cache)
245 		types_cache = g_hash_table_new_full (
246 			g_str_hash, g_str_equal,
247 			(GDestroyNotify) g_free,
248 			(GDestroyNotify) NULL);
249 
250 	if (res) {
251 		tmp = g_hash_table_lookup (types_cache, res);
252 		if (tmp) {
253 			g_free (res);
254 			res = tmp;
255 		} else {
256 			g_hash_table_insert (types_cache, res, res);
257 		}
258 	}
259 
260 	d (printf ("Snooped mime type %s\n", res));
261 	return res;
262 
263 	/* We used to load parts to check their type, we don't anymore,
264 	 * see bug #211778 for some discussion */
265 }
266 
267 /**
268  * e_mail_part_is_attachment
269  * @part: Part to check.
270  *
271  * Returns true if the part is an attachment.
272  *
273  * A part is not considered an attachment if it is a
274  * multipart, or a text part with no filename.  It is used
275  * to determine if an attachment header should be displayed for
276  * the part.
277  *
278  * Content-Disposition is not checked.
279  *
280  * Return value: TRUE/FALSE
281  **/
282 gboolean
e_mail_part_is_attachment(CamelMimePart * part)283 e_mail_part_is_attachment (CamelMimePart *part)
284 {
285 	/*CamelContentType *ct = camel_mime_part_get_content_type(part);*/
286 	CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part);
287 	CamelContentType *mime_type;
288 
289 	if (!dw)
290 		return FALSE;
291 
292 	mime_type = camel_data_wrapper_get_mime_type_field (dw);
293 
294 	if (!mime_type)
295 		return FALSE;
296 
297 	d (printf ("checking is attachment %s/%s\n", mime_type->type, mime_type->subtype));
298 	return !(camel_content_type_is (mime_type, "multipart", "*")
299 		 || camel_content_type_is (mime_type, "application", "xpkcs7mime")
300 		 || camel_content_type_is (mime_type, "application", "xpkcs7-mime")
301 		 || camel_content_type_is (mime_type, "application", "x-pkcs7-mime")
302 		 || camel_content_type_is (mime_type, "application", "pkcs7-mime")
303 		 || camel_content_type_is (mime_type, "application", "x-inlinepgp-signed")
304 		 || camel_content_type_is (mime_type, "application", "x-inlinepgp-encrypted")
305 		 || camel_content_type_is (mime_type, "x-evolution", "evolution-rss-feed")
306 		 || camel_content_type_is (mime_type, "text", "calendar")
307 		 || camel_content_type_is (mime_type, "text", "x-calendar")
308 		 || (camel_content_type_is (mime_type, "text", "*")
309 		     && camel_mime_part_get_filename (part) == NULL));
310 }
311 
312 /**
313  * e_mail_part_preserve_charset_in_content_type:
314  * @ipart: Source #CamelMimePart
315  * @opart: Target #CamelMimePart
316  *
317  * Copies 'charset' part of content-type header from @ipart to @opart.
318  */
319 void
e_mail_part_preserve_charset_in_content_type(CamelMimePart * ipart,CamelMimePart * opart)320 e_mail_part_preserve_charset_in_content_type (CamelMimePart *ipart,
321                                               CamelMimePart *opart)
322 {
323 	CamelDataWrapper *data_wrapper;
324 	CamelContentType *content_type;
325 	const gchar *charset;
326 
327 	g_return_if_fail (ipart != NULL);
328 	g_return_if_fail (opart != NULL);
329 
330 	data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart));
331 	content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
332 
333 	if (content_type == NULL)
334 		return;
335 
336 	charset = camel_content_type_param (content_type, "charset");
337 
338 	if (charset == NULL || *charset == '\0')
339 		return;
340 
341 	data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart));
342 	content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
343 
344 	if (content_type)
345 		camel_content_type_set_param (content_type, "charset", charset);
346 
347 	/* update charset also on the part itself */
348 	data_wrapper = CAMEL_DATA_WRAPPER (opart);
349 	content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
350 	if (content_type)
351 		camel_content_type_set_param (content_type, "charset", charset);
352 }
353 
354 /**
355  * e_mail_part_get_related_display_part:
356  * @part: a multipart/related or multipart/alternative #CamelMimePart
357  * @out_displayid: (out) returns index of the returned part
358  *
359  * Goes through all subparts of given @part and tries to determine which
360  * part should be displayed and which parts are just attachments to the
361  * part.
362  *
363  * Return Value: A #CamelMimePart that should be displayed
364  */
365 CamelMimePart *
e_mail_part_get_related_display_part(CamelMimePart * part,gint * out_displayid)366 e_mail_part_get_related_display_part (CamelMimePart *part,
367                                       gint *out_displayid)
368 {
369 	CamelMultipart *mp;
370 	CamelMimePart *body_part, *display_part = NULL;
371 	CamelContentType *content_type;
372 	const gchar *start;
373 	gint i, nparts, displayid = 0;
374 
375 	mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
376 
377 	if (!CAMEL_IS_MULTIPART (mp))
378 		return NULL;
379 
380 	nparts = camel_multipart_get_number (mp);
381 	content_type = camel_mime_part_get_content_type (part);
382 	start = camel_content_type_param (content_type, "start");
383 	if (start && strlen (start) > 2) {
384 		gint len;
385 		const gchar *cid;
386 
387 		/* strip <>'s from CID */
388 		len = strlen (start) - 2;
389 		start++;
390 
391 		for (i = 0; i < nparts; i++) {
392 			body_part = camel_multipart_get_part (mp, i);
393 			cid = camel_mime_part_get_content_id (body_part);
394 
395 			if (cid && !strncmp (cid, start, len) && strlen (cid) == len) {
396 				display_part = body_part;
397 				displayid = i;
398 				break;
399 			}
400 		}
401 	} else {
402 		display_part = camel_multipart_get_part (mp, 0);
403 	}
404 
405 	if (out_displayid)
406 		*out_displayid = displayid;
407 
408 	return display_part;
409 }
410 
411 void
e_mail_part_animation_extract_frame(GBytes * bytes,gchar ** out_frame,gsize * out_len)412 e_mail_part_animation_extract_frame (GBytes *bytes,
413                                      gchar **out_frame,
414                                      gsize *out_len)
415 {
416 	GdkPixbufLoader *loader;
417 	GdkPixbufAnimation *animation;
418 	GdkPixbuf *frame_buf;
419 	const guchar *bytes_data;
420 	gsize bytes_size;
421 
422 	/* GIF89a (GIF image signature) */
423 	const guchar GIF_HEADER[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
424 	const gint GIF_HEADER_LEN = sizeof (GIF_HEADER);
425 
426 	/* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */
427 	const guchar GIF_APPEXT[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41,
428 				     0x50, 0x45, 0x32, 0x2E, 0x30 };
429 	const gint GIF_APPEXT_LEN = sizeof (GIF_APPEXT);
430 
431 	g_return_if_fail (out_frame != NULL);
432 	g_return_if_fail (out_len != NULL);
433 
434 	*out_frame = NULL;
435 	*out_len = 0;
436 
437 	if (bytes == NULL)
438 		return;
439 
440 	bytes_data = g_bytes_get_data (bytes, &bytes_size);
441 
442 	if (bytes_size == 0)
443 		return;
444 
445 	/* Check if the image is an animated GIF. We don't care about any
446 	 * other animated formats (APNG or MNG) as WebKit does not support them
447 	 * and displays only the first frame. */
448 	if ((bytes_size < 0x331)
449 	    || (memcmp (bytes_data, GIF_HEADER, GIF_HEADER_LEN) != 0)
450 	    || (memcmp (&bytes_data[0x310], GIF_APPEXT, GIF_APPEXT_LEN) != 0)) {
451 		*out_frame = g_memdup (bytes_data, bytes_size);
452 		*out_len = bytes_size;
453 		return;
454 	}
455 
456 	loader = gdk_pixbuf_loader_new ();
457 	gdk_pixbuf_loader_write (loader, bytes_data, bytes_size, NULL);
458 	gdk_pixbuf_loader_close (loader, NULL);
459 	animation = gdk_pixbuf_loader_get_animation (loader);
460 	if (!animation) {
461 		*out_frame = g_memdup (bytes_data, bytes_size);
462 		*out_len = bytes_size;
463 		g_object_unref (loader);
464 		return;
465 	}
466 
467 	/* Extract first frame */
468 	frame_buf = gdk_pixbuf_animation_get_static_image (animation);
469 	if (!frame_buf) {
470 		*out_frame = g_memdup (bytes_data, bytes_size);
471 		*out_len = bytes_size;
472 		g_object_unref (loader);
473 		g_object_unref (animation);
474 		return;
475 	}
476 
477 	/* Unforunately, GdkPixbuf cannot save to GIF, but WebKit does not
478 	 * have any trouble displaying PNG image despite the part having
479 	 * image/gif mime-type */
480 	gdk_pixbuf_save_to_buffer (
481 		frame_buf, out_frame, out_len, "png", NULL, NULL);
482 
483 	g_object_unref (loader);
484 }
485 
486 /**
487  * e_mail_part_build_url:
488  * @folder: (allow-none) a #CamelFolder with the message or %NULL
489  * @message_uid: uid of the message within the @folder
490  * @first_param_name: Name of first query parameter followed by GType of it's value and value
491  * terminated by %NULL.
492  *
493  * Construct a URI for message.
494  *
495  * The URI can contain multiple query parameters. The list of parameters must be
496  * NULL-terminated. Each query must contain name, GType of value and value.
497  *
498  * Return Value: a URL of a message or part
499  */
500 gchar *
e_mail_part_build_uri(CamelFolder * folder,const gchar * message_uid,const gchar * first_param_name,...)501 e_mail_part_build_uri (CamelFolder *folder,
502                        const gchar *message_uid,
503                        const gchar *first_param_name,
504                        ...)
505 {
506 	CamelStore *store;
507 	gchar *uri, *tmp;
508 	va_list ap;
509 	const gchar *name;
510 	const gchar *service_uid, *folder_name;
511 	gchar *encoded_message_uid;
512 	gchar separator;
513 
514 	g_return_val_if_fail (message_uid && *message_uid, NULL);
515 
516 	if (!folder) {
517 		folder_name = "generic";
518 		service_uid = "generic";
519 	} else {
520 		tmp = (gchar *) camel_folder_get_full_name (folder);
521 		folder_name = (const gchar *) soup_uri_encode (tmp, NULL);
522 		store = camel_folder_get_parent_store (folder);
523 		if (store)
524 			service_uid = camel_service_get_uid (CAMEL_SERVICE (store));
525 		else
526 			service_uid = "generic";
527 	}
528 
529 	encoded_message_uid = soup_uri_encode (message_uid, NULL);
530 	tmp = g_strdup_printf (
531 		"mail://%s/%s/%s",
532 		service_uid,
533 		folder_name,
534 		encoded_message_uid);
535 	g_free (encoded_message_uid);
536 
537 	if (folder) {
538 		g_free ((gchar *) folder_name);
539 	}
540 
541 	va_start (ap, first_param_name);
542 	name = first_param_name;
543 	separator = '?';
544 	while (name) {
545 		gchar *tmp2;
546 		gint type = va_arg (ap, gint);
547 		switch (type) {
548 			case G_TYPE_INT:
549 			case G_TYPE_BOOLEAN: {
550 				gint val = va_arg (ap, gint);
551 				tmp2 = g_strdup_printf (
552 					"%s%c%s=%d", tmp,
553 						separator, name, val);
554 				break;
555 			}
556 			case G_TYPE_FLOAT:
557 			case G_TYPE_DOUBLE: {
558 				gdouble val = va_arg (ap, double);
559 				tmp2 = g_strdup_printf (
560 					"%s%c%s=%f", tmp,
561 						separator, name, val);
562 				break;
563 			}
564 			case G_TYPE_STRING: {
565 				gchar *val = va_arg (ap, gchar *);
566 				gchar *escaped = soup_uri_encode (val, NULL);
567 				tmp2 = g_strdup_printf (
568 					"%s%c%s=%s", tmp,
569 						separator, name, escaped);
570 				g_free (escaped);
571 				break;
572 			}
573 			case G_TYPE_POINTER: {
574 				gpointer val = va_arg (ap, gpointer);
575 				tmp2 = g_strdup_printf ("%s%c%s=%p", tmp, separator, name, val);
576 				break;
577 			}
578 			default:
579 				g_warning ("Invalid param type %s", g_type_name (type));
580 				va_end (ap);
581 				return NULL;
582 		}
583 
584 		g_free (tmp);
585 		tmp = tmp2;
586 
587 		if (separator == '?')
588 			separator = '&';
589 
590 		name = va_arg (ap, gchar *);
591 	}
592 	va_end (ap);
593 
594 	uri = tmp;
595 	if (uri == NULL)
596 		return NULL;
597 
598 	/* For some reason, webkit won't accept URL with username, but
599 	 * without password (mail://store@host/folder/mail), so we
600 	 * will replace the '@' symbol by '/' to get URL like
601 	 * mail://store/host/folder/mail which is OK
602 	 */
603 	while ((tmp = strchr (uri, '@')) != NULL) {
604 		tmp[0] = '/';
605 	}
606 
607 	return uri;
608 }
609 
610 /**
611  * e_mail_part_describe:
612  * @part: a #CamelMimePart
613  * @mime_type: MIME type of the content
614  *
615  * Generate a simple textual description of a part, @mime_type represents
616  * the content.
617  *
618  * Return value:
619  **/
620 gchar *
e_mail_part_describe(CamelMimePart * part,const gchar * mime_type)621 e_mail_part_describe (CamelMimePart *part,
622                       const gchar *mime_type)
623 {
624 	GString *stext;
625 	const gchar *filename, *description;
626 	gchar *content_type, *desc;
627 
628 	stext = g_string_new ("");
629 	content_type = g_content_type_from_mime_type (mime_type);
630 	desc = g_content_type_get_description (
631 		content_type != NULL ? content_type : mime_type);
632 	g_free (content_type);
633 	g_string_append_printf (
634 		stext, _("%s attachment"), desc ? desc : mime_type);
635 	g_free (desc);
636 
637 	filename = camel_mime_part_get_filename (part);
638 	description = camel_mime_part_get_description (part);
639 
640 	if (filename && *filename) {
641 		gchar *basename = g_path_get_basename (filename);
642 		g_string_append_printf (stext, " (%s)", basename);
643 		g_free (basename);
644 	} else {
645 		CamelDataWrapper *content;
646 
647 		filename = NULL;
648 		content = camel_medium_get_content (CAMEL_MEDIUM (part));
649 
650 		if (CAMEL_IS_MIME_MESSAGE (content))
651 			filename = camel_mime_message_get_subject (
652 				CAMEL_MIME_MESSAGE (content));
653 
654 		if (filename && *filename)
655 			g_string_append_printf (stext, " (%s)", filename);
656 	}
657 
658 	if (description != NULL && *description != '\0' &&
659 		g_strcmp0 (filename, description) != 0)
660 		g_string_append_printf (stext, ", \"%s\"", description);
661 
662 	return g_string_free (stext, FALSE);
663 }
664 
665 gboolean
e_mail_part_is_inline(CamelMimePart * mime_part,GQueue * extensions)666 e_mail_part_is_inline (CamelMimePart *mime_part,
667                        GQueue *extensions)
668 {
669 	EMailParserExtension *extension;
670 	EMailParserExtensionClass *class;
671 	const gchar *disposition;
672 	gboolean is_inline = FALSE;
673 
674 	disposition = camel_mime_part_get_disposition (mime_part);
675 
676 	if (disposition != NULL) {
677 		is_inline = (g_ascii_strcasecmp (disposition, "inline") == 0);
678 		if (is_inline) {
679 			GSettings *settings;
680 
681 			settings = e_util_ref_settings ("org.gnome.evolution.mail");
682 			is_inline = g_settings_get_boolean (settings, "display-content-disposition-inline");
683 			g_clear_object (&settings);
684 		}
685 	}
686 
687 	if ((extensions == NULL) || g_queue_is_empty (extensions))
688 		return is_inline;
689 
690 	extension = g_queue_peek_head (extensions);
691 	class = E_MAIL_PARSER_EXTENSION_GET_CLASS (extension);
692 
693 	/* Some types need to override the disposition.
694 	 * e.g. application/x-pkcs7-mime */
695 	if (class->flags & E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION)
696 		return TRUE;
697 
698 	if (disposition != NULL)
699 		return is_inline;
700 
701 	/* Otherwise, use the default for this handler type. */
702 	return (class->flags & E_MAIL_PARSER_EXTENSION_INLINE) != 0;
703 }
704 
705 /**
706  * e_mail_part_utils_body_refers:
707  * @body: text body to search for references in; can be %NULL, then returns %FALSE
708  * @cid: a Content-ID to search for; if found in body, it should be of form "cid:xxxxx"; can be %NULL
709  *
710  * Returns whether @body contains a reference to @cid enclosed in quotes;
711  *    returns %FALSE if any of the arguments is %NULL.
712  **/
713 gboolean
e_mail_part_utils_body_refers(const gchar * body,const gchar * cid)714 e_mail_part_utils_body_refers (const gchar *body,
715                                const gchar *cid)
716 {
717 	const gchar *ptr;
718 
719 	if (!body || !cid || !*cid)
720 		return FALSE;
721 
722 	ptr = body;
723 	while (ptr = strstr (ptr, cid), ptr != NULL) {
724 		if (ptr - body > 1 && ptr[-1] == '\"' && ptr[strlen (cid)] == '\"')
725 			return TRUE;
726 
727 		ptr++;
728 	}
729 
730 	return FALSE;
731 }
732 
733 static gboolean
message_find_parent_part_rec(CamelMimePart * part,CamelMimePart * child,CamelMimePart ** out_parent)734 message_find_parent_part_rec (CamelMimePart *part,
735 			      CamelMimePart *child,
736 			      CamelMimePart **out_parent)
737 {
738 	CamelDataWrapper *containee;
739 	gboolean go = TRUE;
740 
741 	if (part == child)
742 		return FALSE;
743 
744 	containee = camel_medium_get_content (CAMEL_MEDIUM (part));
745 
746 	if (!containee)
747 		return go;
748 
749 	/* using the object types is more accurate than using the mime/types */
750 	if (CAMEL_IS_MULTIPART (containee)) {
751 		CamelMultipart *multipart = CAMEL_MULTIPART (containee);
752 		gint parts, ii;
753 
754 		parts = camel_multipart_get_number (multipart);
755 		for (ii = 0; go && ii < parts; ii++) {
756 			CamelMimePart *mpart = camel_multipart_get_part (multipart, ii);
757 
758 			if (mpart == child) {
759 				*out_parent = part;
760 				go = FALSE;
761 			} else {
762 				go = message_find_parent_part_rec (mpart, child, out_parent);
763 			}
764 		}
765 	} else if (CAMEL_IS_MIME_MESSAGE (containee)) {
766 		go = message_find_parent_part_rec (CAMEL_MIME_PART (containee), child, out_parent);
767 	}
768 
769 	return go;
770 }
771 
772 /**
773  * e_mail_part_utils_find_parent_part:
774  * @message: a #CamelMimeMessage
775  * @child: a #CamelMimePart, which is part of @message
776  *
777  * Searches for the parent of the @child in the @message, The @child is
778  * supposed to be in the @message.
779  *
780  * Returns: (transfer none) (nullable): Parent of the @child, or %NULL.
781  *
782  * Since: 3.30
783  **/
784 CamelMimePart *
e_mail_part_utils_find_parent_part(CamelMimeMessage * message,CamelMimePart * child)785 e_mail_part_utils_find_parent_part (CamelMimeMessage *message,
786 				    CamelMimePart *child)
787 {
788 	CamelMimePart *parent = NULL;
789 
790 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
791 	g_return_val_if_fail (CAMEL_IS_MIME_PART (child), NULL);
792 
793 	message_find_parent_part_rec (CAMEL_MIME_PART (message), child, &parent);
794 
795 	return parent;
796 }
797