diff options
Diffstat (limited to 'em-format/e-mail-part-utils.c')
-rw-r--r-- | em-format/e-mail-part-utils.c | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/em-format/e-mail-part-utils.c b/em-format/e-mail-part-utils.c new file mode 100644 index 0000000000..858209b7df --- /dev/null +++ b/em-format/e-mail-part-utils.c @@ -0,0 +1,546 @@ +/* + * e-mail-part-utils.h + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n-lib.h> + +#include "e-mail-part-utils.h" +#include "e-mail-parser-extension.h" + +#include <camel/camel.h> +#include <e-util/e-util.h> +#include <gdk/gdk.h> + +#include <libsoup/soup.h> + +#include <string.h> + +#define d(x) + +/** + * e_mail_parst_is_secured: + * @part: a #CamelMimePart + * + * Whether @part is signed or encrypted or not. + * + * Return Value: TRUE/FALSE + */ +gboolean +e_mail_part_is_secured (CamelMimePart *part) +{ + CamelContentType *ct = camel_mime_part_get_content_type (part); + + return (camel_content_type_is (ct, "multipart", "signed") || + camel_content_type_is (ct, "multipart", "encrypted") || + camel_content_type_is (ct, "application", "x-inlinepgp-signed") || + camel_content_type_is (ct, "application", "x-inlinepgp-encrypted") || + camel_content_type_is (ct, "application", "x-pkcs7-mime") || + camel_content_type_is (ct, "application", "pkcs7-mime")); +} + +/** + * e_mail_partr_snoop_type: + * @part: a #CamelMimePart + * + * Tries to snoop the mime type of a part. + * + * Return value: %NULL if unknown (more likely application/octet-stream). + **/ +const gchar * +e_mail_part_snoop_type (CamelMimePart *part) +{ + /* cache is here only to be able still return const gchar * */ + static GHashTable *types_cache = NULL; + + const gchar *filename; + gchar *name_type = NULL, *magic_type = NULL, *res, *tmp; + CamelDataWrapper *dw; + + filename = camel_mime_part_get_filename (part); + if (filename != NULL) + name_type = e_util_guess_mime_type (filename, FALSE); + + dw = camel_medium_get_content ((CamelMedium *) part); + if (!camel_data_wrapper_is_offline (dw)) { + GByteArray *byte_array; + CamelStream *stream; + + byte_array = g_byte_array_new (); + stream = camel_stream_mem_new_with_byte_array (byte_array); + + if (camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL) > 0) { + gchar *content_type; + + content_type = g_content_type_guess ( + filename, byte_array->data, + byte_array->len, NULL); + + if (content_type != NULL) + magic_type = g_content_type_get_mime_type (content_type); + + g_free (content_type); + } + + g_object_unref (stream); + } + + /* If gvfs doesn't recognize the data by magic, but it + * contains English words, it will call it text/plain. If the + * filename-based check came up with something different, use + * that instead and if it returns "application/octet-stream" + * try to do better with the filename check. + */ + + if (magic_type) { + if (name_type + && (!strcmp(magic_type, "text/plain") + || !strcmp(magic_type, "application/octet-stream"))) + res = name_type; + else + res = magic_type; + } else + res = name_type; + + if (res != name_type) + g_free (name_type); + + if (res != magic_type) + g_free (magic_type); + + if (!types_cache) + types_cache = g_hash_table_new_full ( + g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) NULL); + + if (res) { + tmp = g_hash_table_lookup (types_cache, res); + if (tmp) { + g_free (res); + res = tmp; + } else { + g_hash_table_insert (types_cache, res, res); + } + } + + d(printf("Snooped mime type %s\n", res)); + return res; + + /* We used to load parts to check their type, we don't anymore, + * see bug #211778 for some discussion */ +} + +/** + * e_mail_part_is_attachment + * @part: Part to check. + * + * Returns true if the part is an attachment. + * + * A part is not considered an attachment if it is a + * multipart, or a text part with no filename. It is used + * to determine if an attachment header should be displayed for + * the part. + * + * Content-Disposition is not checked. + * + * Return value: TRUE/FALSE + **/ +gboolean +e_mail_part_is_attachment (CamelMimePart *part) +{ + /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ + CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); + + if (!dw) + return 0; + + d(printf("checking is attachment %s/%s\n", dw->mime_type->type, dw->mime_type->subtype)); + return !(camel_content_type_is (dw->mime_type, "multipart", "*") + || camel_content_type_is ( + dw->mime_type, "application", "x-pkcs7-mime") + || camel_content_type_is ( + dw->mime_type, "application", "pkcs7-mime") + || camel_content_type_is ( + dw->mime_type, "application", "x-inlinepgp-signed") + || camel_content_type_is ( + dw->mime_type, "application", "x-inlinepgp-encrypted") + || camel_content_type_is ( + dw->mime_type, "x-evolution", "evolution-rss-feed") + || camel_content_type_is (dw->mime_type, "text", "calendar") + || camel_content_type_is (dw->mime_type, "text", "x-calendar") + || (camel_content_type_is (dw->mime_type, "text", "*") + && camel_mime_part_get_filename (part) == NULL)); +} + +/** + * e_mail_part_preserve_charset_in_content_type: + * @ipart: Source #CamelMimePart + * @opart: Target #CamelMimePart + * + * Copies 'charset' part of content-type header from @ipart to @opart. + */ +void +e_mail_part_preserve_charset_in_content_type (CamelMimePart *ipart, + CamelMimePart *opart) +{ + CamelDataWrapper *data_wrapper; + CamelContentType *content_type; + const gchar *charset; + + g_return_if_fail (ipart != NULL); + g_return_if_fail (opart != NULL); + + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); + + if (content_type == NULL) + return; + + charset = camel_content_type_param (content_type, "charset"); + + if (charset == NULL || *charset == '\0') + return; + + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); + + if (content_type) + camel_content_type_set_param (content_type, "charset", charset); + + /* update charset also on the part itself */ + data_wrapper = CAMEL_DATA_WRAPPER (opart); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); + if (content_type) + camel_content_type_set_param (content_type, "charset", charset); +} + +/** + * e_mail_part_get_related_display_part: + * @part: a multipart/related or multipart/alternative #CamelMimePart + * @out_displayid: (out) returns index of the returned part + * + * Goes through all subparts of given @part and tries to determine which + * part should be displayed and which parts are just attachments to the + * part. + * + * Return Value: A #CamelMimePart that should be displayed + */ +CamelMimePart * +e_mail_part_get_related_display_part (CamelMimePart *part, + gint *out_displayid) +{ + CamelMultipart *mp; + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const gchar *start; + gint i, nparts, displayid = 0; + + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) + return NULL; + + nparts = camel_multipart_get_number (mp); + content_type = camel_mime_part_get_content_type (part); + start = camel_content_type_param (content_type, "start"); + if (start && strlen (start) > 2) { + gint len; + const gchar *cid; + + /* strip <>'s from CID */ + len = strlen (start) - 2; + start++; + + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part (mp, i); + cid = camel_mime_part_get_content_id (body_part); + + if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { + display_part = body_part; + displayid = i; + break; + } + } + } else { + display_part = camel_multipart_get_part (mp, 0); + } + + if (out_displayid) + *out_displayid = displayid; + + return display_part; +} + +void +e_mail_part_animation_extract_frame (const GByteArray *anim, + gchar **frame, + gsize *len) +{ + GdkPixbufLoader *loader; + GdkPixbufAnimation *animation; + GdkPixbuf *frame_buf; + + /* GIF89a (GIF image signature) */ + const gchar GIF_HEADER[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + const gint GIF_HEADER_LEN = sizeof (GIF_HEADER); + + /* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */ + const gchar GIF_APPEXT[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, + 0x50, 0x45, 0x32, 0x2E, 0x30 }; + const gint GIF_APPEXT_LEN = sizeof (GIF_APPEXT); + + if ((anim == NULL) || (anim->data == NULL)) { + *frame = NULL; + *len = 0; + return; + } + + /* Check if the image is an animated GIF. We don't care about any + * other animated formats (APNG or MNG) as WebKit does not support them + * and displays only the first frame. */ + if ((anim->len < 0x331) + || (memcmp (anim->data, GIF_HEADER, GIF_HEADER_LEN) != 0) + || (memcmp (&anim->data[0x310], GIF_APPEXT, GIF_APPEXT_LEN) != 0)) { + + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + return; + } + + loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_write (loader, (guchar *) anim->data, anim->len, NULL); + gdk_pixbuf_loader_close (loader, NULL); + animation = gdk_pixbuf_loader_get_animation (loader); + if (!animation) { + + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + return; + } + + /* Extract first frame */ + frame_buf = gdk_pixbuf_animation_get_static_image (animation); + if (!frame_buf) { + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + g_object_unref (animation); + return; + } + + /* Unforunatelly, GdkPixbuf cannot save to GIF, but WebKit does not + * have any trouble displaying PNG image despite the part having + * image/gif mime-type */ + gdk_pixbuf_save_to_buffer (frame_buf, frame, len, "png", NULL, NULL); + + g_object_unref (loader); +} + +/** + * e_mail_part_build_url: + * @folder: (allow-none) a #CamelFolder with the message or %NULL + * @message_uid: (allow-none) uid of the message within the @folder or %NULL + * @first_param_name: Name of first query parameter followed by GType of it's value and value + * terminated by %NULL. + * + * Construct a URI for message. + * + * The URI can contain multiple query parameters. The list of parameters must be + * NULL-terminated. Each query must contain name, GType of value and value. + * + * Return Value: a URL of a message or part + */ +gchar * +e_mail_part_build_uri (CamelFolder *folder, + const gchar *message_uid, + const gchar *first_param_name, + ...) +{ + CamelStore *store; + gchar *uri, *tmp; + va_list ap; + const gchar *name; + const gchar *service_uid, *folder_name; + gchar separator; + + g_return_val_if_fail (message_uid && *message_uid, NULL); + + if (!folder) { + folder_name = "generic"; + service_uid = "generic"; + } else { + tmp = (gchar *) camel_folder_get_full_name (folder); + folder_name = (const gchar *) soup_uri_encode (tmp, NULL); + store = camel_folder_get_parent_store (folder); + if (store) + service_uid = camel_service_get_uid (CAMEL_SERVICE (store)); + else + service_uid = "generic"; + } + + tmp = g_strdup_printf ("mail://%s/%s/%s", + service_uid, + folder_name, + message_uid); + + if (folder) { + g_free ((gchar *) folder_name); + } + + va_start (ap, first_param_name); + name = first_param_name; + separator = '?'; + while (name) { + gchar *tmp2; + gint type = va_arg (ap, gint); + switch (type) { + case G_TYPE_INT: + case G_TYPE_BOOLEAN: { + gint val = va_arg (ap, gint); + tmp2 = g_strdup_printf ("%s%c%s=%d", tmp, + separator, name, val); + break; + } + case G_TYPE_FLOAT: + case G_TYPE_DOUBLE: { + gdouble val = va_arg (ap, double); + tmp2 = g_strdup_printf ("%s%c%s=%f", tmp, + separator, name, val); + break; + } + case G_TYPE_STRING: { + gchar *val = va_arg (ap, gchar *); + gchar *escaped = soup_uri_encode (val, NULL); + tmp2 = g_strdup_printf ("%s%c%s=%s", tmp, + separator, name, escaped); + g_free (escaped); + break; + } + default: + g_warning ("Invalid param type %s", g_type_name (type)); + return NULL; + } + + g_free (tmp); + tmp = tmp2; + + if (separator == '?') + separator = '&'; + + name = va_arg (ap, gchar *); + } + va_end (ap); + + uri = tmp; + if (uri == NULL) + return NULL; + + /* For some reason, webkit won't accept URL with username, but + * without password (mail://store@host/folder/mail), so we + * will replace the '@' symbol by '/' to get URL like + * mail://store/host/folder/mail which is OK + */ + while ((tmp = strchr (uri, '@')) != NULL) { + tmp[0] = '/'; + } + + return uri; +} + +/** + * e_mail_part_describe: + * @part: a #CamelMimePart + * @mimetype: mimetype of the content + * + * Generate a simple textual description of a part, @mime_type represents + * the content. + * + * Return value: + **/ +gchar * +e_mail_part_describe (CamelMimePart *part, + const gchar *mime_type) +{ + GString *stext; + const gchar *filename, *description; + gchar *content_type, *desc; + + stext = g_string_new(""); + content_type = g_content_type_from_mime_type (mime_type); + desc = g_content_type_get_description ( + content_type != NULL ? content_type : mime_type); + g_free (content_type); + g_string_append_printf ( + stext, _("%s attachment"), desc ? desc : mime_type); + g_free (desc); + + filename = camel_mime_part_get_filename (part); + description = camel_mime_part_get_description (part); + + if (!filename || !*filename) { + CamelDataWrapper *content; + + content = camel_medium_get_content (CAMEL_MEDIUM (part)); + + if (CAMEL_IS_MIME_MESSAGE (content)) + filename = camel_mime_message_get_subject ( + CAMEL_MIME_MESSAGE (content)); + } + + if (filename != NULL && *filename != '\0') { + gchar *basename = g_path_get_basename (filename); + g_string_append_printf (stext, " (%s)", basename); + g_free (basename); + } + + if (description != NULL && *description != '\0' && + g_strcmp0 (filename, description) != 0) + g_string_append_printf (stext, ", \"%s\"", description); + + return g_string_free (stext, FALSE); +} + +gboolean +e_mail_part_is_inline (CamelMimePart *mime_part, + GQueue *extensions) +{ + const gchar *disposition; + EMailParserExtension *extension; + + if ((extensions == NULL) || g_queue_is_empty (extensions)) + return FALSE; + + extension = g_queue_peek_head (extensions); + /* Some types need to override the disposition. + * e.g. application/x-pkcs7-mime */ + if (e_mail_parser_extension_get_flags (extension) & + E_MAIL_PARSER_EXTENSION_INLINE_DISPOSITION) + return TRUE; + + disposition = camel_mime_part_get_disposition (mime_part); + if (disposition != NULL) + return g_ascii_strcasecmp (disposition, "inline") == 0; + + /* Otherwise, use the default for this handler type. */ + return (e_mail_parser_extension_get_flags (extension) & + E_MAIL_PARSER_EXTENSION_INLINE) != 0; +} |