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.
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
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/>.
18 #include "evolution-config.h"
20 #include <glib/gi18n-lib.h>
22 #include "e-mail-part-utils.h"
23 #include "e-mail-parser-extension.h"
25 #include <e-util/e-util.h>
28 #include <libsoup/soup.h>
35 * e_mail_part_is_secured:
36 * @part: a #CamelMimePart
38 * Whether @part is signed or encrypted or not.
40 * Return Value: TRUE/FALSE
43 e_mail_part_is_secured (CamelMimePart
*part
)
45 CamelContentType
*ct
= camel_mime_part_get_content_type (part
);
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", "x-pkcs7-mime") ||
52 camel_content_type_is (ct
, "application", "pkcs7-mime"));
56 * Returns: one of -e-mail-formatter-frame-security-* style classes
59 e_mail_part_get_frame_security_style (EMailPart
*part
)
61 const gchar
*frame_style
= NULL
;
64 g_return_val_if_fail (part
!= NULL
, "-e-mail-formatter-frame-security-none");
66 flags
= e_mail_part_get_validity_flags (part
);
68 if (flags
== E_MAIL_PART_VALIDITY_NONE
) {
69 return "-e-mail-formatter-frame-security-none";
73 head
= g_queue_peek_head_link (&part
->validities
);
75 for (link
= head
; link
!= NULL
; link
= g_list_next (link
)) {
76 EMailPartValidityPair
*pair
= link
->data
;
77 if (pair
->validity
->sign
.status
== CAMEL_CIPHER_VALIDITY_SIGN_BAD
) {
78 return "-e-mail-formatter-frame-security-bad";
79 } else if (pair
->validity
->sign
.status
== CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN
) {
80 frame_style
= "-e-mail-formatter-frame-security-unknown";
81 } else if (frame_style
== NULL
&& (
82 pair
->validity
->sign
.status
== CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY
|| (
83 pair
->validity
->sign
.status
== CAMEL_CIPHER_VALIDITY_SIGN_GOOD
&&
84 (flags
& E_MAIL_PART_VALIDITY_SENDER_SIGNER_MISMATCH
) != 0))) {
85 frame_style
= "-e-mail-formatter-frame-security-need-key";
86 } else if (frame_style
== NULL
&&
87 pair
->validity
->sign
.status
== CAMEL_CIPHER_VALIDITY_SIGN_GOOD
) {
88 frame_style
= "-e-mail-formatter-frame-security-good";
93 if (frame_style
== NULL
)
94 frame_style
= "-e-mail-formatter-frame-security-none";
100 * e_mail_part_snoop_type:
101 * @part: a #CamelMimePart
103 * Tries to snoop the mime type of a part.
105 * Return value: %NULL if unknown (more likely application/octet-stream).
108 e_mail_part_snoop_type (CamelMimePart
*part
)
110 /* cache is here only to be able still return const gchar * */
111 static GHashTable
*types_cache
= NULL
;
113 const gchar
*filename
;
114 gchar
*name_type
= NULL
, *magic_type
= NULL
, *res
, *tmp
;
115 CamelDataWrapper
*dw
;
117 filename
= camel_mime_part_get_filename (part
);
118 if (filename
!= NULL
)
119 name_type
= e_util_guess_mime_type (filename
, FALSE
);
121 dw
= camel_medium_get_content ((CamelMedium
*) part
);
122 if (!camel_data_wrapper_is_offline (dw
)) {
123 GByteArray
*byte_array
;
126 byte_array
= g_byte_array_new ();
127 stream
= camel_stream_mem_new_with_byte_array (byte_array
);
129 if (camel_data_wrapper_decode_to_stream_sync (dw
, stream
, NULL
, NULL
) > 0) {
132 content_type
= g_content_type_guess (
133 filename
, byte_array
->data
,
134 byte_array
->len
, NULL
);
136 if (content_type
!= NULL
)
137 magic_type
= g_content_type_get_mime_type (content_type
);
139 g_free (content_type
);
142 g_object_unref (stream
);
145 /* If gvfs doesn't recognize the data by magic, but it
146 * contains English words, it will call it text/plain. If the
147 * filename-based check came up with something different, use
148 * that instead and if it returns "application/octet-stream"
149 * try to do better with the filename check.
154 && (!strcmp (magic_type
, "text/plain")
155 || !strcmp (magic_type
, "application/octet-stream")))
162 if (res
!= name_type
)
165 if (res
!= magic_type
)
169 types_cache
= g_hash_table_new_full (
170 g_str_hash
, g_str_equal
,
171 (GDestroyNotify
) g_free
,
172 (GDestroyNotify
) NULL
);
175 tmp
= g_hash_table_lookup (types_cache
, res
);
180 g_hash_table_insert (types_cache
, res
, res
);
184 d (printf ("Snooped mime type %s\n", res
));
187 /* We used to load parts to check their type, we don't anymore,
188 * see bug #211778 for some discussion */
192 * e_mail_part_is_attachment
193 * @part: Part to check.
195 * Returns true if the part is an attachment.
197 * A part is not considered an attachment if it is a
198 * multipart, or a text part with no filename. It is used
199 * to determine if an attachment header should be displayed for
202 * Content-Disposition is not checked.
204 * Return value: TRUE/FALSE
207 e_mail_part_is_attachment (CamelMimePart
*part
)
209 /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/
210 CamelDataWrapper
*dw
= camel_medium_get_content ((CamelMedium
*) part
);
211 CamelContentType
*mime_type
;
216 mime_type
= camel_data_wrapper_get_mime_type_field (dw
);
221 d (printf ("checking is attachment %s/%s\n", mime_type
->type
, mime_type
->subtype
));
222 return !(camel_content_type_is (mime_type
, "multipart", "*")
223 || camel_content_type_is (mime_type
, "application", "x-pkcs7-mime")
224 || camel_content_type_is (mime_type
, "application", "pkcs7-mime")
225 || camel_content_type_is (mime_type
, "application", "x-inlinepgp-signed")
226 || camel_content_type_is (mime_type
, "application", "x-inlinepgp-encrypted")
227 || camel_content_type_is (mime_type
, "x-evolution", "evolution-rss-feed")
228 || camel_content_type_is (mime_type
, "text", "calendar")
229 || camel_content_type_is (mime_type
, "text", "x-calendar")
230 || (camel_content_type_is (mime_type
, "text", "*")
231 && camel_mime_part_get_filename (part
) == NULL
));
235 * e_mail_part_preserve_charset_in_content_type:
236 * @ipart: Source #CamelMimePart
237 * @opart: Target #CamelMimePart
239 * Copies 'charset' part of content-type header from @ipart to @opart.
242 e_mail_part_preserve_charset_in_content_type (CamelMimePart
*ipart
,
243 CamelMimePart
*opart
)
245 CamelDataWrapper
*data_wrapper
;
246 CamelContentType
*content_type
;
247 const gchar
*charset
;
249 g_return_if_fail (ipart
!= NULL
);
250 g_return_if_fail (opart
!= NULL
);
252 data_wrapper
= camel_medium_get_content (CAMEL_MEDIUM (ipart
));
253 content_type
= camel_data_wrapper_get_mime_type_field (data_wrapper
);
255 if (content_type
== NULL
)
258 charset
= camel_content_type_param (content_type
, "charset");
260 if (charset
== NULL
|| *charset
== '\0')
263 data_wrapper
= camel_medium_get_content (CAMEL_MEDIUM (opart
));
264 content_type
= camel_data_wrapper_get_mime_type_field (data_wrapper
);
267 camel_content_type_set_param (content_type
, "charset", charset
);
269 /* update charset also on the part itself */
270 data_wrapper
= CAMEL_DATA_WRAPPER (opart
);
271 content_type
= camel_data_wrapper_get_mime_type_field (data_wrapper
);
273 camel_content_type_set_param (content_type
, "charset", charset
);
277 * e_mail_part_get_related_display_part:
278 * @part: a multipart/related or multipart/alternative #CamelMimePart
279 * @out_displayid: (out) returns index of the returned part
281 * Goes through all subparts of given @part and tries to determine which
282 * part should be displayed and which parts are just attachments to the
285 * Return Value: A #CamelMimePart that should be displayed
288 e_mail_part_get_related_display_part (CamelMimePart
*part
,
292 CamelMimePart
*body_part
, *display_part
= NULL
;
293 CamelContentType
*content_type
;
295 gint i
, nparts
, displayid
= 0;
297 mp
= (CamelMultipart
*) camel_medium_get_content ((CamelMedium
*) part
);
299 if (!CAMEL_IS_MULTIPART (mp
))
302 nparts
= camel_multipart_get_number (mp
);
303 content_type
= camel_mime_part_get_content_type (part
);
304 start
= camel_content_type_param (content_type
, "start");
305 if (start
&& strlen (start
) > 2) {
309 /* strip <>'s from CID */
310 len
= strlen (start
) - 2;
313 for (i
= 0; i
< nparts
; i
++) {
314 body_part
= camel_multipart_get_part (mp
, i
);
315 cid
= camel_mime_part_get_content_id (body_part
);
317 if (cid
&& !strncmp (cid
, start
, len
) && strlen (cid
) == len
) {
318 display_part
= body_part
;
324 display_part
= camel_multipart_get_part (mp
, 0);
328 *out_displayid
= displayid
;
334 e_mail_part_animation_extract_frame (GBytes
*bytes
,
338 GdkPixbufLoader
*loader
;
339 GdkPixbufAnimation
*animation
;
340 GdkPixbuf
*frame_buf
;
341 const guchar
*bytes_data
;
344 /* GIF89a (GIF image signature) */
345 const guchar GIF_HEADER
[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
346 const gint GIF_HEADER_LEN
= sizeof (GIF_HEADER
);
348 /* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */
349 const guchar GIF_APPEXT
[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41,
350 0x50, 0x45, 0x32, 0x2E, 0x30 };
351 const gint GIF_APPEXT_LEN
= sizeof (GIF_APPEXT
);
353 g_return_if_fail (out_frame
!= NULL
);
354 g_return_if_fail (out_len
!= NULL
);
362 bytes_data
= g_bytes_get_data (bytes
, &bytes_size
);
367 /* Check if the image is an animated GIF. We don't care about any
368 * other animated formats (APNG or MNG) as WebKit does not support them
369 * and displays only the first frame. */
370 if ((bytes_size
< 0x331)
371 || (memcmp (bytes_data
, GIF_HEADER
, GIF_HEADER_LEN
) != 0)
372 || (memcmp (&bytes_data
[0x310], GIF_APPEXT
, GIF_APPEXT_LEN
) != 0)) {
373 *out_frame
= g_memdup (bytes_data
, bytes_size
);
374 *out_len
= bytes_size
;
378 loader
= gdk_pixbuf_loader_new ();
379 gdk_pixbuf_loader_write (loader
, bytes_data
, bytes_size
, NULL
);
380 gdk_pixbuf_loader_close (loader
, NULL
);
381 animation
= gdk_pixbuf_loader_get_animation (loader
);
383 *out_frame
= g_memdup (bytes_data
, bytes_size
);
384 *out_len
= bytes_size
;
385 g_object_unref (loader
);
389 /* Extract first frame */
390 frame_buf
= gdk_pixbuf_animation_get_static_image (animation
);
392 *out_frame
= g_memdup (bytes_data
, bytes_size
);
393 *out_len
= bytes_size
;
394 g_object_unref (loader
);
395 g_object_unref (animation
);
399 /* Unforunately, GdkPixbuf cannot save to GIF, but WebKit does not
400 * have any trouble displaying PNG image despite the part having
401 * image/gif mime-type */
402 gdk_pixbuf_save_to_buffer (
403 frame_buf
, out_frame
, out_len
, "png", NULL
, NULL
);
405 g_object_unref (loader
);
409 * e_mail_part_build_url:
410 * @folder: (allow-none) a #CamelFolder with the message or %NULL
411 * @message_uid: uid of the message within the @folder
412 * @first_param_name: Name of first query parameter followed by GType of it's value and value
413 * terminated by %NULL.
415 * Construct a URI for message.
417 * The URI can contain multiple query parameters. The list of parameters must be
418 * NULL-terminated. Each query must contain name, GType of value and value.
420 * Return Value: a URL of a message or part
423 e_mail_part_build_uri (CamelFolder
*folder
,
424 const gchar
*message_uid
,
425 const gchar
*first_param_name
,
432 const gchar
*service_uid
, *folder_name
;
433 gchar
*encoded_message_uid
;
436 g_return_val_if_fail (message_uid
&& *message_uid
, NULL
);
439 folder_name
= "generic";
440 service_uid
= "generic";
442 tmp
= (gchar
*) camel_folder_get_full_name (folder
);
443 folder_name
= (const gchar
*) soup_uri_encode (tmp
, NULL
);
444 store
= camel_folder_get_parent_store (folder
);
446 service_uid
= camel_service_get_uid (CAMEL_SERVICE (store
));
448 service_uid
= "generic";
451 encoded_message_uid
= soup_uri_encode (message_uid
, NULL
);
452 tmp
= g_strdup_printf (
456 encoded_message_uid
);
457 g_free (encoded_message_uid
);
460 g_free ((gchar
*) folder_name
);
463 va_start (ap
, first_param_name
);
464 name
= first_param_name
;
468 gint type
= va_arg (ap
, gint
);
471 case G_TYPE_BOOLEAN
: {
472 gint val
= va_arg (ap
, gint
);
473 tmp2
= g_strdup_printf (
475 separator
, name
, val
);
479 case G_TYPE_DOUBLE
: {
480 gdouble val
= va_arg (ap
, double);
481 tmp2
= g_strdup_printf (
483 separator
, name
, val
);
486 case G_TYPE_STRING
: {
487 gchar
*val
= va_arg (ap
, gchar
*);
488 gchar
*escaped
= soup_uri_encode (val
, NULL
);
489 tmp2
= g_strdup_printf (
491 separator
, name
, escaped
);
495 case G_TYPE_POINTER
: {
496 gpointer val
= va_arg (ap
, gpointer
);
497 tmp2
= g_strdup_printf ("%s%c%s=%p", tmp
, separator
, name
, val
);
501 g_warning ("Invalid param type %s", g_type_name (type
));
509 if (separator
== '?')
512 name
= va_arg (ap
, gchar
*);
520 /* For some reason, webkit won't accept URL with username, but
521 * without password (mail://store@host/folder/mail), so we
522 * will replace the '@' symbol by '/' to get URL like
523 * mail://store/host/folder/mail which is OK
525 while ((tmp
= strchr (uri
, '@')) != NULL
) {
533 * e_mail_part_describe:
534 * @part: a #CamelMimePart
535 * @mime_type: MIME type of the content
537 * Generate a simple textual description of a part, @mime_type represents
543 e_mail_part_describe (CamelMimePart
*part
,
544 const gchar
*mime_type
)
547 const gchar
*filename
, *description
;
548 gchar
*content_type
, *desc
;
550 stext
= g_string_new ("");
551 content_type
= g_content_type_from_mime_type (mime_type
);
552 desc
= g_content_type_get_description (
553 content_type
!= NULL
? content_type
: mime_type
);
554 g_free (content_type
);
555 g_string_append_printf (
556 stext
, _("%s attachment"), desc
? desc
: mime_type
);
559 filename
= camel_mime_part_get_filename (part
);
560 description
= camel_mime_part_get_description (part
);
562 if (filename
&& *filename
) {
563 gchar
*basename
= g_path_get_basename (filename
);
564 g_string_append_printf (stext
, " (%s)", basename
);
567 CamelDataWrapper
*content
;
570 content
= camel_medium_get_content (CAMEL_MEDIUM (part
));
572 if (CAMEL_IS_MIME_MESSAGE (content
))
573 filename
= camel_mime_message_get_subject (
574 CAMEL_MIME_MESSAGE (content
));
576 if (filename
&& *filename
)
577 g_string_append_printf (stext
, " (%s)", filename
);
580 if (description
!= NULL
&& *description
!= '\0' &&
581 g_strcmp0 (filename
, description
) != 0)
582 g_string_append_printf (stext
, ", \"%s\"", description
);
584 return g_string_free (stext
, FALSE
);
588 e_mail_part_is_inline (CamelMimePart
*mime_part
,
591 EMailParserExtension
*extension
;
592 EMailParserExtensionClass
*class;
593 const gchar
*disposition
;
594 gboolean is_inline
= FALSE
;
596 disposition
= camel_mime_part_get_disposition (mime_part
);
598 if (disposition
!= NULL
) {
599 is_inline
= (g_ascii_strcasecmp (disposition
, "inline") == 0);
603 settings
= e_util_ref_settings ("org.gnome.evolution.mail");
604 is_inline
= g_settings_get_boolean (settings
, "display-content-disposition-inline");
605 g_clear_object (&settings
);
609 if ((extensions
== NULL
) || g_queue_is_empty (extensions
))
612 extension
= g_queue_peek_head (extensions
);
613 class = E_MAIL_PARSER_EXTENSION_GET_CLASS (extension
);
615 /* Some types need to override the disposition.
616 * e.g. application/x-pkcs7-mime */
617 if (class->flags
& E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION
)
620 if (disposition
!= NULL
)
623 /* Otherwise, use the default for this handler type. */
624 return (class->flags
& E_MAIL_PARSER_EXTENSION_INLINE
) != 0;
628 * e_mail_part_utils_body_refers:
629 * @body: text body to search for references in; can be %NULL, then returns %FALSE
630 * @cid: a Content-ID to search for; if found in body, it should be of form "cid:xxxxx"; can be %NULL
632 * Returns whether @body contains a reference to @cid enclosed in quotes;
633 * returns %FALSE if any of the arguments is %NULL.
636 e_mail_part_utils_body_refers (const gchar
*body
,
641 if (!body
|| !cid
|| !*cid
)
645 while (ptr
= strstr (ptr
, cid
), ptr
!= NULL
) {
646 if (ptr
- body
> 1 && ptr
[-1] == '\"' && ptr
[strlen (cid
)] == '\"')