/* * e-mail-parser.c * * 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 * */ #include "e-mail-parser.h" #include "e-mail-parser-extension.h" #include "e-mail-format-extensions.h" #include "e-mail-part-attachment.h" #include "e-mail-part-utils.h" #include #include #include #include #include #include #include static gpointer parent_class = 0; struct _EMailParserPrivate { GMutex *mutex; gint last_error; CamelSession *session; }; #define E_MAIL_PARSER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_MAIL_PARSER, EMailParserPrivate)) #define d(x) enum { PROP_0, PROP_SESSION }; static GSList * mail_parser_run (EMailParser *parser, CamelMimeMessage *message, GCancellable *cancellable) { GSList *parts; EMailExtensionRegistry *reg; GQueue *parsers; GList *iter; GString *part_id; reg = e_mail_parser_get_extension_registry (parser); parsers = e_mail_extension_registry_get_for_mime_type ( reg, "application/vnd.evolution.message"); if (!parsers) parsers = e_mail_extension_registry_get_for_mime_type ( reg, "message/*"); /* parsers == NULL means, that the internal Evolution parser extensions * were not loaded. Something is terribly wrong. */ g_return_val_if_fail (parsers != NULL, NULL); part_id = g_string_new (".message"); parts = NULL; if (!parsers) { parts = e_mail_parser_wrap_as_attachment ( parser, CAMEL_MIME_PART (message), NULL, part_id, cancellable); } else { for (iter = parsers->head; iter; iter = iter->next) { EMailParserExtension *extension; if (g_cancellable_is_cancelled (cancellable)) break; extension = iter->data; if (!extension) continue; parts = e_mail_parser_extension_parse ( extension, parser, CAMEL_MIME_PART (message), part_id, cancellable); if (parts != NULL) break; } parts = g_slist_prepend ( parts, e_mail_part_new ( CAMEL_MIME_PART (message), ".message")); } g_string_free (part_id, TRUE); return parts; } static void mail_parser_set_session (EMailParser *parser, CamelSession *session) { g_return_if_fail (E_IS_MAIL_PARSER (parser)); g_return_if_fail (CAMEL_IS_SESSION (session)); g_object_ref (session); if (parser->priv->session) g_object_unref (parser->priv->session); parser->priv->session = session; } static void e_mail_parser_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EMailParser *parser = E_MAIL_PARSER (object); switch (property_id) { case PROP_SESSION: mail_parser_set_session (parser, CAMEL_SESSION (g_value_get_object (value))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_mail_parser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { EMailParser *parser = E_MAIL_PARSER (object); switch (property_id) { case PROP_SESSION: g_value_set_object (value, G_OBJECT (e_mail_parser_get_session (parser))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_mail_parser_finalize (GObject *object) { EMailParserPrivate *priv; priv = E_MAIL_PARSER (object)->priv; if (priv->mutex) { g_mutex_free (priv->mutex); priv->mutex = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void e_mail_parser_init (EMailParser *parser) { parser->priv = E_MAIL_PARSER_GET_PRIVATE (parser); parser->priv->mutex = g_mutex_new (); } static void e_mail_parser_base_init (EMailParserClass *klass) { klass->extension_registry = g_object_new ( E_TYPE_MAIL_PARSER_EXTENSION_REGISTRY, NULL); e_mail_parser_internal_extensions_load ( E_MAIL_EXTENSION_REGISTRY (klass->extension_registry)); e_extensible_load_extensions (E_EXTENSIBLE (klass->extension_registry)); } static void e_mail_parser_base_finalize (EMailParserClass *klass) { g_object_unref (klass->extension_registry); } static void e_mail_parser_class_init (EMailParserClass *klass) { GObjectClass *object_class; parent_class = g_type_class_peek_parent (klass); g_type_class_add_private (klass, sizeof (EMailParserPrivate)); object_class = G_OBJECT_CLASS (klass); object_class->finalize = e_mail_parser_finalize; object_class->set_property = e_mail_parser_set_property; object_class->get_property = e_mail_parser_get_property; g_object_class_install_property ( object_class, PROP_SESSION, g_param_spec_object ( "session", "Camel Session", NULL, CAMEL_TYPE_SESSION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } GType e_mail_parser_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { static const GTypeInfo type_info = { sizeof (EMailParserClass), (GBaseInitFunc) e_mail_parser_base_init, (GBaseFinalizeFunc) e_mail_parser_base_finalize, (GClassInitFunc) e_mail_parser_class_init, (GClassFinalizeFunc) NULL, NULL, /* class_data */ sizeof (EMailParser), 0, /* n_preallocs */ (GInstanceInitFunc) e_mail_parser_init, NULL /* value_table */ }; type = g_type_register_static ( G_TYPE_OBJECT, "EMailParser", &type_info, 0); } return type; } EMailParser * e_mail_parser_new (CamelSession *session) { return g_object_new (E_TYPE_MAIL_PARSER, "session", session, NULL); } /** * e_mail_parser_parse_sync: * @parser: an #EMailParser * @folder: (allow none) a #CamelFolder containing the @message or %NULL * @message_uid: (allow none) UID of the @message within the @folder or %NULL * @message: a #CamelMimeMessage * @cancellable: (allow-none) a #GCancellable * * Parses the @message synchronously. Returns a list of #EMailParts which * represents structure of the message and additional properties of each part. * * Note that this function can block for a while, so it's not a good idea to call * it from main thread. * * Return Value: An #EMailPartsList */ EMailPartList * e_mail_parser_parse_sync (EMailParser *parser, CamelFolder *folder, const gchar *message_uid, CamelMimeMessage *message, GCancellable *cancellable) { EMailPartList *parts_list; g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); parts_list = e_mail_part_list_new (); if (folder) parts_list->folder = g_object_ref (folder); if (message_uid) parts_list->message_uid = g_strdup (message_uid); parts_list->message = g_object_ref (message); parts_list->list = mail_parser_run (parser, message, cancellable); return parts_list; } static void mail_parser_prepare_async (GSimpleAsyncResult *res, GObject *object, GCancellable *cancellable) { CamelMimeMessage *message; GSList *list; message = g_object_get_data (G_OBJECT (res), "message"); list = mail_parser_run (E_MAIL_PARSER (object), message, cancellable); g_simple_async_result_set_op_res_gpointer (res, list, NULL); } /** * e_mail_parser_parse: * @parser: an #EMailParser * @message: a #CamelMimeMessage * @callback: a #GAsyncReadyCallback * @cancellable: (allow-none) a #GCancellable * @user_data: (allow-none) user data passed to the callback * * Asynchronous version of #e_mail_parser_parse_sync(). */ void e_mail_parser_parse (EMailParser *parser, CamelFolder *folder, const gchar *message_uid, CamelMimeMessage *message, GAsyncReadyCallback callback, GCancellable *cancellable, gpointer user_data) { GSimpleAsyncResult *result; g_return_if_fail (E_IS_MAIL_PARSER (parser)); g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); result = g_simple_async_result_new ( G_OBJECT (parser), callback, user_data, e_mail_parser_parse); g_object_set_data (G_OBJECT (result), "message", g_object_ref (message)); if (folder) g_object_set_data (G_OBJECT (result), "folder", g_object_ref (folder)); if (message_uid) g_object_set_data (G_OBJECT (result), "message_uid", g_strdup (message_uid)); g_simple_async_result_run_in_thread ( result, mail_parser_prepare_async, G_PRIORITY_DEFAULT, cancellable); } EMailPartList * e_mail_parser_parse_finish (EMailParser *parser, GAsyncResult *result, GError **error) { EMailPartList *parts_list; parts_list = e_mail_part_list_new (); /* The data were ref'ed or copied in e_mail_parser_parse_async */ parts_list->message = g_object_get_data (G_OBJECT (result), "message"); parts_list->folder = g_object_get_data (G_OBJECT (result), "folder"); parts_list->message_uid = g_object_get_data (G_OBJECT (result), "message_uid"); parts_list->list = g_simple_async_result_get_op_res_gpointer ( G_SIMPLE_ASYNC_RESULT (result)); if (camel_debug_start ("emformat:parser")) { GSList *iter; printf("%s finished with EMailPartList:\n", G_OBJECT_TYPE_NAME (parser)); for (iter = parts_list->list; iter; iter = iter->next) { EMailPart *part = iter->data; if (!part) continue; printf(" id: %s | cid: %s | mime_type: %s | is_hidden: %d | is_attachment: %d\n", part->id, part->cid, part->mime_type, part->is_hidden ? 1 : 0, part->is_attachment ? 1 : 0); } camel_debug_end (); } return parts_list; } GSList * e_mail_parser_parse_part (EMailParser *parser, CamelMimePart *part, GString *part_id, GCancellable *cancellable) { CamelContentType *ct; gchar *mime_type; GSList *list; ct = camel_mime_part_get_content_type (part); if (!ct) { mime_type = (gchar *) "application/vnd.evolution.error"; } else { gchar *tmp; tmp = camel_content_type_simple (ct); mime_type = g_ascii_strdown (tmp, -1); g_free (tmp); } list = e_mail_parser_parse_part_as ( parser, part, part_id, mime_type, cancellable); if (ct) { g_free (mime_type); } return list; } GSList * e_mail_parser_parse_part_as (EMailParser *parser, CamelMimePart *part, GString *part_id, const gchar *mime_type, GCancellable *cancellable) { GQueue *parsers; GList *iter; EMailExtensionRegistry *reg; EMailParserClass *parser_class; GSList *part_list; gchar *as_mime_type; if (g_cancellable_is_cancelled (cancellable)) return NULL; if (mime_type) as_mime_type = g_ascii_strdown (mime_type, -1); else as_mime_type = NULL; parser_class = E_MAIL_PARSER_GET_CLASS (parser); reg = E_MAIL_EXTENSION_REGISTRY (parser_class->extension_registry); parsers = e_mail_extension_registry_get_for_mime_type (reg, as_mime_type); if (!parsers) { parsers = e_mail_extension_registry_get_fallback (reg, as_mime_type); } if (as_mime_type) g_free (as_mime_type); if (!parsers) { return e_mail_parser_wrap_as_attachment ( parser, part, NULL, part_id, cancellable); } for (iter = parsers->head; iter; iter = iter->next) { EMailParserExtension *extension; extension = iter->data; if (!extension) continue; part_list = e_mail_parser_extension_parse ( extension, parser, part, part_id, cancellable); if (part_list) break; } return part_list; } GSList * e_mail_parser_error (EMailParser *parser, GCancellable *cancellable, const gchar *format, ...) { EMailPart *mail_part; CamelMimePart *part; gchar *errmsg; gchar *uri; va_list ap; g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL); g_return_val_if_fail (format != NULL, NULL); va_start (ap, format); errmsg = g_strdup_vprintf (format, ap); part = camel_mime_part_new (); camel_mime_part_set_content (part, errmsg, strlen (errmsg), "application/vnd.evolution.error"); g_free (errmsg); va_end (ap); g_mutex_lock (parser->priv->mutex); parser->priv->last_error++; uri = g_strdup_printf (".error.%d", parser->priv->last_error); g_mutex_unlock (parser->priv->mutex); mail_part = e_mail_part_new (part, uri); mail_part->mime_type = g_strdup ("application/vnd.evolution.error"); mail_part->is_error = TRUE; g_free (uri); g_object_unref (part); return g_slist_append (NULL, mail_part); } static void attachment_loaded (EAttachment *attachment, GAsyncResult *res, gpointer user_data) { EShell *shell; GtkWindow *window; shell = e_shell_get_default (); window = e_shell_get_active_window (shell); e_attachment_load_handle_error (attachment, res, window); g_object_unref (attachment); } /* Idle callback */ static gboolean load_attachment_idle (EAttachment *attachment) { e_attachment_load_async (attachment, (GAsyncReadyCallback) attachment_loaded, NULL); return FALSE; } GSList * e_mail_parser_wrap_as_attachment (EMailParser *parser, CamelMimePart *part, GSList *parts, GString *part_id, GCancellable *cancellable) { EMailPartAttachment *empa; const gchar *snoop_mime_type, *cid; GQueue *extensions; CamelContentType *ct; gchar *mime_type; CamelDataWrapper *dw; GByteArray *ba; gsize size; gint part_id_len; ct = camel_mime_part_get_content_type (part); extensions = NULL; snoop_mime_type = NULL; if (ct) { EMailExtensionRegistry *reg; mime_type = camel_content_type_simple (ct); reg = e_mail_parser_get_extension_registry (parser); extensions = e_mail_extension_registry_get_for_mime_type ( reg, mime_type); if (camel_content_type_is (ct, "text", "*") || camel_content_type_is (ct, "message", "rfc822")) snoop_mime_type = mime_type; else g_free (mime_type); } if (!snoop_mime_type) snoop_mime_type = e_mail_part_snoop_type (part); if (!extensions) { EMailExtensionRegistry *reg; reg = e_mail_parser_get_extension_registry (parser); extensions = e_mail_extension_registry_get_for_mime_type ( reg, snoop_mime_type); if (!extensions) { extensions = e_mail_extension_registry_get_fallback ( reg, snoop_mime_type); } } part_id_len = part_id->len; g_string_append (part_id, ".attachment"); empa = (EMailPartAttachment *) e_mail_part_subclass_new ( part, part_id->str, sizeof (EMailPartAttachment), (GFreeFunc) e_mail_part_attachment_free); empa->parent.mime_type = g_strdup ("application/vnd.evolution.attachment"); empa->parent.is_attachment = TRUE; empa->shown = extensions && (!g_queue_is_empty (extensions) && e_mail_part_is_inline (part, extensions)); empa->snoop_mime_type = snoop_mime_type; empa->attachment = e_attachment_new (); empa->attachment_view_part_id = parts ? g_strdup (E_MAIL_PART (parts->data)->id) : NULL; cid = camel_mime_part_get_content_id (part); if (cid) empa->parent.cid = g_strdup_printf ("cid:%s", cid); e_attachment_set_mime_part (empa->attachment, part); e_attachment_set_shown (empa->attachment, empa->shown); e_attachment_set_can_show (empa->attachment, extensions && !g_queue_is_empty (extensions)); /* Try to guess size of the attachments */ dw = camel_medium_get_content (CAMEL_MEDIUM (part)); ba = camel_data_wrapper_get_byte_array (dw); if (ba) { size = ba->len; if (camel_mime_part_get_encoding (part) == CAMEL_TRANSFER_ENCODING_BASE64) size = size / 1.37; } /* e_attachment_load_async must be called from main thread */ g_idle_add ( (GSourceFunc) load_attachment_idle, g_object_ref (empa->attachment)); if (size != 0) { GFileInfo *fileinfo; fileinfo = e_attachment_get_file_info (empa->attachment); if (!fileinfo) { fileinfo = g_file_info_new (); g_file_info_set_content_type ( fileinfo, empa->snoop_mime_type); } else { g_object_ref (fileinfo); } g_file_info_set_size (fileinfo, size); e_attachment_set_file_info (empa->attachment, fileinfo); g_object_unref (fileinfo); } if (parts && parts->data) { E_MAIL_PART (parts->data)->is_hidden = TRUE; } g_string_truncate (part_id, part_id_len); return g_slist_prepend (parts, empa); } CamelSession * e_mail_parser_get_session (EMailParser *parser) { g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL); return parser->priv->session; } EMailExtensionRegistry * e_mail_parser_get_extension_registry (EMailParser *parser) { EMailParserClass *parser_class; g_return_val_if_fail (E_IS_MAIL_PARSER (parser), NULL); parser_class = E_MAIL_PARSER_GET_CLASS (parser); return E_MAIL_EXTENSION_REGISTRY (parser_class->extension_registry); }