diff options
Diffstat (limited to 'em-format/em-format.c')
-rw-r--r-- | em-format/em-format.c | 3840 |
1 files changed, 1990 insertions, 1850 deletions
diff --git a/em-format/em-format.c b/em-format/em-format.c index d476036f77..4abe35482c 100644 --- a/em-format/em-format.c +++ b/em-format/em-format.c @@ -25,1018 +25,1577 @@ #include <config.h> #endif -#include <stdio.h> #include <string.h> - #include <gio/gio.h> #include <glib/gi18n-lib.h> +#include <libsoup/soup-uri.h> #include "em-format.h" #include "e-util/e-util.h" #include "shell/e-shell.h" #include "shell/e-shell-settings.h" +#define d(x) + #define EM_FORMAT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), EM_TYPE_FORMAT, EMFormatPrivate)) -#define d(x) - -typedef struct _EMFormatCache EMFormatCache; - struct _EMFormatPrivate { - guint redraw_idle_id; -}; + GNode *current_node; -/* Used to cache various data/info for redraws - * The validity stuff could be cached at a higher level but this is easier - * This absolutely relies on the partid being _globally unique_ - * This is still kind of yucky, we should maintain a full tree of all this data, - * along with/as part of the puri tree */ -struct _EMFormatCache { - CamelCipherValidity *valid; /* validity copy */ - CamelMimePart *secured; /* encrypted subpart */ + CamelSession *session; - guint state:2; /* inline state */ + CamelURL *base_url; - gchar partid[1]; -}; + gchar *charset; + gchar *default_charset; + gboolean composer; -#define INLINE_UNSET (0) -#define INLINE_ON (1) -#define INLINE_OFF (2) + gint last_error; +}; -static void emf_builtin_init (EMFormatClass *); +enum { + PROP_0, + PROP_CHARSET, + PROP_DEFAULT_CHARSET, + PROP_COMPOSER, + PROP_BASE_URL +}; enum { - EMF_COMPLETE, - EMF_LAST_SIGNAL + REDRAW_REQUESTED, + LAST_SIGNAL }; +gint signals[LAST_SIGNAL]; + static gpointer parent_class; -static guint signals[EMF_LAST_SIGNAL]; -static void -emf_free_cache (EMFormatCache *efc) -{ - if (efc->valid) - camel_cipher_validity_free (efc->valid); - if (efc->secured) - g_object_unref (efc->secured); - g_free (efc); -} +/* PARSERS */ +static void emf_parse_application_xpkcs7mime (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_application_mbox (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_alternative (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_appledouble (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_mixed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_related (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_multipart_digest (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_inlinepgp_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_inlinepgp_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_message (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_post_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); +static void emf_parse_source (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); + +/* WRITERS */ +static void emf_write_text (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emf_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); +static void emf_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); + +/**************************************************************************/ -static EMFormatCache * -emf_insert_cache (EMFormat *emf, - const gchar *partid) +static gboolean +is_secured (CamelMimePart *part) { - EMFormatCache *new; - - new = g_malloc0 (sizeof (*new) + strlen (partid)); - strcpy (new->partid, partid); - g_hash_table_insert (emf->inline_table, new->partid, new); + CamelContentType *ct = camel_mime_part_get_content_type (part); - return new; + 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")); } static void -emf_clone_inlines (gpointer key, - gpointer val, - gpointer data) +preserve_charset_in_content_type (CamelMimePart *ipart, + CamelMimePart *opart) { - EMFormatCache *emfc = val, *new; + CamelDataWrapper *data_wrapper; + CamelContentType *content_type; + const gchar *charset; - new = emf_insert_cache ((EMFormat *) data, emfc->partid); - new->state = emfc->state; - if (emfc->valid) - new->valid = camel_cipher_validity_clone (emfc->valid); - if (emfc->secured) - g_object_ref ((new->secured = emfc->secured)); -} + g_return_if_fail (ipart != NULL); + g_return_if_fail (opart != NULL); -static gboolean -emf_clear_puri_node (GNode *node) -{ - GQueue *queue = node->data; - EMFormatPURI *pn; + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (ipart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); - while ((pn = g_queue_pop_head (queue)) != NULL) { - if (pn->free != NULL) - pn->free (pn); - g_free (pn->uri); - g_free (pn->cid); - g_free (pn->part_id); - if (pn->part != NULL) - g_object_unref (pn->part); - g_free (pn); - } + if (content_type == NULL) + return; + + charset = camel_content_type_param (content_type, "charset"); - g_queue_free (queue); + if (charset == NULL || *charset == '\0') + return; - return FALSE; + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (opart)); + content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); + + camel_content_type_set_param (content_type, "charset", charset); } -static void -emf_finalize (GObject *object) +static CamelMimePart * +get_related_display_part (CamelMimePart *part, + gint *out_displayid) { - EMFormat *emf = EM_FORMAT (object); - - if (emf->priv->redraw_idle_id > 0) - g_source_remove (emf->priv->redraw_idle_id); + CamelMultipart *mp; + CamelMimePart *body_part, *display_part = NULL; + CamelContentType *content_type; + const gchar *start; + gint i, nparts, displayid = 0; - if (emf->session) - g_object_unref (emf->session); + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - if (emf->message) - g_object_unref (emf->message); + if (!CAMEL_IS_MULTIPART (mp)) + return NULL; - g_hash_table_destroy (emf->inline_table); + 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; - em_format_clear_headers (emf); - camel_cipher_validity_free (emf->valid); - g_free (emf->charset); - g_free (emf->default_charset); - g_string_free (emf->part_id, TRUE); - g_free (emf->current_message_part_id); - g_free (emf->uid); + /* strip <>'s from CID */ + len = strlen (start) - 2; + start++; - if (emf->pending_uri_table != NULL) - g_hash_table_destroy (emf->pending_uri_table); + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part (mp, i); + cid = camel_mime_part_get_content_id (body_part); - if (emf->pending_uri_tree != NULL) { - g_node_traverse ( - emf->pending_uri_tree, - G_IN_ORDER, G_TRAVERSE_ALL, -1, - (GNodeTraverseFunc) emf_clear_puri_node, NULL); - g_node_destroy (emf->pending_uri_tree); + 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); } - /* FIXME: check pending jobs */ + if (out_displayid) + *out_displayid = displayid; - /* Chain up to parent's finalize() method. */ - G_OBJECT_CLASS (parent_class)->finalize (object); + return display_part; } -static const EMFormatHandler * -emf_find_handler (EMFormat *emf, - const gchar *mime_type) +static gboolean +related_display_part_is_attachment (EMFormat *emf, + CamelMimePart *part) { - EMFormatClass *emfc = (EMFormatClass *) G_OBJECT_GET_CLASS (emf); + CamelMimePart *display_part; - return g_hash_table_lookup (emfc->type_handlers, mime_type); + display_part = get_related_display_part (part, NULL); + return display_part && em_format_is_attachment (emf, display_part); +} + +/**************************************************************************/ +void +em_format_empty_parser (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* DO NOTHING */ } +#ifdef ENABLE_SMIME static void -emf_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *emfsource, - GCancellable *cancellable) +emf_parse_application_xpkcs7mime (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - /* Cancel any pending redraws. */ - if (emf->priv->redraw_idle_id > 0) { - g_source_remove (emf->priv->redraw_idle_id); - emf->priv->redraw_idle_id = 0; + CamelCipherContext *context; + CamelMimePart *opart; + CamelCipherValidity *valid; + GError *local_error = NULL; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + context = camel_smime_context_new (emf->priv->session); + + opart = camel_mime_part_new (); + valid = camel_cipher_context_decrypt_sync ( + context, part, opart, cancellable, &local_error); + preserve_charset_in_content_type (part, opart); + if (valid == NULL) { + em_format_format_error ( + emf, "%s", + local_error->message ? local_error->message : + _("Could not parse S/MIME message: Unknown error")); + g_clear_error (&local_error); + } else { + EMFormatParserInfo encinfo = { + info->handler, + info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_SMIME, + valid + }; + gint len = part_id->len; + + g_string_append (part_id, ".encrypted"); + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".encrypted.button"); + em_format_parse_part_as (emf, part, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + camel_cipher_validity_free (valid); } - em_format_clear_puri_tree (emf); + g_object_unref (opart); + g_object_unref (context); +} +#endif + +/* RFC 4155 */ +static void +emf_parse_application_mbox (EMFormat *emf, + CamelMimePart *mime_part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + CamelMimeParser *parser; + CamelStream *mem_stream; + camel_mime_parser_state_t state; + gint old_len; + gint messages; - if (emf != emfsource) { - g_hash_table_remove_all (emf->inline_table); - if (emfsource) { - GList *link; + if (g_cancellable_is_cancelled (cancellable)) + return; - /* We clone the current state here */ - g_hash_table_foreach (emfsource->inline_table, emf_clone_inlines, emf); - emf->mode = emfsource->mode; - g_free (emf->charset); - emf->charset = g_strdup (emfsource->charset); - g_free (emf->default_charset); - emf->default_charset = g_strdup (emfsource->default_charset); + /* Extract messages from the application/mbox part and + * render them as a flat list of messages. */ - em_format_clear_headers (emf); + /* XXX If the mbox has multiple messages, maybe render them + * as a multipart/digest so each message can be expanded + * or collapsed individually. + * + * See attachment_handler_mail_x_uid_list() for example. */ - link = g_queue_peek_head_link (&emfsource->header_list); - while (link != NULL) { - struct _EMFormatHeader *h = link->data; - em_format_add_header (emf, h->name, h->flags); - link = g_list_next (link); - } + /* XXX This is based on em_utils_read_messages_from_stream(). + * Perhaps refactor that function to return an array of + * messages instead of assuming we want to append them + * to a folder? */ + + parser = camel_mime_parser_new (); + camel_mime_parser_scan_from (parser, TRUE); + + mem_stream = camel_stream_mem_new (); + camel_data_wrapper_decode_to_stream_sync ( + camel_medium_get_content (CAMEL_MEDIUM (mime_part)), + mem_stream, NULL, NULL); + g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); + camel_mime_parser_init_with_stream (parser, mem_stream, NULL); + g_object_unref (mem_stream); + + old_len = part_id->len; + + /* Extract messages from the mbox. */ + messages = 0; + state = camel_mime_parser_step (parser, NULL, NULL); + + while (state == CAMEL_MIME_PARSER_STATE_FROM) { + CamelMimeMessage *message; + + message = camel_mime_message_new (); + mime_part = CAMEL_MIME_PART (message); + + if (!camel_mime_part_construct_from_parser_sync ( + mime_part, parser, NULL, NULL)) { + g_object_unref (message); + break; } - } - /* what a mess */ - if (folder != emf->folder) { - if (emf->folder) - g_object_unref (emf->folder); - if (folder) - g_object_ref (folder); - emf->folder = folder; - } + g_string_append_printf (part_id, ".mbox.%d", messages); + em_format_parse_part_as (emf, CAMEL_MIME_PART (message), + part_id, info, "message/rfc822", cancellable); + g_string_truncate (part_id, old_len); - if (uid != emf->uid) { - g_free (emf->uid); - emf->uid = g_strdup (uid); - } + g_object_unref (message); - if (msg != emf->message) { - if (emf->message) - g_object_unref (emf->message); - if (msg) - g_object_ref (msg); - emf->message = msg; + /* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */ + camel_mime_parser_step (parser, NULL, NULL); + + state = camel_mime_parser_step (parser, NULL, NULL); + + messages++; } - g_free (emf->current_message_part_id); - emf->current_message_part_id = g_strdup ("root-message"); - g_string_truncate (emf->part_id, 0); - if (folder != NULL) - /* TODO build some string based on the folder name/location? */ - g_string_append_printf(emf->part_id, ".%p", (gpointer) folder); - if (uid != NULL) - g_string_append_printf(emf->part_id, ".%s", uid); + g_object_unref (parser); } +/* RFC 1740 */ static void -emf_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable) +emf_parse_multipart_alternative (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - CamelCipherValidity *save = emf->valid_parent; - gint len; + CamelMultipart *mp; + gint i, nparts, bestid = 0; + CamelMimePart *best = NULL; - /* Note that this also requires support from higher up in the class chain - * - validity needs to be cleared when you start output - * - also needs to be cleared (but saved) whenever you start a new message. */ + if (g_cancellable_is_cancelled (cancellable)) + return; - if (emf->valid == NULL) { - emf->valid = valid; - } else { - g_queue_push_tail (&emf->valid_parent->children, valid); - camel_cipher_validity_envelope (emf->valid_parent, valid); + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; } - emf->valid_parent = valid; + /* as per rfc, find the last part we know how to display */ + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *mpart; + CamelDataWrapper *data_wrapper; + CamelContentType *type; + CamelStream *null_stream; + gchar *mime_type; + gsize content_size; - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".secured"); - em_format_part (emf, stream, part, cancellable); - g_string_truncate (emf->part_id, len); + if (g_cancellable_is_cancelled (cancellable)) + return; - emf->valid_parent = save; -} + /* is it correct to use the passed in *part here? */ + mpart = camel_multipart_get_part (mp, i); -static gboolean -emf_busy (EMFormat *emf) -{ - return FALSE; -} + if (mpart == NULL) + continue; -static gboolean -emf_is_inline (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle) -{ - EMFormatCache *emfc; - const gchar *disposition; + /* This may block even though the stream does not. + * XXX Pretty inefficient way to test if the MIME part + * is empty. Surely there's a quicker way? */ + null_stream = camel_stream_null_new (); + data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (mpart)); + camel_data_wrapper_decode_to_stream_sync ( + data_wrapper, null_stream, cancellable, NULL); + content_size = CAMEL_STREAM_NULL (null_stream)->written; + g_object_unref (null_stream); - if (handle == NULL) - return FALSE; + if (content_size == 0) + continue; - emfc = g_hash_table_lookup (emf->inline_table, part_id); - if (emfc && emfc->state != INLINE_UNSET) - return emfc->state & 1; + type = camel_mime_part_get_content_type (mpart); + mime_type = camel_content_type_simple (type); - /* Some types need to override the disposition. - * e.g. application/x-pkcs7-mime */ - if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION) - return TRUE; + camel_strdown (mime_type); - disposition = camel_mime_part_get_disposition (mime_part); - if (disposition != NULL) - return g_ascii_strcasecmp (disposition, "inline") == 0; + if (!em_format_is_attachment (emf, mpart) && + ((camel_content_type_is (type, "multipart", "related") == 0) || + !related_display_part_is_attachment (emf, mpart)) && + (em_format_find_handler (emf, mime_type) + || (best == NULL && em_format_fallback_handler (emf, mime_type)))) { + best = mpart; + bestid = i; + } - /* Otherwise, use the default for this handler type. */ - return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0; + g_free (mime_type); + } + + if (best) { + gint len = part_id->len; + + g_string_append_printf(part_id, ".alternative.%d", bestid); + em_format_parse_part (emf, best, part_id, info, cancellable); + g_string_truncate (part_id, len); + } else + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); } +/* RFC 1740 */ static void -emf_base_init (EMFormatClass *class) +emf_parse_multipart_appledouble (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal); - emf_builtin_init (class); + CamelMultipart *mp; + CamelMimePart *mime_part; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + mime_part = camel_multipart_get_part (mp, 1); + if (mime_part) { + gint len; + /* try the data fork for something useful, doubtful but who knows */ + len = part_id->len; + g_string_append_printf(part_id, ".appledouble.1"); + em_format_parse_part (emf, mime_part, part_id, info, cancellable); + g_string_truncate (part_id, len); + } else { + emf_parse_source (emf, part, part_id, info, cancellable); + } } static void -emf_class_init (EMFormatClass *class) +emf_parse_multipart_encrypted (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - GObjectClass *object_class; + CamelCipherContext *context; + const gchar *protocol; + CamelMimePart *opart; + CamelCipherValidity *valid; + CamelMultipartEncrypted *mpe; + GError *local_error = NULL; - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMFormatPrivate)); + if (g_cancellable_is_cancelled (cancellable)) + return; - object_class = G_OBJECT_CLASS (class); - object_class->finalize = emf_finalize; + mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((CamelMedium *) part); + if (!CAMEL_IS_MULTIPART_ENCRYPTED (mpe)) { + em_format_format_error ( + emf, _("Could not parse MIME message. " + "Displaying as source.")); + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } - class->find_handler = emf_find_handler; - class->format_clone = emf_format_clone; - class->format_secure = emf_format_secure; - class->busy = emf_busy; - class->is_inline = emf_is_inline; + /* Currently we only handle RFC2015-style PGP encryption. */ + protocol = camel_content_type_param ( + ((CamelDataWrapper *)mpe)->mime_type, "protocol"); + if (!protocol || g_ascii_strcasecmp (protocol, "application/pgp-encrypted") != 0) { + em_format_format_error (emf, _("Unsupported encryption type for multipart/encrypted")); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + return; + } - signals[EMF_COMPLETE] = g_signal_new ( - "complete", - G_OBJECT_CLASS_TYPE (class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (EMFormatClass, complete), - NULL, NULL, - g_cclosure_marshal_VOID__VOID, - G_TYPE_NONE, 0); + context = camel_gpg_context_new (emf->priv->session); + opart = camel_mime_part_new (); + valid = camel_cipher_context_decrypt_sync ( + context, part, opart, cancellable, &local_error); + preserve_charset_in_content_type (part, opart); + if (valid == NULL) { + em_format_format_error ( + emf, local_error->message ? + _("Could not parse PGP/MIME message") : + _("Could not parse PGP/MIME message: Unknown error")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", local_error->message); + g_clear_error (&local_error); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + } else { + gint len = part_id->len; + + EMFormatParserInfo encinfo = { + info->handler, + info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP, + }; + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + + encinfo.validity = valid; + + g_string_append (part_id, ".encrypted"); + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".encrypted.button"); + em_format_parse_part_as (emf, part, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + camel_cipher_validity_free (valid); + } + + /* TODO: Make sure when we finalize this part, it is zero'd out */ + g_object_unref (opart); + g_object_unref (context); } +/* RFC 2046 */ static void -emf_init (EMFormat *emf) +emf_parse_multipart_mixed (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EShell *shell; - EShellSettings *shell_settings; + CamelMultipart *mp; + gint i, nparts, len; - emf->priv = EM_FORMAT_GET_PRIVATE (emf); + if (g_cancellable_is_cancelled (cancellable)) + return; - emf->inline_table = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) NULL, - (GDestroyNotify) emf_free_cache); - emf->composer = FALSE; - emf->print = FALSE; - g_queue_init (&emf->header_list); - em_format_default_headers (emf); - emf->part_id = g_string_new(""); - emf->current_message_part_id = NULL; - emf->validity_found = 0; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - shell = e_shell_get_default (); - shell_settings = e_shell_get_shell_settings (shell); + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + len = part_id->len; + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; - emf->session = e_shell_settings_get_pointer (shell_settings, "mail-session"); - g_return_if_fail (emf->session != NULL); + subpart = camel_multipart_get_part (mp, i); - g_object_ref (emf->session); + g_string_append_printf(part_id, ".mixed.%d", i); + em_format_parse_part (emf, subpart, part_id, info, cancellable); + g_string_truncate (part_id, len); + } } -GType -em_format_get_type (void) +static void +emf_parse_multipart_signed (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - static GType type = 0; + CamelMimePart *cpart; + CamelMultipartSigned *mps; + CamelCipherContext *cipher = NULL; + guint32 validity_type; - if (G_UNLIKELY (type == 0)) { - static const GTypeInfo type_info = { - sizeof (EMFormatClass), - (GBaseInitFunc) emf_base_init, - (GBaseFinalizeFunc) NULL, - (GClassInitFunc) emf_class_init, - (GClassFinalizeFunc) NULL, - NULL, /* class_data */ - sizeof (EMFormat), - 0, /* n_preallocs */ - (GInstanceInitFunc) emf_init, - NULL /* value_table */ - }; + if (g_cancellable_is_cancelled (cancellable)) + return; - type = g_type_register_static ( - G_TYPE_OBJECT, "EMFormat", &type_info, 0); + mps = (CamelMultipartSigned *) camel_medium_get_content ((CamelMedium *) part); + if (!CAMEL_IS_MULTIPART_SIGNED (mps) + || (cpart = camel_multipart_get_part ((CamelMultipart *) mps, + CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { + em_format_format_error ( + emf, _("Could not parse MIME message. " + "Displaying as source.")); + emf_parse_source (emf, part, part_id, info, cancellable); + return; } - return type; -} + /* FIXME: Should be done via a plugin interface */ + /* FIXME: duplicated in em-format-html-display.c */ + if (mps->protocol) { +#ifdef ENABLE_SMIME + if (g_ascii_strcasecmp("application/x-pkcs7-signature", mps->protocol) == 0 + || g_ascii_strcasecmp("application/pkcs7-signature", mps->protocol) == 0) { + cipher = camel_smime_context_new (emf->priv->session); + validity_type = EM_FORMAT_VALIDITY_FOUND_SMIME; + } else +#endif + if (g_ascii_strcasecmp("application/pgp-signature", mps->protocol) == 0) { + cipher = camel_gpg_context_new (emf->priv->session); + validity_type = EM_FORMAT_VALIDITY_FOUND_PGP; + } + } -/** - * em_format_class_add_handler: - * @emfc: EMFormatClass - * @info: Callback information. - * - * Add a mime type handler to this class. This is only used by - * implementing classes. The @info.old pointer will automatically be - * setup to point to the old handler if one was already set. This can - * be used for overrides a fallback. - * - * When a mime type described by @info is encountered, the callback will - * be invoked. Note that @info may be extended by sub-classes if - * they require additional context information. - * - * Use a mime type of "foo/ *" to insert a fallback handler for type "foo". - **/ -void -em_format_class_add_handler (EMFormatClass *emfc, - EMFormatHandler *info) -{ - info->old = g_hash_table_lookup (emfc->type_handlers, info->mime_type); - g_hash_table_insert (emfc->type_handlers, (gpointer) info->mime_type, info); -} + if (cipher == NULL) { + em_format_format_error(emf, _("Unsupported signature format")); + emf_parse_multipart_mixed (emf, part, part_id, info, cancellable); + } else { + CamelCipherValidity *valid; + GError *local_error = NULL; -struct _class_handlers { - EMFormatClass *old; - EMFormatClass *new; -}; + valid = camel_cipher_context_verify_sync ( + cipher, part, cancellable, &local_error); + if (valid == NULL) { + em_format_format_error ( + emf, local_error->message ? + _("Error verifying signature") : + _("Unknown error verifying signature")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", + local_error->message); + g_clear_error (&local_error); + emf_parse_multipart_mixed (emf, part, part_id,info, cancellable); + } else { + gint i, nparts, len = part_id->len; + gboolean secured; + + EMFormatParserInfo signinfo = { + info->handler, + info->validity_type | validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED, + }; + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + signinfo.validity = valid; + + nparts = camel_multipart_get_number (CAMEL_MULTIPART (mps)); + secured = FALSE; + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; + subpart = camel_multipart_get_part (CAMEL_MULTIPART (mps), i); + + g_string_append_printf(part_id, ".signed.%d", i); + em_format_parse_part (emf, subpart, part_id, &signinfo, cancellable); + g_string_truncate (part_id, len); + + if (!secured) + secured = is_secured (subpart); + } -static void -merge_missing (gpointer key, - gpointer value, - gpointer userdata) -{ - struct _class_handlers *classes = (struct _class_handlers *) userdata; - EMFormatHandler *info; + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!secured) { + g_string_append (part_id, ".signed.button"); + em_format_parse_part_as (emf, part, part_id, &signinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } - info = g_hash_table_lookup (classes->new->type_handlers, key); - if (!info) { - /* Might be from a plugin */ - g_hash_table_insert (classes->new->type_handlers, key, value); + camel_cipher_validity_free (valid); + } } + g_object_unref (cipher); } -void -em_format_merge_handler (EMFormat *new, - EMFormat *old) +/* RFC 2046 */ +static void +emf_parse_multipart_digest (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *oldc = (EMFormatClass *) G_OBJECT_GET_CLASS (old); - EMFormatClass *newc = (EMFormatClass *) G_OBJECT_GET_CLASS (new); - struct _class_handlers fclasses; + CamelMultipart *mp; + gint i, nparts, len; + + if (g_cancellable_is_cancelled (cancellable)) + return; - fclasses.old = oldc; - fclasses.new = newc; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - g_hash_table_foreach (oldc->type_handlers, merge_missing, &fclasses); + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + len = part_id->len; + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + CamelMimePart *subpart; + CamelContentType *ct; + gchar *cts; + const EMFormatHandler *handler; + + subpart = camel_multipart_get_part (mp, i); + + if (!subpart) + continue; + + g_string_append_printf(part_id, ".digest.%d", i); + + ct = camel_mime_part_get_content_type (subpart); + /* According to RFC this shouldn't happen, but who knows... */ + if (ct && !camel_content_type_is (ct, "message", "rfc822")) { + cts = camel_content_type_simple (ct); + em_format_parse_part_as (emf, part, part_id, info, cts, cancellable); + g_free (cts); + g_string_truncate (part_id, len); + continue; + } + + handler = em_format_find_handler (emf, "message/rfc822"); + if (handler && handler->parse_func) + handler->parse_func (emf, subpart, part_id, info, cancellable); + + g_string_truncate (part_id, len); + } } -/** - * em_format_class_remove_handler: - * @emfc: - * @info: - * - * Remove a handler. @info must be a value which was previously - * added. - **/ -void -em_format_class_remove_handler (EMFormatClass *emfc, - EMFormatHandler *info) +/* RFC 2387 */ +static void +emf_parse_multipart_related (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatHandler *current; + CamelMultipart *mp; + CamelMimePart *body_part, *display_part = NULL; + gint i, nparts, partidlen, displayid = 0; - /* TODO: thread issues? */ + if (g_cancellable_is_cancelled (cancellable)) + return; - current = g_hash_table_lookup (emfc->type_handlers, info->mime_type); - if (current == info) { - current = info->old; - if (current) - g_hash_table_insert ( - emfc->type_handlers, - (gpointer) current->mime_type, current); - else - g_hash_table_remove ( - emfc->type_handlers, info->mime_type); - } else { - while (current && current->old != info) - current = current->old; - g_return_if_fail (current != NULL); - current->old = info->old; + mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); + + if (!CAMEL_IS_MULTIPART (mp)) { + emf_parse_source (emf, part, part_id, info, cancellable); + return; + } + + display_part = get_related_display_part (part, &displayid); + + if (display_part == NULL) { + emf_parse_multipart_mixed ( + emf, part, part_id, info, cancellable); + return; + } + + /* The to-be-displayed part goes first */ + partidlen = part_id->len; + g_string_append_printf(part_id, ".related.%d", displayid); + em_format_parse_part (emf, display_part, part_id, info, cancellable); + g_string_truncate (part_id, partidlen); + + /* Process the related parts */ + nparts = camel_multipart_get_number (mp); + for (i = 0; i < nparts; i++) { + body_part = camel_multipart_get_part (mp, i); + if (body_part != display_part) { + g_string_append_printf(part_id, ".related.%d", i); + em_format_parse_part (emf, body_part, part_id, info, cancellable); + g_string_truncate (part_id, partidlen); + } } } -/** - * em_format_find_handler: - * @emf: - * @mime_type: - * - * Find a format handler by @mime_type. - * - * Return value: NULL if no handler is available. - **/ -const EMFormatHandler * -em_format_find_handler (EMFormat *emf, - const gchar *mime_type) +static void +emf_parse_message_deliverystatus (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + EMFormatPURI *puri; + gint len; - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (mime_type != NULL, NULL); + if (g_cancellable_is_cancelled (cancellable)) + return; + + len = part_id->len; + g_string_append (part_id, ".deliverystatus"); - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->find_handler != NULL, NULL); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = emf_write_text; + puri->mime_type = g_strdup ("text/html"); + puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; + puri->validity_type = info->validity_type; - return class->find_handler (emf, mime_type); + g_string_truncate (part_id, len); + + em_format_add_puri (emf, puri); } -/** - * em_format_fallback_handler: - * @emf: - * @mime_type: - * - * Try to find a format handler based on the major type of the @mime_type. - * - * The subtype is replaced with "*" and a lookup performed. - * - * Return value: - **/ -const EMFormatHandler * -em_format_fallback_handler (EMFormat *emf, - const gchar *mime_type) +static void +emf_parse_inlinepgp_signed (EMFormat *emf, + CamelMimePart *ipart, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - gchar *mime, *s; + CamelStream *filtered_stream; + CamelMimeFilterPgp *pgp_filter; + CamelContentType *content_type; + CamelCipherContext *cipher; + CamelCipherValidity *valid; + CamelDataWrapper *dw; + CamelMimePart *opart; + CamelStream *ostream; + gchar *type; + gint len; + GError *local_error = NULL; + EMFormatParserInfo signinfo; + GByteArray *ba; - s = strchr (mime_type, '/'); - if (s == NULL) - mime = (gchar *) mime_type; - else { - gsize len = (s - mime_type) + 1; + if (g_cancellable_is_cancelled (cancellable)) + return; - mime = g_alloca (len + 2); - strncpy (mime, mime_type, len); - strcpy(mime+len, "*"); + if (!ipart) { + em_format_format_error(emf, _("Unknown error verifying signature")); + return; } - return em_format_find_handler (emf, mime); + cipher = camel_gpg_context_new (emf->priv->session); + /* Verify the signature of the message */ + valid = camel_cipher_context_verify_sync ( + cipher, ipart, cancellable, &local_error); + if (!valid) { + em_format_format_error ( + emf, local_error->message ? + _("Error verifying signature") : + _("Unknown error verifying signature")); + if (local_error->message) + em_format_format_error ( + emf, "%s", local_error->message); + emf_parse_source (emf, ipart, part_id, info, cancellable); + /* XXX I think this will loop: + * em_format_part_as(emf, stream, part, "text/plain"); */ + g_clear_error (&local_error); + g_object_unref (cipher); + return; + } + + /* Setup output stream */ + ostream = camel_stream_mem_new (); + filtered_stream = camel_stream_filter_new (ostream); + + /* Add PGP header / footer filter */ + pgp_filter = (CamelMimeFilterPgp *) camel_mime_filter_pgp_new (); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), + CAMEL_MIME_FILTER (pgp_filter)); + g_object_unref (pgp_filter); + + /* Pass through the filters that have been setup */ + dw = camel_medium_get_content ((CamelMedium *) ipart); + camel_data_wrapper_decode_to_stream_sync ( + dw, (CamelStream *) filtered_stream, cancellable, NULL); + camel_stream_flush ((CamelStream *) filtered_stream, cancellable, NULL); + g_object_unref (filtered_stream); + + /* Create a new text/plain MIME part containing the signed + * content preserving the original part's Content-Type params. */ + content_type = camel_mime_part_get_content_type (ipart); + type = camel_content_type_format (content_type); + content_type = camel_content_type_decode (type); + g_free (type); + + g_free (content_type->type); + content_type->type = g_strdup ("text"); + g_free (content_type->subtype); + content_type->subtype = g_strdup ("plain"); + type = camel_content_type_format (content_type); + camel_content_type_unref (content_type); + + ba = camel_stream_mem_get_byte_array ((CamelStreamMem *) ostream); + opart = camel_mime_part_new (); + camel_mime_part_set_content (opart, (gchar *) ba->data, ba->len, type); + g_free (type); + + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); + + /* Pass it off to the real formatter */ + len = part_id->len; + g_string_append (part_id, ".inlinepgp_signed"); + signinfo.handler = info->handler; + signinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_SIGNED | EM_FORMAT_VALIDITY_FOUND_PGP; + signinfo.validity = valid; + em_format_parse_part (emf, opart, part_id, &signinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".inlinepgp_signed.button"); + em_format_parse_part_as (emf, opart, part_id, &signinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); + } + + /* Clean Up */ + camel_cipher_validity_free (valid); + g_object_unref (dw); + g_object_unref (opart); + g_object_unref (ostream); + g_object_unref (cipher); } -/** - * em_format_add_puri: - * @emf: - * @size: - * @cid: Override the autogenerated content id. - * @part: - * @func: - * - * Add a pending-uri handler. When formatting parts that reference - * other parts, a pending-uri (PURI) can be used to track the reference. - * - * @size is used to allocate the structure, so that it can be directly - * subclassed by implementors. - * - * @cid can be used to override the key used to retreive the PURI, if NULL, - * then the content-location and the content-id of the @part are stored - * as lookup keys for the part. - * - * FIXME: This may need a free callback. - * - * Return value: A new PURI, with a referenced copy of @part, and the cid - * always set. The uri will be set if one is available. Clashes - * are resolved by forgetting the old PURI in the global index. - **/ -EMFormatPURI * -em_format_add_puri (EMFormat *emf, - gsize size, - const gchar *cid, - CamelMimePart *part, - EMFormatPURIFunc func) +static void +emf_parse_inlinepgp_encrypted (EMFormat *emf, + CamelMimePart *ipart, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatPURI *puri; - const gchar *tmp; + CamelCipherContext *cipher; + CamelCipherValidity *valid; + CamelMimePart *opart; + CamelDataWrapper *dw; + gchar *mime_type; + gint len; + GError *local_error = NULL; + EMFormatParserInfo encinfo; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + cipher = camel_gpg_context_new (emf->priv->session); + opart = camel_mime_part_new (); - d(printf("adding puri for part: %s\n", emf->part_id->str)); + /* Decrypt the message */ + valid = camel_cipher_context_decrypt_sync ( + cipher, ipart, opart, cancellable, &local_error); + + if (!valid) { + em_format_format_error ( + emf, _("Could not parse PGP message: ")); + if (local_error->message != NULL) + em_format_format_error ( + emf, "%s", local_error->message); + else + em_format_format_error ( + emf, _("Unknown error")); + emf_parse_source (emf, ipart, part_id, info, cancellable); + /* XXX I think this will loop: + * em_format_part_as(emf, stream, part, "text/plain"); */ - if (size < sizeof (*puri)) { - g_warning ( - "size (%" G_GSIZE_FORMAT - ") less than size of puri\n", size); - size = sizeof (*puri); + g_clear_error (&local_error); + g_object_unref (cipher); + g_object_unref (opart); + return; } - puri = g_malloc0 (size); + dw = camel_medium_get_content ((CamelMedium *) opart); + mime_type = camel_data_wrapper_get_mime_type (dw); - puri->format = emf; - puri->func = func; - puri->use_count = 0; - puri->cid = g_strdup (cid); - puri->part_id = g_strdup (emf->part_id->str); + /* this ensures to show the 'opart' as inlined, if possible */ + if (mime_type && g_ascii_strcasecmp (mime_type, "application/octet-stream") == 0) { + const gchar *snoop = em_format_snoop_type (opart); - if (part) { - g_object_ref (part); - puri->part = part; + if (snoop) + camel_data_wrapper_set_mime_type (dw, snoop); } - if (part != NULL && cid == NULL) { - tmp = camel_mime_part_get_content_id (part); - if (tmp) - puri->cid = g_strdup_printf("cid:%s", tmp); - else - puri->cid = g_strdup_printf("em-no-cid:%s", emf->part_id->str); + preserve_charset_in_content_type (ipart, opart); + g_free (mime_type); - d(printf("built cid '%s'\n", puri->cid)); + if (info->validity) + camel_cipher_validity_envelope (valid, info->validity); - /* Not quite same as old behaviour, it also put in the - * relative uri and a fallback for no parent uri. */ - tmp = camel_mime_part_get_content_location (part); - puri->uri = NULL; - if (tmp == NULL) { - /* No location, don't set a uri at all, - * html parts do this themselves. */ - } else { - if (strchr (tmp, ':') == NULL && emf->base != NULL) { - CamelURL *uri; - - uri = camel_url_new_with_base (emf->base, tmp); - puri->uri = camel_url_to_string (uri, 0); - camel_url_free (uri); - } else { - puri->uri = g_strdup (tmp); - } - } + /* Pass it off to the real formatter */ + len = part_id->len; + g_string_append (part_id, ".inlinepgp_encrypted"); + encinfo.handler = info->handler; + encinfo.validity_type = info->validity_type | EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | EM_FORMAT_VALIDITY_FOUND_PGP; + encinfo.validity = valid; + em_format_parse_part (emf, opart, part_id, &encinfo, cancellable); + g_string_truncate (part_id, len); + + /* Add a widget with details about the encryption, but only when + * the encrypted isn't itself secured, in that case it has created + * the button itself */ + if (!is_secured (opart)) { + g_string_append (part_id, ".inlinepgp_encrypted.button"); + em_format_parse_part_as (emf, opart, part_id, &encinfo, + "x-evolution/message/x-secure-button", cancellable); + g_string_truncate (part_id, len); } - g_return_val_if_fail (puri->cid != NULL, NULL); - g_return_val_if_fail (emf->pending_uri_level != NULL, NULL); - g_return_val_if_fail (emf->pending_uri_table != NULL, NULL); + /* Clean Up */ + camel_cipher_validity_free (valid); + g_object_unref (opart); + g_object_unref (cipher); +} - g_queue_push_tail (emf->pending_uri_level->data, puri); +static void +emf_parse_message (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* Headers */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/headers", cancellable); - if (puri->uri) - g_hash_table_insert (emf->pending_uri_table, puri->uri, puri); - g_hash_table_insert (emf->pending_uri_table, puri->cid, puri); + /* Anything that comes between headers and message body */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/post-headers", cancellable); - return puri; + /* Begin parsing the message */ + info->force_handler = FALSE; + em_format_parse_part (emf, part, part_id, info, cancellable); } -/** - * em_format_push_level: - * @emf: - * - * This is used to build a hierarchy of visible PURI objects based on - * the structure of the message. Used by multipart/alternative formatter. - * - * FIXME: This could probably also take a uri so it can automatically update - * the base location. - **/ -void -em_format_push_level (EMFormat *emf) +static void +emf_parse_headers (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - GNode *node; + EMFormatPURI *puri; + gint len; - g_return_if_fail (EM_IS_FORMAT (emf)); + len = part_id->len; + g_string_append (part_id, ".headers"); - node = g_node_new (g_queue_new ()); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = info->handler->write_func; + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); - if (emf->pending_uri_tree == NULL) - emf->pending_uri_tree = node; - else - g_node_append (emf->pending_uri_tree, node); + g_string_truncate (part_id, len); +} - emf->pending_uri_level = node; +static void +emf_parse_post_headers (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + /* Add attachment bar */ + info->force_handler = TRUE; + em_format_parse_part_as (emf, part, part_id, info, + "x-evolution/message/attachment-bar", cancellable); } -/** - * em_format_pull_level: - * @emf: - * - * Drop a level of visibility back to the parent. Note that - * no PURI values are actually freed. - **/ +static void +emf_parse_source (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + gint len; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + len = part_id->len; + g_string_append (part_id, ".source"); + + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); + puri->write_func = info->handler->write_func; + puri->mime_type = g_strdup ("text/html"); + g_string_truncate (part_id, len); + + em_format_add_puri (emf, puri); +} + +/**************************************************************************/ + void -em_format_pull_level (EMFormat *emf) +em_format_empty_writer (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + /* DO NOTHING */ +} + +static void +emf_write_error (EMFormat * emf, + EMFormatPURI * puri, + CamelStream * stream, + EMFormatWriterInfo * info, + GCancellable * cancellable) +{ + camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part, + stream, cancellable, NULL); +} + +static void +emf_write_text (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + CamelContentType *ct; + + ct = camel_mime_part_get_content_type (puri->part); + if (!camel_content_type_is (ct, "text", "plain")) { + camel_stream_write_string (stream, _("Cannot proccess non-text mime/part"), + cancellable, NULL); + return; + } + + camel_data_wrapper_decode_to_stream_sync ((CamelDataWrapper *) puri->part, + stream, cancellable, NULL); +} + +static void +emf_write_source (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { + GByteArray *ba; + gchar *data; + g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (emf->pending_uri_level != NULL); - emf->pending_uri_level = emf->pending_uri_level->parent; + ba = camel_data_wrapper_get_byte_array ((CamelDataWrapper *) puri->part); + + data = g_strndup ((gchar *) ba->data, ba->len); + camel_stream_write_string (stream, data, cancellable, NULL); + g_free (data); } -/** - * em_format_find_visible_puri: - * @emf: - * @uri: - * - * Search for a PURI based on the visibility defined by :push_level() - * and :pull_level(). - * - * Return value: - **/ -EMFormatPURI * -em_format_find_visible_puri (EMFormat *emf, - const gchar *uri) +/**************************************************************************/ + +static gboolean +emf_is_inline (EMFormat *emf, + const gchar *part_id, + CamelMimePart *mime_part, + const EMFormatHandler *handle) { - GNode *node; + //EMFormatCache *emfc; + const gchar *disposition; - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (uri != NULL, NULL); + if (handle == NULL) + return FALSE; - node = emf->pending_uri_level; + /* Some types need to override the disposition. + * e.g. application/x-pkcs7-mime */ + if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION) + return TRUE; - while (node != NULL) { - GQueue *queue = node->data; - GList *link; + disposition = camel_mime_part_get_disposition (mime_part); + if (disposition != NULL) + return g_ascii_strcasecmp (disposition, "inline") == 0; - link = g_queue_peek_head_link (queue); + /* Otherwise, use the default for this handler type. */ + return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0; +} - while (link != NULL) { - EMFormatPURI *pw = link->data; +/**************************************************************************/ - if (g_strcmp0 (pw->uri, uri) == 0) - return pw; +static EMFormatHandler type_handlers[] = { +#ifdef ENABLE_SMIME + { (gchar *) "application/x-pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, +#endif + { (gchar *) "application/mbox", emf_parse_application_mbox, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "multipart/alternative", emf_parse_multipart_alternative, }, + { (gchar *) "multipart/appledouble", emf_parse_multipart_appledouble, }, + { (gchar *) "multipart/encrypted", emf_parse_multipart_encrypted, }, + { (gchar *) "multipart/mixed", emf_parse_multipart_mixed, }, + { (gchar *) "multipart/signed", emf_parse_multipart_signed, }, + { (gchar *) "multipart/related", emf_parse_multipart_related, }, + { (gchar *) "multipart/digest", emf_parse_multipart_digest, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "multipart/*", emf_parse_multipart_mixed, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "message/deliverystatus", emf_parse_message_deliverystatus, 0, }, + + /* Ignore PGP signature part */ + { (gchar *) "application/pgp-signature", em_format_empty_parser, }, + + /* Insert brokenly-named parts here */ +#ifdef ENABLE_SMIME + { (gchar *) "application/pkcs7-mime", emf_parse_application_xpkcs7mime, 0, EM_FORMAT_HANDLER_INLINE_DISPOSITION }, +#endif - if (g_strcmp0 (pw->cid, uri) == 0) - return pw; + /* internal types */ + { (gchar *) "application/x-inlinepgp-signed", emf_parse_inlinepgp_signed, }, + { (gchar *) "application/x-inlinepgp-encrypted", emf_parse_inlinepgp_encrypted, }, + { (gchar *) "x-evolution/message", emf_parse_message, 0, EM_FORMAT_HANDLER_COMPOUND_TYPE }, + { (gchar *) "x-evolution/message/headers", emf_parse_headers, }, + { (gchar *) "x-evolution/message/post-headers", emf_parse_post_headers, }, + { (gchar *) "x-evolution/message/source", emf_parse_source, emf_write_source }, +}; - link = g_list_next (link); - } +/* note: also copied in em-mailer-prefs.c */ +static const struct { + const gchar *name; + guint32 flags; +} default_headers[] = { + { N_("From"), EM_FORMAT_HEADER_BOLD }, + { N_("Reply-To"), EM_FORMAT_HEADER_BOLD }, + { N_("To"), EM_FORMAT_HEADER_BOLD }, + { N_("Cc"), EM_FORMAT_HEADER_BOLD }, + { N_("Bcc"), EM_FORMAT_HEADER_BOLD }, + { N_("Subject"), EM_FORMAT_HEADER_BOLD }, + { N_("Date"), EM_FORMAT_HEADER_BOLD }, + { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD }, + { N_("Face"), 0 }, +}; + +static void +em_format_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + EMFormat *emf = EM_FORMAT (object); - node = node->parent; + switch (property_id) { + case PROP_CHARSET: + g_value_set_string ( + value, em_format_get_charset (emf)); + return; + case PROP_DEFAULT_CHARSET: + g_value_set_string ( + value, em_format_get_default_charset (emf)); + return; + case PROP_COMPOSER: + g_value_set_boolean ( + value, em_format_get_composer (emf)); + return; + case PROP_BASE_URL: + g_value_set_object ( + value, em_format_get_base_url (emf)); + return; } - return NULL; + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } -/** - * em_format_find_puri: - * @emf: - * @uri: - * - * Search for a PURI based on a uri. Both the content-id - * and content-location are checked. - * - * Return value: - **/ -EMFormatPURI * -em_format_find_puri (EMFormat *emf, - const gchar *uri) +static void +em_format_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) { - g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - g_return_val_if_fail (uri != NULL, NULL); + EMFormat *emf = EM_FORMAT (object); - g_return_val_if_fail (emf->pending_uri_table != NULL, NULL); + switch (property_id) { + case PROP_CHARSET: + em_format_set_charset (emf, + g_value_get_string (value)); + return; + case PROP_DEFAULT_CHARSET: + em_format_set_default_charset (emf, + g_value_get_string (value)); + return; + case PROP_COMPOSER: + em_format_set_composer (emf, + g_value_get_boolean (value)); + return; + case PROP_BASE_URL: + em_format_set_base_url (emf, + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - return g_hash_table_lookup (emf->pending_uri_table, uri); } -/** - * em_format_clear_puri_tree: - * @emf: - * - * For use by implementors to clear out the message structure - * data. - **/ -void -em_format_clear_puri_tree (EMFormat *emf) +static void +em_format_finalize (GObject *object) { - if (emf->pending_uri_table == NULL) - emf->pending_uri_table = - g_hash_table_new (g_str_hash, g_str_equal); + EMFormat *emf = EM_FORMAT (object); - else { - g_hash_table_remove_all (emf->pending_uri_table); + if (emf->message_uid) { + g_free (emf->message_uid); + emf->message_uid = NULL; + } + + if (emf->uri_base) { + g_free (emf->uri_base); + emf->uri_base = NULL; + } + + if (emf->message) { + g_object_unref (emf->message); + emf->message = NULL; + } + + if (emf->folder) { + g_object_unref (emf->folder); + emf->folder = NULL; + } + + if (emf->mail_part_table) { + /* This will destroy all the EMFormatPURI objects stored + * inside!!!! */ + g_hash_table_destroy (emf->mail_part_table); + emf->mail_part_table = NULL; + } + + if (emf->mail_part_list) { + g_list_free (emf->mail_part_list); + emf->mail_part_list = NULL; + } + + if (emf->priv->base_url) { + camel_url_free (emf->priv->base_url); + emf->priv->base_url = NULL; + } - g_node_traverse ( - emf->pending_uri_tree, - G_IN_ORDER, G_TRAVERSE_ALL, -1, - (GNodeTraverseFunc) emf_clear_puri_node, NULL); - g_node_destroy (emf->pending_uri_tree); + if (emf->priv->session) { + g_object_unref (emf->priv->session); + emf->priv->session = NULL; + } - emf->pending_uri_tree = NULL; - emf->pending_uri_level = NULL; + if (emf->priv->charset) { + g_free (emf->priv->charset); + emf->priv->charset = NULL; } - em_format_push_level (emf); + em_format_clear_headers (emf); + + /* Chain up to parent's finalize() method */ + G_OBJECT_CLASS (parent_class)->finalize (object); } -/* use mime_type == NULL to force showing as application/octet-stream */ -void -em_format_part_as (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - GCancellable *cancellable) +static void +em_format_base_init (EMFormatClass *klass) { - const EMFormatHandler *handle = NULL; - const gchar *snoop_save = emf->snoop_mime_type, *tmp; - CamelURL *base_save = emf->base, *base = NULL; - gchar *basestr = NULL; + gint i; - d(printf("format_part_as()\n")); + klass->type_handlers = g_hash_table_new (g_str_hash, g_str_equal); - emf->snoop_mime_type = NULL; + for (i = 0; i < G_N_ELEMENTS (type_handlers); i++) { + g_hash_table_insert (klass->type_handlers, + type_handlers[i].mime_type, + &type_handlers[i]); + } +} - /* RFC 2110, we keep track of content-base, and absolute content-location headers - * This is actually only required for html, but, *shrug * */ - tmp = camel_medium_get_header((CamelMedium *)part, "Content-Base"); - if (tmp == NULL) { - tmp = camel_mime_part_get_content_location (part); - if (tmp && strchr (tmp, ':') == NULL) - tmp = NULL; - } else { - tmp = basestr = camel_header_location_decode (tmp); - } - d(printf("content-base is '%s'\n", tmp?tmp:"<unset>")); - if (tmp - && (base = camel_url_new (tmp, NULL))) { - emf->base = base; - d(printf("Setting content base '%s'\n", tmp)); - } - g_free (basestr); - - if (mime_type != NULL) { - gboolean is_fallback = FALSE; - if (g_ascii_strcasecmp(mime_type, "application/octet-stream") == 0) { - emf->snoop_mime_type = mime_type = em_format_snoop_type (part); - if (mime_type == NULL) - mime_type = "application/octet-stream"; - } +static void +em_format_class_init (EMFormatClass *klass) +{ + GObjectClass *object_class; - handle = em_format_find_handler (emf, mime_type); - if (handle == NULL) { - handle = em_format_fallback_handler (emf, mime_type); - is_fallback = TRUE; - } + parent_class = g_type_class_peek_parent (klass); + + g_type_class_add_private (klass, sizeof (EMFormatPrivate)); + + klass->is_inline = emf_is_inline; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = em_format_finalize; + object_class->get_property = em_format_get_property; + object_class->set_property = em_format_set_property; + + g_object_class_install_property (object_class, + PROP_CHARSET, + g_param_spec_string ("charset", + NULL, + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_DEFAULT_CHARSET, + g_param_spec_string ("default-charset", + NULL, + NULL, + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_COMPOSER, + g_param_spec_boolean ("composer", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_BASE_URL, + g_param_spec_pointer ("base-url", + NULL, + NULL, + G_PARAM_READWRITE)); + + signals[REDRAW_REQUESTED] = g_signal_new ( + "redraw-requested", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EMFormatClass, redraw_requested), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE,0); +} - if (handle != NULL - && !em_format_is_attachment (emf, part)) { - d(printf("running handler for type '%s'\n", mime_type)); - handle->handler ( - emf, stream, part, handle, - cancellable, is_fallback); - goto finish; - } - d(printf("this type is an attachment? '%s'\n", mime_type)); - } else { - mime_type = "application/octet-stream"; - } +static void +mail_part_table_item_free (gpointer data) +{ + GList *iter = data; + EMFormatPURI *puri = iter->data; + + em_format_puri_free (puri); +} + +static void +em_format_init (EMFormat *emf) +{ + EShell *shell; + EShellSettings *shell_settings; - EM_FORMAT_GET_CLASS (emf)->format_attachment ( - emf, stream, part, mime_type, handle, cancellable); + emf->priv = G_TYPE_INSTANCE_GET_PRIVATE (emf, + EM_TYPE_FORMAT, EMFormatPrivate); -finish: - emf->base = base_save; - emf->snoop_mime_type = snoop_save; + emf->message = NULL; + emf->folder = NULL; + emf->mail_part_list = NULL; + emf->mail_part_table = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, (GDestroyNotify) mail_part_table_item_free); + /* No need to free the key, because it's owned and free'd by the PURI */ - if (base) - camel_url_free (base); + shell = e_shell_get_default (); + shell_settings = e_shell_get_shell_settings (shell); + + emf->priv->last_error = 0; + + emf->priv->session = e_shell_settings_get_pointer (shell_settings, "mail-session"); + g_return_if_fail (emf->priv->session); + + g_object_ref (emf->priv->session); + + em_format_default_headers (emf); } -void -em_format_part (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable) +EMFormat * +em_format_new (void) { - gchar *mime_type; - CamelDataWrapper *dw; + EMFormat *emf = g_object_new (EM_TYPE_FORMAT, NULL); - dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part)); - mime_type = camel_data_wrapper_get_mime_type (dw); - if (mime_type != NULL) { - camel_strdown (mime_type); - em_format_part_as ( - emf, stream, mime_part, mime_type, cancellable); - g_free (mime_type); - } else - em_format_part_as ( - emf, stream, mime_part, "text/plain", cancellable); + return emf; } -/** - * em_format_format_clone: - * @emf: an #EMFormat - * @folder: a #CamelFolder or %NULL - * @uid: Message UID or %NULL - * @msg: a #CamelMimeMessage or %NULL - * @emfsource: Used as a basis for user-altered layout, e.g. inline viewed - * attachments. - * @cancellable: a #GCancellable, or %NULL - * - * Format a message @msg. If @emfsource is non NULL, then the status of - * inlined expansion and so forth is copied direction from @emfsource. - * - * By passing the same value for @emf and @emfsource, you can perform - * a display refresh, or it can be used to generate an identical layout, - * e.g. to print what the user has shown inline. - **/ -void -em_format_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - EMFormat *source, - GCancellable *cancellable) +GType +em_format_get_type (void) { - EMFormatClass *class; + static GType type = 0; - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (folder == NULL || CAMEL_IS_FOLDER (folder)); - g_return_if_fail (message == NULL || CAMEL_IS_MIME_MESSAGE (message)); - g_return_if_fail (source == NULL || EM_IS_FORMAT (source)); + if (G_UNLIKELY (type == 0)) { + static const GTypeInfo type_info = { + sizeof (EMFormatClass), + (GBaseInitFunc) em_format_base_init, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) em_format_class_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (EMFormat), + 0, /* n_preallocs */ + (GInstanceInitFunc) em_format_init, + NULL /* value_table */ + }; - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_clone != NULL); + type = g_type_register_static ( + G_TYPE_OBJECT, "EMFormat", &type_info, 0); + } - class->format_clone (emf, folder, uid, message, source, cancellable); + return type; } void -em_format_format (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *message, - GCancellable *cancellable) +em_format_set_charset (EMFormat *emf, + const gchar *charset) { - /* em_format_format_clone() will check the arguments. */ - em_format_format_clone (emf, folder, uid, message, NULL, cancellable); + g_return_if_fail (EM_IS_FORMAT (emf)); + + if (emf->priv->charset) + g_free (emf->priv->charset); + + emf->priv->charset = g_strdup (charset); + + g_object_notify (G_OBJECT (emf), "charset"); } -static gboolean -format_redraw_idle_cb (EMFormat *emf) +const gchar * +em_format_get_charset (EMFormat *emf) { - emf->priv->redraw_idle_id = 0; - - /* FIXME Not passing a GCancellable here. */ - em_format_format_clone ( - emf, emf->folder, emf->uid, emf->message, emf, NULL); + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); - return FALSE; + return emf->priv->charset; } void -em_format_queue_redraw (EMFormat *emf) +em_format_set_default_charset (EMFormat *emf, + const gchar *charset) { g_return_if_fail (EM_IS_FORMAT (emf)); - if (emf->priv->redraw_idle_id == 0) - emf->priv->redraw_idle_id = g_idle_add ( - (GSourceFunc) format_redraw_idle_cb, emf); + if (emf->priv->default_charset) + g_free (emf->priv->default_charset); + + emf->priv->default_charset = g_strdup (charset); + + g_object_notify (G_OBJECT (emf), "default-charset"); +} + +const gchar * +em_format_get_default_charset (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + + return emf->priv->default_charset; } -/** - * em_format_set_mode: - * @emf: - * @type: - * - * Set display mode, EM_FORMAT_MODE_SOURCE, EM_FORMAT_MODE_ALLHEADERS, - * or EM_FORMAT_MODE_NORMAL. - **/ void -em_format_set_mode (EMFormat *emf, - EMFormatMode mode) +em_format_set_composer (EMFormat *emf, + gboolean composer) { g_return_if_fail (EM_IS_FORMAT (emf)); - if (emf->mode == mode) + if (emf->priv->composer && composer) return; - emf->mode = mode; + emf->priv->composer = composer; - /* force redraw if type changed afterwards */ - if (emf->message != NULL) - em_format_queue_redraw (emf); + g_object_notify (G_OBJECT (emf), "composer"); +} + +gboolean +em_format_get_composer (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); + + return emf->priv->composer; } -/** - * em_format_set_charset: - * @emf: - * @charset: - * - * set override charset on formatter. message will be redisplayed if - * required. - **/ void -em_format_set_charset (EMFormat *emf, - const gchar *charset) +em_format_set_base_url (EMFormat *emf, + CamelURL *url) { - if ((emf->charset && charset && g_ascii_strcasecmp (emf->charset, charset) == 0) - || (emf->charset == NULL && charset == NULL) - || (emf->charset == charset)) - return; + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (url); - g_free (emf->charset); - emf->charset = g_strdup (charset); + if (emf->priv->base_url) + camel_url_free (emf->priv->base_url); - if (emf->message) - em_format_queue_redraw (emf); + emf->priv->base_url = camel_url_copy (url); + + g_object_notify (G_OBJECT (emf), "base-url"); } -/** - * em_format_set_default_charset: - * @emf: - * @charset: - * - * Set the fallback, default system charset to use when no other charsets - * are present. Message will be redisplayed if required (and sometimes - * redisplayed when it isn't). - **/ void -em_format_set_default_charset (EMFormat *emf, - const gchar *charset) +em_format_set_base_url_string (EMFormat *emf, + const gchar *url_string) { - if ((emf->default_charset && charset && - g_ascii_strcasecmp (emf->default_charset, charset) == 0) - || (emf->default_charset == NULL && charset == NULL) - || (emf->default_charset == charset)) - return; + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (url_string && *url_string); + + if (emf->priv->base_url) + camel_url_free (emf->priv->base_url); - g_free (emf->default_charset); - emf->default_charset = g_strdup (charset); + emf->priv->base_url = camel_url_new (url_string, NULL); - if (emf->message && emf->charset == NULL) - em_format_queue_redraw (emf); + g_object_notify (G_OBJECT (emf), "base-url"); +} + +CamelURL * +em_format_get_base_url (EMFormat *emf) +{ + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + + return emf->priv->base_url; } /** @@ -1051,44 +1610,26 @@ em_format_clear_headers (EMFormat *emf) { EMFormatHeader *eh; - while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) - g_free (eh); -} + g_return_if_fail (EM_IS_FORMAT (emf)); -/* note: also copied in em-mailer-prefs.c */ -static const struct { - const gchar *name; - guint32 flags; -} default_headers[] = { - { N_("From"), EM_FORMAT_HEADER_BOLD }, - { N_("Reply-To"), EM_FORMAT_HEADER_BOLD }, - { N_("To"), EM_FORMAT_HEADER_BOLD }, - { N_("Cc"), EM_FORMAT_HEADER_BOLD }, - { N_("Bcc"), EM_FORMAT_HEADER_BOLD }, - { N_("Subject"), EM_FORMAT_HEADER_BOLD }, - { N_("Date"), EM_FORMAT_HEADER_BOLD }, - { N_("Newsgroups"), EM_FORMAT_HEADER_BOLD }, - { N_("Face"), 0 }, -}; + while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) { + em_format_header_free (eh); + } + +} -/** - * em_format_default_headers: - * @emf: - * - * Set the headers to show to the default list. - * - * From, Reply-To, To, Cc, Bcc, Subject and Date. - **/ void em_format_default_headers (EMFormat *emf) { gint ii; - em_format_clear_headers (emf); + g_return_if_fail (EM_IS_FORMAT (emf)); + /* Set the default headers */ + em_format_clear_headers (emf); for (ii = 0; ii < G_N_ELEMENTS (default_headers); ii++) em_format_add_header ( - emf, default_headers[ii].name, + emf, default_headers[ii].name, NULL, default_headers[ii].flags); } @@ -1096,6 +1637,7 @@ em_format_default_headers (EMFormat *emf) * em_format_add_header: * @emf: * @name: The name of the header, as it will appear during output. + * @value: Value of the header. Can be NULL. * @flags: EM_FORMAT_HEAD_* defines to control display attributes. * * Add a specific header to show. If any headers are set, they will @@ -1106,248 +1648,465 @@ em_format_default_headers (EMFormat *emf) void em_format_add_header (EMFormat *emf, const gchar *name, + const gchar *value, guint32 flags) { EMFormatHeader *h; - h = g_malloc (sizeof (*h) + strlen (name)); + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (name && *name); + + h = em_format_header_new (name, value); h->flags = flags; - strcpy (h->name, name); g_queue_push_tail (&emf->header_list, h); } -/** - * em_format_is_attachment: - * @emf: - * @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 - **/ -gint -em_format_is_attachment (EMFormat *emf, - CamelMimePart *part) +void +em_format_add_header_struct (EMFormat *emf, + EMFormatHeader *header) { - /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (header && header->name); - if (!dw) - return 0; + em_format_add_header (emf, header->name, header->value, header->flags); +} - /*printf("checking is attachment %s/%s\n", ct->type, ct->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)); +void +em_format_remove_header (EMFormat * emf, + const gchar *name, + const gchar *value) +{ + GList *iter = NULL; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (name && *name); + + iter = g_queue_peek_head_link (&emf->header_list); + while (iter) { + EMFormatHeader *header = iter->data; + + if (!header->value || !*header->value) { + GList *next = iter->next; + if (g_strcmp0 (name, header->name) == 0) + g_queue_delete_link (&emf->header_list, iter); + + iter = next; + continue; + } + + if (value && *value) { + if ((g_strcmp0 (name, header->name) == 0) && + (g_strcmp0 (value, header->value) == 0)) + break; + } else { + if (g_strcmp0 (name, header->name) == 0) + break; + } + + iter = iter->next; + } + + if (iter) { + em_format_header_free (iter->data); + g_queue_delete_link (&emf->header_list, iter); + } +} + +void +em_format_remove_header_struct (EMFormat * emf, + const EMFormatHeader * header) +{ + g_return_if_fail (header); + + em_format_remove_header (emf, header->name, header->value); +} + +void +em_format_add_puri (EMFormat *emf, + EMFormatPURI *puri) +{ + GList *item; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (puri != NULL); + + emf->mail_part_list = g_list_append (emf->mail_part_list, puri); + item = g_list_last (emf->mail_part_list); + + g_hash_table_insert (emf->mail_part_table, + puri->uri, item); + + d(printf("Added PURI %s\n", puri->uri)); +} + +EMFormatPURI * +em_format_find_puri (EMFormat *emf, + const gchar *id) +{ + GList *list_iter; + + /* First handle CIDs... */ + if (g_str_has_prefix (id, "CID:") || g_str_has_prefix (id, "cid:")) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, emf->mail_part_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + EMFormatPURI *puri = ((GList *) value)->data; + if (g_strcmp0 (puri->cid, id) == 0) + return puri; + } + + return NULL; + } + + list_iter = g_hash_table_lookup (emf->mail_part_table, id); + if (list_iter) + return list_iter->data; + + return NULL; +} + +void +em_format_class_add_handler (EMFormatClass *emfc, + EMFormatHandler *handler) +{ + EMFormatHandler *old_handler; + + g_return_if_fail (EM_IS_FORMAT_CLASS (emfc)); + g_return_if_fail (handler); + + old_handler = g_hash_table_lookup ( + emfc->type_handlers, handler->mime_type); + + handler->old = old_handler; + + /* If parse_func or write_func of the new handler is not set, + * use function from the old handler (if it exists). + * This way we can assign a new write_func for to an existing + * parse_func */ + if (old_handler && handler->parse_func == NULL) { + handler->parse_func = old_handler->parse_func; + } + + if (old_handler && handler->write_func == NULL) { + handler->write_func = old_handler->write_func; + } + + g_hash_table_insert (emfc->type_handlers, + handler->mime_type, handler); +} + +void +em_format_class_remove_handler (EMFormatClass *emfc, + EMFormatHandler *handler) +{ + g_return_if_fail (EM_IS_FORMAT_CLASS (emfc)); + g_return_if_fail (handler); + + g_hash_table_remove (emfc->type_handlers, handler->mime_type); +} + +const EMFormatHandler * +em_format_find_handler (EMFormat *emf, + const gchar *mime_type) +{ + EMFormatClass *emfc; + gchar *s; + const EMFormatHandler *handler; + + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + g_return_val_if_fail (mime_type && *mime_type, NULL); + + emfc = (EMFormatClass *) G_OBJECT_GET_CLASS (emf); + + s = g_ascii_strdown (mime_type, -1); + + handler = g_hash_table_lookup ( + emfc->type_handlers, s); + + g_free (s); + + return handler; } /** - * em_format_is_inline: + * em_format_fallback_handler: * @emf: - * @part: - * @part_id: format->part_id part id of this part. - * @handle: handler for this part + * @mime_type: * - * Returns true if the part should be displayed inline. Any part with - * a Content-Disposition of inline, or if the @handle has a default - * inline set, will be shown inline. + * Try to find a format handler based on the major type of the @mime_type. * - * :set_inline() called on the same part will override any calculated - * value. + * The subtype is replaced with "*" and a lookup performed. * * Return value: **/ -gboolean -em_format_is_inline (EMFormat *emf, - const gchar *part_id, - CamelMimePart *mime_part, - const EMFormatHandler *handle) +const EMFormatHandler * +em_format_fallback_handler (EMFormat *emf, + const gchar *mime_type) { - EMFormatClass *class; + gchar *mime, *s; - g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); - g_return_val_if_fail (part_id != NULL, FALSE); - g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE); + s = strchr (mime_type, '/'); + if (s == NULL) + mime = (gchar *) mime_type; + else { + gsize len = (s - mime_type) + 1; - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->is_inline != NULL, FALSE); + mime = g_alloca (len + 2); + strncpy (mime, mime_type, len); + strcpy(mime+len, "*"); + } - return class->is_inline (emf, part_id, mime_part, handle); + return em_format_find_handler (emf, mime); } -/** - * em_format_set_inline: - * @emf: - * @part_id: id of part - * @state: - * - * Force the attachment @part to be expanded or hidden explictly to match - * @state. This is used only to record the change for a redraw or - * cloned layout render and does not force a redraw. - **/ void -em_format_set_inline (EMFormat *emf, - const gchar *part_id, - gint state) +em_format_parse (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable) { - EMFormatCache *emfc; + GString *part_id; + EMFormatPURI *puri; + EMFormatParserInfo info = { 0 }; g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (part_id != NULL); - emfc = g_hash_table_lookup (emf->inline_table, part_id); - if (emfc == NULL) { - emfc = emf_insert_cache (emf, part_id); - } else if (emfc->state != INLINE_UNSET && (emfc->state & 1) == state) + if (g_cancellable_is_cancelled (cancellable)) return; - emfc->state = state ? INLINE_ON : INLINE_OFF; + if (message) { + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + if (emf->message) + g_object_unref (emf->message); + emf->message = g_object_ref (message); + } + + if (folder) { + g_return_if_fail (CAMEL_IS_FOLDER (folder)); - if (emf->message) - em_format_queue_redraw (emf); + if (emf->folder) + g_object_unref (emf->folder); + emf->folder = g_object_ref (folder); + } + + /* Before the actual parsing starts, let child classes prepare themselves. */ + if (EM_FORMAT_GET_CLASS (emf)->preparse) + EM_FORMAT_GET_CLASS (emf)->preparse (emf); + + part_id = g_string_new (".message"); + + /* Create a special PURI with entire message */ + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), + (CamelMimePart *) emf->message, part_id->str); + puri->mime_type = g_strdup ("text/html"); + em_format_add_puri (emf, puri); + + info.force_handler = TRUE; + em_format_parse_part_as (emf, CAMEL_MIME_PART (emf->message), part_id, &info, + "x-evolution/message", cancellable); + + g_string_free (part_id, TRUE); } void -em_format_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const gchar *mime_type, - const EMFormatHandler *info, - GCancellable *cancellable) +em_format_write (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + EMFormatClass *emf_class; g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); - g_return_if_fail (mime_type != NULL); - g_return_if_fail (info != NULL); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_attachment != NULL); + emf_class = EM_FORMAT_GET_CLASS (emf); + if (emf_class->write) + emf_class->write (emf, stream, info, cancellable); +} - class->format_attachment ( - emf, stream, mime_part, mime_type, info, cancellable); +static void +emf_start_async_parser (GSimpleAsyncResult *result, + GObject *object, + GCancellable *cancellable) +{ + em_format_parse (EM_FORMAT (object), NULL, NULL, cancellable); } void -em_format_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *format, - ...) +em_format_parse_async (EMFormat *emf, + CamelMimeMessage *message, + CamelFolder *folder, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - EMFormatClass *class; - gchar *errmsg; - va_list ap; + GSimpleAsyncResult *result; g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (format != NULL); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_error != NULL); + if (g_cancellable_is_cancelled (cancellable)) + return; - va_start (ap, format); - errmsg = g_strdup_vprintf (format, ap); - class->format_error (emf, stream, errmsg); - g_free (errmsg); - va_end (ap); + if (message) { + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + if (emf->message) + g_object_unref (emf->message); + + emf->message = g_object_ref (message); + + } + + if (folder) { + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + + if (emf->folder) + g_object_unref (emf->folder); + + emf->folder = g_object_ref (folder); + + } + + result = g_simple_async_result_new (G_OBJECT (emf), callback, + user_data, em_format_parse_async); + g_simple_async_result_run_in_thread (result, emf_start_async_parser, + G_PRIORITY_DEFAULT, cancellable); } void -em_format_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - CamelCipherValidity *valid, +em_format_parse_part_as (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + const gchar *mime_type, GCancellable *cancellable) { - EMFormatClass *class; - - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); - g_return_if_fail (valid != NULL); + const EMFormatHandler *handler; + const CamelContentDisposition *disposition; + EMFormatParserInfo ninfo = { + .handler = 0, + .validity_type = info ? info->validity_type : 0, + .validity = info ? info->validity : 0, + .force_handler = 0 + }; + + /* Let everything that claims to be an attachment or inlined part to be parsed + * as an attachment. The parser will decide how to display it. */ + disposition = camel_mime_part_get_content_disposition (part); + if (!info->force_handler && disposition && + (g_strcmp0 (disposition->disposition, "attachment") == 0)) { + ninfo.is_attachment = TRUE; + handler = em_format_find_handler (emf, "x-evolution/message/attachment"); + ninfo.handler = handler; + + if (handler && handler->parse_func) + handler->parse_func (emf, part, part_id, &ninfo, cancellable); - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_secure != NULL); + return; + } - class->format_secure (emf, stream, mime_part, valid, cancellable); + handler = em_format_find_handler (emf, mime_type); + if (handler && handler->parse_func) { + ninfo.handler = handler; + handler->parse_func (emf, part, part_id, &ninfo, cancellable); + } else { + handler = em_format_find_handler (emf, "x-evolution/message/attachment"); + ninfo.handler = handler; - if (emf->valid_parent == NULL && emf->valid != NULL) { - camel_cipher_validity_free (emf->valid); - emf->valid = NULL; + /* When this fails, something is probably very wrong...*/ + if (handler && handler->parse_func) + handler->parse_func (emf, part, part_id, &ninfo, cancellable); } } void -em_format_format_source (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - GCancellable *cancellable) +em_format_parse_part (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormatClass *class; + CamelContentType *ct; + gchar *mime_type; - g_return_if_fail (EM_IS_FORMAT (emf)); - g_return_if_fail (CAMEL_IS_STREAM (stream)); - g_return_if_fail (CAMEL_IS_MIME_PART (mime_part)); + ct = camel_mime_part_get_content_type (part); + if (ct) { + mime_type = camel_content_type_simple (ct); + } else { + mime_type = (gchar *) "text/plain"; + } - class = EM_FORMAT_GET_CLASS (emf); - g_return_if_fail (class->format_source != NULL); + em_format_parse_part_as (emf, part, part_id, info, mime_type, cancellable); - class->format_source (emf, stream, mime_part, cancellable); + if (ct) + g_free (mime_type); } gboolean -em_format_busy (EMFormat *emf) +em_format_is_inline (EMFormat *emf, + const gchar *part_id, + CamelMimePart *part, + const EMFormatHandler *handler) { - EMFormatClass *class; + EMFormatClass *klass; g_return_val_if_fail (EM_IS_FORMAT (emf), FALSE); + g_return_val_if_fail (part_id && *part_id, FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_PART (part), FALSE); + g_return_val_if_fail (handler, FALSE); - class = EM_FORMAT_GET_CLASS (emf); - g_return_val_if_fail (class->busy != NULL, FALSE); + klass = EM_FORMAT_GET_CLASS (emf); + g_return_val_if_fail (klass->is_inline != NULL, FALSE); + + return klass->is_inline (emf, part_id, part, handler); - return class->busy (emf); } -/* should this be virtual? */ void -em_format_format_content (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - GCancellable *cancellable) +em_format_format_error (EMFormat *emf, + const gchar *format, + ...) { - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); + EMFormatPURI *puri; + CamelMimePart *part; + const EMFormatHandler *handler; + gchar *errmsg; + gchar *uri; + va_list ap; + + g_return_if_fail (EM_IS_FORMAT (emf)); + g_return_if_fail (format != NULL); + + va_start (ap, format); + errmsg = g_strdup_vprintf (format, ap); - if (camel_content_type_is (dw->mime_type, "text", "*")) - em_format_format_text ( - emf, stream, (CamelDataWrapper *) part, cancellable); + part = camel_mime_part_new (); + camel_mime_part_set_content (part, errmsg, strlen (errmsg), "text/plain"); + g_free (errmsg); + va_end (ap); + + handler = em_format_find_handler (emf, "x-evolution/error"); + + emf->priv->last_error++; + uri = g_strdup_printf (".error.%d", emf->priv->last_error); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, uri); + puri->mime_type = g_strdup ("text/html"); + if (handler && handler->write_func) + puri->write_func = handler->write_func; else - camel_data_wrapper_decode_to_stream_sync ( - dw, stream, cancellable, NULL); + puri->write_func = emf_write_error; + + em_format_add_puri (emf, puri); + + g_free (uri); + g_object_unref (part); } /** - * em_format_format_content: + * em_format_format_text: * @emf: * @stream: Where to write the converted text * @part: Part whose container is to be formatted @@ -1370,8 +2129,11 @@ em_format_format_text (EMFormat *emf, gsize max; GSettings *settings; - if (emf->charset) { - charset = emf->charset; + if (g_cancellable_is_cancelled (cancellable)) + return; + + if (emf->priv->charset) { + charset = emf->priv->charset; } else if (dw->mime_type && (charset = camel_content_type_param (dw->mime_type, "charset")) && g_ascii_strncasecmp(charset, "iso-8859-", 9) == 0) { @@ -1398,7 +2160,7 @@ em_format_format_text (EMFormat *emf, charset = camel_mime_filter_windows_real_charset (windows); } else if (charset == NULL) { - charset = emf->default_charset; + charset = emf->priv->default_charset; } mem_stream = (CamelStream *) camel_stream_mem_new (); @@ -1422,8 +2184,6 @@ em_format_format_text (EMFormat *emf, g_object_unref (settings); size = camel_data_wrapper_decode_to_stream_sync ( - emf->mode == EM_FORMAT_MODE_SOURCE ? - (CamelDataWrapper *) dw : camel_medium_get_content ((CamelMedium *) dw), (CamelStream *) filter_stream, cancellable, NULL); camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL); @@ -1431,14 +2191,23 @@ em_format_format_text (EMFormat *emf, g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); - if (max == -1 || size == -1 || size < (max * 1024) || emf->composer) { + if (max == -1 || size == -1 || size < (max * 1024) || emf->priv->composer) { camel_stream_write_to_stream ( mem_stream, (CamelStream *) stream, cancellable, NULL); - camel_stream_flush ((CamelStream *) stream, cancellable, NULL); + camel_stream_flush ((CamelStream *) mem_stream, cancellable, NULL); } else { - EM_FORMAT_GET_CLASS (emf)->format_optional ( - emf, stream, (CamelMimePart *) dw, - mem_stream, cancellable); + /* Parse it as an attachment */ + CamelMimePart *part = camel_mime_part_new (); + EMFormatParserInfo info = { 0 }; + GString *part_id = g_string_new (".attachment"); + camel_medium_set_content ((CamelMedium *) part, dw); + + info.is_attachment = TRUE; + em_format_parse_part_as (emf, part, part_id, &info, + "x-evolution/message/attachment", cancellable); + + g_string_free (part_id, TRUE); + g_object_unref (part); } if (windows) @@ -1452,7 +2221,7 @@ em_format_format_text (EMFormat *emf, * @part: * @mimetype: * - * Generate a simple textual description of a part, @mime_type represents the + * Generate a simple textual description of a part, @mime_type represents * the content. * * Return value: @@ -1500,909 +2269,48 @@ em_format_describe_part (CamelMimePart *part, return g_string_free (stext, FALSE); } -static void -add_validity_found (EMFormat *emf, - CamelCipherValidity *valid) -{ - g_return_if_fail (emf != NULL); - - if (!valid) - return; - - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED; - - if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_ENCRYPTED; -} - -/* ********************************************************************** */ - -static void -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); - - camel_content_type_set_param (content_type, "charset", charset); -} - -#ifdef ENABLE_SMIME -static void -emf_application_xpkcs7mime (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *context; - CamelMimePart *opart; - CamelCipherValidity *valid; - EMFormatCache *emfc; - GError *local_error = NULL; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - context = camel_smime_context_new (emf->session); - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_SMIME; - - opart = camel_mime_part_new (); - valid = camel_cipher_context_decrypt_sync ( - context, part, opart, cancellable, &local_error); - preserve_charset_in_content_type (part, opart); - if (valid == NULL) { - em_format_format_error ( - emf, stream, "%s", - local_error->message ? local_error->message : - _("Could not parse S/MIME message: Unknown error")); - g_clear_error (&local_error); - - em_format_part_as (emf, stream, part, NULL, cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = opart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, opart, valid, cancellable); - } - - g_object_unref (opart); - g_object_unref (context); -} -#endif - -/* RFC 1740 */ -static void -emf_multipart_appledouble (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - CamelMimePart *mime_part; - gint len; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - mime_part = camel_multipart_get_part (mp, 1); - if (mime_part) { - /* try the data fork for something useful, doubtful but who knows */ - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".appledouble.1"); - em_format_part (emf, stream, mime_part, cancellable); - g_string_truncate (emf->part_id, len); - } else - em_format_format_source (emf, stream, part, cancellable); - -} - -/* RFC ??? */ -static void -emf_multipart_mixed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - gint i, nparts, len; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - len = emf->part_id->len; - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - part = camel_multipart_get_part (mp, i); - g_string_append_printf(emf->part_id, ".mixed.%d", i); - em_format_part (emf, stream, part, cancellable); - g_string_truncate (emf->part_id, len); - } -} - -static gboolean related_display_part_is_attachment - (EMFormat *emf, - CamelMimePart *part); - -/* RFC 1740 */ -static void -emf_multipart_alternative (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - gint i, nparts, bestid = 0; - CamelMimePart *best = NULL; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* as per rfc, find the last part we know how to display */ - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - CamelDataWrapper *data_wrapper; - CamelContentType *type; - CamelStream *null_stream; - gchar *mime_type; - gsize content_size; - - /* is it correct to use the passed in *part here? */ - part = camel_multipart_get_part (mp, i); - - if (part == NULL) - continue; - - /* This may block even though the stream does not. - * XXX Pretty inefficient way to test if the MIME part - * is empty. Surely there's a quicker way? */ - null_stream = camel_stream_null_new (); - data_wrapper = camel_medium_get_content (CAMEL_MEDIUM (part)); - camel_data_wrapper_decode_to_stream_sync ( - data_wrapper, null_stream, cancellable, NULL); - content_size = CAMEL_STREAM_NULL (null_stream)->written; - g_object_unref (null_stream); - - if (content_size == 0) - continue; - - type = camel_mime_part_get_content_type (part); - mime_type = camel_content_type_simple (type); - - camel_strdown (mime_type); - - /*if (want_plain && !strcmp (mime_type, "text/plain")) - return part;*/ - - if (!em_format_is_attachment (emf, part) && - (!camel_content_type_is (type, "multipart", "related") || - !related_display_part_is_attachment (emf, part)) && - (em_format_find_handler (emf, mime_type) - || (best == NULL && em_format_fallback_handler (emf, mime_type)))) { - best = part; - bestid = i; - } - - g_free (mime_type); - } - - if (best) { - gint len = emf->part_id->len; - - g_string_append_printf(emf->part_id, ".alternative.%d", bestid); - em_format_part (emf, stream, best, cancellable); - g_string_truncate (emf->part_id, len); - } else - emf_multipart_mixed ( - emf, stream, part, info, cancellable, is_fallback); -} - -static void -emf_multipart_encrypted (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *context; - const gchar *protocol; - CamelMimePart *opart; - CamelCipherValidity *valid; - CamelMultipartEncrypted *mpe; - EMFormatCache *emfc; - GError *local_error = NULL; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - mpe = (CamelMultipartEncrypted *) camel_medium_get_content ((CamelMedium *) part); - if (!CAMEL_IS_MULTIPART_ENCRYPTED (mpe)) { - em_format_format_error ( - emf, stream, _("Could not parse MIME message. " - "Displaying as source.")); - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* Currently we only handle RFC2015-style PGP encryption. */ - protocol = camel_content_type_param ( - ((CamelDataWrapper *)mpe)->mime_type, "protocol"); - if (protocol == NULL || g_ascii_strcasecmp (protocol, "application/pgp-encrypted") != 0) { - em_format_format_error ( - emf, stream, _("Unsupported encryption " - "type for multipart/encrypted")); - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - return; - } - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - context = camel_gpg_context_new (emf->session); - opart = camel_mime_part_new (); - valid = camel_cipher_context_decrypt_sync ( - context, part, opart, cancellable, &local_error); - preserve_charset_in_content_type (part, opart); - if (valid == NULL) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Could not parse PGP/MIME message") : - _("Could not parse PGP/MIME message: Unknown error")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", local_error->message); - g_clear_error (&local_error); - - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = opart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, opart, valid, cancellable); - } - - /* TODO: Make sure when we finalize this part, it is zero'd out */ - g_object_unref (opart); - g_object_unref (context); -} - -static CamelMimePart * -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 */ - 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; -} - -static gboolean -related_display_part_is_attachment (EMFormat *emf, - CamelMimePart *part) -{ - CamelMimePart *display_part; - - display_part = get_related_display_part (part, NULL); - return display_part && em_format_is_attachment (emf, display_part); -} - -static void -emf_write_related (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - em_format_format_content (emf, stream, puri->part, cancellable); - camel_stream_close (stream, cancellable, NULL); -} - -/* RFC 2387 */ -static void -emf_multipart_related (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp; - CamelMimePart *body_part, *display_part = NULL; - gint i, nparts, partidlen, displayid = 0; - gchar *oldpartid; - GList *link; - - mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - display_part = get_related_display_part (part, &displayid); - - if (display_part == NULL) { - emf_multipart_mixed ( - emf, stream, part, info, cancellable, is_fallback); - return; - } - - em_format_push_level (emf); - - oldpartid = g_strdup (emf->part_id->str); - partidlen = emf->part_id->len; - - /* queue up the parts for possible inclusion */ - nparts = camel_multipart_get_number (mp); - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part != display_part) { - /* set the partid since add_puri uses it */ - g_string_append_printf(emf->part_id, ".related.%d", i); - em_format_add_puri ( - emf, sizeof (EMFormatPURI), NULL, - body_part, emf_write_related); - g_string_truncate (emf->part_id, partidlen); - } - } - - g_string_append_printf(emf->part_id, ".related.%d", displayid); - em_format_part (emf, stream, display_part, cancellable); - g_string_truncate (emf->part_id, partidlen); - camel_stream_flush (stream, NULL, NULL); - - link = g_queue_peek_head_link (emf->pending_uri_level->data); - - while (link && link->next != NULL) { - EMFormatPURI *puri = link->data; - - if (puri->use_count == 0) { - if (puri->func == emf_write_related) { - g_string_printf(emf->part_id, "%s", puri->part_id); - em_format_part ( - emf, stream, puri->part, cancellable); - } - } - - link = g_list_next (link); - } - - g_string_printf(emf->part_id, "%s", oldpartid); - g_free (oldpartid); - - em_format_pull_level (emf); -} - -static void -emf_multipart_signed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMimePart *cpart; - CamelMultipartSigned *mps; - CamelCipherContext *cipher = NULL; - EMFormatCache *emfc; - - /* should this perhaps run off a key of ".secured" ? */ - emfc = g_hash_table_lookup (emf->inline_table, emf->part_id->str); - if (emfc && emfc->valid) { - em_format_format_secure ( - emf, stream, emfc->secured, - camel_cipher_validity_clone (emfc->valid), - cancellable); - return; - } - - mps = (CamelMultipartSigned *) camel_medium_get_content ((CamelMedium *) part); - if (!CAMEL_IS_MULTIPART_SIGNED (mps) - || (cpart = camel_multipart_get_part ((CamelMultipart *) mps, - CAMEL_MULTIPART_SIGNED_CONTENT)) == NULL) { - em_format_format_error ( - emf, stream, _("Could not parse MIME message. " - "Displaying as source.")); - em_format_format_source (emf, stream, part, cancellable); - return; - } - - /* FIXME: Should be done via a plugin interface */ - /* FIXME: duplicated in em-format-html-display.c */ - if (mps->protocol) { -#ifdef ENABLE_SMIME - if (g_ascii_strcasecmp ("application/x-pkcs7-signature", mps->protocol) == 0 - || g_ascii_strcasecmp ("application/pkcs7-signature", mps->protocol) == 0) { - cipher = camel_smime_context_new (emf->session); - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SMIME; - } else -#endif - if (g_ascii_strcasecmp ("application/pgp-signature", mps->protocol) == 0) { - cipher = camel_gpg_context_new (emf->session); - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_PGP; - } - } - - emf->validity_found |= EM_FORMAT_VALIDITY_FOUND_SIGNED; - - if (cipher == NULL) { - em_format_format_error(emf, stream, _("Unsupported signature format")); - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - CamelCipherValidity *valid; - GError *local_error = NULL; - - valid = camel_cipher_context_verify_sync ( - cipher, part, cancellable, &local_error); - if (valid == NULL) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Error verifying signature") : - _("Unknown error verifying signature")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", - local_error->message); - g_clear_error (&local_error); - - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - } else { - if (emfc == NULL) - emfc = emf_insert_cache (emf, emf->part_id->str); - - emfc->valid = camel_cipher_validity_clone (valid); - g_object_ref ((emfc->secured = cpart)); - - add_validity_found (emf, valid); - em_format_format_secure ( - emf, stream, cpart, valid, cancellable); - } - - g_object_unref (cipher); - } -} - -/* RFC 4155 */ -static void -emf_application_mbox (EMFormat *emf, - CamelStream *stream, - CamelMimePart *mime_part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - const EMFormatHandler *handle; - CamelMimeParser *parser; - CamelStream *mem_stream; - camel_mime_parser_state_t state; - - /* Extract messages from the application/mbox part and - * render them as a flat list of messages. */ - - /* XXX If the mbox has multiple messages, maybe render them - * as a multipart/digest so each message can be expanded - * or collapsed individually. - * - * See attachment_handler_mail_x_uid_list() for example. */ - - /* XXX This is based on em_utils_read_messages_from_stream(). - * Perhaps refactor that function to return an array of - * messages instead of assuming we want to append them - * to a folder? */ - - handle = em_format_find_handler (emf, "x-evolution/message/rfc822"); - g_return_if_fail (handle != NULL); - - parser = camel_mime_parser_new (); - camel_mime_parser_scan_from (parser, TRUE); - - mem_stream = camel_stream_mem_new (); - camel_data_wrapper_decode_to_stream_sync ( - camel_medium_get_content (CAMEL_MEDIUM (mime_part)), - mem_stream, NULL, NULL); - g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); - camel_mime_parser_init_with_stream (parser, mem_stream, NULL); - g_object_unref (mem_stream); - - /* Extract messages from the mbox. */ - state = camel_mime_parser_step (parser, NULL, NULL); - while (state == CAMEL_MIME_PARSER_STATE_FROM) { - CamelMimeMessage *message; - - message = camel_mime_message_new (); - mime_part = CAMEL_MIME_PART (message); - - if (!camel_mime_part_construct_from_parser_sync ( - mime_part, parser, NULL, NULL)) { - g_object_unref (message); - break; - } - - /* Render the message. */ - handle->handler ( - emf, stream, mime_part, - handle, cancellable, FALSE); - - g_object_unref (message); - - /* Skip past CAMEL_MIME_PARSER_STATE_FROM_END. */ - camel_mime_parser_step (parser, NULL, NULL); - - state = camel_mime_parser_step (parser, NULL, NULL); - } - - g_object_unref (parser); -} - -static void -emf_message_rfc822 (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) +/** + * em_format_is_attachment: + * @emf: + * @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 + **/ +gint +em_format_is_attachment (EMFormat *emf, + CamelMimePart *part) { + /*CamelContentType *ct = camel_mime_part_get_content_type(part);*/ CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) part); - const EMFormatHandler *handle; - gint len; - gchar *parent_message_part_id; - - if (!CAMEL_IS_MIME_MESSAGE (dw)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - parent_message_part_id = emf->current_message_part_id; - emf->current_message_part_id = g_strdup (emf->part_id->str); - - len = emf->part_id->len; - g_string_append_printf(emf->part_id, ".rfc822"); - - handle = em_format_find_handler(emf, "x-evolution/message/rfc822"); - if (handle) - handle->handler ( - emf, stream, CAMEL_MIME_PART (dw), - handle, cancellable, FALSE); - g_string_truncate (emf->part_id, len); - - g_free (emf->current_message_part_id); - emf->current_message_part_id = parent_message_part_id; -} - -static void -emf_message_deliverystatus (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - em_format_format_text ( - emf, stream, (CamelDataWrapper *) part, cancellable); -} - -static void -emf_inlinepgp_signed (EMFormat *emf, - CamelStream *stream, - CamelMimePart *ipart, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelStream *filtered_stream; - CamelMimeFilterPgp *pgp_filter; - CamelContentType *content_type; - CamelCipherContext *cipher; - CamelCipherValidity *valid; - CamelDataWrapper *dw; - CamelMimePart *opart; - CamelStream *ostream; - gchar *type; - GError *local_error = NULL; - - if (!ipart) { - em_format_format_error(emf, stream, _("Unknown error verifying signature")); - return; - } - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_SIGNED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - cipher = camel_gpg_context_new (emf->session); - /* Verify the signature of the message */ - valid = camel_cipher_context_verify_sync ( - cipher, ipart, cancellable, &local_error); - if (!valid) { - em_format_format_error ( - emf, stream, local_error->message ? - _("Error verifying signature") : - _("Unknown error verifying signature")); - if (local_error->message) - em_format_format_error ( - emf, stream, "%s", local_error->message); - em_format_format_source (emf, stream, ipart, cancellable); - /* XXX I think this will loop: - * em_format_part_as(emf, stream, part, "text/plain"); */ - g_clear_error (&local_error); - g_object_unref (cipher); - return; - } - - /* Setup output stream */ - ostream = camel_stream_mem_new (); - filtered_stream = camel_stream_filter_new (ostream); - - /* Add PGP header / footer filter */ - pgp_filter = (CamelMimeFilterPgp *) camel_mime_filter_pgp_new (); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), - CAMEL_MIME_FILTER (pgp_filter)); - g_object_unref (pgp_filter); - - /* Pass through the filters that have been setup */ - dw = camel_medium_get_content ((CamelMedium *) ipart); - camel_data_wrapper_decode_to_stream_sync ( - dw, (CamelStream *) filtered_stream, NULL, NULL); - camel_stream_flush ((CamelStream *) filtered_stream, NULL, NULL); - g_object_unref (filtered_stream); - - /* Create a new text/plain MIME part containing the signed - * content preserving the original part's Content-Type params. */ - content_type = camel_mime_part_get_content_type (ipart); - type = camel_content_type_format (content_type); - content_type = camel_content_type_decode (type); - g_free (type); - - g_free (content_type->type); - content_type->type = g_strdup ("text"); - g_free (content_type->subtype); - content_type->subtype = g_strdup ("plain"); - type = camel_content_type_format (content_type); - camel_content_type_unref (content_type); - - dw = camel_data_wrapper_new (); - camel_data_wrapper_construct_from_stream_sync (dw, ostream, NULL, NULL); - camel_data_wrapper_set_mime_type (dw, type); - g_free (type); - - opart = camel_mime_part_new (); - camel_medium_set_content ((CamelMedium *) opart, dw); - camel_data_wrapper_set_mime_type_field ( - (CamelDataWrapper *) opart, dw->mime_type); - - add_validity_found (emf, valid); - /* Pass it off to the real formatter */ - em_format_format_secure (emf, stream, opart, valid, cancellable); - - /* Clean Up */ - g_object_unref (dw); - g_object_unref (opart); - g_object_unref (ostream); - g_object_unref (cipher); -} - -static void -emf_inlinepgp_encrypted (EMFormat *emf, - CamelStream *stream, - CamelMimePart *ipart, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelCipherContext *cipher; - CamelCipherValidity *valid; - CamelMimePart *opart; - CamelDataWrapper *dw; - gchar *mime_type; - GError *local_error = NULL; - - emf->validity_found |= - EM_FORMAT_VALIDITY_FOUND_ENCRYPTED | - EM_FORMAT_VALIDITY_FOUND_PGP; - - cipher = camel_gpg_context_new (emf->session); - opart = camel_mime_part_new (); - - /* Decrypt the message */ - valid = camel_cipher_context_decrypt_sync ( - cipher, ipart, opart, cancellable, &local_error); - - if (!valid) { - em_format_format_error ( - emf, stream, _("Could not parse PGP message: ")); - if (local_error->message != NULL) - em_format_format_error ( - emf, stream, "%s", local_error->message); - else - em_format_format_error ( - emf, stream, _("Unknown error")); - em_format_format_source (emf, stream, ipart, cancellable); - /* XXX I think this will loop: - * em_format_part_as(emf, stream, part, "text/plain"); */ - - g_clear_error (&local_error); - g_object_unref (cipher); - g_object_unref (opart); - return; - } - - dw = camel_medium_get_content ((CamelMedium *) opart); - mime_type = camel_data_wrapper_get_mime_type (dw); - - /* this ensures to show the 'opart' as inlined, if possible */ - if (mime_type != NULL && g_ascii_strcasecmp ( - mime_type, "application/octet-stream") == 0) { - const gchar *snoop = em_format_snoop_type (opart); - - if (snoop) - camel_data_wrapper_set_mime_type (dw, snoop); - } - - preserve_charset_in_content_type (ipart, opart); - g_free (mime_type); - - add_validity_found (emf, valid); - /* Pass it off to the real formatter */ - em_format_format_secure (emf, stream, opart, valid, cancellable); - - /* Clean Up */ - g_object_unref (opart); - g_object_unref (cipher); -} - -static EMFormatHandler type_builtin_table[] = { -#ifdef ENABLE_SMIME - { (gchar *) "application/x-pkcs7-mime", - emf_application_xpkcs7mime, - EM_FORMAT_HANDLER_INLINE_DISPOSITION }, -#endif - { (gchar *) "application/mbox", emf_application_mbox, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "multipart/alternative", emf_multipart_alternative }, - { (gchar *) "multipart/appledouble", emf_multipart_appledouble }, - { (gchar *) "multipart/encrypted", emf_multipart_encrypted }, - { (gchar *) "multipart/mixed", emf_multipart_mixed }, - { (gchar *) "multipart/signed", emf_multipart_signed }, - { (gchar *) "multipart/related", emf_multipart_related }, - { (gchar *) "multipart/*", emf_multipart_mixed }, - { (gchar *) "message/rfc822", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "message/news", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - { (gchar *) "message/delivery-status", emf_message_deliverystatus }, - { (gchar *) "message/*", emf_message_rfc822, EM_FORMAT_HANDLER_INLINE }, - - /* Insert brokenly-named parts here */ -#ifdef ENABLE_SMIME - { (gchar *) "application/pkcs7-mime", - emf_application_xpkcs7mime, - EM_FORMAT_HANDLER_INLINE_DISPOSITION }, -#endif - - /* internal types */ - { (gchar *) "application/x-inlinepgp-signed", emf_inlinepgp_signed }, - { (gchar *) "application/x-inlinepgp-encrypted", emf_inlinepgp_encrypted }, -}; - -static void -emf_builtin_init (EMFormatClass *class) -{ - gint ii; + if (!dw) + return 0; - for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) - g_hash_table_insert ( - class->type_handlers, - type_builtin_table[ii].mime_type, - &type_builtin_table[ii]); + 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)); } /** @@ -2493,5 +2401,237 @@ em_format_snoop_type (CamelMimePart *part) return res; /* We used to load parts to check their type, we don't anymore, - * see bug #11778 for some discussion */ + * see bug #211778 for some discussion */ +} + +/** + * 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. + * + * @param folder Folder wit the message + * @param message_uid ID of message within the \p folder + * @param first_param_name Name of first query parameter followed by GType of it's value and value. + */ +gchar * +em_format_build_mail_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 { + folder_name = camel_folder_get_full_name (folder); + 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); + + 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; + + /* 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 + */ + tmp = strchr (tmp, '@'); + if (tmp) { + tmp[0] = '/'; + } + + return uri; +} + +void +em_format_redraw (EMFormat *emf) +{ + g_return_if_fail (EM_IS_FORMAT (emf)); + + g_signal_emit (emf, signals[REDRAW_REQUESTED], 0); +} + +/**************************************************************************/ +EMFormatPURI * +em_format_puri_new (EMFormat *emf, + gsize puri_size, + CamelMimePart *part, + const gchar *uri) +{ + EMFormatPURI *puri; + + g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); + g_return_val_if_fail (puri_size >= sizeof (EMFormatPURI), NULL); + + puri = (EMFormatPURI *) g_malloc0 (puri_size); + puri->emf = emf; + + if (part) + puri->part = g_object_ref (part); + + if (uri) + puri->uri = g_strdup (uri); + + return puri; +} + +void +em_format_puri_free (EMFormatPURI *puri) +{ + g_return_if_fail (puri); + + if (puri->part) + g_object_unref (puri->part); + + if (puri->uri) + g_free (puri->uri); + + if (puri->cid) + g_free (puri->cid); + + if (puri->mime_type) + g_free (puri->mime_type); + + if (puri->validity) + camel_cipher_validity_free (puri->validity); + + if (puri->validity_parent) + camel_cipher_validity_free (puri->validity_parent); + + if (puri->free) + puri->free (puri); + + g_free (puri); +} + +void +em_format_puri_write (EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + g_return_if_fail (puri); + g_return_if_fail (CAMEL_IS_STREAM (stream)); + + if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) { + const EMFormatHandler *handler; + handler = em_format_find_handler (puri->emf, "x-evolution/message/source"); + handler->write_func (puri->emf, puri, stream, info, cancellable); + return; + } + + if (puri->write_func) { + puri->write_func (puri->emf, puri, stream, info, cancellable); + } else { + const EMFormatHandler *handler; + const gchar *mime_type; + + if (puri->mime_type) { + mime_type = puri->mime_type; + } else { + mime_type = (gchar *) "plain/text"; + } + + handler = em_format_find_handler (puri->emf, mime_type); + if (handler && handler->write_func) { + handler->write_func (puri->emf, + puri, stream, info, cancellable); + } + } +} + +EMFormatHeader * +em_format_header_new (const gchar *name, + const gchar *value) +{ + EMFormatHeader *header; + + g_return_val_if_fail (name && *name, NULL); + + header = g_new0 (EMFormatHeader, 1); + header->name = g_strdup (name); + if (value && *value) + header->value = g_strdup (value); + + return header; +} + +void +em_format_header_free (EMFormatHeader * header) +{ + g_return_if_fail (header != NULL); + + if (header->name) { + g_free (header->name); + header->name = NULL; + } + + if (header->value) { + g_free (header->value); + header->value = NULL; + } + + g_free (header); } |