/* * 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 * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE /* Enable strcasestr in string.h */ #include #include #include #include #include #include #include #ifdef G_OS_WIN32 /* Work around 'DATADIR' and 'interface' lossage in */ #define DATADIR crap_DATADIR #include #undef DATADIR #undef interface #endif #include #include "e-util/e-datetime-format.h" #include "e-util/e-icon-factory.h" #include "e-util/e-util-private.h" #include "e-util/e-util.h" #include "misc/e-web-view.h" #include #include #include #include #include #include #include #include #include "em-format-html.h" #include "em-utils.h" #include "e-mail-display.h" #include #define EM_FORMAT_HTML_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), EM_TYPE_FORMAT_HTML, EMFormatHTMLPrivate)) #define d(x) struct _EMFormatHTMLPrivate { GdkColor colors[EM_FORMAT_HTML_NUM_COLOR_TYPES]; EMailImageLoadingPolicy image_loading_policy; guint can_load_images : 1; guint only_local_photos : 1; guint show_sender_photo : 1; guint show_real_date : 1; guint animate_images : 1; }; static gpointer parent_class; enum { PROP_0, PROP_BODY_COLOR, PROP_CITATION_COLOR, PROP_CONTENT_COLOR, PROP_FRAME_COLOR, PROP_HEADER_COLOR, PROP_IMAGE_LOADING_POLICY, PROP_MARK_CITATIONS, PROP_ONLY_LOCAL_PHOTOS, PROP_SHOW_SENDER_PHOTO, PROP_SHOW_REAL_DATE, PROP_TEXT_COLOR, PROP_ANIMATE_IMAGES }; #define EFM_MESSAGE_START_ANAME "evolution_message_start" #define EFH_MESSAGE_START "" static void efh_parse_image (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_message_external (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_message_deliverystatus (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_parse_message_rfc822 (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable); static void efh_write_image (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_text_enriched (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_text_plain (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_text_html (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_headers (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_write_message_rfc822 (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); static void efh_format_full_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean all_headers, gboolean visible, GCancellable *cancellable); static void efh_format_short_headers (EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean visible, GCancellable *cancellable); static void efh_write_message (EMFormat *emf, GList *puris, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable); /*****************************************************************************/ static void efh_parse_image (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; const gchar *tmp; gchar *cid; gint len; if (g_cancellable_is_cancelled (cancellable)) return; tmp = camel_mime_part_get_content_id (part); if (!tmp) { em_format_parse_part_as (emf, part, part_id, info, "x-evolution/message/attachment", cancellable); return; } cid = g_strdup_printf ("cid:%s", tmp); len = part_id->len; g_string_append (part_id, ".image"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->cid = cid; puri->write_func = efh_write_image; puri->mime_type = g_strdup (info->handler->mime_type); puri->is_attachment = TRUE; puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; puri->validity_type = info->validity_type; em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void efh_parse_text_enriched (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; const gchar *tmp; gchar *cid; gint len; if (g_cancellable_is_cancelled (cancellable)) return; tmp = camel_mime_part_get_content_id (part); if (!tmp) { cid = g_strdup_printf ("em-no-cid:%s", part_id->str); } else { cid = g_strdup_printf ("cid:%s", tmp); } len = part_id->len; g_string_append (part_id, ".text_enriched"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->cid = cid; puri->mime_type = g_strdup (info->handler->mime_type); puri->write_func = efh_write_text_enriched; puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; puri->validity_type = info->validity_type; puri->is_attachment = info->is_attachment; em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void efh_parse_text_plain (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; CamelStream *filtered_stream, *null; CamelMultipart *mp; CamelDataWrapper *dw; CamelContentType *type; gint i, count, len; EMInlineFilter *inline_filter; gboolean charset_added = FALSE; const gchar *snoop_type = NULL; if (g_cancellable_is_cancelled (cancellable)) return; dw = camel_medium_get_content ((CamelMedium *) part); if (!dw) return; /* This scans the text part for inline-encoded data, creates * a multipart of all the parts inside it. */ /* FIXME: We should discard this multipart if it only contains * the original text, but it makes this hash lookup more complex */ /* TODO: We could probably put this in the superclass, since * no knowledge of html is required - but this messes with * filters a bit. Perhaps the superclass should just deal with * html anyway and be done with it ... */ if (!dw->mime_type) snoop_type = em_format_snoop_type (part); /* if we had to snoop the part type to get here, then * use that as the base type, yuck */ if (snoop_type == NULL || (type = camel_content_type_decode (snoop_type)) == NULL) { type = dw->mime_type; camel_content_type_ref (type); } if (dw->mime_type && type != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) { camel_content_type_set_param (type, "charset", camel_content_type_param (dw->mime_type, "charset")); charset_added = TRUE; } null = camel_stream_null_new (); filtered_stream = camel_stream_filter_new (null); g_object_unref (null); inline_filter = em_inline_filter_new (camel_mime_part_get_encoding (part), type); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), CAMEL_MIME_FILTER (inline_filter)); camel_data_wrapper_decode_to_stream_sync ( dw, (CamelStream *) filtered_stream, cancellable, NULL); camel_stream_close ((CamelStream *) filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); mp = em_inline_filter_get_multipart (inline_filter); if (charset_added) { camel_content_type_set_param (type, "charset", NULL); } g_object_unref (inline_filter); camel_content_type_unref (type); /* We handle our made-up multipart here, so we don't recursively call ourselves */ len = part_id->len; count = camel_multipart_get_number (mp); for (i = 0; i < count; i++) { CamelMimePart *newpart = camel_multipart_get_part (mp, i); if (!newpart) continue; type = camel_mime_part_get_content_type (newpart); if (camel_content_type_is (type, "text", "*") && (!camel_content_type_is (type, "text", "calendar"))) { gint s_len = part_id->len; g_string_append (part_id, ".plain_text"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), newpart, part_id->str); puri->write_func = efh_write_text_plain; puri->mime_type = g_strdup ("text/html"); puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; puri->validity_type = info->validity_type; puri->is_attachment = info->is_attachment; g_string_truncate (part_id, s_len); em_format_add_puri (emf, puri); } else { g_string_append_printf (part_id, ".inline.%d", i); em_format_parse_part (emf, CAMEL_MIME_PART (newpart), part_id, info, cancellable); g_string_truncate (part_id, len); } } g_object_unref (mp); } static void efh_parse_text_html (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; const gchar *location; gchar *cid = NULL; CamelURL *base; gint len; if (g_cancellable_is_cancelled (cancellable)) return; base = em_format_get_base_url (emf); location = camel_mime_part_get_content_location (part); if (location == NULL) { if (base) cid = camel_url_to_string (base, 0); else cid = g_strdup (part_id->str); } else { if (strchr (location, ':') == NULL && base != NULL) { CamelURL *uri; uri = camel_url_new_with_base (base, location); cid = camel_url_to_string (uri, 0); camel_url_free (uri); } else { cid = g_strdup (location); } } len = part_id->len; g_string_append (part_id, ".text_html"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = efh_write_text_html; puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; puri->validity_type = info->validity_type; puri->is_attachment = info->is_attachment; em_format_add_puri (emf, puri); g_string_truncate (part_id, len); if (cid) g_free (cid); } static void efh_parse_message_external (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { EMFormatPURI *puri; CamelMimePart *newpart; CamelContentType *type; const gchar *access_type; gchar *url = NULL, *desc = NULL; gchar *content; gint len; if (g_cancellable_is_cancelled (cancellable)) return; newpart = camel_mime_part_new (); /* needs to be cleaner */ type = camel_mime_part_get_content_type (part); access_type = camel_content_type_param (type, "access-type"); if (!access_type) { const gchar *msg = _("Malformed external-body part"); camel_mime_part_set_content (newpart, msg, strlen (msg), "text/plain"); goto addPart; } if (!g_ascii_strcasecmp(access_type, "ftp") || !g_ascii_strcasecmp(access_type, "anon-ftp")) { const gchar *name, *site, *dir, *mode; gchar *path; gchar ftype[16]; name = camel_content_type_param (type, "name"); site = camel_content_type_param (type, "site"); dir = camel_content_type_param (type, "directory"); mode = camel_content_type_param (type, "mode"); if (name == NULL || site == NULL) goto fail; /* Generate the path. */ if (dir) path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name); else path = g_strdup_printf("/%s", *name=='/'?name+1:name); if (mode && *mode) sprintf(ftype, ";type=%c", *mode); else ftype[0] = 0; url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype); g_free (path); desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url); } else if (!g_ascii_strcasecmp (access_type, "local-file")) { const gchar *name, *site; name = camel_content_type_param (type, "name"); site = camel_content_type_param (type, "site"); if (name == NULL) goto fail; url = g_filename_to_uri (name, NULL, NULL); if (site) desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site); else desc = g_strdup_printf(_("Pointer to local file (%s)"), name); } else if (!g_ascii_strcasecmp (access_type, "URL")) { const gchar *urlparam; gchar *s, *d; /* RFC 2017 */ urlparam = camel_content_type_param (type, "url"); if (urlparam == NULL) goto fail; /* For obscure MIMEy reasons, the URL may be split into words */ url = g_strdup (urlparam); s = d = url; while (*s) { if (!isspace ((guchar) * s)) *d++ = *s; s++; } *d = 0; desc = g_strdup_printf (_("Pointer to remote data (%s)"), url); } else goto fail; content = g_strdup_printf ("%s", url, desc); camel_mime_part_set_content (newpart, content, strlen (content), "text/html"); g_free (content); g_free (url); g_free (desc); fail: content = g_strdup_printf ( _("Pointer to unknown external data (\"%s\" type)"), access_type); camel_mime_part_set_content (newpart, content, strlen (content), "text/plain"); g_free (content); addPart: len = part_id->len; g_string_append (part_id, ".msg_external"); puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = efh_write_text_html; puri->mime_type = g_strdup ("text/html"); em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void efh_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 = efh_write_source; puri->mime_type = g_strdup ("text/html"); puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL; puri->validity_type = info->validity_type; puri->is_attachment = info->is_attachment; em_format_add_puri (emf, puri); g_string_truncate (part_id, len); } static void efh_parse_message_rfc822 (EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable) { CamelDataWrapper *dw; CamelMimePart *opart; CamelStream *stream; CamelMimeParser *parser; gint len; EMFormatParserInfo oinfo = *info; EMFormatPURI *puri; len = part_id->len; g_string_append (part_id, ".rfc822"); /* Create an empty PURI that will represent start of the RFC message */ puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str); puri->write_func = efh_write_message_rfc822; puri->mime_type = g_strdup ("text/html"); puri->is_attachment = info->is_attachment; em_format_add_puri (emf, puri); /* Now parse the message, creating multiple sub-PURIs */ stream = camel_stream_mem_new (); dw = camel_medium_get_content ((CamelMedium *) part); camel_data_wrapper_write_to_stream_sync (dw, stream, cancellable, NULL); g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, NULL); parser = camel_mime_parser_new (); camel_mime_parser_init_with_stream (parser, stream, NULL); opart = camel_mime_part_new (); camel_mime_part_construct_from_parser_sync (opart, parser, cancellable, NULL); em_format_parse_part_as (emf, opart, part_id, &oinfo, "x-evolution/message", cancellable); /* Add another generic PURI that represents end of the RFC message. * The em_format_write() function will skip all PURIs between the ".rfc822" * PURI and ".rfc822.end" PURI as they will be rendered in an " "", uri, e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]), e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT])); camel_stream_write_string (stream, str, cancellable, NULL); g_free (str); g_free (uri); } } static void efh_write_source (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { EMFormatHTML *efh = (EMFormatHTML *) emf; GString *buffer; CamelStream *filtered_stream; CamelMimeFilter *filter; CamelDataWrapper *dw = (CamelDataWrapper *) puri->part; filtered_stream = camel_stream_filter_new (stream); filter = camel_mime_filter_tohtml_new ( CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0); camel_stream_filter_add ( CAMEL_STREAM_FILTER (filtered_stream), filter); g_object_unref (filter); buffer = g_string_new (""); g_string_append_printf ( buffer, "
", e_color_to_value ( &efh->priv->colors[ EM_FORMAT_HTML_COLOR_BODY]), e_color_to_value ( &efh->priv->colors[ EM_FORMAT_HTML_COLOR_TEXT])); camel_stream_write_string ( stream, buffer->str, cancellable, NULL); camel_stream_write_string ( stream, "", cancellable, NULL); camel_data_wrapper_write_to_stream_sync (dw, filtered_stream, cancellable, NULL); camel_stream_write_string ( stream, "", cancellable, NULL); g_object_unref (filtered_stream); g_string_free (buffer, TRUE); } static void efh_write_headers (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { GString *buffer; EMFormatHTML *efh = (EMFormatHTML *) emf; gint bg_color; if (!puri->part) return; buffer = g_string_new (""); if (info->mode & EM_FORMAT_WRITE_MODE_PRINTING) { GdkColor white = { 0, G_MAXUINT16, G_MAXUINT16, G_MAXUINT16 }; bg_color = e_color_to_value (&white); } else { bg_color = e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]); } g_string_append_printf ( buffer, "
" "\n" "
\n", bg_color, e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER])); if (info->headers_collapsable) { g_string_append_printf (buffer, "" "", EVOLUTION_IMAGESDIR, (info->headers_collapsed) ? "plus.png" : "minus.png"); efh_format_short_headers (efh, buffer, (CamelMedium *) puri->part, info->headers_collapsed, cancellable); } efh_format_full_headers (efh, buffer, (CamelMedium *) puri->part, (info->mode == EM_FORMAT_WRITE_MODE_ALL_HEADERS), !info->headers_collapsed, cancellable); g_string_append (buffer, "
"); camel_stream_write_string (stream, buffer->str, cancellable, NULL); g_string_free (buffer, true); } static void efh_write_error (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { CamelStream *filtered_stream; CamelMimeFilter *filter; CamelDataWrapper *dw; dw = camel_medium_get_content ((CamelMedium *) puri->part); camel_stream_write_string (stream, "", cancellable, NULL); filtered_stream = camel_stream_filter_new (stream); filter = camel_mime_filter_tohtml_new (CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); camel_stream_filter_add (CAMEL_STREAM_FILTER (filtered_stream), filter); g_object_unref (filter); camel_data_wrapper_decode_to_stream_sync (dw, filtered_stream, cancellable, NULL); g_object_unref (filtered_stream); camel_stream_write_string (stream, "
", cancellable, NULL); } static void efh_write_message_rfc822 (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { GList *puris; GList *iter; EMFormatWriterInfo msgInfo = *info; msgInfo.mode = EM_FORMAT_WRITE_MODE_NORMAL; /* Create a new fake list of PURIs which will contain only * PURIs from this message. */ iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); if (!iter || !iter->next) return; iter = iter->next; puris = NULL; while (iter) { EMFormatPURI *p; p = iter->data; if (g_str_has_suffix (p->uri, ".rfc822.end")) break; puris = g_list_append (puris, p); iter = iter->next; }; efh_write_message (emf, puris, stream, &msgInfo, cancellable); g_list_free (puris); } else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) { GList *iter; gboolean can_write = FALSE; iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); if (!iter || !iter->next) return; /* Skip everything before attachment bar, inclusive */\ iter = iter->next; while (iter) { EMFormatPURI *p = iter->data; /* EMFormatHTMLPrint has registered a special writer * for headers, try to find it and use it. */ if (g_str_has_suffix (p->uri, ".headers")) { const EMFormatHandler *handler; handler = em_format_find_handler ( emf, "x-evolution/message/headers"); if (handler && handler->write_func) handler->write_func (emf, p, stream, info, cancellable); iter = iter->next; continue; } if (g_str_has_suffix (p->uri, ".rfc822.end")) break; if (g_str_has_suffix (p->uri, ".attachment-bar")) can_write = TRUE; if (can_write && p->write_func) { p->write_func ( emf, p, stream, info, cancellable); } iter = iter->next; } } else { gchar *str; gchar *uri; EMFormatHTML *efh = (EMFormatHTML *) emf; EMFormatPURI *p; GList *iter; iter = g_hash_table_lookup (emf->mail_part_table, puri->uri); if (!iter || !iter->next) return; iter = iter->next; p = iter->data; uri = em_format_build_mail_uri (emf->folder, emf->message_uid, "part_id", G_TYPE_STRING, p->uri, "mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, NULL); str = g_strdup_printf ( "
" "
\n" "" "
", e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]), e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]), uri, puri->uri); camel_stream_write_string (stream, str, cancellable, NULL); g_free (str); g_free (uri); } } /*****************************************************************************/ /* Notes: * * image/tiff is omitted because it's a multi-page image format, but * gdk-pixbuf unconditionally renders the first page only, and doesn't * even indicate through meta-data whether multiple pages are present * (see bug 335959). Therefore, make no attempt to render TIFF images * inline and defer to an application that can handle multi-page TIFF * files properly like Evince or Gimp. Once the referenced bug is * fixed we can reevaluate this policy. */ static EMFormatHandler type_builtin_table[] = { { (gchar *) "image/gif", efh_parse_image, efh_write_image, }, { (gchar *) "image/jpeg", efh_parse_image, efh_write_image, }, { (gchar *) "image/png", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-png", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-bmp", efh_parse_image, efh_write_image, }, { (gchar *) "image/bmp", efh_parse_image, efh_write_image, }, { (gchar *) "image/svg", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-cmu-raster", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-ico", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-portable-anymap", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-portable-bitmap", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-portable-graymap", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-portable-pixmap", efh_parse_image, efh_write_image, }, { (gchar *) "image/x-xpixmap", efh_parse_image, efh_write_image, }, { (gchar *) "text/enriched", efh_parse_text_enriched, efh_write_text_enriched, }, { (gchar *) "text/plain", efh_parse_text_plain, efh_write_text_plain, }, { (gchar *) "text/html", efh_parse_text_html, efh_write_text_html, }, { (gchar *) "text/richtext", efh_parse_text_enriched, efh_write_text_enriched, }, { (gchar *) "text/*", efh_parse_text_plain, efh_write_text_plain, }, { (gchar *) "message/rfc822", efh_parse_message_rfc822, efh_write_message_rfc822, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, { (gchar *) "message/news", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, { (gchar *) "message/delivery-status", efh_parse_message_deliverystatus, efh_write_text_plain, }, { (gchar *) "message/external-body", efh_parse_message_external, efh_write_text_plain, }, { (gchar *) "message/*", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE }, /* This is where one adds those busted, non-registered types, * that some idiot mailer writers out there decide to pull out * of their proverbials at random. */ { (gchar *) "image/jpg", efh_parse_image, efh_write_image, }, { (gchar *) "image/pjpeg", efh_parse_image, efh_write_image, }, /* special internal types */ { (gchar *) "x-evolution/message/rfc822", 0, efh_write_text_plain, }, { (gchar *) "x-evolution/message/headers", 0, efh_write_headers, }, { (gchar *) "x-evolution/message/source", 0, efh_write_source, }, { (gchar *) "x-evolution/message/attachment", 0, efh_write_attachment, }, { (gchar *) "x-evolution/message/error", 0, efh_write_error, }, }; static void efh_builtin_init (EMFormatHTMLClass *efhc) { EMFormatClass *emfc; gint ii; emfc = (EMFormatClass *) efhc; for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) em_format_class_add_handler ( emfc, &type_builtin_table[ii]); } static void efh_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_BODY_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_BODY, g_value_get_boxed (value)); return; case PROP_CITATION_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_CITATION, g_value_get_boxed (value)); return; case PROP_CONTENT_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_CONTENT, g_value_get_boxed (value)); return; case PROP_FRAME_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_FRAME, g_value_get_boxed (value)); return; case PROP_HEADER_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_HEADER, g_value_get_boxed (value)); return; case PROP_IMAGE_LOADING_POLICY: em_format_html_set_image_loading_policy ( EM_FORMAT_HTML (object), g_value_get_enum (value)); return; case PROP_MARK_CITATIONS: em_format_html_set_mark_citations ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); return; case PROP_ONLY_LOCAL_PHOTOS: em_format_html_set_only_local_photos ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); return; case PROP_SHOW_SENDER_PHOTO: em_format_html_set_show_sender_photo ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); return; case PROP_SHOW_REAL_DATE: em_format_html_set_show_real_date ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); return; case PROP_TEXT_COLOR: em_format_html_set_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_TEXT, g_value_get_boxed (value)); return; case PROP_ANIMATE_IMAGES: em_format_html_set_animate_images ( EM_FORMAT_HTML (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void efh_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GdkColor color; switch (property_id) { case PROP_BODY_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_BODY, &color); g_value_set_boxed (value, &color); return; case PROP_CITATION_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_CITATION, &color); g_value_set_boxed (value, &color); return; case PROP_CONTENT_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_CONTENT, &color); g_value_set_boxed (value, &color); return; case PROP_FRAME_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_FRAME, &color); g_value_set_boxed (value, &color); return; case PROP_HEADER_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_HEADER, &color); g_value_set_boxed (value, &color); return; case PROP_IMAGE_LOADING_POLICY: g_value_set_enum ( value, em_format_html_get_image_loading_policy ( EM_FORMAT_HTML (object))); return; case PROP_MARK_CITATIONS: g_value_set_boolean ( value, em_format_html_get_mark_citations ( EM_FORMAT_HTML (object))); return; case PROP_ONLY_LOCAL_PHOTOS: g_value_set_boolean ( value, em_format_html_get_only_local_photos ( EM_FORMAT_HTML (object))); return; case PROP_SHOW_SENDER_PHOTO: g_value_set_boolean ( value, em_format_html_get_show_sender_photo ( EM_FORMAT_HTML (object))); return; case PROP_SHOW_REAL_DATE: g_value_set_boolean ( value, em_format_html_get_show_real_date ( EM_FORMAT_HTML (object))); return; case PROP_TEXT_COLOR: em_format_html_get_color ( EM_FORMAT_HTML (object), EM_FORMAT_HTML_COLOR_TEXT, &color); g_value_set_boxed (value, &color); return; case PROP_ANIMATE_IMAGES: g_value_set_boolean ( value, em_format_html_get_animate_images ( EM_FORMAT_HTML (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void efh_finalize (GObject *object) { /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } static void efh_constructed (GObject *object) { /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (parent_class)->constructed (object); e_extensible_load_extensions (E_EXTENSIBLE (object)); } static void efh_write_attachment (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { gchar *text, *html; CamelContentType *ct; gchar *mime_type; const EMFormatHandler *handler; /* we display all inlined attachments only */ /* this could probably be cleaned up ... */ camel_stream_write_string ( stream, "" "
" "" "
" "
\n", cancellable, NULL); ct = camel_mime_part_get_content_type (puri->part); mime_type = camel_content_type_simple (ct); /* output some info about it */ text = em_format_describe_part (puri->part, mime_type); html = camel_text_to_html ( text, ((EMFormatHTML *) emf)->text_html_flags & CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); camel_stream_write_string (stream, html, cancellable, NULL); g_free (html); g_free (text); camel_stream_write_string ( stream, "
", cancellable, NULL); handler = em_format_find_handler (emf, mime_type); if (handler && handler->write_func && handler->write_func != efh_write_attachment) { if (em_format_is_inline (emf, puri->uri, puri->part, handler)) handler->write_func (emf, puri, stream, info, cancellable); } g_free (mime_type); } static void efh_preparse (EMFormat *emf) { EMFormatHTML *efh = EM_FORMAT_HTML (emf); CamelInternetAddress *addr; CamelSession *session; ESourceRegistry *registry; if (!emf->message) { efh->priv->can_load_images = FALSE; return; } session = em_format_get_session (emf); registry = e_mail_session_get_registry (E_MAIL_SESSION (session)); addr = camel_mime_message_get_from (emf->message); efh->priv->can_load_images = em_utils_in_addressbook ( registry, addr, FALSE); } static void efh_write_message (EMFormat *emf, GList *puris, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable) { GList *iter; EMFormatHTML *efh; gchar *header; efh = (EMFormatHTML *) emf; header = g_strdup_printf ( "\n\n" "\n\n" "Evolution Mail Display\n" "\n" "\n" "", e_color_to_value (&efh->priv->colors[ EM_FORMAT_HTML_COLOR_BODY])); camel_stream_write_string (stream, header, cancellable, NULL); g_free (header); if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) { efh_write_source (emf, emf->mail_part_list->data, stream, info, cancellable); camel_stream_write_string (stream, "", cancellable, NULL); return; } for (iter = puris; iter; iter = iter->next) { EMFormatPURI *puri = iter->data; if (!puri) continue; /* If current PURI has suffix .rfc822 then iterate through all * subsequent PURIs until PURI with suffix .rfc822.end is found. * These skipped PURIs contain entire RFC message which will * be written in