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