diff options
Diffstat (limited to 'mail/em-format-html.c')
-rw-r--r-- | mail/em-format-html.c | 3755 |
1 files changed, 1640 insertions, 2115 deletions
diff --git a/mail/em-format-html.c b/mail/em-format-html.c index 3c130e63d4..c481693eed 100644 --- a/mail/em-format-html.c +++ b/mail/em-format-html.c @@ -24,6 +24,8 @@ #include <config.h> #endif +#define _GNU_SOURCE /* Enable strcasestr in string.h */ + #include <stdio.h> #include <string.h> #include <sys/types.h> @@ -52,20 +54,20 @@ #include <shell/e-shell.h> -#include <gtkhtml/gtkhtml.h> -#include <gtkhtml/gtkhtml-stream.h> - #include <glib/gi18n.h> -#include <libemail-utils/mail-mt.h> +#include <JavaScriptCore/JavaScript.h> +#include <webkit/webkit.h> +#include <libemail-utils/mail-mt.h> #include <libemail-engine/e-mail-enumtypes.h> #include <libemail-engine/e-mail-utils.h> #include <libemail-engine/mail-config.h> #include "em-format-html.h" -#include "em-html-stream.h" #include "em-utils.h" +#include "e-mail-display.h" +#include <em-format/em-inline-filter.h> #define EM_FORMAT_HTML_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ @@ -73,45 +75,19 @@ #define d(x) -#define EM_FORMAT_HTML_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE \ - ((obj), EM_TYPE_FORMAT_HTML, EMFormatHTMLPrivate)) - -#define EFM_MESSAGE_START_ANAME "evolution#message#start" -#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>" - -struct _EMFormatHTMLCache { - CamelMultipart *textmp; - - gchar partid[1]; -}; - struct _EMFormatHTMLPrivate { - EWebView *web_view; - - CamelMimeMessage *last_part; /* not reffed, DO NOT dereference */ - volatile gint format_id; /* format thread id */ - guint format_timeout_id; - struct _format_msg *format_timeout_msg; - - /* Table that re-maps text parts into a mutlipart/mixed, EMFormatHTMLCache * */ - GHashTable *text_inline_parts; - - GQueue pending_jobs; - GMutex *lock; - GdkColor colors[EM_FORMAT_HTML_NUM_COLOR_TYPES]; EMailImageLoadingPolicy image_loading_policy; - EMFormatHTMLHeadersState headers_state; - gboolean headers_collapsable; - - guint load_images_now : 1; + 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, @@ -125,345 +101,1214 @@ enum { PROP_SHOW_SENDER_PHOTO, PROP_SHOW_REAL_DATE, PROP_TEXT_COLOR, - PROP_WEB_VIEW, - PROP_HEADERS_STATE, - PROP_HEADERS_COLLAPSABLE + PROP_ANIMATE_IMAGES }; -static void efh_url_requested (GtkHTML *html, const gchar *url, GtkHTMLStream *handle, EMFormatHTML *efh); -static gboolean efh_object_requested (GtkHTML *html, GtkHTMLEmbedded *eb, EMFormatHTML *efh); -static void efh_gtkhtml_destroy (GtkHTML *html, EMFormatHTML *efh); +#define EFM_MESSAGE_START_ANAME "evolution_message_start" +#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>" -static void efh_format_message (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback); +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; -static void efh_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable); + if (g_cancellable_is_cancelled (cancellable)) + return; -static void efh_builtin_init (EMFormatHTMLClass *efhc); + 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; + } -static void efh_write_image (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable); + 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; -static gpointer parent_class; -static CamelDataCache *emfh_http_cache; + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} -#define EMFH_HTTP_CACHE_PATH "http" +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; -/* Sigh, this is so we have a cancellable, async rendering thread */ -struct _format_msg { - MailMsg base; + if (g_cancellable_is_cancelled (cancellable)) + return; - EMFormatHTML *format; - EMFormat *format_source; - EMHTMLStream *estream; - CamelFolder *folder; - gchar *uid; - CamelMimeMessage *message; - gboolean cancelled; -}; + 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); + } -static gchar * -efh_format_desc (struct _format_msg *m) + 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) { - return g_strdup(_("Formatting message")); + 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_format_exec (struct _format_msg *m, - GCancellable *cancellable, - GError **error) +efh_parse_text_html (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) { - EMFormat *format; - CamelStream *stream; - struct _EMFormatHTMLJob *job; - GNode *puri_level; + 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 (m->format->priv->web_view == NULL) { - m->cancelled = TRUE; + 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; } - format = EM_FORMAT (m->format); - stream = CAMEL_STREAM (m->estream); + 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 ("<a href=\"%s\">%s</a>", 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 ( - "<!doctype html public \"-//W3C//DTD HTML 4.0 TRANSITIONAL//EN\">\n<html>\n" - "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\">\n</head>\n" - "<body bgcolor =\"#%06x\" text=\"#%06x\" marginwidth=6 marginheight=6>\n", - e_color_to_value ( - &m->format->priv->colors[ - EM_FORMAT_HTML_COLOR_BODY]), - e_color_to_value ( - &m->format->priv->colors[ - EM_FORMAT_HTML_COLOR_HEADER])); - camel_stream_write_string (stream, content, cancellable, NULL); + _("Pointer to unknown external data (\"%s\" type)"), + access_type); + camel_mime_part_set_content (newpart, content, strlen (content), "text/plain"); g_free (content); - /* <insert top-header stuff here> */ +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"); - if (format->mode == EM_FORMAT_MODE_SOURCE) { - em_format_format_source ( - format, stream, - (CamelMimePart *) m->message, cancellable); - } else { - const EMFormatHandler *handle; - const gchar *mime_type; - - mime_type = "x-evolution/message/prefix"; - handle = em_format_find_handler (format, mime_type); - - if (handle != NULL) - handle->handler ( - format, stream, - CAMEL_MIME_PART (m->message), handle, - cancellable, FALSE); - - mime_type = "x-evolution/message/rfc822"; - handle = em_format_find_handler (format, mime_type); - - if (handle != NULL) - handle->handler ( - format, stream, - CAMEL_MIME_PART (m->message), handle, - cancellable, FALSE); - } + em_format_add_puri (emf, puri); + g_string_truncate (part_id, len); +} - camel_stream_flush (stream, cancellable, NULL); +static void +efh_parse_message_deliverystatus (EMFormat *emf, + CamelMimePart *part, + GString *part_id, + EMFormatParserInfo *info, + GCancellable *cancellable) +{ + EMFormatPURI *puri; + gint len; - puri_level = format->pending_uri_level; - base = format->base; + if (g_cancellable_is_cancelled (cancellable)) + return; - do { - /* now dispatch any added tasks ... */ - g_mutex_lock (m->format->priv->lock); - while ((job = g_queue_pop_head (&m->format->priv->pending_jobs))) { - g_mutex_unlock (m->format->priv->lock); - - /* This is an implicit check to see if the gtkhtml has been destroyed */ - if (m->format->priv->web_view == NULL) - g_cancellable_cancel (cancellable); - - /* call jobs even if cancelled, so they can clean up resources */ - format->pending_uri_level = job->puri_level; - if (job->base) - format->base = job->base; - job->callback (job, cancellable); - format->base = base; - - /* clean up the job */ - g_object_unref (job->stream); - if (job->base) - camel_url_free (job->base); - g_free (job); - - g_mutex_lock (m->format->priv->lock); - } - g_mutex_unlock (m->format->priv->lock); - - if (m->estream) { - /* Closing this base stream can queue more jobs, so we need - * to check the list again after we've finished */ - d(printf("out of jobs, closing root stream\n")); - camel_stream_write_string ( - (CamelStream *) m->estream, - "</body>\n</html>\n", cancellable, NULL); - camel_stream_close ((CamelStream *) m->estream, cancellable, NULL); - if (g_cancellable_is_cancelled (cancellable)) { - m->cancelled = TRUE; - m->estream->sync.cancel = TRUE; - } - g_object_unref (m->estream); - m->estream = NULL; - } + 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); - } while (!g_queue_is_empty (&m->format->priv->pending_jobs)); + opart = camel_mime_part_new (); + camel_mime_part_construct_from_parser_sync (opart, parser, cancellable, NULL); - d(printf("out of jobs, done\n")); + em_format_parse_part_as (emf, opart, part_id, &oinfo, + "x-evolution/message", cancellable); - format->pending_uri_level = puri_level; - m->cancelled = m->cancelled || g_cancellable_is_cancelled (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 <iframe> */ + g_string_append (part_id, ".end"); + puri = em_format_puri_new (emf, sizeof (EMFormatPURI), NULL, part_id->str); + em_format_add_puri (emf, puri); + + g_string_truncate (part_id, len); + + g_object_unref (opart); + g_object_unref (parser); + g_object_unref (stream); } +/*****************************************************************************/ + static void -efh_format_done (struct _format_msg *m) +efh_write_image (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - d(printf("formatting finished\n")); + gchar *content; + EMFormatHTML *efh; + CamelDataWrapper *dw; + GByteArray *ba; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + efh = (EMFormatHTML *) emf; + + dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part)); + g_return_if_fail (dw); + + ba = camel_data_wrapper_get_byte_array (dw); + + if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { - m->format->priv->format_id = -1; - m->format->priv->load_images_now = FALSE; - m->format->state = EM_FORMAT_HTML_STATE_NONE; - g_signal_emit_by_name(m->format, "complete"); + if (!efh->priv->animate_images) { + + gchar *buff; + gsize len; + gchar *data; + GByteArray anim; + + data = g_strndup ((gchar *) ba->data, (gsize) ba->len); + anim.data = g_base64_decode (data, (gsize *) &(anim.len)); + g_free (data); + + em_format_html_animation_extract_frame (&anim, &buff, &len); + + camel_stream_write (stream, buff, len, cancellable, NULL); + + g_free (buff); + g_free (anim.data); + + } else { + CamelStream *stream_filter; + CamelMimeFilter *filter; + + stream_filter = camel_stream_filter_new (stream); + filter = camel_mime_filter_basic_new ( + CAMEL_MIME_FILTER_BASIC_BASE64_DEC); + + camel_stream_write ( + stream_filter, + (gchar *) ba->data, ba->len, + cancellable, NULL); + g_object_unref (stream_filter); + g_object_unref (filter); + } + + } else { + + gchar *buffer; + + if (!efh->priv->animate_images) { + + gchar *buff; + gsize len; + gchar *data; + GByteArray raw_data; + + data = g_strndup ((gchar *) ba->data, ba->len); + raw_data.data = (guint8 *) g_base64_decode ( + data, (gsize *) &(raw_data.len)); + g_free (data); + + em_format_html_animation_extract_frame (&raw_data, &buff, &len); + + content = g_base64_encode ((guchar *) buff, len); + g_free (buff); + g_free (raw_data.data); + + } else { + content = g_strndup ((gchar *) ba->data, ba->len); + } + + /* The image is already base64-encrypted so we can directly + * paste it to the output */ + buffer = g_strdup_printf ( + "<img src=\"data:%s;base64,%s\" style=\"max-width: 100%%;\" />", + puri->mime_type ? puri->mime_type : "image/*", content); + + camel_stream_write_string (stream, buffer, cancellable, NULL); + g_free (buffer); + g_free (content); + } } static void -efh_format_free (struct _format_msg *m) +efh_write_text_enriched (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - d(printf("formatter freed\n")); - g_object_unref (m->format); - if (m->estream) { - camel_stream_close ((CamelStream *) m->estream, NULL, NULL); - if (m->cancelled) - m->estream->sync.cancel = TRUE; - g_object_unref (m->estream); + EMFormatHTML *efh = EM_FORMAT_HTML (emf); + CamelStream *filtered_stream; + CamelMimeFilter *enriched; + guint32 flags = 0; + GString *buffer; + CamelContentType *ct; + gchar *mime_type = NULL; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + ct = camel_mime_part_get_content_type (puri->part); + if (ct) { + mime_type = camel_content_type_simple (ct); } - if (m->folder) - g_object_unref (m->folder); - g_free (m->uid); - if (m->message) - g_object_unref (m->message); - if (m->format_source) - g_object_unref (m->format_source); + + if (!g_strcmp0(mime_type, "text/richtext")) { + flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; + camel_stream_write_string ( + stream, "\n<!-- text/richtext -->\n", + cancellable, NULL); + } else { + camel_stream_write_string ( + stream, "\n<!-- text/enriched -->\n", + cancellable, NULL); + } + + if (mime_type) + g_free (mime_type); + + enriched = camel_mime_filter_enriched_new (flags); + filtered_stream = camel_stream_filter_new (stream); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), enriched); + g_object_unref (enriched); + + buffer = g_string_new (""); + + g_string_append_printf (buffer, + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x; color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\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]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_TEXT])); + + camel_stream_write_string (stream, buffer->str, cancellable, NULL); + g_string_free (buffer, TRUE); + + em_format_format_text ( + emf, (CamelStream *) filtered_stream, + (CamelDataWrapper *) puri->part, cancellable); + + g_object_unref (filtered_stream); + camel_stream_write_string (stream, "</div></div>", cancellable, NULL); } -static MailMsgInfo efh_format_info = { - sizeof (struct _format_msg), - (MailMsgDescFunc) efh_format_desc, - (MailMsgExecFunc) efh_format_exec, - (MailMsgDoneFunc) efh_format_done, - (MailMsgFreeFunc) efh_format_free -}; +static void +efh_write_text_plain (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + CamelDataWrapper *dw; + CamelStream *filtered_stream; + CamelMimeFilter *html_filter; + EMFormatHTML *efh = (EMFormatHTML *) emf; + gchar *content; + const gchar *format; + guint32 flags; + guint32 rgb; + + if (g_cancellable_is_cancelled (cancellable)) + return; + + flags = efh->text_html_flags; + + dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part)); + + /* Check for RFC 2646 flowed text. */ + if (camel_content_type_is(dw->mime_type, "text", "plain") + && (format = camel_content_type_param(dw->mime_type, "format")) + && !g_ascii_strcasecmp(format, "flowed")) + flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; + + rgb = e_color_to_value ( + &efh->priv->colors[EM_FORMAT_HTML_COLOR_CITATION]); + filtered_stream = camel_stream_filter_new (stream); + html_filter = camel_mime_filter_tohtml_new (flags, rgb); + camel_stream_filter_add ( + CAMEL_STREAM_FILTER (filtered_stream), html_filter); + g_object_unref (html_filter); + + content = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x; color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\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]), + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_TEXT])); + + camel_stream_write_string (stream, content, cancellable, NULL); + em_format_format_text (emf, filtered_stream, (CamelDataWrapper *) puri->part, cancellable); + + g_object_unref (filtered_stream); + g_free (content); -static gboolean -efh_format_helper (struct _format_msg *m, - gboolean async) + camel_stream_write_string (stream, "</div></div>\n", cancellable, NULL); +} + +static gchar * +get_tag (const gchar *tag_name, + gchar *opening, + gchar *closing) { - GtkHTMLStream *hstream; - EMFormatHTML *efh = m->format; - struct _EMFormatHTMLPrivate *p = efh->priv; - EWebView *web_view; + gchar *t; + gboolean has_end; + + for (t = closing - 1; t != opening; t--) { + if (*t != ' ') + break; + } - web_view = em_format_html_get_web_view (m->format); + /* Not a pair tag */ + if (*t == '/') + return g_strndup (opening, closing - opening + 1); - if (web_view == NULL) { - mail_msg_unref (m); - return FALSE; + for (t = closing; t && *t; t++) { + if (*t == '<') + break; } - if (async) { - d(printf("timeout called ...\n")); - if (p->format_id != -1) { - d(printf(" still waiting for cancellation to take effect, waiting ...\n")); - return TRUE; + do { + if (*t == '/') { + has_end = TRUE; + break; } - } - g_return_val_if_fail (g_queue_is_empty (&p->pending_jobs), FALSE); + if (*t == '>') { + has_end = FALSE; + break; + } + + t++; + + } while (t && *t); + + /* Broken HTML? */ + if (!has_end) + return g_strndup (opening, closing - opening + 1); + + do { + if ((*t != ' ') && (*t != '/')) + break; + + t++; + } while (t && *t); - d(printf(" ready to go, firing off format thread\n")); + if (g_strncasecmp (t, tag_name, strlen (tag_name)) == 0) { - /* call super-class to kick it off */ - /* FIXME Not passing a GCancellable here. */ - EM_FORMAT_CLASS (parent_class)->format_clone ( - EM_FORMAT (efh), m->folder, m->uid, - m->message, m->format_source, NULL); - em_format_html_clear_pobject (m->format); + closing = strstr (t, ">"); - /* FIXME: method off EMFormat? */ - if (((EMFormat *) efh)->valid) { - camel_cipher_validity_free (((EMFormat *) efh)->valid); - ((EMFormat *) efh)->valid = NULL; - ((EMFormat *) efh)->valid_parent = NULL; + return g_strndup (opening, closing - opening + strlen (tag_name)); } - if (m->message == NULL) { - hstream = gtk_html_begin (GTK_HTML (web_view)); - gtk_html_stream_close (hstream, GTK_HTML_STREAM_OK); - mail_msg_unref (m); - p->last_part = NULL; - } else { - efh->state = EM_FORMAT_HTML_STATE_RENDERING; -#if HAVE_CLUTTER - if (p->last_part != m->message && !e_shell_get_express_mode (e_shell_get_default ())) { -#else - if (p->last_part != m->message) { -#endif - hstream = gtk_html_begin (GTK_HTML (web_view)); - gtk_html_stream_printf (hstream, "<h5>%s</h5>", _("Formatting Message...")); - gtk_html_stream_close (hstream, GTK_HTML_STREAM_OK); - } + /* Broken HTML? */ + return g_strndup (opening, closing - opening + 1); +} - hstream = NULL; - m->estream = (EMHTMLStream *) em_html_stream_new ( - GTK_HTML (web_view), hstream); +static void +efh_write_text_html (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + EMFormatHTML *efh = (EMFormatHTML *) emf; - if (p->last_part == m->message) { - em_html_stream_set_flags (m->estream, - GTK_HTML_BEGIN_KEEP_SCROLL | GTK_HTML_BEGIN_KEEP_IMAGES - | GTK_HTML_BEGIN_BLOCK_UPDATES | GTK_HTML_BEGIN_BLOCK_IMAGES); - } else { - /* clear cache of inline-scanned text parts */ - g_hash_table_remove_all (p->text_inline_parts); + if (g_cancellable_is_cancelled (cancellable)) + return; + + if (info->mode == EM_FORMAT_WRITE_MODE_RAW) { + em_format_format_text (emf, stream, + (CamelDataWrapper *) puri->part, cancellable); + + } else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) { + GString *string; + GByteArray *ba; + gchar *pos; + GList *tags, *iter; + gboolean valid; + gchar *tag; + const gchar *document_end; + gint length; + gint i; + CamelStream *decoded_stream; + + decoded_stream = camel_stream_mem_new (); + em_format_format_text (emf, decoded_stream, + (CamelDataWrapper *) puri->part, cancellable); + g_seekable_seek (G_SEEKABLE (decoded_stream), 0, G_SEEK_SET, cancellable, NULL); + + ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (decoded_stream)); + string = g_string_new_len ((gchar *) ba->data, ba->len); + + g_object_unref (decoded_stream); + + tags = NULL; + pos = string->str; + valid = FALSE; + do { + gchar *closing; + gchar *opening; + + pos = strstr (pos + 1, "<"); + if (!pos) + break; + + opening = pos; + closing = strstr (pos, ">"); + + /* Find where the actual tag name begins */ + for (tag = pos + 1; tag && *tag; tag++) { + if (*tag != ' ') + break; + } + + if (g_ascii_strncasecmp (tag, "style", 5) == 0) { + tags = g_list_append ( + tags, + get_tag ("style", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "script", 6) == 0) { + tags = g_list_append ( + tags, + get_tag ("script", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "link", 4) == 0) { + tags = g_list_append ( + tags, + get_tag ("link", opening, closing)); + } else if (g_ascii_strncasecmp (tag, "body", 4) == 0) { + valid = TRUE; + break; + } + + } while (TRUE); - p->last_part = m->message; + /* Something's wrong, let's write the entire HTML and hope + * that WebKit can handle it */ + if (!valid) { + EMFormatWriterInfo i = *info; + i.mode = EM_FORMAT_WRITE_MODE_RAW; + efh_write_text_html (emf, puri, stream, &i, cancellable); + return; } - efh->priv->format_id = m->base.seq; + /* include the "body" as well -----v */ + g_string_erase (string, 0, tag - string->str + 4); + g_string_prepend (string, "<div "); - if (async) { - mail_msg_unordered_push (m); - } else { - efh_format_exec (m, NULL, NULL); + for (iter = tags; iter; iter = iter->next) { + g_string_prepend (string, iter->data); } - } - efh->priv->format_timeout_id = 0; - efh->priv->format_timeout_msg = NULL; + g_list_free_full (tags, g_free); + + /* that's reversed </body></html>... */ + document_end = ">lmth/<>ydob/<"; + length = strlen (document_end); + tag = string->str + string->len - 1; + i = 0; + valid = FALSE; + while (i < length - 1) { + gchar c; - return FALSE; + if (g_ascii_isspace (*tag)) { + tag--; + continue; + } + + if ((*tag >= 'A') && (*tag <= 'Z')) + c = *tag + 32; + else + c = *tag; + + if (c == document_end[i]) { + tag--; + i++; + valid = TRUE; + continue; + } + + valid = FALSE; + } + + if (valid) + g_string_truncate (string, tag - string->str); + + camel_stream_write_string (stream, string->str, cancellable, NULL); + + g_string_free (string, TRUE); + } else { + gchar *str; + gchar *uri; + + uri = em_format_build_mail_uri ( + emf->folder, emf->message_uid, + "part_id", G_TYPE_STRING, puri->uri, + "mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW, + NULL); + + str = g_strdup_printf ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n" + "<iframe width=\"100%%\" height=\"auto\"" + " frameborder=\"0\" src=\"%s\"></iframe>" + "</div></div>", + 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); + + camel_stream_write_string (stream, str, cancellable, NULL); + + g_free (str); + g_free (uri); + } } static void -efh_free_cache (struct _EMFormatHTMLCache *efhc) +efh_write_source (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - if (efhc->textmp) - g_object_unref (efhc->textmp); - g_free (efhc); + 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, "<div class=\"part-container\" style=\"background: #%06x; color: #%06x;\" >", + e_color_to_value ( + &efh->priv->colors[ + EM_FORMAT_HTML_COLOR_BODY]), + e_color_to_value ( + &efh->priv->colors[ + EM_FORMAT_HTML_COLOR_HEADER])); + + camel_stream_write_string ( + stream, buffer->str, cancellable, NULL); + camel_stream_write_string ( + stream, "<code class=\"pre\">", cancellable, NULL); + camel_data_wrapper_write_to_stream_sync (dw, filtered_stream, + cancellable, NULL); + camel_stream_write_string ( + stream, "</code>", cancellable, NULL); + + g_object_unref (filtered_stream); + g_string_free (buffer, TRUE); } static void -efh_gtkhtml_destroy (GtkHTML *html, - EMFormatHTML *efh) +efh_write_headers (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - if (efh->priv->format_timeout_id != 0) { - g_source_remove (efh->priv->format_timeout_id); - efh->priv->format_timeout_id = 0; - mail_msg_unref (efh->priv->format_timeout_msg); - efh->priv->format_timeout_msg = NULL; + 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, + "<div class=\"headers\" style=\"background: #%06x;\">" + "<table border=\"0\" width=\"100%%\" style=\"color: #%06x;\">\n" + "<tr><td valign=\"top\" width=\"16\">\n", + bg_color, + e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER])); + + if (info->headers_collapsable) { + g_string_append_printf (buffer, + "<img src=\"evo-file://%s/%s\" class=\"navigable\" " + "id=\"__evo-collapse-headers-img\" />" + "</td><td>", + EVOLUTION_IMAGESDIR, + (info->headers_collapsed) ? "plus.png" : "minus.png"); + + efh_format_short_headers (efh, buffer, (CamelMedium *) puri->part, + info->headers_collapsed, + cancellable); } - /* This probably works ... */ - if (efh->priv->format_id != -1) - mail_msg_cancel (efh->priv->format_id); + 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, "</td></tr></table></div>"); + + camel_stream_write_string (stream, buffer->str, cancellable, NULL); - if (efh->priv->web_view != NULL) { - g_object_unref (efh->priv->web_view); - efh->priv->web_view = 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, "<em><font color=\"red\">", 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, "</font></em><br>", 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; + + /* 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, info, 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 ( + "<div class=\"part-container\" style=\"border-color: #%06x; " + "background-color: #%06x;\">" + "<div class=\"part-container-inner-margin\">\n" + "<iframe width=\"100%%\" height=\"auto\"" + " frameborder=\"0\" src=\"%s\" name=\"%s\"></iframe>" + "</div></div>", + 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); } + } -static struct _EMFormatHTMLCache * -efh_insert_cache (EMFormatHTML *efh, - const gchar *partid) +/*****************************************************************************/ + +/* 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) { - struct _EMFormatHTMLCache *efhc; + EMFormatClass *emfc; + gint ii; - efhc = g_malloc0 (sizeof (*efh) + strlen (partid)); - strcpy (efhc->partid, partid); - g_hash_table_insert (efh->priv->text_inline_parts, efhc->partid, efhc); + emfc = (EMFormatClass *) efhc; - return efhc; + for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) + em_format_class_add_handler ( + emfc, &type_builtin_table[ii]); } static void @@ -544,15 +1389,12 @@ efh_set_property (GObject *object, EM_FORMAT_HTML_COLOR_TEXT, g_value_get_boxed (value)); return; - case PROP_HEADERS_STATE: - em_format_html_set_headers_state ( - EM_FORMAT_HTML (object), - g_value_get_int (value)); - return; - case PROP_HEADERS_COLLAPSABLE: - em_format_html_set_headers_collapsable ( + + 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); @@ -645,21 +1487,11 @@ efh_get_property (GObject *object, &color); g_value_set_boxed (value, &color); return; - - case PROP_WEB_VIEW: - g_value_set_object ( - value, em_format_html_get_web_view ( - EM_FORMAT_HTML (object))); - return; - case PROP_HEADERS_STATE: - g_value_set_int ( - value, em_format_html_get_headers_state ( - EM_FORMAT_HTML (object))); - return; - case PROP_HEADERS_COLLAPSABLE: + case PROP_ANIMATE_IMAGES: g_value_set_boolean ( - value, em_format_html_get_headers_collapsable ( + value, em_format_html_get_animate_images ( EM_FORMAT_HTML (object))); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -668,230 +1500,198 @@ efh_get_property (GObject *object, static void efh_finalize (GObject *object) { - EMFormatHTML *efh = EM_FORMAT_HTML (object); - - em_format_html_clear_pobject (efh); - efh_gtkhtml_destroy (GTK_HTML (efh->priv->web_view), efh); - - g_hash_table_destroy (efh->priv->text_inline_parts); - - g_mutex_free (efh->priv->lock); - /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (parent_class)->finalize (object); } -static gboolean -efh_format_timeout (struct _format_msg *m) -{ - return efh_format_helper (m, TRUE); -} - -void -em_format_html_clone_sync (CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormatHTML *efh, - EMFormat *source) -{ - struct _format_msg *m; - - m = mail_msg_new (&efh_format_info); - m->format = g_object_ref (efh); - if (source) - m->format_source = g_object_ref (source); - m->folder = g_object_ref (folder); - m->uid = g_strdup (uid); - m->message = g_object_ref (msg); - - efh_format_helper (m, FALSE); - efh_format_free (m); -} - static void -efh_format_clone (EMFormat *emf, - CamelFolder *folder, - const gchar *uid, - CamelMimeMessage *msg, - EMFormat *emfsource, - GCancellable *cancellable) +efh_write_attachment (EMFormat *emf, + EMFormatPURI *puri, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) { - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - struct _format_msg *m; - - /* How to sub-class ? Might need to adjust api ... */ - - if (efh->priv->web_view == NULL) - return; + gchar *text, *html; + CamelContentType *ct; + gchar *mime_type; + const EMFormatHandler *handler; - d(printf("efh_format called\n")); - if (efh->priv->format_timeout_id != 0) { - d(printf(" timeout for last still active, removing ...\n")); - g_source_remove (efh->priv->format_timeout_id); - efh->priv->format_timeout_id = 0; - mail_msg_unref (efh->priv->format_timeout_msg); - efh->priv->format_timeout_msg = NULL; - } + /* we display all inlined attachments only */ - if (emfsource != NULL) - g_object_ref (emfsource); + /* this could probably be cleaned up ... */ + camel_stream_write_string ( + stream, + "<table border=1 cellspacing=0 cellpadding=0><tr><td>" + "<table width=10 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td>" + "<td><table width=3 cellspacing=0 cellpadding=0>" + "<tr><td></td></tr></table></td><td><font size=-1>\n", + cancellable, NULL); - if (folder != NULL) - g_object_ref (folder); + ct = camel_mime_part_get_content_type (puri->part); + mime_type = camel_content_type_simple (ct); - if (msg != NULL) - g_object_ref (msg); + /* 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); - m = mail_msg_new (&efh_format_info); - m->format = g_object_ref (emf); - m->format_source = emfsource; - m->folder = folder; - m->uid = g_strdup (uid); - m->message = msg; + camel_stream_write_string ( + stream, "</font></td></tr><tr></table>", cancellable, NULL); - if (efh->priv->format_id == -1) { - d(printf(" idle, forcing format\n")); - efh_format_timeout (m); - } else { - d(printf(" still busy, cancelling and queuing wait\n")); - /* cancel and poll for completion */ - mail_msg_cancel (efh->priv->format_id); - efh->priv->format_timeout_msg = m; - efh->priv->format_timeout_id = g_timeout_add ( - 100, (GSourceFunc) efh_format_timeout, m); + 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_format_error (EMFormat *emf, - CamelStream *stream, - const gchar *txt) +efh_preparse (EMFormat *emf) { - GString *buffer; - gchar *html; - - buffer = g_string_new ("<em><font color=\"red\">"); - - html = camel_text_to_html ( - txt, CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | - CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0); - g_string_append (buffer, html); - g_free (html); + CamelInternetAddress *addr; - g_string_append (buffer, "</font></em><br>"); + EMFormatHTML *efh = EM_FORMAT_HTML (emf); - camel_stream_write (stream, buffer->str, buffer->len, NULL, NULL); + if (!emf->message) { + efh->priv->can_load_images = FALSE; + return; + } - g_string_free (buffer, TRUE); + addr = camel_mime_message_get_from (emf->message); + efh->priv->can_load_images = em_utils_in_addressbook (addr, FALSE); } static void -efh_format_source (EMFormat *emf, +efh_write_message (EMFormat *emf, + GList *puris, CamelStream *stream, - CamelMimePart *part, + EMFormatWriterInfo *info, GCancellable *cancellable) { - CamelStream *filtered_stream; - CamelMimeFilter *filter; - CamelDataWrapper *dw = (CamelDataWrapper *) part; + GList *iter; + EMFormatHTML *efh; + gchar *header; - filtered_stream = camel_stream_filter_new (stream); + efh = (EMFormatHTML *) emf; - 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); + header = g_strdup_printf ( + "<!DOCTYPE HTML>\n<html>\n" + "<head>\n<meta name=\"generator\" content=\"Evolution Mail Component\" />\n" + "<title>Evolution Mail Display</title>\n" + "<link type=\"text/css\" rel=\"stylesheet\" href=\"evo-file://" EVOLUTION_PRIVDATADIR "/theme/webview.css\" />\n" + "<style type=\"text/css\">\n" + " table th { color: #000; font-weight: bold; }\n" + "</style>\n" + "</head><body bgcolor=\"#%06x\">", + e_color_to_value (&efh->priv->colors[ + EM_FORMAT_HTML_COLOR_BODY])); - camel_stream_write_string ( - stream, "<table><tr><td><tt>", cancellable, NULL); - em_format_format_text (emf, filtered_stream, dw, cancellable); - camel_stream_write_string ( - stream, "</tt></td></tr></table>", cancellable, NULL); + camel_stream_write_string (stream, header, cancellable, NULL); + g_free (header); - g_object_unref (filtered_stream); -} + if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) { -static void -efh_format_attachment (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const gchar *mime_type, - const EMFormatHandler *handle, - GCancellable *cancellable) -{ - gchar *text, *html; + efh_write_source (emf, emf->mail_part_list->data, + stream, info, cancellable); - /* we display all inlined attachments only */ + camel_stream_write_string (stream, "</body></html>", cancellable, NULL); + return; + } - /* this could probably be cleaned up ... */ - camel_stream_write_string ( - stream, - "<table border=1 cellspacing=0 cellpadding=0><tr><td>" - "<table width=10 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td>" - "<td><table width=3 cellspacing=0 cellpadding=0>" - "<tr><td></td></tr></table></td><td><font size=-1>\n", - cancellable, NULL); + for (iter = puris; iter; iter = iter->next) { - /* output some info about it */ - text = em_format_describe_part (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); + EMFormatPURI *puri = iter->data; - camel_stream_write_string ( - stream, "</font></td></tr><tr></table>", cancellable, NULL); + if (!puri) + continue; - if (handle && em_format_is_inline (emf, emf->part_id->str, part, handle)) - handle->handler (emf, stream, part, handle, cancellable, FALSE); -} + /* 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 <iframe> as attachment. + */ + if (g_str_has_suffix (puri->uri, ".rfc822")) { + + /* If the PURI is not an attachment, then we must + * inline it here otherwise it would not be displayed. */ + if (!puri->is_attachment && puri->write_func) { + /* efh_write_message_rfc822 starts parsing _after_ + * the passed PURI, so we must give it previous PURI here */ + EMFormatPURI *p; + if (!iter->prev) + continue; -static gboolean -efh_busy (EMFormat *emf) -{ - EMFormatHTMLPrivate *priv; + p = iter->prev->data; + puri->write_func (emf, p, stream, info, cancellable); + } + + while (iter && !g_str_has_suffix (puri->uri, ".rfc822.end")) { + + iter = iter->next; + if (iter) + puri = iter->data; - priv = EM_FORMAT_HTML_GET_PRIVATE (emf); + d(printf(".rfc822 - skipping %s\n", puri->uri)); + } + + /* Skip the .rfc822.end PURI as well. */ + if (!iter) + break; - return (priv->format_id != -1); + continue; + } + + if (puri->write_func && !puri->is_attachment) { + puri->write_func (emf, puri, stream, info, cancellable); + d(printf("Writing PURI %s\n", puri->uri)); + } else { + d(printf("Skipping PURI %s\n", puri->uri)); + } + } + + camel_stream_write_string (stream, "</body></html>", cancellable, NULL); +} + +static void +efh_write (EMFormat *emf, + CamelStream *stream, + EMFormatWriterInfo *info, + GCancellable *cancellable) +{ + efh_write_message (emf, emf->mail_part_list, stream, info, cancellable); } + static void -efh_base_init (EMFormatHTMLClass *class) +efh_base_init (EMFormatHTMLClass *klass) { - efh_builtin_init (class); + efh_builtin_init (klass); } static void -efh_class_init (EMFormatHTMLClass *class) +efh_class_init (EMFormatHTMLClass *klass) { GObjectClass *object_class; - EMFormatClass *format_class; - const gchar *user_cache_dir; + EMFormatClass *emf_class; + + parent_class = g_type_class_peek_parent (klass); + g_type_class_add_private (klass, sizeof (EMFormatHTMLPrivate)); - parent_class = g_type_class_peek_parent (class); - g_type_class_add_private (class, sizeof (EMFormatHTMLPrivate)); + emf_class = EM_FORMAT_CLASS (klass); + emf_class->preparse = efh_preparse; + emf_class->write = efh_write; - object_class = G_OBJECT_CLASS (class); + object_class = G_OBJECT_CLASS (klass); object_class->set_property = efh_set_property; object_class->get_property = efh_get_property; object_class->finalize = efh_finalize; - format_class = EM_FORMAT_CLASS (class); - format_class->format_clone = efh_format_clone; - format_class->format_error = efh_format_error; - format_class->format_source = efh_format_source; - format_class->format_attachment = efh_format_attachment; - format_class->format_secure = efh_format_secure; - format_class->busy = efh_busy; - - class->html_widget_type = E_TYPE_WEB_VIEW; - g_object_class_install_property ( object_class, PROP_BODY_COLOR, @@ -982,7 +1782,7 @@ efh_class_init (EMFormatHTMLClass *class) "show-sender-photo", "Show Sender Photo", NULL, - TRUE, + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); @@ -1009,79 +1809,24 @@ efh_class_init (EMFormatHTMLClass *class) g_object_class_install_property ( object_class, - PROP_WEB_VIEW, - g_param_spec_object ( - "web-view", - "Web View", - NULL, - E_TYPE_WEB_VIEW, - G_PARAM_READABLE)); - - g_object_class_install_property ( - object_class, - PROP_HEADERS_STATE, - g_param_spec_int ( - "headers-state", - "Headers state", - NULL, - EM_FORMAT_HTML_HEADERS_STATE_EXPANDED, - EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED, - EM_FORMAT_HTML_HEADERS_STATE_EXPANDED, - G_PARAM_READWRITE)); - - g_object_class_install_property ( - object_class, - PROP_HEADERS_STATE, + PROP_ANIMATE_IMAGES, g_param_spec_boolean ( - "headers-collapsable", - NULL, + "animate-images", + "Animate images", NULL, FALSE, G_PARAM_READWRITE)); - - /* cache expiry - 2 hour access, 1 day max */ - user_cache_dir = e_get_user_cache_dir (); - emfh_http_cache = camel_data_cache_new (user_cache_dir, NULL); - if (emfh_http_cache) { - camel_data_cache_set_expire_age (emfh_http_cache, 24 *60 *60); - camel_data_cache_set_expire_access (emfh_http_cache, 2 *60 *60); - } } static void efh_init (EMFormatHTML *efh, - EMFormatHTMLClass *class) + EMFormatHTMLClass *klass) { - EWebView *web_view; GdkColor *color; efh->priv = EM_FORMAT_HTML_GET_PRIVATE (efh); g_queue_init (&efh->pending_object_list); - g_queue_init (&efh->priv->pending_jobs); - efh->priv->lock = g_mutex_new (); - efh->priv->format_id = -1; - efh->priv->text_inline_parts = g_hash_table_new_full ( - g_str_hash, g_str_equal, - (GDestroyNotify) NULL, - (GDestroyNotify) efh_free_cache); - - web_view = g_object_new (class->html_widget_type, NULL); - efh->priv->web_view = g_object_ref_sink (web_view); - - gtk_html_set_blocking (GTK_HTML (web_view), FALSE); - gtk_html_set_caret_first_focus_anchor ( - GTK_HTML (web_view), EFM_MESSAGE_START_ANAME); - gtk_html_set_default_content_type ( - GTK_HTML (web_view), "text/html; charset=utf-8"); - e_web_view_set_editable (web_view, FALSE); - - g_signal_connect ( - web_view, "url-requested", - G_CALLBACK (efh_url_requested), efh); - g_signal_connect ( - web_view, "object-requested", - G_CALLBACK (efh_object_requested), efh); color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]; gdk_color_parse ("#eeeeee", color); @@ -1103,7 +1848,6 @@ efh_init (EMFormatHTML *efh, CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | CAMEL_MIME_FILTER_TOHTML_MARK_CITATION; efh->show_icon = TRUE; - efh->state = EM_FORMAT_HTML_STATE_NONE; e_extensible_load_extensions (E_EXTENSIBLE (efh)); } @@ -1144,28 +1888,7 @@ em_format_html_get_type (void) return type; } -EWebView * -em_format_html_get_web_view (EMFormatHTML *efh) -{ - g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), NULL); - - return efh->priv->web_view; -} - -void -em_format_html_load_images (EMFormatHTML *efh) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - if (efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) - return; - - /* This will remain set while we're still - * rendering the same message, then it wont be. */ - efh->priv->load_images_now = TRUE; - em_format_queue_redraw (EM_FORMAT (efh)); -} - +/*****************************************************************************/ void em_format_html_get_color (EMFormatHTML *efh, EMFormatHTMLColorType type, @@ -1338,42 +2061,23 @@ em_format_html_set_show_real_date (EMFormatHTML *efh, g_object_notify (G_OBJECT (efh), "show-real-date"); } -EMFormatHTMLHeadersState -em_format_html_get_headers_state (EMFormatHTML *efh) -{ - g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), EM_FORMAT_HTML_HEADERS_STATE_EXPANDED); - - return efh->priv->headers_state; -} - -void -em_format_html_set_headers_state (EMFormatHTML *efh, - EMFormatHTMLHeadersState state) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - - efh->priv->headers_state = state; - - g_object_notify (G_OBJECT (efh), "headers-state"); -} - gboolean -em_format_html_get_headers_collapsable (EMFormatHTML *efh) +em_format_html_get_animate_images (EMFormatHTML *efh) { g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE); - return efh->priv->headers_collapsable; + return efh->priv->animate_images; } void -em_format_html_set_headers_collapsable (EMFormatHTML *efh, - gboolean collapsable) +em_format_html_set_animate_images (EMFormatHTML *efh, + gboolean animate_images) { g_return_if_fail (EM_IS_FORMAT_HTML (efh)); - efh->priv->headers_collapsable = collapsable; + efh->priv->animate_images = animate_images; - g_object_notify (G_OBJECT (efh), "headers-collapsable"); + g_object_notify (G_OBJECT (efh), "animate-images"); } CamelMimePart * @@ -1407,1184 +2111,60 @@ em_format_html_file_part (EMFormatHTML *efh, return part; } -/* all this api is a pain in the bum ... */ - -EMFormatHTMLPObject * -em_format_html_add_pobject (EMFormatHTML *efh, - gsize size, - const gchar *classid, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func) -{ - EMFormatHTMLPObject *pobj; - - if (size < sizeof (EMFormatHTMLPObject)) { - g_warning ("size is less than the size of EMFormatHTMLPObject\n"); - size = sizeof (EMFormatHTMLPObject); - } - - pobj = g_malloc0 (size); - if (classid) - pobj->classid = g_strdup (classid); - else - pobj->classid = g_strdup_printf("e-object:///%s", ((EMFormat *)efh)->part_id->str); - - pobj->format = efh; - pobj->func = func; - pobj->part = part; - - g_queue_push_tail (&efh->pending_object_list, pobj); - - return pobj; -} - -EMFormatHTMLPObject * -em_format_html_find_pobject (EMFormatHTML *emf, - const gchar *classid) -{ - GList *link; - - g_return_val_if_fail (EM_IS_FORMAT_HTML (emf), NULL); - g_return_val_if_fail (classid != NULL, NULL); - - link = g_queue_peek_head_link (&emf->pending_object_list); - - while (link != NULL) { - EMFormatHTMLPObject *pw = link->data; - - if (!strcmp (pw->classid, classid)) - return pw; - - link = g_list_next (link); - } - - return NULL; -} - -EMFormatHTMLPObject * -em_format_html_find_pobject_func (EMFormatHTML *emf, - CamelMimePart *part, - EMFormatHTMLPObjectFunc func) -{ - GList *link; - - g_return_val_if_fail (EM_IS_FORMAT_HTML (emf), NULL); - - link = g_queue_peek_head_link (&emf->pending_object_list); - - while (link != NULL) { - EMFormatHTMLPObject *pw = link->data; - - if (pw->func == func && pw->part == part) - return pw; - - link = g_list_next (link); - } - - return NULL; -} - -void -em_format_html_remove_pobject (EMFormatHTML *emf, - EMFormatHTMLPObject *pobject) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (emf)); - g_return_if_fail (pobject != NULL); - - g_queue_remove (&emf->pending_object_list, pobject); - - if (pobject->free != NULL) - pobject->free (pobject); - - g_free (pobject->classid); - g_free (pobject); -} - -void -em_format_html_clear_pobject (EMFormatHTML *emf) -{ - g_return_if_fail (EM_IS_FORMAT_HTML (emf)); - - while (!g_queue_is_empty (&emf->pending_object_list)) { - EMFormatHTMLPObject *pobj; - - pobj = g_queue_pop_head (&emf->pending_object_list); - em_format_html_remove_pobject (emf, pobj); - } -} - -struct _EMFormatHTMLJob * -em_format_html_job_new (EMFormatHTML *emfh, - void (*callback) (struct _EMFormatHTMLJob *job, - GCancellable *cancellable), - gpointer data) -{ - struct _EMFormatHTMLJob *job = g_malloc0 (sizeof (*job)); - - job->format = emfh; - job->puri_level = ((EMFormat *) emfh)->pending_uri_level; - job->callback = callback; - job->u.data = data; - if (((EMFormat *) emfh)->base) - job->base = camel_url_copy (((EMFormat *) emfh)->base); - - return job; -} - void -em_format_html_job_queue (EMFormatHTML *emfh, - struct _EMFormatHTMLJob *job) -{ - g_mutex_lock (emfh->priv->lock); - g_queue_push_tail (&emfh->priv->pending_jobs, job); - g_mutex_unlock (emfh->priv->lock); -} - -/* ********************************************************************** */ - -static void -emfh_getpuri (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - d(printf(" running getpuri task\n")); - if (!g_cancellable_is_cancelled (cancellable)) - job->u.puri->func ( - EM_FORMAT (job->format), job->stream, - job->u.puri, cancellable); -} - -static void -emfh_configure_stream_for_proxy (CamelHttpStream *stream, - const gchar *uri) -{ - EProxy *proxy; - SoupURI *proxy_uri; - gchar *basic; - gchar *basic64; - const gchar *user = ""; - const gchar *password = ""; - - proxy = em_utils_get_proxy (); - - if (!e_proxy_require_proxy_for_uri (proxy, uri)) - return; - - proxy_uri = e_proxy_peek_uri_for (proxy, uri); - - if (proxy_uri == NULL) - return; - - if (proxy_uri->user != NULL) - user = proxy_uri->user; - - if (proxy_uri->password != NULL) - password = proxy_uri->password; - - if (*user == '\0' && *password == '\0') - return; - - basic = g_strdup_printf ("%s:%s", user, password); - basic64 = g_base64_encode ((guchar *) basic, strlen (basic)); - camel_http_stream_set_proxy_authpass (stream, basic64); - g_free (basic64); - g_free (basic); -} - -static void -emfh_gethttp (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - CamelStream *cistream = NULL, *costream = NULL, *instream = NULL; - CamelURL *url; - CamelHttpStream *tmp_stream; - gssize n, total = 0, pc_complete = 0, nread = 0; - gchar buffer[1500]; - const gchar *length; - - if (g_cancellable_is_cancelled (cancellable) - || (url = camel_url_new (job->u.uri, NULL)) == NULL) - goto badurl; - - d(printf(" running load uri task: %s\n", job->u.uri)); - - if (emfh_http_cache) - instream = cistream = camel_data_cache_get (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - - if (instream == NULL) { - EMailImageLoadingPolicy policy; - - policy = em_format_html_get_image_loading_policy (job->format); - - if (!(job->format->priv->load_images_now - || policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS - || (policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES - && em_utils_in_addressbook ((CamelInternetAddress *) camel_mime_message_get_from (job->format->parent.message), FALSE)))) { - /* TODO: Ideally we would put the http requests into - * another queue and only send them out if the user - * selects 'load images', when they do. The problem - * is how to maintain this state with multiple - * renderings, and how to adjust the thread - * dispatch/setup routine to handle it */ - camel_url_free (url); - goto done; - } - - instream = camel_http_stream_new (CAMEL_HTTP_METHOD_GET, ((EMFormat *) job->format)->session, url); - camel_http_stream_set_user_agent((CamelHttpStream *) instream, "CamelHttpStream/1.0 Evolution/" VERSION); - emfh_configure_stream_for_proxy ((CamelHttpStream *) instream, job->u.uri); - - camel_operation_push_message ( - cancellable, _("Retrieving '%s'"), job->u.uri); - tmp_stream = (CamelHttpStream *) instream; - if (camel_stream_read (CAMEL_STREAM (instream), NULL, 0, cancellable, NULL) == 0) { - CamelContentType *content_type; - - content_type = camel_http_stream_get_content_type (tmp_stream); - length = camel_header_raw_find(&tmp_stream->headers, "Content-Length", NULL); - d(printf(" Content-Length: %s\n", length)); - if (length != NULL) - total = atoi (length); - camel_content_type_unref (content_type); - } - } else - camel_operation_push_message ( - cancellable, _("Retrieving '%s'"), job->u.uri); - - camel_url_free (url); - - if (instream == NULL) - goto done; - - if (emfh_http_cache != NULL && cistream == NULL) - costream = camel_data_cache_add (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - - do { - if (g_cancellable_is_cancelled (cancellable)) { - n = -1; - break; - } - /* FIXME: progress reporting in percentage, can we get the length always? do we care? */ - n = camel_stream_read (instream, buffer, sizeof (buffer), cancellable, NULL); - if (n > 0) { - nread += n; - /* If we didn't get a valid Content-Length header, do not try to calculate percentage */ - if (total != 0) { - pc_complete = ((nread * 100) / total); - camel_operation_progress (cancellable, pc_complete); - } - d(printf(" read %d bytes\n", n)); - if (costream && camel_stream_write (costream, buffer, n, cancellable, NULL) == -1) { - n = -1; - break; - } - - camel_stream_write (job->stream, buffer, n, cancellable, NULL); - } - } while (n > 0); - - /* indicates success */ - if (n == 0) - camel_stream_close (job->stream, cancellable, NULL); - - if (costream) { - /* do not store broken files in a cache */ - if (n != 0) - camel_data_cache_remove (emfh_http_cache, EMFH_HTTP_CACHE_PATH, job->u.uri, NULL); - g_object_unref (costream); - } - - g_object_unref (instream); -done: - camel_operation_pop_message (cancellable); -badurl: - g_free (job->u.uri); -} - -/* ********************************************************************** */ - -static void -efh_url_requested (GtkHTML *html, - const gchar *url, - GtkHTMLStream *handle, - EMFormatHTML *efh) -{ - EMFormatPURI *puri; - struct _EMFormatHTMLJob *job = NULL; - - d(printf("url requested, html = %p, url '%s'\n", html, url)); - - puri = em_format_find_visible_puri ((EMFormat *) efh, url); - if (puri) { - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) puri->part); - CamelContentType *ct = dw ? dw->mime_type : NULL; - - /* GtkHTML only handles text and images. - * application/octet-stream parts are the only ones - * which are snooped for other content. So only try - * to pass these to it - any other types are badly - * formed or intentionally malicious emails. They - * will still show as attachments anyway */ - - if (ct && (camel_content_type_is(ct, "text", "*") - || camel_content_type_is(ct, "image", "*") - || camel_content_type_is(ct, "application", "octet-stream"))) { - puri->use_count++; - - d(printf(" adding puri job\n")); - job = em_format_html_job_new (efh, emfh_getpuri, puri); - } else { - d(printf(" part is unknown type '%s', not using\n", ct?camel_content_type_format(ct):"<unset>")); - gtk_html_stream_close (handle, GTK_HTML_STREAM_ERROR); - } - } else if (g_ascii_strncasecmp(url, "http:", 5) == 0 || g_ascii_strncasecmp(url, "https:", 6) == 0) { - d(printf(" adding job, get %s\n", url)); - job = em_format_html_job_new (efh, emfh_gethttp, g_strdup (url)); - } else if (g_str_has_prefix (url, "file://")) { - gchar *data = NULL; - gsize length = 0; - gboolean status; - gchar *path; - - path = g_filename_from_uri (url, NULL, NULL); - g_return_if_fail (path != NULL); - - status = g_file_get_contents (path, &data, &length, NULL); - if (status) - gtk_html_stream_write (handle, data, length); - - gtk_html_stream_close (handle, status ? GTK_HTML_STREAM_OK : GTK_HTML_STREAM_ERROR); - g_free (data); - g_free (path); - } else { - d(printf("HTML Includes reference to unknown uri '%s'\n", url)); - gtk_html_stream_close (handle, GTK_HTML_STREAM_ERROR); - } - - if (job) { - job->stream = em_html_stream_new (html, handle); - em_format_html_job_queue (efh, job); - } - - g_signal_stop_emission_by_name (html, "url-requested"); -} - -static gboolean -efh_object_requested (GtkHTML *html, - GtkHTMLEmbedded *eb, - EMFormatHTML *efh) -{ - EMFormatHTMLPObject *pobject; - gint res = FALSE; - - if (eb->classid == NULL) - return FALSE; - - pobject = em_format_html_find_pobject (efh, eb->classid); - if (pobject) { - /* This stops recursion of the part */ - g_queue_remove (&efh->pending_object_list, pobject); - res = pobject->func (efh, eb, pobject); - g_queue_push_head (&efh->pending_object_list, pobject); - } else { - d(printf("HTML Includes reference to unknown object '%s'\n", eb->classid)); - } - - return res; -} - -/* ********************************************************************** */ -#include "em-format/em-inline-filter.h" - -/* FIXME: This is duplicated in em-format-html-display, should be exported or in security module */ -static const struct { - const gchar *icon, *shortdesc; -} smime_sign_table[5] = { - { "stock_signature-bad", N_("Unsigned") }, - { "stock_signature-ok", N_("Valid signature") }, - { "stock_signature-bad", N_("Invalid signature") }, - { "stock_signature", N_("Valid signature, but cannot verify sender") }, - { "stock_signature-bad", N_("Signature exists, but need public key") }, -}; - -static const struct { - const gchar *icon, *shortdesc; -} smime_encrypt_table[4] = { - { "stock_lock-broken", N_("Unencrypted") }, - { "stock_lock", N_("Encrypted, weak"),}, - { "stock_lock-ok", N_("Encrypted") }, - { "stock_lock-ok", N_("Encrypted, strong") }, -}; - -static const gchar *smime_sign_colour[4] = { - "", " bgcolor=\"#88bb88\"", " bgcolor=\"#bb8888\"", " bgcolor=\"#e8d122\"" -}; - -/* TODO: this could probably be virtual on em-format-html - * then we only need one version of each type handler */ -static void -efh_format_secure (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - CamelCipherValidity *valid, - GCancellable *cancellable) -{ - EMFormatClass *format_class; - - format_class = EM_FORMAT_CLASS (parent_class); - g_return_if_fail (format_class->format_secure != NULL); - format_class->format_secure (emf, stream, part, valid, cancellable); - - /* To explain, if the validity is the same, then we are the - * base validity and now have a combined sign/encrypt validity - * we can display. Primarily a new verification context is - * created when we have an embeded message. */ - if (emf->valid == valid - && (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE - || valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE)) { - gchar *classid, *iconpath; - const gchar *icon; - CamelMimePart *iconpart; - GString *buffer; - - buffer = g_string_sized_new (1024); - - g_string_append_printf ( - buffer, - "<table border=0 width=\"100%%\" " - "cellpadding=3 cellspacing=0%s><tr>", - smime_sign_colour[valid->sign.status]); - - classid = g_strdup_printf ( - "smime:///em-format-html/%s/icon/signed", - emf->part_id->str); - g_string_append_printf ( - buffer, - "<td valign=\"top\"><img src=\"%s\"></td>" - "<td valign=\"top\" width=\"100%%\">", classid); - - if (valid->sign.status != 0) - icon = smime_sign_table[valid->sign.status].icon; - else - icon = smime_encrypt_table[valid->encrypt.status].icon; - iconpath = e_icon_factory_get_icon_filename (icon, GTK_ICON_SIZE_DIALOG); - iconpart = em_format_html_file_part((EMFormatHTML *)emf, "image/png", iconpath, cancellable); - if (iconpart) { - (void) em_format_add_puri (emf, sizeof (EMFormatPURI), classid, iconpart, efh_write_image); - g_object_unref (iconpart); - } - g_free (iconpath); - g_free (classid); - - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) { - g_string_append ( - buffer, _(smime_sign_table[valid->sign.status].shortdesc)); - - em_format_html_format_cert_infos ( - &valid->sign.signers, buffer); - } - - if (valid->encrypt.status != CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE) { - if (valid->sign.status != CAMEL_CIPHER_VALIDITY_SIGN_NONE) - g_string_append (buffer, "<br>"); - - g_string_append ( - buffer, _(smime_encrypt_table[valid->encrypt.status].shortdesc)); - } - - g_string_append (buffer, "</td></tr></table>"); - - camel_stream_write ( - stream, buffer->str, - buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); - } -} - -static void -efh_text_plain (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *html_filter; - CamelMultipart *mp; - CamelDataWrapper *dw; - CamelContentType *type; - const gchar *format; - guint32 flags; - guint32 rgb; - gint i, count, len; - struct _EMFormatHTMLCache *efhc; - - flags = efh->text_html_flags; - - dw = camel_medium_get_content ((CamelMedium *) part); - if (!dw) - return; - - /* Check for RFC 2646 flowed text. */ - if (camel_content_type_is(dw->mime_type, "text", "plain") - && (format = camel_content_type_param(dw->mime_type, "format")) - && !g_ascii_strcasecmp(format, "flowed")) - flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED; - - /* 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 ... */ - - efhc = g_hash_table_lookup ( - efh->priv->text_inline_parts, - emf->part_id->str); - - if (efhc == NULL || (mp = efhc->textmp) == NULL) { - EMInlineFilter *inline_filter; - CamelStream *null; - CamelContentType *ct; - gboolean charset_added = FALSE; - - /* if we had to snoop the part type to get here, then - * use that as the base type, yuck */ - if (emf->snoop_mime_type == NULL - || (ct = camel_content_type_decode (emf->snoop_mime_type)) == NULL) { - ct = dw->mime_type; - camel_content_type_ref (ct); - } - - if (dw->mime_type && ct != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) { - camel_content_type_set_param (ct, "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), ct); - 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 (efhc == NULL) - efhc = efh_insert_cache (efh, emf->part_id->str); - efhc->textmp = mp; - - if (charset_added) { - camel_content_type_set_param (ct, "charset", NULL); - } - - g_object_unref (inline_filter); - camel_content_type_unref (ct); - } - - rgb = e_color_to_value ( - &efh->priv->colors[EM_FORMAT_HTML_COLOR_CITATION]); - filtered_stream = camel_stream_filter_new (stream); - html_filter = camel_mime_filter_tohtml_new (flags, rgb); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), html_filter); - g_object_unref (html_filter); - - /* We handle our made-up multipart here, so we don't recursively call ourselves */ - - len = emf->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", "*") && (is_fallback || !camel_content_type_is (type, "text", "calendar"))) { - gchar *content; - - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\n<tt>\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string ( - stream, content, cancellable, NULL); - g_free (content); - - em_format_format_text ( - emf, filtered_stream, - (CamelDataWrapper *) newpart, - cancellable); - camel_stream_flush ((CamelStream *) filtered_stream, cancellable, NULL); - camel_stream_write_string (stream, "</tt>\n", cancellable, NULL); - camel_stream_write_string (stream, "</div>\n", cancellable, NULL); - } else { - g_string_append_printf (emf->part_id, ".inline.%d", i); - em_format_part ( - emf, stream, newpart, cancellable); - g_string_truncate (emf->part_id, len); - } - } - - g_object_unref (filtered_stream); -} - -static void -efh_text_enriched (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *enriched; - guint32 flags = 0; - gchar *content; - - if (!strcmp(info->mime_type, "text/richtext")) { - flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT; - camel_stream_write_string ( - stream, "\n<!-- text/richtext -->\n", - cancellable, NULL); - } else { - camel_stream_write_string ( - stream, "\n<!-- text/enriched -->\n", - cancellable, NULL); - } - - enriched = camel_mime_filter_enriched_new (flags); - filtered_stream = camel_stream_filter_new (stream); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), enriched); - g_object_unref (enriched); - - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - em_format_format_text ( - emf, (CamelStream *) filtered_stream, - (CamelDataWrapper *) part, cancellable); - - g_object_unref (filtered_stream); - camel_stream_write_string (stream, "</div>", cancellable, NULL); -} - -static void -efh_write_text_html (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) +em_format_html_format_cert_infos (GQueue *cert_infos, + GString *output_buffer) { -#if d(!)0 - CamelStream *out; - gint fd; - CamelDataWrapper *dw; - - fd = dup (STDOUT_FILENO); - out = camel_stream_fs_new_with_fd (fd); - printf("writing text content to frame '%s'\n", puri->cid); - dw = camel_medium_get_content (puri->part); - if (dw) - camel_data_wrapper_write_to_stream (dw, out); - g_object_unref (out); -#endif - em_format_format_text ( - emf, stream, (CamelDataWrapper *) puri->part, cancellable); -} + GQueue valid = G_QUEUE_INIT; + GList *head, *link; -static void -efh_text_html (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - const gchar *location; - gchar *cid = NULL; - gchar *content; + g_return_if_fail (cert_infos != NULL); + g_return_if_fail (output_buffer != NULL); - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; color: #%06x;\">\n" - "<!-- text/html -->\n" EFH_MESSAGE_START, - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_FRAME]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_CONTENT]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); + head = g_queue_peek_head_link (cert_infos); - /* TODO: perhaps we don't need to calculate this anymore now base is handled better */ - /* calculate our own location string so add_puri doesn't do it - * for us. our iframes are special cases, we need to use the - * proper base url to access them, but other children parts - * shouldn't blindly inherit the container's location. */ - location = camel_mime_part_get_content_location (part); - if (location == NULL) { - if (emf->base) - cid = camel_url_to_string (emf->base, 0); - else - cid = g_strdup (emf->part_id->str); - } else { - if (strchr (location, ':') == NULL && emf->base != NULL) { - CamelURL *uri; + /* Make sure we have a valid CamelCipherCertInfo before + * appending anything to the output buffer, so we don't + * end up with "()". */ + for (link = head; link != NULL; link = g_list_next (link)) { + CamelCipherCertInfo *cinfo = link->data; - uri = camel_url_new_with_base (emf->base, location); - cid = camel_url_to_string (uri, 0); - camel_url_free (uri); - } else { - cid = g_strdup (location); + if ((cinfo->name != NULL && *cinfo->name != '\0') || + (cinfo->email != NULL && *cinfo->email != '\0')) { + g_queue_push_tail (&valid, cinfo); } } - em_format_add_puri ( - emf, sizeof (EMFormatPURI), cid, - part, efh_write_text_html); - d(printf("adding iframe, location %s\n", cid)); - content = g_strdup_printf ( - "<iframe src=\"%s\" frameborder=0 scrolling=no>" - "could not get %s</iframe>\n</div>\n", cid, cid); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - g_free (cid); -} - -/* This is a lot of code for something useless ... */ -static void -efh_message_external (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelContentType *type; - const gchar *access_type; - gchar *url = NULL, *desc = NULL; - gchar *content; - - if (!part) { - camel_stream_write_string ( - stream, _("Unknown external-body part."), - cancellable, NULL); - return; - } - - /* needs to be cleaner */ - type = camel_mime_part_get_content_type (part); - access_type = camel_content_type_param (type, "access-type"); - if (!access_type) { - camel_stream_write_string ( - stream, _("Malformed external-body part."), - cancellable, NULL); - return; - } - - 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) { - /* FIXME: use camel_isspace */ - 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 ("<a href=\"%s\">%s</a>", url, desc); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - g_free (url); - g_free (desc); - - return; - -fail: - content = g_strdup_printf ( - _("Pointer to unknown external data (\"%s\" type)"), - access_type); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); -} - -static void -efh_message_deliverystatus (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatHTML *efh = EM_FORMAT_HTML (emf); - CamelStream *filtered_stream; - CamelMimeFilter *html_filter; - guint32 rgb = 0x737373; - gchar *content; - - /* Yuck, this is copied from efh_text_plain */ - content = g_strdup_printf ( - "<div style=\"border: solid #%06x 1px; " - "background-color: #%06x; padding: 10px; " - "color: #%06x;\">\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]), - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_TEXT])); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); - - filtered_stream = camel_stream_filter_new (stream); - html_filter = camel_mime_filter_tohtml_new (efh->text_html_flags, rgb); - camel_stream_filter_add ( - CAMEL_STREAM_FILTER (filtered_stream), html_filter); - g_object_unref (html_filter); - - camel_stream_write_string (stream, "<tt>\n" EFH_MESSAGE_START, cancellable, NULL); - em_format_format_text ( - emf, filtered_stream, - (CamelDataWrapper *) part, cancellable); - camel_stream_flush (filtered_stream, cancellable, NULL); - camel_stream_write_string (stream, "</tt>\n", cancellable, NULL); - - camel_stream_write_string (stream, "</div>", cancellable, NULL); -} - -static void -emfh_write_related (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - em_format_format_content (emf, stream, puri->part, cancellable); - - camel_stream_close (stream, cancellable, NULL); -} - -static void -emfh_multipart_related_check (struct _EMFormatHTMLJob *job, - GCancellable *cancellable) -{ - EMFormat *format; - GList *link; - gchar *oldpartid; - - if (g_cancellable_is_cancelled (cancellable)) + if (g_queue_is_empty (&valid)) return; - format = EM_FORMAT (job->format); + g_string_append (output_buffer, " ("); - d(printf(" running multipart/related check task\n")); - oldpartid = g_strdup (format->part_id->str); + while (!g_queue_is_empty (&valid)) { + CamelCipherCertInfo *cinfo; - link = g_queue_peek_head_link (job->puri_level->data); + cinfo = g_queue_pop_head (&valid); - if (!link) { - g_string_printf (format->part_id, "%s", oldpartid); - g_free (oldpartid); - return; - } + if (cinfo->name != NULL && *cinfo->name != '\0') { + g_string_append (output_buffer, cinfo->name); - while (link != NULL) { - EMFormatPURI *puri = link->data; - - if (puri->use_count == 0) { - d(printf("part '%s' '%s' used '%d'\n", puri->uri?puri->uri:"", puri->cid, puri->use_count)); - if (puri->func == emfh_write_related) { - g_string_printf (format->part_id, "%s", puri->part_id); - /* FIXME Not passing a GCancellable here. */ - em_format_part ( - format, CAMEL_STREAM (job->stream), - puri->part, NULL); + if (cinfo->email != NULL && *cinfo->email != '\0') { + g_string_append (output_buffer, " <"); + g_string_append (output_buffer, cinfo->email); + g_string_append (output_buffer, ">"); } - /* else it was probably added by a previous format this loop */ - } - - link = g_list_next (link); - } - - g_string_printf (format->part_id, "%s", oldpartid); - g_free (oldpartid); -} - -/* RFC 2387 */ -static void -efh_multipart_related (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - CamelMultipart *mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) part); - CamelMimePart *body_part, *display_part = NULL; - CamelContentType *content_type; - const gchar *start; - gint i, nparts, partidlen, displayid = 0; - struct _EMFormatHTMLJob *job; - - if (!CAMEL_IS_MULTIPART (mp)) { - em_format_format_source (emf, stream, part, cancellable); - return; - } - - nparts = camel_multipart_get_number (mp); - content_type = camel_mime_part_get_content_type (part); - start = camel_content_type_param (content_type, "start"); - if (start && strlen (start) > 2) { - gint len; - const gchar *cid; - /* strip <>'s */ - len = strlen (start) - 2; - start++; - - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - cid = camel_mime_part_get_content_id (body_part); - - if (cid && !strncmp (cid, start, len) && strlen (cid) == len) { - display_part = body_part; - displayid = i; - break; - } + } else if (cinfo->email != NULL && *cinfo->email != '\0') { + g_string_append (output_buffer, cinfo->email); } - } else { - display_part = camel_multipart_get_part (mp, 0); - } - if (display_part == NULL) { - em_format_part_as ( - emf, stream, part, - "multipart/mixed", cancellable); - return; - } - - em_format_push_level (emf); - - partidlen = emf->part_id->len; - - /* queue up the parts for possible inclusion */ - for (i = 0; i < nparts; i++) { - body_part = camel_multipart_get_part (mp, i); - if (body_part != display_part) { - g_string_append_printf(emf->part_id, "related.%d", i); - em_format_add_puri (emf, sizeof (EMFormatPURI), NULL, body_part, emfh_write_related); - g_string_truncate (emf->part_id, partidlen); - d(printf(" part '%s' '%s' added\n", puri->uri?puri->uri:"", puri->cid)); - } + if (!g_queue_is_empty (&valid)) + g_string_append (output_buffer, ", "); } - g_string_append_printf(emf->part_id, "related.%d", displayid); - em_format_part (emf, stream, display_part, cancellable); - g_string_truncate (emf->part_id, partidlen); - camel_stream_flush (stream, cancellable, NULL); - - /* queue a job to check for un-referenced parts to add as attachments */ - job = em_format_html_job_new ( - EM_FORMAT_HTML (emf), emfh_multipart_related_check, NULL); - job->stream = stream; - g_object_ref (stream); - em_format_html_job_queue ((EMFormatHTML *) emf, job); - - em_format_pull_level (emf); -} - -static void -efh_write_image (EMFormat *emf, - CamelStream *stream, - EMFormatPURI *puri, - GCancellable *cancellable) -{ - CamelDataWrapper *dw = camel_medium_get_content ((CamelMedium *) puri->part); - - d(printf("writing image '%s'\n", puri->cid)); - camel_data_wrapper_decode_to_stream_sync ( - dw, stream, cancellable, NULL); - camel_stream_close (stream, cancellable, NULL); -} - -static void -efh_image (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - EMFormatPURI *puri; - gchar *content; - - puri = em_format_add_puri ( - emf, sizeof (EMFormatPURI), NULL, part, efh_write_image); - - content = g_strdup_printf ( - "<img hspace=10 vspace=10 src=\"%s\">", puri->cid); - camel_stream_write_string (stream, content, cancellable, NULL); - g_free (content); -} - -/* 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_image }, - { (gchar *) "image/jpeg", efh_image }, - { (gchar *) "image/png", efh_image }, - { (gchar *) "image/x-png", efh_image }, - { (gchar *) "image/x-bmp", efh_image }, - { (gchar *) "image/bmp", efh_image }, - { (gchar *) "image/svg", efh_image }, - { (gchar *) "image/x-cmu-raster", efh_image }, - { (gchar *) "image/x-ico", efh_image }, - { (gchar *) "image/x-portable-anymap", efh_image }, - { (gchar *) "image/x-portable-bitmap", efh_image }, - { (gchar *) "image/x-portable-graymap", efh_image }, - { (gchar *) "image/x-portable-pixmap", efh_image }, - { (gchar *) "image/x-xpixmap", efh_image }, - { (gchar *) "text/enriched", efh_text_enriched }, - { (gchar *) "text/plain", efh_text_plain }, - { (gchar *) "text/html", efh_text_html }, - { (gchar *) "text/richtext", efh_text_enriched }, - { (gchar *) "text/*", efh_text_plain }, - { (gchar *) "message/external-body", efh_message_external }, - { (gchar *) "message/delivery-status", efh_message_deliverystatus }, - { (gchar *) "multipart/related", efh_multipart_related }, - - /* 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_image }, - { (gchar *) "image/pjpeg", efh_image }, - - /* special internal types */ - - { (gchar *) "x-evolution/message/rfc822", efh_format_message } -}; - -static void -efh_builtin_init (EMFormatHTMLClass *efhc) -{ - EMFormatClass *efc; - gint ii; - - efc = (EMFormatClass *) efhc; - - for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++) - em_format_class_add_handler ( - efc, &type_builtin_table[ii]); + g_string_append_c (output_buffer, ')'); } -/* ********************************************************************** */ - static void efh_format_text_header (EMFormatHTML *emfh, GString *buffer, @@ -2608,37 +2188,34 @@ efh_format_text_header (EMFormatHTML *emfh, html = value; is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL; - if (emfh->simple_headers) { - fmt = "<b>%s</b>: %s<br>"; + + if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) { + if (flags & EM_FORMAT_HEADER_BOLD) { + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td><b>%s:</b> %s</td></tr>"; + } else { + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td>%s: %s</td></tr>"; + } + } else if (flags & EM_FORMAT_HTML_HEADER_NODEC) { + if (is_rtl) + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th valign=top align=\"left\" nowrap>%1$s<b> </b></th></tr>"; + else + fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s<b> </b></th><td valign=top>%s</td></tr>"; } else { - if (flags & EM_FORMAT_HTML_HEADER_NOCOLUMNS) { - if (flags & EM_FORMAT_HEADER_BOLD) { - fmt = "<tr><td><b>%s:</b> %s</td></tr>"; - } else { - fmt = "<tr><td>%s: %s</td></tr>"; - } - } else if (flags & EM_FORMAT_HTML_HEADER_NODEC) { + if (flags & EM_FORMAT_HEADER_BOLD) { if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th valign=top align=\"left\" nowrap>%1$s<b> </b></th></tr>"; + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th align=\"left\" nowrap>%1$s:<b> </b></th></tr>"; else - fmt = "<tr><th align=\"right\" valign=\"top\" nowrap>%s<b> </b></th><td valign=top>%s</td></tr>"; + fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s:<b> </b></th><td>%s</td></tr>"; } else { - - if (flags & EM_FORMAT_HEADER_BOLD) { - if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%%\">%2$s</td><th align=\"left\" nowrap>%1$s:<b> </b></th></tr>"; - else - fmt = "<tr><th align=\"right\" valign=\"top\" nowrap>%s:<b> </b></th><td>%s</td></tr>"; - } else { - if (is_rtl) - fmt = "<tr><td align=\"right\" valign=\"top\" width=\"100%\">%2$s</td><td align=\"left\" nowrap>%1$s:<b> </b></td></tr>"; - else - fmt = "<tr><td align=\"right\" valign=\"top\" nowrap>%s:<b> </b></td><td>%s</td></tr>"; - } + if (is_rtl) + fmt = "<tr class=\"header-item rtl\" style=\"display: %s\"><td align=\"right\" valign=\"top\" width=\"100%\">%2$s</td><td align=\"left\" nowrap>%1$s:<b> </b></td></tr>"; + else + fmt = "<tr class=\"header-item\" style=\"display: %s\"><td align=\"right\" valign=\"top\" nowrap>%s:<b> </b></td><td>%s</td></tr>"; } } - g_string_append_printf (buffer, fmt, label, html); + g_string_append_printf (buffer, fmt, + (flags & EM_FORMAT_HTML_HEADER_HIDDEN ? "none" : "table-row"), label, html); g_free (mhtml); } @@ -2653,22 +2230,15 @@ static gchar * efh_format_address (EMFormatHTML *efh, GString *out, struct _camel_header_address *a, - gchar *field) + gchar *field, + gboolean no_links) { guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES; gchar *name, *mailto, *addr; gint i = 0; - gboolean wrap = FALSE; gchar *str = NULL; gint limit = mail_config_get_address_count (); - if (field ) { - if ((!strcmp (field, _("To")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_TO)) - || (!strcmp (field, _("Cc")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_CC)) - || (!strcmp (field, _("Bcc")) && !(efh->header_wrap_flags & EM_FORMAT_HTML_HEADER_BCC))) - wrap = TRUE; - } - while (a) { if (a->name) name = camel_text_to_html (a->name, flags, 0); @@ -2700,7 +2270,10 @@ efh_format_address (EMFormatHTML *efh, mailto = camel_url_encode (a->v.addr, "?=&()"); } addr = camel_text_to_html (a->v.addr, flags, 0); - g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr); + if (no_links) + g_string_append_printf (out, "%s", addr); + else + g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr); g_free (mailto); g_free (addr); @@ -2709,7 +2282,7 @@ efh_format_address (EMFormatHTML *efh, break; case CAMEL_HEADER_ADDRESS_GROUP: g_string_append_printf (out, "%s: ", name); - efh_format_address (efh, out, a->v.members, field); + efh_format_address (efh, out, a->v.members, field, no_links); g_string_append_printf (out, ";"); break; default: @@ -2725,48 +2298,51 @@ efh_format_address (EMFormatHTML *efh, g_string_append (out, ", "); /* Let us add a '...' if we have more addresses */ - if (limit > 0 && wrap && a && (i > (limit - 1))) { - gchar *evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); - - if (!strcmp (field, _("To"))) { - g_string_append (out, "<a href=\"##TO##\">...</a>"); - str = g_strdup_printf ("<a href=\"##TO##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Cc"))) { - g_string_append (out, "<a href=\"##CC##\">...</a>"); - str = g_strdup_printf ("<a href=\"##CC##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); + if (limit > 0 && (i == limit - 1)) { + const gchar *id = NULL; + + if (strcmp (field, _("To")) == 0) { + id = "to"; + } else if (strcmp (field, _("Cc")) == 0) { + id = "cc"; + } else if (strcmp (field, _("Bcc")) == 0) { + id = "bcc"; } - else if (!strcmp (field, _("Bcc"))) { - g_string_append (out, "<a href=\"##BCC##\">...</a>"); - str = g_strdup_printf ("<a href=\"##BCC##\"><img src=\"%s/plus.png\"></a> ", evolution_imagesdir); - } - - g_free (evolution_imagesdir); - if (str) - return str; + if (id) { + g_string_append_printf (out, + "<span id=\"__evo-moreaddr-%s\" " + "style=\"display: none;\">", id); + str = g_strdup_printf ( + "<img src=\"evo-file://%s/plus.png\" " + "id=\"__evo-moreaddr-img-%s\" class=\"navigable\">", + EVOLUTION_IMAGESDIR, id); + } } - } - if (limit > 0 && i > (limit)) { - gchar *evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + if (str) { + const gchar *id = NULL; - if (!strcmp (field, _("To"))) { - str = g_strdup_printf ("<a href=\"##TO##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Cc"))) { - str = g_strdup_printf ("<a href=\"##CC##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); - } - else if (!strcmp (field, _("Bcc"))) { - str = g_strdup_printf ("<a href=\"##BCC##\"><img src=\"%s/minus.png\"></a> ", evolution_imagesdir); + if (strcmp (field, _("To")) == 0) { + id = "to"; + } else if (strcmp (field, _("Cc")) == 0) { + id = "cc"; + } else if (strcmp (field, _("Bcc")) == 0) { + id = "bcc"; } - g_free (evolution_imagesdir); + if (id) { + g_string_append_printf (out, + "</span>" + "<span class=\"navigable\" " + "id=\"__evo-moreaddr-ellipsis-%s\" " + "style=\"display: inline;\">...</span>", + id); + } } return str; - } static void @@ -2793,15 +2369,15 @@ canon_header_name (gchar *name) } } -static void -efh_format_header (EMFormat *emf, - GString *buffer, - CamelMedium *part, - struct _camel_header_raw *header, - guint32 flags, - const gchar *charset) +void +em_format_html_format_header (EMFormat *emf, + GString *buffer, + CamelMedium *part, + struct _camel_header_raw *header, + guint32 flags, + const gchar *charset) { - EMFormatHTML *efh = (EMFormatHTML *) emf; + EMFormatHTML *efh = EM_FORMAT_HTML (emf); gchar *name, *buf, *value = NULL; const gchar *label, *txt; gboolean addrspec = FALSE; @@ -2825,9 +2401,11 @@ efh_format_header (EMFormat *emf, struct _camel_header_address *addrs; GString *html; gchar *img; + const gchar *charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); buf = camel_header_unfold (header->value); - if (!(addrs = camel_header_address_decode (buf, emf->charset ? emf->charset : emf->default_charset))) { + if (!(addrs = camel_header_address_decode (buf, charset))) { g_free (buf); return; } @@ -2835,7 +2413,8 @@ efh_format_header (EMFormat *emf, g_free (buf); html = g_string_new(""); - img = efh_format_address (efh, html, addrs, (gchar *) label); + img = efh_format_address (efh, html, addrs, (gchar *) label, + (flags & EM_FORMAT_HTML_HEADER_NOLINKS)); if (img) { str_field = g_strdup_printf ("%s%s:", img, label); @@ -2921,7 +2500,11 @@ efh_format_header (EMFormat *emf, html = g_string_new(""); scan = ng; while (scan) { - g_string_append_printf(html, "<a href=\"news:%s\">%s</a>", scan->newsgroup, scan->newsgroup); + if (flags & EM_FORMAT_HTML_HEADER_NOLINKS) + g_string_append_printf (html, "%s", scan->newsgroup); + else + g_string_append_printf(html, "<a href=\"news:%s\">%s</a>", + scan->newsgroup, scan->newsgroup); scan = scan->next; if (scan) g_string_append_printf(html, ", "); @@ -2949,100 +2532,129 @@ efh_format_header (EMFormat *emf, } static void -efh_format_headers (EMFormatHTML *efh, - GString *buffer, - CamelMedium *part, - GCancellable *cancellable) +efh_format_short_headers (EMFormatHTML *efh, + GString *buffer, + CamelMedium *part, + gboolean visible, + GCancellable *cancellable) { - EMFormat *emf = (EMFormat *) efh; + EMFormat *emf = EM_FORMAT (efh); const gchar *charset; CamelContentType *ct; - struct _camel_header_raw *header; - gboolean have_icon = FALSE; - const gchar *photo_name = NULL; - CamelInternetAddress *cia = NULL; - gboolean face_decoded = FALSE, contact_has_photo = FALSE; - guchar *face_header_value = NULL; - gsize face_header_len = 0; - gchar *header_sender = NULL, *header_from = NULL, *name; - gboolean mail_from_delegate = FALSE; const gchar *hdr_charset; gchar *evolution_imagesdir; + gchar *subject = NULL; + struct _camel_header_address *addrs = NULL; + struct _camel_header_raw *header; + GString *from; + gboolean is_rtl; - if (!part) + if (cancellable && g_cancellable_is_cancelled (cancellable)) return; ct = camel_mime_part_get_content_type ((CamelMimePart *) part); charset = camel_content_type_param (ct, "charset"); charset = camel_iconv_charset_name (charset); + hdr_charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); - if (!efh->simple_headers) - g_string_append_printf ( - buffer, "<font color=\"#%06x\">\n" - "<table cellpadding=\"0\" width=\"100%%\">", - e_color_to_value ( - &efh->priv->colors[ - EM_FORMAT_HTML_COLOR_HEADER])); - - hdr_charset = emf->charset ? emf->charset : emf->default_charset; evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + from = g_string_new (""); - /* If the header is collapsed, display just subject and sender in one row and leave */ - if (efh->priv->headers_state == EM_FORMAT_HTML_HEADERS_STATE_COLLAPSED && efh->priv->headers_collapsable) { - gchar *subject = NULL; - struct _camel_header_address *addrs = NULL; - GString *from = g_string_new (""); + g_string_append_printf (buffer, + "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" " + "id=\"__evo-short-headers\" style=\"display: %s\">", + visible ? "block" : "none"); - header = ((CamelMimePart *) part)->headers; - while (header) { - if (!g_ascii_strcasecmp (header->name, "From")) { - GString *tmp; - if (!(addrs = camel_header_address_decode (header->value, hdr_charset))) { - header = header->next; - continue; - } - tmp = g_string_new (""); - efh_format_address (efh, tmp, addrs, header->name); - - if (tmp->len) - g_string_printf (from, _("From: %s"), tmp->str); - g_string_free (tmp, TRUE); - } else if (!g_ascii_strcasecmp (header->name, "Subject")) { - gchar *buf = NULL; - buf = camel_header_unfold (header->value); - g_free (subject); - subject = camel_header_decode_string (buf, hdr_charset); - g_free (buf); + header = ((CamelMimePart *) part)->headers; + while (header) { + if (!g_ascii_strcasecmp (header->name, "From")) { + GString *tmp; + if (!(addrs = camel_header_address_decode (header->value, hdr_charset))) { + header = header->next; + continue; } - header = header->next; + tmp = g_string_new (""); + efh_format_address (efh, tmp, addrs, header->name, FALSE); + + if (tmp->len) + g_string_printf (from, _("From: %s"), tmp->str); + g_string_free (tmp, TRUE); + + } else if (!g_ascii_strcasecmp (header->name, "Subject")) { + gchar *buf = NULL; + subject = camel_header_unfold (header->value); + buf = camel_header_decode_string (subject, hdr_charset); + g_free (subject); + subject = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0); + g_free (buf); } + header = header->next; + } + is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL; + if (is_rtl) { g_string_append_printf ( buffer, - "<tr>" - "<td width=\"20\" valign=\"top\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/plus.png\">" - "</a></td>" - "<td><strong>%s</strong> %s%s%s</td>" - "</tr>", - evolution_imagesdir, - subject ? subject : _("(no subject)"), - from->len ? "(" : "", - from->str, - from->len ? ")" : ""); - - g_free (subject); - if (addrs) - camel_header_address_list_clear (&addrs); - g_string_free (from, TRUE); + "<tr><td width=\"100%%\" align=\"right\">%s%s%s <strong>%s</strong></td></tr>", + from->len ? "(" : "", from->str, from->len ? ")" : "", + subject ? subject : _("(no subject)")); + } else { + g_string_append_printf ( + buffer, + "<tr><td><strong>%s</strong> %s%s%s</td></tr>", + subject ? subject : _("(no subject)"), + from->len ? "(" : "", from->str, from->len ? ")" : ""); + } + + g_string_append (buffer, "</table>"); + + g_free (subject); + if (addrs) + camel_header_address_list_clear (&addrs); - g_string_append (buffer, "</table>"); + g_string_free (from, TRUE); + g_free (evolution_imagesdir); +} - g_free (evolution_imagesdir); +static void +efh_format_full_headers (EMFormatHTML *efh, + GString *buffer, + CamelMedium *part, + gboolean all_headers, + gboolean visible, + GCancellable *cancellable) +{ + EMFormat *emf = EM_FORMAT (efh); + const gchar *charset; + CamelContentType *ct; + struct _camel_header_raw *header; + gboolean have_icon = FALSE; + const gchar *photo_name = NULL; + CamelInternetAddress *cia = NULL; + gboolean face_decoded = FALSE, contact_has_photo = FALSE; + guchar *face_header_value = NULL; + gsize face_header_len = 0; + gchar *header_sender = NULL, *header_from = NULL, *name; + gboolean mail_from_delegate = FALSE; + const gchar *hdr_charset; + gchar *evolution_imagesdir; + if (cancellable && g_cancellable_is_cancelled (cancellable)) return; - } + + ct = camel_mime_part_get_content_type ((CamelMimePart *) part); + charset = camel_content_type_param (ct, "charset"); + charset = camel_iconv_charset_name (charset); + hdr_charset = em_format_get_charset (emf) ? + em_format_get_charset (emf) : em_format_get_default_charset (emf); + + evolution_imagesdir = g_filename_to_uri (EVOLUTION_IMAGESDIR, NULL, NULL); + + g_string_append_printf (buffer, + "<table cellspacing=\"0\" cellpadding=\"0\" border=\"0\" " + "id=\"__evo-full-headers\" style=\"display: %s\" width=\"100%%\">", + visible ? "block" : "none"); header = ((CamelMimePart *) part)->headers; while (header) { @@ -3054,7 +2666,7 @@ efh_format_headers (EMFormatHTML *efh, break; html = g_string_new(""); - name = efh_format_address (efh, html, addrs, header->name); + name = efh_format_address (efh, html, addrs, header->name, FALSE); header_sender = html->str; camel_header_address_list_clear (&addrs); @@ -3069,7 +2681,7 @@ efh_format_headers (EMFormatHTML *efh, break; html = g_string_new(""); - name = efh_format_address (efh, html, addrs, header->name); + name = efh_format_address (efh, html, addrs, header->name, FALSE); header_from = html->str; camel_header_address_list_clear (&addrs); @@ -3113,50 +2725,15 @@ efh_format_headers (EMFormatHTML *efh, g_free (header_sender); g_free (header_from); - if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) { - if (efh->priv->headers_collapsable) - g_string_append_printf ( - buffer, - "<tr>" - "<td valign=\"top\" width=\"20\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/minus.png\">" - "</a></td>" - "<td><table width=\"100%%\" border=0 " - "cellpadding=\"0\">\n", - evolution_imagesdir); - else - g_string_append ( - buffer, - "<tr><td>" - "<table width=\"100%%\" border=0 " - "cellpadding=\"0\">\n"); - - } else { - if (efh->priv->headers_collapsable) - g_string_append_printf ( - buffer, - "<tr>" - "<td valign=\"top\" width=\"20\">" - "<a href=\"##HEADERS##\">" - "<img src=\"%s/minus.png\">" - "</a></td>" - "<td><table border=0 cellpadding=\"0\">\n", - evolution_imagesdir); - else - g_string_append ( - buffer, - "<tr><td>" - "<table border=0 cellpadding=\"0\">\n"); - } + g_string_append (buffer, "<tr><td><table border=0 cellpadding=\"0\">\n"); g_free (evolution_imagesdir); /* dump selected headers */ - if (emf->mode == EM_FORMAT_MODE_ALLHEADERS) { + if (all_headers) { header = ((CamelMimePart *) part)->headers; while (header) { - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, header, EM_FORMAT_HTML_HEADER_NOCOLUMNS, charset); header = header->next; @@ -3205,7 +2782,7 @@ efh_format_headers (EMFormatHTML *efh, xmailer.value = use_header->value; mailer_shown = TRUE; - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, &xmailer, h->flags, charset); if (strstr(use_header->value, "Evolution")) @@ -3226,7 +2803,7 @@ efh_format_headers (EMFormatHTML *efh, face_decoded = TRUE; /* Showing an encoded "Face" header makes little sense */ } else if (!g_ascii_strcasecmp (header->name, h->name) && !face) { - efh_format_header ( + em_format_html_format_header ( emf, buffer, part, header, h->flags, charset); } @@ -3238,214 +2815,162 @@ efh_format_headers (EMFormatHTML *efh, } } - if (!efh->simple_headers) { - g_string_append (buffer, "</table></td>"); - - if (photo_name) { - gchar *classid; - CamelMimePart *photopart; - gboolean only_local_photo; - - cia = camel_internet_address_new (); - camel_address_decode ((CamelAddress *) cia, (const gchar *) photo_name); - only_local_photo = em_format_html_get_only_local_photos (efh); - photopart = em_utils_contact_photo (cia, only_local_photo); - - if (photopart) { - contact_has_photo = TRUE; - classid = g_strdup_printf ( - "icon:///em-format-html/%s/photo/header", - emf->part_id->str); - g_string_append_printf ( - buffer, - "<td align=\"right\" valign=\"top\">" - "<img width=64 src=\"%s\"></td>", - classid); - em_format_add_puri (emf, sizeof (EMFormatPURI), classid, - photopart, efh_write_image); - g_object_unref (photopart); - - g_free (classid); - } - g_object_unref (cia); - } + g_string_append (buffer, "</table></td>"); - if (!contact_has_photo && face_decoded) { - gchar *classid; - CamelMimePart *part; - - part = camel_mime_part_new (); - camel_mime_part_set_content ( - (CamelMimePart *) part, - (const gchar *) face_header_value, - face_header_len, "image/png"); - classid = g_strdup_printf ( - "icon:///em-format-html/face/photo/header"); - g_string_append_printf ( - buffer, - "<td align=\"right\" valign=\"top\">" - "<img width=48 src=\"%s\"></td>", - classid); - em_format_add_puri ( - emf, sizeof (EMFormatPURI), - classid, part, efh_write_image); - g_object_unref (part); - } + if (photo_name) { + const gchar *classid; + CamelMimePart *photopart; + gboolean only_local_photo; - if (have_icon && efh->show_icon) { - GtkIconInfo *icon_info; - gchar *classid; - CamelMimePart *iconpart = NULL; + cia = camel_internet_address_new (); + camel_address_decode ((CamelAddress *) cia, (const gchar *) photo_name); + only_local_photo = em_format_html_get_only_local_photos (efh); + photopart = em_utils_contact_photo (cia, only_local_photo); - classid = g_strdup_printf ( - "icon:///em-format-html/%s/icon/header", - emf->part_id->str); + if (photopart) { + EMFormatPURI *puri; + contact_has_photo = TRUE; + classid = "icon:///em-format-html/headers/photo"; g_string_append_printf ( buffer, "<td align=\"right\" valign=\"top\">" - "<img width=16 height=16 src=\"%s\"></td>", + "<img width=64 src=\"%s\"></td>", classid); - - icon_info = gtk_icon_theme_lookup_icon ( - gtk_icon_theme_get_default (), - "evolution", 16, GTK_ICON_LOOKUP_NO_SVG); - if (icon_info != NULL) { - iconpart = em_format_html_file_part ( - (EMFormatHTML *) emf, "image/png", - gtk_icon_info_get_filename (icon_info), - cancellable); - gtk_icon_info_free (icon_info); - } - - if (iconpart) { - em_format_add_puri ( - emf, sizeof (EMFormatPURI), - classid, iconpart, efh_write_image); - g_object_unref (iconpart); - } - g_free (classid); + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), photopart, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); + g_object_unref (photopart); } - - g_string_append (buffer, "</tr></table>\n</font>\n"); + g_object_unref (cia); } -} - -static void -efh_format_message (EMFormat *emf, - CamelStream *stream, - CamelMimePart *part, - const EMFormatHandler *info, - GCancellable *cancellable, - gboolean is_fallback) -{ - const EMFormatHandler *handle; - GString *buffer; - - /* TODO: make this validity stuff a method */ - EMFormatHTML *efh = (EMFormatHTML *) emf; - CamelCipherValidity *save = emf->valid, *save_parent = emf->valid_parent; - - emf->valid = NULL; - emf->valid_parent = NULL; - - buffer = g_string_sized_new (1024); - if (emf->message != (CamelMimeMessage *) part) - g_string_append (buffer, "<blockquote>\n"); - - if (!efh->hide_headers) - efh_format_headers ( - efh, buffer, CAMEL_MEDIUM (part), cancellable); - - camel_stream_write ( - stream, buffer->str, buffer->len, cancellable, NULL); - - g_string_free (buffer, TRUE); + if (!contact_has_photo && face_decoded) { + const gchar *classid; + CamelMimePart *part; + EMFormatPURI *puri; + + part = camel_mime_part_new (); + camel_mime_part_set_content ( + (CamelMimePart *) part, + (const gchar *) face_header_value, + face_header_len, "image/png"); + classid = "icon:///em-format-html/headers/face/photo"; + g_string_append_printf ( + buffer, + "<td align=\"right\" valign=\"top\">" + "<img width=48 src=\"%s\"></td>", + classid); - handle = em_format_find_handler(emf, "x-evolution/message/post-header"); - if (handle) - handle->handler ( - emf, stream, part, handle, cancellable, FALSE); + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), part, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); - camel_stream_write_string ( - stream, EM_FORMAT_HTML_VPAD, cancellable, NULL); - em_format_part (emf, stream, part, cancellable); + g_object_unref (part); + g_free (face_header_value); + } - if (emf->message != (CamelMimeMessage *) part) - camel_stream_write_string ( - stream, "</blockquote>\n", cancellable, NULL); + if (have_icon && efh->show_icon) { + GtkIconInfo *icon_info; + const gchar *classid; + CamelMimePart *iconpart = NULL; + EMFormatPURI *puri; - camel_cipher_validity_free (emf->valid); + classid = "icon:///em-format-html/header/icon"; + g_string_append_printf ( + buffer, + "<td align=\"right\" valign=\"top\">" + "<img width=16 height=16 src=\"%s\"></td>", + classid); + icon_info = gtk_icon_theme_lookup_icon ( + gtk_icon_theme_get_default (), + "evolution", 16, GTK_ICON_LOOKUP_NO_SVG); + if (icon_info != NULL) { + iconpart = em_format_html_file_part ( + (EMFormatHTML *) emf, "image/png", + gtk_icon_info_get_filename (icon_info), + cancellable); + gtk_icon_info_free (icon_info); + } + if (iconpart) { + puri = em_format_puri_new ( + emf, sizeof (EMFormatPURI), iconpart, classid); + puri->write_func = efh_write_image; + em_format_add_puri (emf, puri); + g_object_unref (iconpart); + } + } - emf->valid = save; - emf->valid_parent = save_parent; + g_string_append (buffer, "</tr></table>"); } -void -em_format_html_format_cert_infos (GQueue *cert_infos, - GString *output_buffer) +gboolean +em_format_html_can_load_images (EMFormatHTML *efh) { - GQueue valid = G_QUEUE_INIT; - GList *head, *link; + g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE); - g_return_if_fail (cert_infos != NULL); - g_return_if_fail (output_buffer != NULL); + return ((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_ALWAYS) || + ((efh->priv->image_loading_policy == E_MAIL_IMAGE_LOADING_POLICY_SOMETIMES) && + efh->priv->can_load_images)); +} - head = g_queue_peek_head_link (cert_infos); +void +em_format_html_animation_extract_frame (const GByteArray *anim, + gchar **frame, + gsize *len) +{ + GdkPixbufLoader *loader; + GdkPixbufAnimation *animation; + GdkPixbuf *frame_buf; + + /* GIF89a (GIF image signature) */ + const gchar GIF_HEADER[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + const gint GIF_HEADER_LEN = sizeof (GIF_HEADER); + + /* NETSCAPE2.0 (extension describing animated GIF, starts on 0x310) */ + const gchar GIF_APPEXT[] = { 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, + 0x50, 0x45, 0x32, 0x2E, 0x30 }; + const gint GIF_APPEXT_LEN = sizeof (GIF_APPEXT); + + /* Check if the image is an animated GIF. We don't care about any + * other animated formats (APNG or MNG) as WebKit does not support them + * and displays only the first frame. */ + if ((anim->len < 0x331) + || (memcmp (anim->data, GIF_HEADER, GIF_HEADER_LEN) != 0) + || (memcmp (&anim->data[0x310], GIF_APPEXT, GIF_APPEXT_LEN) != 0)) { + + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + return; + } - /* Make sure we have a valid CamelCipherCertInfo before - * appending anything to the output buffer, so we don't - * end up with "()". */ - for (link = head; link != NULL; link = g_list_next (link)) { - CamelCipherCertInfo *cinfo = link->data; + loader = gdk_pixbuf_loader_new (); + gdk_pixbuf_loader_write (loader, (guchar *) anim->data, anim->len, NULL); + gdk_pixbuf_loader_close (loader, NULL); + animation = gdk_pixbuf_loader_get_animation (loader); + if (!animation) { - if ((cinfo->name != NULL && *cinfo->name != '\0') || - (cinfo->email != NULL && *cinfo->email != '\0')) - g_queue_push_tail (&valid, cinfo); + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + return; } - if (g_queue_is_empty (&valid)) + /* Extract first frame */ + frame_buf = gdk_pixbuf_animation_get_static_image (animation); + if (!frame_buf) { + *frame = g_memdup (anim->data, anim->len); + *len = anim->len; + g_object_unref (loader); + g_object_unref (animation); return; - - g_string_append (output_buffer, " ("); - - while (!g_queue_is_empty (&valid)) { - CamelCipherCertInfo *cinfo; - - cinfo = g_queue_pop_head (&valid); - - if (cinfo->name != NULL && *cinfo->name != '\0') { - g_string_append (output_buffer, cinfo->name); - - if (cinfo->email != NULL && *cinfo->email != '\0') { - g_string_append (output_buffer, " <"); - g_string_append (output_buffer, cinfo->email); - g_string_append (output_buffer, ">"); - } - - } else if (cinfo->email != NULL && *cinfo->email != '\0') { - g_string_append (output_buffer, cinfo->email); - } - - if (!g_queue_is_empty (&valid)) - g_string_append (output_buffer, ", "); } - g_string_append_c (output_buffer, ')'); -} + /* Unforunatelly, GdkPixbuf cannot save to GIF, but WebKit does not + * have any trouble displaying PNG image despite the part having + * image/gif mime-type */ + gdk_pixbuf_save_to_buffer (frame_buf, frame, len, "png", NULL, NULL); -/* unref returned pointer with g_object_unref(), if not NULL */ -CamelStream * -em_format_html_get_cached_image (EMFormatHTML *efh, - const gchar *image_uri) -{ - g_return_val_if_fail (efh != NULL, NULL); - g_return_val_if_fail (image_uri != NULL, NULL); - - if (!emfh_http_cache) - return NULL; - - return camel_data_cache_get ( - emfh_http_cache, EMFH_HTTP_CACHE_PATH, image_uri, NULL); + g_object_unref (loader); } - |