Bug 793109 - Name of attached mail with dash in Subject truncated
[evolution.git] / src / em-format / e-mail-part-utils.c
blob1b908b987d882532eea624091e27ad25fdb69914
1 /*
2 * e-mail-part-utils.h
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
11 * for more details.
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>
26 #include <gdk/gdk.h>
28 #include <libsoup/soup.h>
30 #include <string.h>
32 #define d(x)
34 /**
35 * e_mail_part_is_secured:
36 * @part: a #CamelMimePart
38 * Whether @part is signed or encrypted or not.
40 * Return Value: TRUE/FALSE
42 gboolean
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
58 const gchar *
59 e_mail_part_get_frame_security_style (EMailPart *part)
61 const gchar *frame_style = NULL;
62 guint32 flags;
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";
70 } else {
71 GList *head, *link;
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";
96 return frame_style;
99 /**
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).
107 const gchar *
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;
124 CamelStream *stream;
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) {
130 gchar *content_type;
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.
152 if (magic_type) {
153 if (name_type
154 && (!strcmp (magic_type, "text/plain")
155 || !strcmp (magic_type, "application/octet-stream")))
156 res = name_type;
157 else
158 res = magic_type;
159 } else
160 res = name_type;
162 if (res != name_type)
163 g_free (name_type);
165 if (res != magic_type)
166 g_free (magic_type);
168 if (!types_cache)
169 types_cache = g_hash_table_new_full (
170 g_str_hash, g_str_equal,
171 (GDestroyNotify) g_free,
172 (GDestroyNotify) NULL);
174 if (res) {
175 tmp = g_hash_table_lookup (types_cache, res);
176 if (tmp) {
177 g_free (res);
178 res = tmp;
179 } else {
180 g_hash_table_insert (types_cache, res, res);
184 d (printf ("Snooped mime type %s\n", res));
185 return 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
200 * the part.
202 * Content-Disposition is not checked.
204 * Return value: TRUE/FALSE
206 gboolean
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;
213 if (!dw)
214 return FALSE;
216 mime_type = camel_data_wrapper_get_mime_type_field (dw);
218 if (!mime_type)
219 return FALSE;
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.
241 void
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)
256 return;
258 charset = camel_content_type_param (content_type, "charset");
260 if (charset == NULL || *charset == '\0')
261 return;
263 data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart));
264 content_type = camel_data_wrapper_get_mime_type_field (data_wrapper);
266 if (content_type)
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);
272 if (content_type)
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
283 * part.
285 * Return Value: A #CamelMimePart that should be displayed
287 CamelMimePart *
288 e_mail_part_get_related_display_part (CamelMimePart *part,
289 gint *out_displayid)
291 CamelMultipart *mp;
292 CamelMimePart *body_part, *display_part = NULL;
293 CamelContentType *content_type;
294 const gchar *start;
295 gint i, nparts, displayid = 0;
297 mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part);
299 if (!CAMEL_IS_MULTIPART (mp))
300 return NULL;
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) {
306 gint len;
307 const gchar *cid;
309 /* strip <>'s from CID */
310 len = strlen (start) - 2;
311 start++;
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;
319 displayid = i;
320 break;
323 } else {
324 display_part = camel_multipart_get_part (mp, 0);
327 if (out_displayid)
328 *out_displayid = displayid;
330 return display_part;
333 void
334 e_mail_part_animation_extract_frame (GBytes *bytes,
335 gchar **out_frame,
336 gsize *out_len)
338 GdkPixbufLoader *loader;
339 GdkPixbufAnimation *animation;
340 GdkPixbuf *frame_buf;
341 const guchar *bytes_data;
342 gsize bytes_size;
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);
356 *out_frame = NULL;
357 *out_len = 0;
359 if (bytes == NULL)
360 return;
362 bytes_data = g_bytes_get_data (bytes, &bytes_size);
364 if (bytes_size == 0)
365 return;
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;
375 return;
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);
382 if (!animation) {
383 *out_frame = g_memdup (bytes_data, bytes_size);
384 *out_len = bytes_size;
385 g_object_unref (loader);
386 return;
389 /* Extract first frame */
390 frame_buf = gdk_pixbuf_animation_get_static_image (animation);
391 if (!frame_buf) {
392 *out_frame = g_memdup (bytes_data, bytes_size);
393 *out_len = bytes_size;
394 g_object_unref (loader);
395 g_object_unref (animation);
396 return;
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
422 gchar *
423 e_mail_part_build_uri (CamelFolder *folder,
424 const gchar *message_uid,
425 const gchar *first_param_name,
426 ...)
428 CamelStore *store;
429 gchar *uri, *tmp;
430 va_list ap;
431 const gchar *name;
432 const gchar *service_uid, *folder_name;
433 gchar *encoded_message_uid;
434 gchar separator;
436 g_return_val_if_fail (message_uid && *message_uid, NULL);
438 if (!folder) {
439 folder_name = "generic";
440 service_uid = "generic";
441 } else {
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);
445 if (store)
446 service_uid = camel_service_get_uid (CAMEL_SERVICE (store));
447 else
448 service_uid = "generic";
451 encoded_message_uid = soup_uri_encode (message_uid, NULL);
452 tmp = g_strdup_printf (
453 "mail://%s/%s/%s",
454 service_uid,
455 folder_name,
456 encoded_message_uid);
457 g_free (encoded_message_uid);
459 if (folder) {
460 g_free ((gchar *) folder_name);
463 va_start (ap, first_param_name);
464 name = first_param_name;
465 separator = '?';
466 while (name) {
467 gchar *tmp2;
468 gint type = va_arg (ap, gint);
469 switch (type) {
470 case G_TYPE_INT:
471 case G_TYPE_BOOLEAN: {
472 gint val = va_arg (ap, gint);
473 tmp2 = g_strdup_printf (
474 "%s%c%s=%d", tmp,
475 separator, name, val);
476 break;
478 case G_TYPE_FLOAT:
479 case G_TYPE_DOUBLE: {
480 gdouble val = va_arg (ap, double);
481 tmp2 = g_strdup_printf (
482 "%s%c%s=%f", tmp,
483 separator, name, val);
484 break;
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 (
490 "%s%c%s=%s", tmp,
491 separator, name, escaped);
492 g_free (escaped);
493 break;
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);
498 break;
500 default:
501 g_warning ("Invalid param type %s", g_type_name (type));
502 va_end (ap);
503 return NULL;
506 g_free (tmp);
507 tmp = tmp2;
509 if (separator == '?')
510 separator = '&';
512 name = va_arg (ap, gchar *);
514 va_end (ap);
516 uri = tmp;
517 if (uri == NULL)
518 return NULL;
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) {
526 tmp[0] = '/';
529 return uri;
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
538 * the content.
540 * Return value:
542 gchar *
543 e_mail_part_describe (CamelMimePart *part,
544 const gchar *mime_type)
546 GString *stext;
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);
557 g_free (desc);
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);
565 g_free (basename);
566 } else {
567 CamelDataWrapper *content;
569 filename = NULL;
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);
587 gboolean
588 e_mail_part_is_inline (CamelMimePart *mime_part,
589 GQueue *extensions)
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);
600 if (is_inline) {
601 GSettings *settings;
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))
610 return is_inline;
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)
618 return TRUE;
620 if (disposition != NULL)
621 return is_inline;
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.
635 gboolean
636 e_mail_part_utils_body_refers (const gchar *body,
637 const gchar *cid)
639 const gchar *ptr;
641 if (!body || !cid || !*cid)
642 return FALSE;
644 ptr = body;
645 while (ptr = strstr (ptr, cid), ptr != NULL) {
646 if (ptr - body > 1 && ptr[-1] == '\"' && ptr[strlen (cid)] == '\"')
647 return TRUE;
649 ptr++;
652 return FALSE;