/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Michael Zucchi * Jeffrey Stedfast * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #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)) struct _EMFormatPrivate { GNode *current_node; CamelSession *session; CamelURL *base_url; gchar *charset; gchar *default_charset; gboolean composer; gint last_error; }; enum { PROP_0, PROP_BASE_URL, PROP_CHARSET, PROP_COMPOSER, PROP_DEFAULT_CHARSET, PROP_SESSION }; enum { REDRAW_REQUESTED, LAST_SIGNAL }; gint signals[LAST_SIGNAL]; static gpointer parent_class; /* 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 gboolean is_secured (CamelMimePart *part) { CamelContentType *ct = camel_mime_part_get_content_type (part); return (camel_content_type_is (ct, "multipart", "signed") || camel_content_type_is (ct, "multipart", "encrypted") || camel_content_type_is (ct, "application", "x-inlinepgp-signed") || camel_content_type_is (ct, "application", "x-inlinepgp-encrypted") || camel_content_type_is (ct, "application", "x-pkcs7-mime") || camel_content_type_is (ct, "application", "pkcs7-mime")); } 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); if (content_type) camel_content_type_set_param (content_type, "charset", charset); /* update charset also on the part itself */ data_wrapper = CAMEL_DATA_WRAPPER (opart); content_type = camel_data_wrapper_get_mime_type_field (data_wrapper); if (content_type) camel_content_type_set_param (content_type, "charset", charset); } 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 from CID */ len = strlen (start) - 2; start++; for (i = 0; i < nparts; i++) { body_part = camel_multipart_get_part (mp, i); cid = camel_mime_part_get_content_id (body_part); if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { display_part = body_part; displayid = i; break; } } } else { display_part = camel_multipart_get_part (mp, 0); } if (out_displayid) *out_displayid = displayid; return display_part; } 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); } /**************************************************************************/ void em_format_empty_parser (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { /* DO NOTHING */ } #ifdef ENABLE_SMIME static void emf_parse_application_xpkcs7mime (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { 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); } 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 (g_cancellable_is_cancelled (cancellable)) return; /* 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? */ 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; } 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); 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); messages++; } g_object_unref (parser); } /* RFC 1740 */ static void emf_parse_multipart_alternative (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelMultipart *mp; gint i, nparts, bestid = 0; CamelMimePart *best = NULL; 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; } /* 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; if (g_cancellable_is_cancelled (cancellable)) return; /* is it correct to use the passed in *part here? */ mpart = camel_multipart_get_part (mp, i); if (mpart == 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 (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 (content_size == 0) continue; type = camel_mime_part_get_content_type (mpart); mime_type = camel_content_type_simple (type); camel_strdown (mime_type); 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; } 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_parse_multipart_appledouble (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { 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_parse_multipart_encrypted (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelCipherContext *context; const gchar *protocol; CamelMimePart *opart; CamelCipherValidity *valid; CamelMultipartEncrypted *mpe; GError *local_error = NULL; if (g_cancellable_is_cancelled (cancellable)) return; 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; } /* 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; } 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_parse_multipart_mixed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelMultipart *mp; gint i, nparts, len; 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; } len = part_id->len; nparts = camel_multipart_get_number (mp); for (i = 0; i < nparts; i++) { CamelMimePart *subpart; subpart = camel_multipart_get_part (mp, i); 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); } } static void emf_parse_multipart_signed (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelMimePart *cpart; CamelMultipartSigned *mps; CamelCipherContext *cipher = NULL; guint32 validity_type; if (g_cancellable_is_cancelled (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, _("Could not parse MIME message. " "Displaying as source.")); emf_parse_source (emf, part, part_id, info, 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->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; } } 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; 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); } /* 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); } camel_cipher_validity_free (valid); } } g_object_unref (cipher); } /* RFC 2046 */ static void emf_parse_multipart_digest (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelMultipart *mp; gint i, nparts, len; 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; } 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); } } /* RFC 2387 */ static void emf_parse_multipart_related (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelMultipart *mp; CamelMimePart *body_part, *display_part = NULL; gint i, nparts, partidlen, displayid = 0; 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; } 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); } } } static void emf_parse_message_deliverystatus (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, ".deliverystatus"); 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; g_string_truncate (part_id, len); em_format_add_puri (emf, puri); } static void emf_parse_inlinepgp_signed (EMFormat *emf, CamelMimePart *ipart, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { 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; if (g_cancellable_is_cancelled (cancellable)) return; if (!ipart) { em_format_format_error(emf, _("Unknown error verifying signature")); return; } 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); } static void emf_parse_inlinepgp_encrypted (EMFormat *emf, CamelMimePart *ipart, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { 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 (); /* 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"); */ 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 && 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); 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_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); } /* Clean Up */ camel_cipher_validity_free (valid); g_object_unref (opart); g_object_unref (cipher); } 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); /* 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); /* Begin parsing the message */ info->force_handler = FALSE; em_format_parse_part (emf, part, part_id, info, cancellable); } static void emf_parse_headers (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; gint len; len = part_id->len; g_string_append (part_id, ".headers"); 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); g_string_truncate (part_id, len); } 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); } 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_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)); 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); } /**************************************************************************/ static gboolean emf_is_inline (EMFormat *emf, const gchar *part_id, CamelMimePart *mime_part, const EMFormatHandler *handle) { //EMFormatCache *emfc; const gchar *disposition; if (handle == NULL) return FALSE; /* Some types need to override the disposition. * e.g. application/x-pkcs7-mime */ if (handle->flags & EM_FORMAT_HANDLER_INLINE_DISPOSITION) return TRUE; disposition = camel_mime_part_get_disposition (mime_part); if (disposition != NULL) return g_ascii_strcasecmp (disposition, "inline") == 0; /* Otherwise, use the default for this handler type. */ return (handle->flags & EM_FORMAT_HANDLER_INLINE) != 0; } /**************************************************************************/ 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 /* 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 }, }; /* 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_set_session (EMFormat *emf, CamelSession *session) { g_return_if_fail (CAMEL_IS_SESSION (session)); g_return_if_fail (emf->priv->session == NULL); emf->priv->session = g_object_ref (session); } static void em_format_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BASE_URL: em_format_set_base_url ( EM_FORMAT (object), g_value_get_object (value)); return; case PROP_CHARSET: em_format_set_charset ( EM_FORMAT (object), g_value_get_string (value)); return; case PROP_COMPOSER: em_format_set_composer ( EM_FORMAT (object), g_value_get_boolean (value)); return; case PROP_DEFAULT_CHARSET: em_format_set_default_charset ( EM_FORMAT (object), g_value_get_string (value)); return; case PROP_SESSION: em_format_set_session ( EM_FORMAT (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void em_format_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BASE_URL: g_value_set_object ( value, em_format_get_base_url ( EM_FORMAT (object))); return; case PROP_CHARSET: g_value_set_string ( value, em_format_get_charset ( EM_FORMAT (object))); return; case PROP_COMPOSER: g_value_set_boolean ( value, em_format_get_composer ( EM_FORMAT (object))); return; case PROP_DEFAULT_CHARSET: g_value_set_string ( value, em_format_get_default_charset ( EM_FORMAT (object))); return; case PROP_SESSION: g_value_set_object ( value, em_format_get_session ( EM_FORMAT (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void em_format_finalize (GObject *object) { EMFormat *emf = EM_FORMAT (object); 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; } if (emf->priv->session) { g_object_unref (emf->priv->session); emf->priv->session = NULL; } if (emf->priv->charset) { g_free (emf->priv->charset); emf->priv->charset = NULL; } em_format_clear_headers (emf); /* Chain up to parent's finalize() method */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void em_format_base_init (EMFormatClass *class) { gint i; class->type_handlers = g_hash_table_new (g_str_hash, g_str_equal); for (i = 0; i < G_N_ELEMENTS (type_handlers); i++) { g_hash_table_insert (class->type_handlers, type_handlers[i].mime_type, &type_handlers[i]); } } static void em_format_class_init (EMFormatClass *class) { GObjectClass *object_class; parent_class = g_type_class_peek_parent (class); g_type_class_add_private (class, sizeof (EMFormatPrivate)); class->is_inline = emf_is_inline; object_class = G_OBJECT_CLASS (class); object_class->set_property = em_format_set_property; object_class->get_property = em_format_get_property; object_class->finalize = em_format_finalize; g_object_class_install_property ( object_class, PROP_BASE_URL, g_param_spec_pointer ( "base-url", NULL, NULL, G_PARAM_READWRITE)); 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_COMPOSER, g_param_spec_boolean ( "composer", NULL, NULL, FALSE, 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_SESSION, g_param_spec_object ( "session", "Session", "A CamelSession", CAMEL_TYPE_SESSION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); signals[REDRAW_REQUESTED] = g_signal_new ( "redraw-requested", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (EMFormatClass, redraw_requested), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,0); } 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) { emf->priv = EM_FORMAT_GET_PRIVATE (emf); 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 */ emf->priv->last_error = 0; em_format_default_headers (emf); } EMFormat * em_format_new (void) { return g_object_new (EM_TYPE_FORMAT, NULL); } GType em_format_get_type (void) { static GType type = 0; 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 */ }; type = g_type_register_static ( G_TYPE_OBJECT, "EMFormat", &type_info, 0); } return type; } void em_format_set_charset (EMFormat *emf, const gchar *charset) { 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"); } const gchar * em_format_get_charset (EMFormat *emf) { g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); return emf->priv->charset; } void em_format_set_default_charset (EMFormat *emf, const gchar *charset) { g_return_if_fail (EM_IS_FORMAT (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; } void em_format_set_composer (EMFormat *emf, gboolean composer) { g_return_if_fail (EM_IS_FORMAT (emf)); if (emf->priv->composer && composer) return; emf->priv->composer = composer; 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; } CamelSession * em_format_get_session (EMFormat *emf) { g_return_val_if_fail (EM_IS_FORMAT (emf), NULL); return emf->priv->session; } void em_format_set_base_url (EMFormat *emf, CamelURL *url) { g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (url); if (emf->priv->base_url) camel_url_free (emf->priv->base_url); emf->priv->base_url = camel_url_copy (url); g_object_notify (G_OBJECT (emf), "base-url"); } void em_format_set_base_url_string (EMFormat *emf, const gchar *url_string) { 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); emf->priv->base_url = camel_url_new (url_string, NULL); 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; } /** * em_format_clear_headers: * @emf: * * Clear the list of headers to be displayed. This will force all headers to * be shown. **/ void em_format_clear_headers (EMFormat *emf) { EMFormatHeader *eh; g_return_if_fail (EM_IS_FORMAT (emf)); while ((eh = g_queue_pop_head (&emf->header_list)) != NULL) { em_format_header_free (eh); } } void em_format_default_headers (EMFormat *emf) { gint ii; 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, NULL, default_headers[ii].flags); } /** * 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 * be displayed in the order set by this function. Certain known * headers included in this list will be shown using special * formatting routines. **/ void em_format_add_header (EMFormat *emf, const gchar *name, const gchar *value, guint32 flags) { EMFormatHeader *h; g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (name && *name); h = em_format_header_new (name, value); h->flags = flags; g_queue_push_tail (&emf->header_list, h); } void em_format_add_header_struct (EMFormat *emf, EMFormatHeader *header) { g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (header && header->name); em_format_add_header (emf, header->name, header->value, header->flags); } 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_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) { gchar *mime, *s; s = strchr (mime_type, '/'); if (s == NULL) mime = (gchar *) mime_type; else { gsize len = (s - mime_type) + 1; mime = g_alloca (len + 2); strncpy (mime, mime_type, len); strcpy(mime+len, "*"); } return em_format_find_handler (emf, mime); } void em_format_parse (EMFormat *emf, CamelMimeMessage *message, CamelFolder *folder, GCancellable *cancellable) { GString *part_id; EMFormatPURI *puri; EMFormatParserInfo info = { 0 }; g_return_if_fail (EM_IS_FORMAT (emf)); if (g_cancellable_is_cancelled (cancellable)) return; 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); } /* 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_write (EMFormat *emf, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { EMFormatClass *emf_class; g_return_if_fail (EM_IS_FORMAT (emf)); g_return_if_fail (CAMEL_IS_STREAM (stream)); emf_class = EM_FORMAT_GET_CLASS (emf); if (emf_class->write) emf_class->write (emf, stream, 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_parse_async (EMFormat *emf, CamelMimeMessage *message, CamelFolder *folder, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *simple; g_return_if_fail (EM_IS_FORMAT (emf)); if (g_cancellable_is_cancelled (cancellable)) return; 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); } simple = g_simple_async_result_new ( G_OBJECT (emf), callback, user_data, em_format_parse_async); g_simple_async_result_set_check_cancellable (simple, cancellable); g_simple_async_result_run_in_thread ( simple, emf_start_async_parser, G_PRIORITY_DEFAULT, cancellable); g_object_unref (simple); } void em_format_parse_part_as (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, const gchar *mime_type, GCancellable *cancellable) { 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); return; } 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; /* 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_parse_part (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelContentType *ct; gchar *mime_type; ct = camel_mime_part_get_content_type (part); if (ct) { mime_type = camel_content_type_simple (ct); } else { mime_type = (gchar *) "text/plain"; } em_format_parse_part_as (emf, part, part_id, info, mime_type, cancellable); if (ct) g_free (mime_type); } gboolean em_format_is_inline (EMFormat *emf, const gchar *part_id, CamelMimePart *part, const EMFormatHandler *handler) { EMFormatClass *class; 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->is_inline != NULL, FALSE); return class->is_inline (emf, part_id, part, handler); } void em_format_format_error (EMFormat *emf, const gchar *format, ...) { 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); 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 puri->write_func = emf_write_error; em_format_add_puri (emf, puri); g_free (uri); g_object_unref (part); } /** * em_format_format_text: * @emf: * @stream: Where to write the converted text * @part: Part whose container is to be formatted * @cancellable: optional #GCancellable object, or %NULL * * Decode/output a part's content to @stream. **/ void em_format_format_text (EMFormat *emf, CamelStream *stream, CamelDataWrapper *dw, GCancellable *cancellable) { CamelStream *filter_stream; CamelMimeFilter *filter; const gchar *charset = NULL; CamelMimeFilterWindows *windows = NULL; CamelStream *mem_stream = NULL; 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) { CamelStream *null; /* Since a few Windows mailers like to claim they sent * out iso-8859-# encoded text when they really sent * out windows-cp125#, do some simple sanity checking * before we move on... */ null = camel_stream_null_new (); filter_stream = camel_stream_filter_new (null); g_object_unref (null); windows = (CamelMimeFilterWindows *) camel_mime_filter_windows_new (charset); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filter_stream), CAMEL_MIME_FILTER (windows)); camel_data_wrapper_decode_to_stream_sync ( dw, (CamelStream *) filter_stream, cancellable, NULL); camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL); g_object_unref (filter_stream); charset = camel_mime_filter_windows_real_charset (windows); } else if (charset == NULL) { charset = emf->priv->default_charset; } mem_stream = (CamelStream *) camel_stream_mem_new (); filter_stream = camel_stream_filter_new (mem_stream); if ((filter = camel_mime_filter_charset_new (charset, "UTF-8"))) { camel_stream_filter_add ( CAMEL_STREAM_FILTER (filter_stream), CAMEL_MIME_FILTER (filter)); g_object_unref (filter); } camel_data_wrapper_decode_to_stream_sync ( camel_medium_get_content ((CamelMedium *) dw), (CamelStream *) filter_stream, cancellable, NULL); camel_stream_flush ((CamelStream *) filter_stream, cancellable, NULL); g_object_unref (filter_stream); g_seekable_seek (G_SEEKABLE (mem_stream), 0, G_SEEK_SET, NULL, NULL); camel_stream_write_to_stream ( mem_stream, (CamelStream *) stream, cancellable, NULL); camel_stream_flush ((CamelStream *) mem_stream, cancellable, NULL); if (windows) g_object_unref (windows); g_object_unref (mem_stream); } /** * em_format_describe_part: * @part: * @mimetype: * * Generate a simple textual description of a part, @mime_type represents * the content. * * Return value: **/ gchar * em_format_describe_part (CamelMimePart *part, const gchar *mime_type) { GString *stext; const gchar *filename, *description; gchar *content_type, *desc; stext = g_string_new(""); content_type = g_content_type_from_mime_type (mime_type); desc = g_content_type_get_description ( content_type != NULL ? content_type : mime_type); g_free (content_type); g_string_append_printf ( stext, _("%s attachment"), desc ? desc : mime_type); g_free (desc); filename = camel_mime_part_get_filename (part); description = camel_mime_part_get_description (part); if (!filename || !*filename) { CamelDataWrapper *content; content = camel_medium_get_content (CAMEL_MEDIUM (part)); if (CAMEL_IS_MIME_MESSAGE (content)) filename = camel_mime_message_get_subject ( CAMEL_MIME_MESSAGE (content)); } if (filename != NULL && *filename != '\0') { gchar *basename = g_path_get_basename (filename); g_string_append_printf (stext, " (%s)", basename); g_free (basename); } if (description != NULL && *description != '\0' && g_strcmp0 (filename, description) != 0) g_string_append_printf (stext, ", \"%s\"", description); return g_string_free (stext, FALSE); } /** * 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); if (!dw) return 0; d(printf("checking is attachment %s/%s\n", dw->mime_type->type, dw->mime_type->subtype)); return !(camel_content_type_is (dw->mime_type, "multipart", "*") || camel_content_type_is ( dw->mime_type, "application", "x-pkcs7-mime") || camel_content_type_is ( dw->mime_type, "application", "pkcs7-mime") || camel_content_type_is ( dw->mime_type, "application", "x-inlinepgp-signed") || camel_content_type_is ( dw->mime_type, "application", "x-inlinepgp-encrypted") || camel_content_type_is ( dw->mime_type, "x-evolution", "evolution-rss-feed") || camel_content_type_is (dw->mime_type, "text", "calendar") || camel_content_type_is (dw->mime_type, "text", "x-calendar") || (camel_content_type_is (dw->mime_type, "text", "*") && camel_mime_part_get_filename (part) == NULL)); } /** * em_format_snoop_type: * @part: * * Tries to snoop the mime type of a part. * * Return value: NULL if unknown (more likely application/octet-stream). **/ const gchar * em_format_snoop_type (CamelMimePart *part) { /* cache is here only to be able still return const gchar * */ static GHashTable *types_cache = NULL; const gchar *filename; gchar *name_type = NULL, *magic_type = NULL, *res, *tmp; CamelDataWrapper *dw; filename = camel_mime_part_get_filename (part); if (filename != NULL) name_type = e_util_guess_mime_type (filename, FALSE); dw = camel_medium_get_content ((CamelMedium *) part); if (!camel_data_wrapper_is_offline (dw)) { GByteArray *byte_array; CamelStream *stream; byte_array = g_byte_array_new (); stream = camel_stream_mem_new_with_byte_array (byte_array); if (camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL) > 0) { gchar *content_type; content_type = g_content_type_guess ( filename, byte_array->data, byte_array->len, NULL); if (content_type != NULL) magic_type = g_content_type_get_mime_type (content_type); g_free (content_type); } g_object_unref (stream); } /* If gvfs doesn't recognize the data by magic, but it * contains English words, it will call it text/plain. If the * filename-based check came up with something different, use * that instead and if it returns "application/octet-stream" * try to do better with the filename check. */ if (magic_type) { if (name_type && (!strcmp(magic_type, "text/plain") || !strcmp(magic_type, "application/octet-stream"))) res = name_type; else res = magic_type; } else res = name_type; if (res != name_type) g_free (name_type); if (res != magic_type) g_free (magic_type); if (!types_cache) types_cache = g_hash_table_new_full ( g_str_hash, g_str_equal, (GDestroyNotify) g_free, (GDestroyNotify) NULL); if (res) { tmp = g_hash_table_lookup (types_cache, res); if (tmp) { g_free (res); res = tmp; } else { g_hash_table_insert (types_cache, res, res); } } return res; /* We used to load parts to check their type, we don't anymore, * 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 { tmp = (gchar *) camel_folder_get_full_name (folder); folder_name = (const gchar *) soup_uri_encode (tmp, NULL); store = camel_folder_get_parent_store (folder); if (store) service_uid = camel_service_get_uid (CAMEL_SERVICE (store)); else service_uid = "generic"; } tmp = g_strdup_printf ("mail://%s/%s/%s", service_uid, folder_name, message_uid); if (folder) { g_free ((gchar *) folder_name); } va_start (ap, first_param_name); name = first_param_name; separator = '?'; while (name) { gchar *tmp2; gint type = va_arg (ap, gint); switch (type) { case G_TYPE_INT: case G_TYPE_BOOLEAN: { gint val = va_arg (ap, gint); tmp2 = g_strdup_printf ("%s%c%s=%d", tmp, separator, name, val); break; } case G_TYPE_FLOAT: case G_TYPE_DOUBLE: { gdouble val = va_arg (ap, double); tmp2 = g_strdup_printf ("%s%c%s=%f", tmp, separator, name, val); break; } case G_TYPE_STRING: { gchar *val = va_arg (ap, gchar *); gchar *escaped = soup_uri_encode (val, NULL); tmp2 = g_strdup_printf ("%s%c%s=%s", tmp, separator, name, escaped); g_free (escaped); break; } default: g_warning ("Invalid param type %s", g_type_name (type)); return NULL; } g_free (tmp); tmp = tmp2; if (separator == '?') separator = '&'; name = va_arg (ap, gchar *); } va_end (ap); uri = tmp; if (uri == NULL) return NULL; /* For some reason, webkit won't accept URL with username, but * without password (mail://store@host/folder/mail), so we * will replace the '@' symbol by '/' to get URL like * mail://store/host/folder/mail which is OK */ while ((tmp = strchr (uri, '@')) != NULL) { tmp[0] = '/'; } return uri; } 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); }