/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Michael Zucchi <notzed@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE /* Enable strcasestr in string.h */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#include <gtk/gtk.h>
#ifdef G_OS_WIN32
/* Work around 'DATADIR' and 'interface' lossage in <windows.h> */
#define DATADIR crap_DATADIR
#include <windows.h>
#undef DATADIR
#undef interface
#endif

#include <libebackend/libebackend.h>

#include "e-util/e-datetime-format.h"
#include "e-util/e-icon-factory.h"
#include "e-util/e-util-private.h"
#include "e-util/e-util.h"
#include "misc/e-web-view.h"

#include <shell/e-shell.h>

#include <glib/gi18n.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-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 \
	((obj), EM_TYPE_FORMAT_HTML, EMFormatHTMLPrivate))

#define d(x)

struct _EMFormatHTMLPrivate {
	GdkColor colors[EM_FORMAT_HTML_NUM_COLOR_TYPES];
	EMailImageLoadingPolicy image_loading_policy;

	guint can_load_images	: 1;
	guint only_local_photos	: 1;
	guint show_sender_photo	: 1;
	guint show_real_date	: 1;
        guint animate_images    : 1;
};

static gpointer parent_class;

enum {
	PROP_0,
	PROP_BODY_COLOR,
	PROP_CITATION_COLOR,
	PROP_CONTENT_COLOR,
	PROP_FRAME_COLOR,
	PROP_HEADER_COLOR,
	PROP_IMAGE_LOADING_POLICY,
	PROP_MARK_CITATIONS,
	PROP_ONLY_LOCAL_PHOTOS,
	PROP_SHOW_SENDER_PHOTO,
	PROP_SHOW_REAL_DATE,
	PROP_TEXT_COLOR,
        PROP_ANIMATE_IMAGES
};

#define EFM_MESSAGE_START_ANAME "evolution_message_start"
#define EFH_MESSAGE_START "<A name=\"" EFM_MESSAGE_START_ANAME "\"></A>"

static void efh_parse_image			(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_text_enriched		(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_text_plain		(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_text_html			(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_message_external		(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_message_deliverystatus	(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);
static void efh_parse_message_rfc822		(EMFormat *emf, CamelMimePart *part, GString *part_id, EMFormatParserInfo *info, GCancellable *cancellable);

static void efh_write_image			(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_text_enriched		(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_text_plain		(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_text_html			(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_source			(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_headers			(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_attachment		(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_error			(EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);
static void efh_write_message_rfc822            (EMFormat *emf, EMFormatPURI *puri, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);

static void efh_format_full_headers		(EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean all_headers, gboolean visible, GCancellable *cancellable);
static void efh_format_short_headers		(EMFormatHTML *efh, GString *buffer, CamelMedium *part, gboolean visible, GCancellable *cancellable);

static void efh_write_message                   (EMFormat *emf, GList *puris, CamelStream *stream, EMFormatWriterInfo *info, GCancellable *cancellable);

/*****************************************************************************/
static void
efh_parse_image (EMFormat *emf,
                 CamelMimePart *part,
                 GString *part_id,
                 EMFormatParserInfo *info,
                 GCancellable *cancellable)
{
	EMFormatPURI *puri;
	const gchar *tmp;
	gchar *cid;
	gint len;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	tmp = camel_mime_part_get_content_id (part);
	if (!tmp) {
		em_format_parse_part_as (emf, part, part_id, info,
				"x-evolution/message/attachment", cancellable);
		return;
	}

	cid = g_strdup_printf ("cid:%s", tmp);
	len = part_id->len;
	g_string_append (part_id, ".image");
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->cid = cid;
	puri->write_func = efh_write_image;
	puri->mime_type = g_strdup (info->handler->mime_type);
	puri->is_attachment = TRUE;
	puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
	puri->validity_type = info->validity_type;

	em_format_add_puri (emf, puri);
	g_string_truncate (part_id, len);
}

static void
efh_parse_text_enriched (EMFormat *emf,
                         CamelMimePart *part,
                         GString *part_id,
                         EMFormatParserInfo *info,
                         GCancellable *cancellable)
{
	EMFormatPURI *puri;
	const gchar *tmp;
	gchar *cid;
	gint len;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	tmp = camel_mime_part_get_content_id (part);
	if (!tmp) {
		cid = g_strdup_printf ("em-no-cid:%s", part_id->str);
	} else {
		cid = g_strdup_printf ("cid:%s", tmp);
	}

	len = part_id->len;
	g_string_append (part_id, ".text_enriched");
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->cid = cid;
	puri->mime_type = g_strdup (info->handler->mime_type);
	puri->write_func = efh_write_text_enriched;
	puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
	puri->validity_type = info->validity_type;
	puri->is_attachment = info->is_attachment;

	em_format_add_puri (emf, puri);
	g_string_truncate (part_id, len);
}

static void
efh_parse_text_plain (EMFormat *emf,
                      CamelMimePart *part,
                      GString *part_id,
                      EMFormatParserInfo *info,
                      GCancellable *cancellable)
{
	EMFormatPURI *puri;
	CamelStream *filtered_stream, *null;
	CamelMultipart *mp;
	CamelDataWrapper *dw;
	CamelContentType *type;
	gint i, count, len;
	EMInlineFilter *inline_filter;
	gboolean charset_added = FALSE;
	const gchar *snoop_type = NULL;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	dw = camel_medium_get_content ((CamelMedium *) part);
	if (!dw)
		return;

	/* This scans the text part for inline-encoded data, creates
	 * a multipart of all the parts inside it. */

	/* FIXME: We should discard this multipart if it only contains
	 * the original text, but it makes this hash lookup more complex */

	/* TODO: We could probably put this in the superclass, since
	 * no knowledge of html is required - but this messes with
	 * filters a bit.  Perhaps the superclass should just deal with
	 * html anyway and be done with it ... */

	if (!dw->mime_type)
		snoop_type = em_format_snoop_type (part);

	/* if we had to snoop the part type to get here, then
		* use that as the base type, yuck */
	if (snoop_type == NULL
		|| (type = camel_content_type_decode (snoop_type)) == NULL) {
		type = dw->mime_type;
		camel_content_type_ref (type);
	}

	if (dw->mime_type && type != dw->mime_type && camel_content_type_param (dw->mime_type, "charset")) {
		camel_content_type_set_param (type, "charset", camel_content_type_param (dw->mime_type, "charset"));
		charset_added = TRUE;
	}

	null = camel_stream_null_new ();
	filtered_stream = camel_stream_filter_new (null);
	g_object_unref (null);
	inline_filter = em_inline_filter_new (camel_mime_part_get_encoding (part), type);
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream),
		CAMEL_MIME_FILTER (inline_filter));
	camel_data_wrapper_decode_to_stream_sync (
		dw, (CamelStream *) filtered_stream, cancellable, NULL);
	camel_stream_close ((CamelStream *) filtered_stream, cancellable, NULL);
	g_object_unref (filtered_stream);

	mp = em_inline_filter_get_multipart (inline_filter);

	if (charset_added) {
		camel_content_type_set_param (type, "charset", NULL);
	}

	g_object_unref (inline_filter);
	camel_content_type_unref (type);

	/* We handle our made-up multipart here, so we don't recursively call ourselves */
	len = part_id->len;
	count = camel_multipart_get_number (mp);
	for (i = 0; i < count; i++) {
		CamelMimePart *newpart = camel_multipart_get_part (mp, i);

		if (!newpart)
			continue;

		type = camel_mime_part_get_content_type (newpart);
		if (camel_content_type_is (type, "text", "*") && (!camel_content_type_is (type, "text", "calendar"))) {
			gint s_len = part_id->len;

			g_string_append (part_id, ".plain_text");
			puri = em_format_puri_new (emf, sizeof (EMFormatPURI), newpart, part_id->str);
			puri->write_func = efh_write_text_plain;
			puri->mime_type = g_strdup ("text/html");
			puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
			puri->validity_type = info->validity_type;
			puri->is_attachment = info->is_attachment;
			g_string_truncate (part_id, s_len);
			em_format_add_puri (emf, puri);
		} else {
			g_string_append_printf (part_id, ".inline.%d", i);
			em_format_parse_part (emf, CAMEL_MIME_PART (newpart), part_id, info, cancellable);
			g_string_truncate (part_id, len);
		}
	}

	g_object_unref (mp);
}

static void
efh_parse_text_html (EMFormat *emf,
                     CamelMimePart *part,
                     GString *part_id,
                     EMFormatParserInfo *info,
                     GCancellable *cancellable)
{
	EMFormatPURI *puri;
	const gchar *location;
	gchar *cid = NULL;
	CamelURL *base;
	gint len;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	base = em_format_get_base_url (emf);
	location = camel_mime_part_get_content_location (part);
	if (location == NULL) {
		if (base)
			cid = camel_url_to_string (base, 0);
		else
			cid = g_strdup (part_id->str);
	} else {
		if (strchr (location, ':') == NULL && base != NULL) {
			CamelURL *uri;

			uri = camel_url_new_with_base (base, location);
			cid = camel_url_to_string (uri, 0);
			camel_url_free (uri);
		} else {
			cid = g_strdup (location);
		}
	}

	len = part_id->len;
	g_string_append (part_id, ".text_html");
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->write_func = efh_write_text_html;
	puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
	puri->validity_type = info->validity_type;
	puri->is_attachment = info->is_attachment;

	em_format_add_puri (emf, puri);
	g_string_truncate (part_id, len);

	if (cid)
		g_free (cid);
}

static void
efh_parse_message_external (EMFormat *emf,
                            CamelMimePart *part,
                            GString *part_id,
                            EMFormatParserInfo *info,
                            GCancellable *cancellable)
{
	EMFormatPURI *puri;
	CamelMimePart *newpart;
	CamelContentType *type;
	const gchar *access_type;
	gchar *url = NULL, *desc = NULL;
	gchar *content;
	gint len;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	newpart = camel_mime_part_new ();

	/* needs to be cleaner */
	type = camel_mime_part_get_content_type (part);
	access_type = camel_content_type_param (type, "access-type");
	if (!access_type) {
		const gchar *msg = _("Malformed external-body part");
		camel_mime_part_set_content (newpart, msg, strlen (msg),
				"text/plain");
		goto addPart;
	}

	if (!g_ascii_strcasecmp(access_type, "ftp") ||
	    !g_ascii_strcasecmp(access_type, "anon-ftp")) {
		const gchar *name, *site, *dir, *mode;
		gchar *path;
		gchar ftype[16];

		name = camel_content_type_param (type, "name");
		site = camel_content_type_param (type, "site");
		dir = camel_content_type_param (type, "directory");
		mode = camel_content_type_param (type, "mode");
		if (name == NULL || site == NULL)
			goto fail;

		/* Generate the path. */
		if (dir)
			path = g_strdup_printf("/%s/%s", *dir=='/'?dir+1:dir, name);
		else
			path = g_strdup_printf("/%s", *name=='/'?name+1:name);

		if (mode && *mode)
			sprintf(ftype, ";type=%c",  *mode);
		else
			ftype[0] = 0;

		url = g_strdup_printf ("ftp://%s%s%s", site, path, ftype);
		g_free (path);
		desc = g_strdup_printf (_("Pointer to FTP site (%s)"), url);
	} else if (!g_ascii_strcasecmp (access_type, "local-file")) {
		const gchar *name, *site;

		name = camel_content_type_param (type, "name");
		site = camel_content_type_param (type, "site");
		if (name == NULL)
			goto fail;

		url = g_filename_to_uri (name, NULL, NULL);
		if (site)
			desc = g_strdup_printf(_("Pointer to local file (%s) valid at site \"%s\""), name, site);
		else
			desc = g_strdup_printf(_("Pointer to local file (%s)"), name);
	} else if (!g_ascii_strcasecmp (access_type, "URL")) {
		const gchar *urlparam;
		gchar *s, *d;

		/* RFC 2017 */
		urlparam = camel_content_type_param (type, "url");
		if (urlparam == NULL)
			goto fail;

		/* For obscure MIMEy reasons, the URL may be split into words */
		url = g_strdup (urlparam);
		s = d = url;
		while (*s) {
			if (!isspace ((guchar) * s))
				*d++ = *s;
			s++;
		}
		*d = 0;
		desc = g_strdup_printf (_("Pointer to remote data (%s)"), url);
	} else
		goto fail;

	content = g_strdup_printf ("<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 (
		_("Pointer to unknown external data (\"%s\" type)"),
		access_type);
	camel_mime_part_set_content (newpart, content, strlen (content), "text/plain");
	g_free (content);

addPart:
	len = part_id->len;
	g_string_append (part_id, ".msg_external");
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->write_func = efh_write_text_html;
	puri->mime_type = g_strdup ("text/html");

	em_format_add_puri (emf, puri);
	g_string_truncate (part_id, len);
}

static void
efh_parse_message_deliverystatus (EMFormat *emf,
                                  CamelMimePart *part,
                                  GString *part_id,
                                  EMFormatParserInfo *info,
                                  GCancellable *cancellable)
{
	EMFormatPURI *puri;
	gint len;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	len = part_id->len;
	g_string_append (part_id, ".deliverystatus");
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->write_func = efh_write_source;
	puri->mime_type = g_strdup ("text/html");
	puri->validity = info->validity ? camel_cipher_validity_clone (info->validity) : NULL;
	puri->validity_type = info->validity_type;
	puri->is_attachment = info->is_attachment;

	em_format_add_puri (emf, puri);
	g_string_truncate (part_id, len);
}

static void
efh_parse_message_rfc822 (EMFormat *emf,
                          CamelMimePart *part,
                          GString *part_id,
                          EMFormatParserInfo *info,
                          GCancellable *cancellable)
{
	CamelDataWrapper *dw;
	CamelMimePart *opart;
	CamelStream *stream;
	CamelMimeParser *parser;
	gint len;
	EMFormatParserInfo oinfo = *info;
	EMFormatPURI *puri;

	len = part_id->len;
	g_string_append (part_id, ".rfc822");

        /* Create an empty PURI that will represent start of the RFC message */
	puri = em_format_puri_new (emf, sizeof (EMFormatPURI), part, part_id->str);
	puri->write_func = efh_write_message_rfc822;
	puri->mime_type = g_strdup ("text/html");
	puri->is_attachment = info->is_attachment;
	em_format_add_puri (emf, puri);

        /* Now parse the message, creating multiple sub-PURIs */
	stream = camel_stream_mem_new ();
	dw = camel_medium_get_content ((CamelMedium *) part);
	camel_data_wrapper_write_to_stream_sync (dw, stream, cancellable, NULL);
	g_seekable_seek (G_SEEKABLE (stream), 0, G_SEEK_SET, cancellable, NULL);

	parser = camel_mime_parser_new ();
	camel_mime_parser_init_with_stream (parser, stream, NULL);

	opart = camel_mime_part_new ();
	camel_mime_part_construct_from_parser_sync (opart, parser, cancellable, NULL);

	em_format_parse_part_as (emf, opart, part_id, &oinfo,
		"x-evolution/message", cancellable);

        /* Add another generic PURI that represents end of the RFC message.
         * The em_format_write() function will skip all PURIs between the ".rfc822" 
         * PURI and ".rfc822.end" PURI as they will be rendered in an <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_write_image (EMFormat *emf,
                 EMFormatPURI *puri,
                 CamelStream *stream,
                  EMFormatWriterInfo *info,
                 GCancellable *cancellable)
{
	gchar *content;
	EMFormatHTML *efh;
	CamelDataWrapper *dw;
	GByteArray *ba;
	CamelStream *raw_content;

	if (g_cancellable_is_cancelled (cancellable))
		return;

	efh = (EMFormatHTML *) emf;

	dw = camel_medium_get_content (CAMEL_MEDIUM (puri->part));
	g_return_if_fail (dw);

	raw_content = camel_stream_mem_new ();
	camel_data_wrapper_decode_to_stream_sync (dw, raw_content, cancellable, NULL);
	ba = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (raw_content));

	if (info->mode == EM_FORMAT_WRITE_MODE_RAW) {

		if (!efh->priv->animate_images) {

			gchar *buff;
			gsize len;

			em_format_html_animation_extract_frame (ba, &buff, &len);

			camel_stream_write (stream, buff, len, cancellable, NULL);

			g_free (buff);

		} else {

			camel_stream_write_to_stream (raw_content, stream, cancellable, NULL);
		}

	} else {

		gchar *buffer;

		if (!efh->priv->animate_images) {

			gchar *buff;
			gsize len;

			em_format_html_animation_extract_frame (ba, &buff, &len);

			content = g_base64_encode ((guchar *) buff, len);
			g_free (buff);

		} else {
			content = g_base64_encode ((guchar *) 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);
	}

	g_object_unref (raw_content);
}

static void
efh_write_text_enriched (EMFormat *emf,
                         EMFormatPURI *puri,
                         CamelStream *stream,
                         EMFormatWriterInfo *info,
                         GCancellable *cancellable)
{
	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 (!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 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 pre\">\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);
	camel_stream_flush (filtered_stream, cancellable, NULL);

	g_object_unref (filtered_stream);
	g_free (content);

	camel_stream_write_string (stream, "</div></div>\n", cancellable, NULL);
}

static gchar *
get_tag (const gchar *tag_name,
         gchar *opening,
         gchar *closing)
{
	gchar *t;
	gboolean has_end;

	for (t = closing - 1; t != opening; t--) {
		if (*t != ' ')
			break;
	}

	/* Not a pair tag */
	if (*t == '/')
		return g_strndup (opening, closing - opening + 1);

	for (t = closing; t && *t; t++) {
		if (*t == '<')
			break;
	}

	do {
		if (*t == '/') {
			has_end = TRUE;
			break;
		}

		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);

	if (g_strncasecmp (t, tag_name, strlen (tag_name)) == 0) {

		closing = strstr (t, ">");

		return g_strndup (opening, closing - opening + strlen (tag_name));
	}

	/* Broken HTML? */
	return g_strndup (opening, closing - opening + 1);
}

static void
efh_write_text_html (EMFormat *emf,
                     EMFormatPURI *puri,
                     CamelStream *stream,
                     EMFormatWriterInfo *info,
                     GCancellable *cancellable)
{
	EMFormatHTML *efh = (EMFormatHTML *) emf;

	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);

		/* 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;
		}

		/*	        include the "body" as well -----v */
		g_string_erase (string, 0, tag - string->str + 4);
		g_string_prepend (string, "<div ");

		for (iter = tags; iter; iter = iter->next) {
			g_string_prepend (string, iter->data);
		}

		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;

			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-nostyle\">"
			"<iframe width=\"100%%\" height=\"auto\""
			" frameborder=\"0\" src=\"%s\" "
			" style=\"border: 1px solid #%06x; background-color: #%06x;\">"
			"</iframe>"
			"</div>",
			uri,
			e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME]),
			e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT]));

		camel_stream_write_string (stream, str, cancellable, NULL);

		g_free (str);
		g_free (uri);
	}
}

static void
efh_write_source (EMFormat *emf,
                  EMFormatPURI *puri,
                  CamelStream *stream,
                  EMFormatWriterInfo *info,
                  GCancellable *cancellable)
{
	EMFormatHTML *efh = (EMFormatHTML *) emf;
	GString *buffer;
	CamelStream *filtered_stream;
	CamelMimeFilter *filter;
	CamelDataWrapper *dw = (CamelDataWrapper *) puri->part;

	filtered_stream = camel_stream_filter_new (stream);

	filter = camel_mime_filter_tohtml_new (
		CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
		CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
		CAMEL_MIME_FILTER_TOHTML_PRESERVE_8BIT, 0);
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream), filter);
	g_object_unref (filter);

	buffer = g_string_new ("");

	g_string_append_printf (
		buffer, "<div class=\"part-container\" style=\"border: 0; 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_TEXT]));

	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_write_headers (EMFormat *emf,
                   EMFormatPURI *puri,
                   CamelStream *stream,
                   EMFormatWriterInfo *info,
                   GCancellable *cancellable)
{
	GString *buffer;
	EMFormatHTML *efh = (EMFormatHTML *) emf;
	gint bg_color;

	if (!puri->part)
		return;

	buffer = g_string_new ("");

	if (info->mode & EM_FORMAT_WRITE_MODE_PRINTING) {
		GdkColor white = { 0, G_MAXUINT16, G_MAXUINT16, G_MAXUINT16 };
		bg_color = e_color_to_value (&white);
	} else {
		bg_color = e_color_to_value (&efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY]);
	}

	g_string_append_printf (
		buffer,
		"<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);
	}

	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);

	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;
		EMFormatWriterInfo msgInfo = *info;
		msgInfo.mode = EM_FORMAT_WRITE_MODE_NORMAL;

                /* Create a new fake list of PURIs which will contain only
                 * PURIs from this message. */
		iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
		if (!iter || !iter->next)
			return;

		iter = iter->next;
		puris = NULL;
		while (iter) {

			EMFormatPURI *p;
			p = iter->data;

			if (g_str_has_suffix (p->uri, ".rfc822.end"))
				break;

			puris = g_list_append (puris, p);
			iter = iter->next;

		};

		efh_write_message (emf, puris, stream, &msgInfo, cancellable);

		g_list_free (puris);

	} else if (info->mode == EM_FORMAT_WRITE_MODE_PRINTING) {

		GList *iter;
		gboolean can_write = FALSE;

		iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
		if (!iter || !iter->next)
			return;

                /* Skip everything before attachment bar, inclusive */\
		iter = iter->next;
		while (iter) {

			EMFormatPURI *p = iter->data;

                        /* EMFormatHTMLPrint has registered a special writer
                         * for headers, try to find it and use it. */
			if (g_str_has_suffix (p->uri, ".headers")) {

				const EMFormatHandler *handler;

				handler = em_format_find_handler (
					emf, "x-evolution/message/headers");
				if (handler && handler->write_func)
					handler->write_func (emf, p, stream, info, cancellable);

				iter = iter->next;
				continue;
			}

			if (g_str_has_suffix (p->uri, ".rfc822.end"))
				break;

			if (g_str_has_suffix (p->uri, ".attachment-bar"))
				can_write = TRUE;

			if (can_write && p->write_func) {
				p->write_func (
					emf, p, stream, info, cancellable);
			}

			iter = iter->next;
		}

	} else {
		gchar *str;
		gchar *uri;

		EMFormatHTML *efh = (EMFormatHTML *) emf;
		EMFormatPURI *p;
		GList *iter;

		iter = g_hash_table_lookup (emf->mail_part_table, puri->uri);
		if (!iter || !iter->next)
			return;

		iter = iter->next;
		p = iter->data;

		uri = em_format_build_mail_uri (emf->folder, emf->message_uid,
			"part_id", G_TYPE_STRING, p->uri,
			"mode", G_TYPE_INT, EM_FORMAT_WRITE_MODE_RAW,
			NULL);

		str = g_strdup_printf (
			"<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);
	}

}

/*****************************************************************************/

/* Notes:
 *
 * image/tiff is omitted because it's a multi-page image format, but
 * gdk-pixbuf unconditionally renders the first page only, and doesn't
 * even indicate through meta-data whether multiple pages are present
 * (see bug 335959).  Therefore, make no attempt to render TIFF images
 * inline and defer to an application that can handle multi-page TIFF
 * files properly like Evince or Gimp.  Once the referenced bug is
 * fixed we can reevaluate this policy.
 */
static EMFormatHandler type_builtin_table[] = {
	{ (gchar *) "image/gif", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/jpeg", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/png", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-png", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-bmp", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/bmp", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/svg", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-cmu-raster", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-ico", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-portable-anymap", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-portable-bitmap", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-portable-graymap", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-portable-pixmap", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/x-xpixmap", efh_parse_image, efh_write_image, },
	{ (gchar *) "text/enriched", efh_parse_text_enriched, efh_write_text_enriched, },
	{ (gchar *) "text/plain", efh_parse_text_plain, efh_write_text_plain, },
	{ (gchar *) "text/html", efh_parse_text_html, efh_write_text_html, },
	{ (gchar *) "text/richtext", efh_parse_text_enriched, efh_write_text_enriched, },
	{ (gchar *) "text/*", efh_parse_text_plain, efh_write_text_plain, },
        { (gchar *) "message/rfc822", efh_parse_message_rfc822, efh_write_message_rfc822, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE }, 
        { (gchar *) "message/news", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE | EM_FORMAT_HANDLER_COMPOUND_TYPE },
        { (gchar *) "message/delivery-status", efh_parse_message_deliverystatus, efh_write_text_plain, },
	{ (gchar *) "message/external-body", efh_parse_message_external, efh_write_text_plain, },
        { (gchar *) "message/*", efh_parse_message_rfc822, 0, EM_FORMAT_HANDLER_INLINE },

	/* This is where one adds those busted, non-registered types,
	 * that some idiot mailer writers out there decide to pull out
	 * of their proverbials at random. */
	{ (gchar *) "image/jpg", efh_parse_image, efh_write_image, },
	{ (gchar *) "image/pjpeg", efh_parse_image, efh_write_image, },

	/* special internal types */
	{ (gchar *) "x-evolution/message/rfc822", 0, efh_write_text_plain, },
	{ (gchar *) "x-evolution/message/headers", 0, efh_write_headers, },
	{ (gchar *) "x-evolution/message/source", 0, efh_write_source, },
	{ (gchar *) "x-evolution/message/attachment", 0, efh_write_attachment, },
	{ (gchar *) "x-evolution/message/error", 0, efh_write_error, },
};

static void
efh_builtin_init (EMFormatHTMLClass *efhc)
{
	EMFormatClass *emfc;
	gint ii;

	emfc = (EMFormatClass *) efhc;

	for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++)
		em_format_class_add_handler (
			emfc, &type_builtin_table[ii]);
}

static void
efh_set_property (GObject *object,
                  guint property_id,
                  const GValue *value,
                  GParamSpec *pspec)
{
	switch (property_id) {
		case PROP_BODY_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_BODY,
				g_value_get_boxed (value));
			return;

		case PROP_CITATION_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_CITATION,
				g_value_get_boxed (value));
			return;

		case PROP_CONTENT_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_CONTENT,
				g_value_get_boxed (value));
			return;

		case PROP_FRAME_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_FRAME,
				g_value_get_boxed (value));
			return;

		case PROP_HEADER_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_HEADER,
				g_value_get_boxed (value));
			return;

		case PROP_IMAGE_LOADING_POLICY:
			em_format_html_set_image_loading_policy (
				EM_FORMAT_HTML (object),
				g_value_get_enum (value));
			return;

		case PROP_MARK_CITATIONS:
			em_format_html_set_mark_citations (
				EM_FORMAT_HTML (object),
				g_value_get_boolean (value));
			return;

		case PROP_ONLY_LOCAL_PHOTOS:
			em_format_html_set_only_local_photos (
				EM_FORMAT_HTML (object),
				g_value_get_boolean (value));
			return;

		case PROP_SHOW_SENDER_PHOTO:
			em_format_html_set_show_sender_photo (
				EM_FORMAT_HTML (object),
				g_value_get_boolean (value));
			return;

		case PROP_SHOW_REAL_DATE:
			em_format_html_set_show_real_date (
				EM_FORMAT_HTML (object),
				g_value_get_boolean (value));
			return;

		case PROP_TEXT_COLOR:
			em_format_html_set_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_TEXT,
				g_value_get_boxed (value));
			return;

		case PROP_ANIMATE_IMAGES:
			em_format_html_set_animate_images (
				EM_FORMAT_HTML (object),
				g_value_get_boolean (value));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
efh_get_property (GObject *object,
                  guint property_id,
                  GValue *value,
                  GParamSpec *pspec)
{
	GdkColor color;

	switch (property_id) {
		case PROP_BODY_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_BODY,
				&color);
			g_value_set_boxed (value, &color);
			return;

		case PROP_CITATION_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_CITATION,
				&color);
			g_value_set_boxed (value, &color);
			return;

		case PROP_CONTENT_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_CONTENT,
				&color);
			g_value_set_boxed (value, &color);
			return;

		case PROP_FRAME_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_FRAME,
				&color);
			g_value_set_boxed (value, &color);
			return;

		case PROP_HEADER_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_HEADER,
				&color);
			g_value_set_boxed (value, &color);
			return;

		case PROP_IMAGE_LOADING_POLICY:
			g_value_set_enum (
				value,
				em_format_html_get_image_loading_policy (
				EM_FORMAT_HTML (object)));
			return;

		case PROP_MARK_CITATIONS:
			g_value_set_boolean (
				value, em_format_html_get_mark_citations (
				EM_FORMAT_HTML (object)));
			return;

		case PROP_ONLY_LOCAL_PHOTOS:
			g_value_set_boolean (
				value, em_format_html_get_only_local_photos (
				EM_FORMAT_HTML (object)));
			return;

		case PROP_SHOW_SENDER_PHOTO:
			g_value_set_boolean (
				value, em_format_html_get_show_sender_photo (
				EM_FORMAT_HTML (object)));
			return;

		case PROP_SHOW_REAL_DATE:
			g_value_set_boolean (
				value, em_format_html_get_show_real_date (
				EM_FORMAT_HTML (object)));
			return;

		case PROP_TEXT_COLOR:
			em_format_html_get_color (
				EM_FORMAT_HTML (object),
				EM_FORMAT_HTML_COLOR_TEXT,
				&color);
			g_value_set_boxed (value, &color);
			return;
		case PROP_ANIMATE_IMAGES:
			g_value_set_boolean (
				value, em_format_html_get_animate_images (
				EM_FORMAT_HTML (object)));
			return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
efh_finalize (GObject *object)
{
	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
efh_constructed (GObject *object)
{
	/* Chain up to parent's constructed() method. */
	G_OBJECT_CLASS (parent_class)->constructed (object);

	e_extensible_load_extensions (E_EXTENSIBLE (object));
}

static void
efh_write_attachment (EMFormat *emf,
                                          EMFormatPURI *puri,
                                          CamelStream *stream,
                                          EMFormatWriterInfo *info,
                      GCancellable *cancellable)
{
	gchar *text, *html;
	CamelContentType *ct;
	gchar *mime_type;
	const EMFormatHandler *handler;

	/* we display all inlined attachments only */

	/* this could probably be cleaned up ... */
	camel_stream_write_string (
		stream,
		"<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);

	ct = camel_mime_part_get_content_type (puri->part);
	mime_type = camel_content_type_simple (ct);

	/* output some info about it */
	text = em_format_describe_part (puri->part, mime_type);
	html = camel_text_to_html (
		text, ((EMFormatHTML *) emf)->text_html_flags &
		CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
	camel_stream_write_string (stream, html, cancellable, NULL);
	g_free (html);
	g_free (text);

	camel_stream_write_string (
		stream, "</font></td></tr><tr></table>", cancellable, NULL);

	handler = em_format_find_handler (emf, mime_type);
	if (handler && handler->write_func && handler->write_func != efh_write_attachment) {
		if (em_format_is_inline (emf, puri->uri, puri->part, handler))
			handler->write_func (emf, puri, stream, info, cancellable);
	}

	g_free (mime_type);
}

static void
efh_preparse (EMFormat *emf)
{
	EMFormatHTML *efh = EM_FORMAT_HTML (emf);
	CamelInternetAddress *addr;
	CamelSession *session;
	ESourceRegistry *registry;

	if (!emf->message) {
		efh->priv->can_load_images = FALSE;
		return;
	}

	session = em_format_get_session (emf);
	registry = e_mail_session_get_registry (E_MAIL_SESSION (session));

	addr = camel_mime_message_get_from (emf->message);
	efh->priv->can_load_images = em_utils_in_addressbook (
		registry, addr, FALSE);
}

static void
efh_write_message (EMFormat *emf,
                   GList *puris,
                   CamelStream *stream,
                   EMFormatWriterInfo *info,
                   GCancellable *cancellable)
{
	GList *iter;
	EMFormatHTML *efh;
	gchar *header;

	efh = (EMFormatHTML *) emf;

	header = g_strdup_printf (
		"<!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, header, cancellable, NULL);
	g_free (header);

	if (info->mode == EM_FORMAT_WRITE_MODE_SOURCE) {

		efh_write_source (emf, emf->mail_part_list->data,
				  stream, info, cancellable);

		camel_stream_write_string (stream, "</body></html>", cancellable, NULL);
		return;
	}

	for (iter = puris; iter; iter = iter->next) {

		EMFormatPURI *puri = iter->data;

		if (!puri)
			continue;

                /* If current PURI has suffix .rfc822 then iterate through all
                 * subsequent PURIs until PURI with suffix .rfc822.end is found.
                 * These skipped PURIs contain entire RFC message which will
                 * be written in <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;

				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;

				d(printf(".rfc822 - skipping %s\n", puri->uri));
			}

                        /* Skip the .rfc822.end PURI as well. */
			if (!iter)
				break;

			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 *klass)
{
	efh_builtin_init (klass);
}

static void
efh_class_init (EMFormatHTMLClass *klass)
{
	GObjectClass *object_class;
	EMFormatClass *emf_class;

	parent_class = g_type_class_peek_parent (klass);
	g_type_class_add_private (klass, sizeof (EMFormatHTMLPrivate));

	emf_class = EM_FORMAT_CLASS (klass);
	emf_class->preparse = efh_preparse;
	emf_class->write = efh_write;

	object_class = G_OBJECT_CLASS (klass);
	object_class->constructed = efh_constructed;
	object_class->set_property = efh_set_property;
	object_class->get_property = efh_get_property;
	object_class->finalize = efh_finalize;

	g_object_class_install_property (
		object_class,
		PROP_BODY_COLOR,
		g_param_spec_boxed (
			"body-color",
			"Body Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CITATION_COLOR,
		g_param_spec_boxed (
			"citation-color",
			"Citation Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_CONTENT_COLOR,
		g_param_spec_boxed (
			"content-color",
			"Content Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_FRAME_COLOR,
		g_param_spec_boxed (
			"frame-color",
			"Frame Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_HEADER_COLOR,
		g_param_spec_boxed (
			"header-color",
			"Header Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	/* FIXME Make this a proper enum property. */
	g_object_class_install_property (
		object_class,
		PROP_IMAGE_LOADING_POLICY,
		g_param_spec_enum (
			"image-loading-policy",
			"Image Loading Policy",
			NULL,
			E_TYPE_MAIL_IMAGE_LOADING_POLICY,
			E_MAIL_IMAGE_LOADING_POLICY_ALWAYS,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_MARK_CITATIONS,
		g_param_spec_boolean (
			"mark-citations",
			"Mark Citations",
			NULL,
			TRUE,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_ONLY_LOCAL_PHOTOS,
		g_param_spec_boolean (
			"only-local-photos",
			"Only Local Photos",
			NULL,
			TRUE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT));

	g_object_class_install_property (
		object_class,
		PROP_SHOW_SENDER_PHOTO,
		g_param_spec_boolean (
			"show-sender-photo",
			"Show Sender Photo",
			NULL,
			FALSE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT));

	g_object_class_install_property (
		object_class,
		PROP_SHOW_REAL_DATE,
		g_param_spec_boolean (
			"show-real-date",
			"Show real Date header value",
			NULL,
			TRUE,
			G_PARAM_READWRITE |
			G_PARAM_CONSTRUCT));

	g_object_class_install_property (
		object_class,
		PROP_TEXT_COLOR,
		g_param_spec_boxed (
			"text-color",
			"Text Color",
			NULL,
			GDK_TYPE_COLOR,
			G_PARAM_READWRITE));

	g_object_class_install_property (
		object_class,
		PROP_ANIMATE_IMAGES,
		g_param_spec_boolean (
			"animate-images",
			"Animate images",
			NULL,
			FALSE,
			G_PARAM_READWRITE));
}

static void
efh_init (EMFormatHTML *efh,
          EMFormatHTMLClass *klass)
{
	GdkColor *color;

	efh->priv = EM_FORMAT_HTML_GET_PRIVATE (efh);

	g_queue_init (&efh->pending_object_list);

	color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_BODY];
	gdk_color_parse ("#eeeeee", color);

	color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_CONTENT];
	gdk_color_parse ("#ffffff", color);

	color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_FRAME];
	gdk_color_parse ("#3f3f3f", color);

	color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_HEADER];
	gdk_color_parse ("#eeeeee", color);

	color = &efh->priv->colors[EM_FORMAT_HTML_COLOR_TEXT];
	gdk_color_parse ("#000000", color);

	efh->text_html_flags =
		CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
		CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
		CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
	efh->show_icon = TRUE;
}

GType
em_format_html_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0)) {
		static const GTypeInfo type_info = {
			sizeof (EMFormatHTMLClass),
			(GBaseInitFunc) efh_base_init,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) efh_class_init,
			(GClassFinalizeFunc) NULL,
			NULL,  /* class_data */
			sizeof (EMFormatHTML),
			0,     /* n_preallocs */
			(GInstanceInitFunc) efh_init,
			NULL   /* value_table */
		};

		static const GInterfaceInfo extensible_info = {
			(GInterfaceInitFunc) NULL,
			(GInterfaceFinalizeFunc) NULL,
			NULL   /* interface_data */
		};

		type = g_type_register_static (
			em_format_get_type(), "EMFormatHTML",
			&type_info, G_TYPE_FLAG_ABSTRACT);

		g_type_add_interface_static (
			type, E_TYPE_EXTENSIBLE, &extensible_info);
	}

	return type;
}

/*****************************************************************************/
void
em_format_html_get_color (EMFormatHTML *efh,
                          EMFormatHTMLColorType type,
                          GdkColor *color)
{
	GdkColor *format_color;

	g_return_if_fail (EM_IS_FORMAT_HTML (efh));
	g_return_if_fail (type < EM_FORMAT_HTML_NUM_COLOR_TYPES);
	g_return_if_fail (color != NULL);

	format_color = &efh->priv->colors[type];

	color->red   = format_color->red;
	color->green = format_color->green;
	color->blue  = format_color->blue;
}

void
em_format_html_set_color (EMFormatHTML *efh,
                          EMFormatHTMLColorType type,
                          const GdkColor *color)
{
	GdkColor *format_color;
	const gchar *property_name;

	g_return_if_fail (EM_IS_FORMAT_HTML (efh));
	g_return_if_fail (type < EM_FORMAT_HTML_NUM_COLOR_TYPES);
	g_return_if_fail (color != NULL);

	format_color = &efh->priv->colors[type];

	if (gdk_color_equal (color, format_color))
		return;

	format_color->red   = color->red;
	format_color->green = color->green;
	format_color->blue  = color->blue;

	switch (type) {
		case EM_FORMAT_HTML_COLOR_BODY:
			property_name = "body-color";
			break;
		case EM_FORMAT_HTML_COLOR_CITATION:
			property_name = "citation-color";
			break;
		case EM_FORMAT_HTML_COLOR_CONTENT:
			property_name = "content-color";
			break;
		case EM_FORMAT_HTML_COLOR_FRAME:
			property_name = "frame-color";
			break;
		case EM_FORMAT_HTML_COLOR_HEADER:
			property_name = "header-color";
			break;
		case EM_FORMAT_HTML_COLOR_TEXT:
			property_name = "text-color";
			break;
		default:
			g_return_if_reached ();
	}

	g_object_notify (G_OBJECT (efh), property_name);
}

EMailImageLoadingPolicy
em_format_html_get_image_loading_policy (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), 0);

	return efh->priv->image_loading_policy;
}

void
em_format_html_set_image_loading_policy (EMFormatHTML *efh,
                                         EMailImageLoadingPolicy policy)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	if (policy == efh->priv->image_loading_policy)
		return;

	efh->priv->image_loading_policy = policy;

	g_object_notify (G_OBJECT (efh), "image-loading-policy");
}

gboolean
em_format_html_get_mark_citations (EMFormatHTML *efh)
{
	guint32 flags;

	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	flags = efh->text_html_flags;

	return ((flags & CAMEL_MIME_FILTER_TOHTML_MARK_CITATION) != 0);
}

void
em_format_html_set_mark_citations (EMFormatHTML *efh,
                                   gboolean mark_citations)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	if (mark_citations)
		efh->text_html_flags |=
			CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;
	else
		efh->text_html_flags &=
			~CAMEL_MIME_FILTER_TOHTML_MARK_CITATION;

	g_object_notify (G_OBJECT (efh), "mark-citations");
}

gboolean
em_format_html_get_only_local_photos (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	return efh->priv->only_local_photos;
}

void
em_format_html_set_only_local_photos (EMFormatHTML *efh,
                                      gboolean only_local_photos)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	efh->priv->only_local_photos = only_local_photos;

	g_object_notify (G_OBJECT (efh), "only-local-photos");
}

gboolean
em_format_html_get_show_sender_photo (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	return efh->priv->show_sender_photo;
}

void
em_format_html_set_show_sender_photo (EMFormatHTML *efh,
                                      gboolean show_sender_photo)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	efh->priv->show_sender_photo = show_sender_photo;

	g_object_notify (G_OBJECT (efh), "show-sender-photo");
}

gboolean
em_format_html_get_show_real_date (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	return efh->priv->show_real_date;
}

void
em_format_html_set_show_real_date (EMFormatHTML *efh,
                                   gboolean show_real_date)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	efh->priv->show_real_date = show_real_date;

	g_object_notify (G_OBJECT (efh), "show-real-date");
}

gboolean
em_format_html_get_animate_images (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	return efh->priv->animate_images;
}

void
em_format_html_set_animate_images (EMFormatHTML *efh,
                                   gboolean animate_images)
{
	g_return_if_fail (EM_IS_FORMAT_HTML (efh));

	efh->priv->animate_images = animate_images;

	g_object_notify (G_OBJECT (efh), "animate-images");
}

CamelMimePart *
em_format_html_file_part (EMFormatHTML *efh,
                          const gchar *mime_type,
                          const gchar *filename,
                          GCancellable *cancellable)
{
	CamelMimePart *part;
	CamelStream *stream;
	CamelDataWrapper *dw;
	gchar *basename;

	stream = camel_stream_fs_new_with_name (filename, O_RDONLY, 0, NULL);
	if (stream == NULL)
		return NULL;

	dw = camel_data_wrapper_new ();
	camel_data_wrapper_construct_from_stream_sync (
		dw, stream, cancellable, NULL);
	g_object_unref (stream);
	if (mime_type)
		camel_data_wrapper_set_mime_type (dw, mime_type);
	part = camel_mime_part_new ();
	camel_medium_set_content ((CamelMedium *) part, dw);
	g_object_unref (dw);
	basename = g_path_get_basename (filename);
	camel_mime_part_set_filename (part, basename);
	g_free (basename);

	return part;
}

void
em_format_html_format_cert_infos (GQueue *cert_infos,
                                  GString *output_buffer)
{
	GQueue valid = G_QUEUE_INIT;
	GList *head, *link;

	g_return_if_fail (cert_infos != NULL);
	g_return_if_fail (output_buffer != NULL);

	head = g_queue_peek_head_link (cert_infos);

	/* 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;

		if ((cinfo->name != NULL && *cinfo->name != '\0') ||
		    (cinfo->email != NULL && *cinfo->email != '\0')) {
			g_queue_push_tail (&valid, cinfo);
		}
	}

	if (g_queue_is_empty (&valid))
		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, ')');
}

static void
efh_format_text_header (EMFormatHTML *emfh,
                        GString *buffer,
                        const gchar *label,
                        const gchar *value,
                        guint32 flags)
{
	const gchar *fmt, *html;
	gchar *mhtml = NULL;
	gboolean is_rtl;

	if (value == NULL)
		return;

	while (*value == ' ')
		value++;

	if (!(flags & EM_FORMAT_HTML_HEADER_HTML))
		html = mhtml = camel_text_to_html (value, emfh->text_html_flags, 0);
	else
		html = value;

	is_rtl = gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL;

	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>&nbsp;</b></th></tr>";
		else
			fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s<b>&nbsp;</b></th><td valign=top>%s</td></tr>";
	} else {
		if (flags & EM_FORMAT_HEADER_BOLD) {
			if (is_rtl)
				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>&nbsp;</b></th></tr>";
			else
				fmt = "<tr class=\"header-item\" style=\"display: %s\"><th align=\"right\" valign=\"top\" nowrap>%s:<b>&nbsp;</b></th><td>%s</td></tr>";
		} else {
			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>&nbsp;</b></td></tr>";
			else
				fmt = "<tr class=\"header-item\" style=\"display: %s\"><td align=\"right\" valign=\"top\" nowrap>%s:<b>&nbsp;</b></td><td>%s</td></tr>";
		}
	}

	g_string_append_printf (buffer, fmt,
		(flags & EM_FORMAT_HTML_HEADER_HIDDEN ? "none" : "table-row"), label, html);

	g_free (mhtml);
}

static const gchar *addrspec_hdrs[] = {
	"Sender", "From", "Reply-To", "To", "Cc", "Bcc",
	"Resent-Sender", "Resent-From", "Resent-Reply-To",
	"Resent-To", "Resent-Cc", "Resent-Bcc", NULL
};

static gchar *
efh_format_address (EMFormatHTML *efh,
                    GString *out,
                    struct _camel_header_address *a,
                    gchar *field,
                    gboolean no_links)
{
	guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES;
	gchar *name, *mailto, *addr;
	gint i = 0;
	gchar *str = NULL;
	gint limit = mail_config_get_address_count ();

	while (a) {
		if (a->name)
			name = camel_text_to_html (a->name, flags, 0);
		else
			name = NULL;

		switch (a->type) {
		case CAMEL_HEADER_ADDRESS_NAME:
			if (name && *name) {
				gchar *real, *mailaddr;

				if (strchr (a->name, ',') || strchr (a->name, ';'))
					g_string_append_printf (out, "&quot;%s&quot;", name);
				else
					g_string_append (out, name);

				g_string_append (out, " &lt;");

				/* rfc2368 for mailto syntax and url encoding extras */
				if ((real = camel_header_encode_phrase ((guchar *) a->name))) {
					mailaddr = g_strdup_printf("%s <%s>", real, a->v.addr);
					g_free (real);
					mailto = camel_url_encode (mailaddr, "?=&()");
					g_free (mailaddr);
				} else {
					mailto = camel_url_encode (a->v.addr, "?=&()");
				}
			} else {
				mailto = camel_url_encode (a->v.addr, "?=&()");
			}
			addr = camel_text_to_html (a->v.addr, flags, 0);
			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);

			if (name && *name)
				g_string_append (out, "&gt;");
			break;
		case CAMEL_HEADER_ADDRESS_GROUP:
			g_string_append_printf (out, "%s: ", name);
			efh_format_address (efh, out, a->v.members, field, no_links);
			g_string_append_printf (out, ";");
			break;
		default:
			g_warning ("Invalid address type");
			break;
		}

		g_free (name);

		i++;
		a = a->next;
		if (a)
			g_string_append (out, ", ");

		/* Let us add a '...' if we have more addresses */
		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";
			}

			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 (str) {
		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";
		}

		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
canon_header_name (gchar *name)
{
	gchar *inptr = name;

	/* canonicalise the header name... first letter is
	 * capitalised and any letter following a '-' also gets
	 * capitalised */

	if (*inptr >= 'a' && *inptr <= 'z')
		*inptr -= 0x20;

	inptr++;

	while (*inptr) {
		if (inptr[-1] == '-' && *inptr >= 'a' && *inptr <= 'z')
			*inptr -= 0x20;
		else if (*inptr >= 'A' && *inptr <= 'Z')
			*inptr += 0x20;

		inptr++;
	}
}

void
em_format_html_format_header (EMFormat *emf,
                              GString *buffer,
                              CamelMedium *part,
                              struct _camel_header_raw *header,
                              guint32 flags,
                              const gchar *charset)
{
	EMFormatHTML *efh = EM_FORMAT_HTML (emf);
	gchar *name, *buf, *value = NULL;
	const gchar *label, *txt;
	gboolean addrspec = FALSE;
	gchar *str_field = NULL;
	gint i;

	name = g_alloca (strlen (header->name) + 1);
	strcpy (name, header->name);
	canon_header_name (name);

	for (i = 0; addrspec_hdrs[i]; i++) {
		if (!strcmp (name, addrspec_hdrs[i])) {
			addrspec = TRUE;
			break;
		}
	}

	label = _(name);

	if (addrspec) {
		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, charset))) {
			g_free (buf);
			return;
		}

		g_free (buf);

		html = g_string_new("");
		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);
			label = str_field;
			flags |= EM_FORMAT_HTML_HEADER_NODEC;
			g_free (img);
		}

		camel_header_address_list_clear (&addrs);
		txt = value = html->str;
		g_string_free (html, FALSE);

		flags |= EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_HTML;
	} else if (!strcmp (name, "Subject")) {
		buf = camel_header_unfold (header->value);
		txt = value = camel_header_decode_string (buf, charset);
		g_free (buf);

		flags |= EM_FORMAT_HEADER_BOLD;
	} else if (!strcmp(name, "X-evolution-mailer")) {
		/* pseudo-header */
		label = _("Mailer");
		txt = value = camel_header_format_ctext (header->value, charset);
		flags |= EM_FORMAT_HEADER_BOLD;
	} else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) {
		gint msg_offset, local_tz;
		time_t msg_date;
		struct tm local;
		gchar *html;
		gboolean hide_real_date;

		hide_real_date = !em_format_html_get_show_real_date (efh);

		txt = header->value;
		while (*txt == ' ' || *txt == '\t')
			txt++;

		html = camel_text_to_html (txt, efh->text_html_flags, 0);

		msg_date = camel_header_decode_date (txt, &msg_offset);
		e_localtime_with_offset (msg_date, &local, &local_tz);

		/* Convert message offset to minutes (e.g. -0400 --> -240) */
		msg_offset = ((msg_offset / 100) * 60) + (msg_offset % 100);
		/* Turn into offset from localtime, not UTC */
		msg_offset -= local_tz / 60;

		/* value will be freed at the end */
		if (!hide_real_date && !msg_offset) {
			/* No timezone difference; just show the real Date: header */
			txt = value = html;
		} else {
			gchar *date_str;

			date_str = e_datetime_format_format ("mail", "header",
							     DTFormatKindDateTime, msg_date);

			if (hide_real_date) {
				/* Show only the local-formatted date, losing all timezone
				 * information like Outlook does. Should we attempt to show
				 * it somehow? */
				txt = value = date_str;
			} else {
				txt = value = g_strdup_printf ("%s (<I>%s</I>)", html, date_str);
				g_free (date_str);
			}
			g_free (html);
		}
		flags |= EM_FORMAT_HTML_HEADER_HTML | EM_FORMAT_HEADER_BOLD;
	} else if (!strcmp(name, "Newsgroups")) {
		struct _camel_header_newsgroup *ng, *scan;
		GString *html;

		buf = camel_header_unfold (header->value);

		if (!(ng = camel_header_newsgroups_decode (buf))) {
			g_free (buf);
			return;
		}

		g_free (buf);

		html = g_string_new("");
		scan = ng;
		while (scan) {
			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, ", ");
		}

		camel_header_newsgroups_free (ng);

		txt = html->str;
		g_string_free (html, FALSE);
		flags |= EM_FORMAT_HEADER_BOLD | EM_FORMAT_HTML_HEADER_HTML;
	} else if (!strcmp (name, "Received") || !strncmp (name, "X-", 2)) {
		/* don't unfold Received nor extension headers */
		txt = value = camel_header_decode_string (header->value, charset);
	} else {
		/* don't unfold Received nor extension headers */
		buf = camel_header_unfold (header->value);
		txt = value = camel_header_decode_string (buf, charset);
		g_free (buf);
	}

	efh_format_text_header (efh, buffer, label, txt, flags);

	g_free (value);
	g_free (str_field);
}

static void
efh_format_short_headers (EMFormatHTML *efh,
                          GString *buffer,
                          CamelMedium *part,
                          gboolean visible,
                          GCancellable *cancellable)
{
	EMFormat *emf = EM_FORMAT (efh);
	const gchar *charset;
	CamelContentType *ct;
	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 (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);
	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, 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=\"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_free (from, TRUE);
	g_free (evolution_imagesdir);
}

static void
write_contact_picture (CamelMimePart *part,
                       gint size,
                       GString *buffer)
{
	gchar *b64, *content_type;
	CamelDataWrapper *dw;
	CamelContentType *ct;
	GByteArray *ba;

	ba = NULL;
	dw = camel_medium_get_content (CAMEL_MEDIUM (part));
	if (dw) {
		ba = camel_data_wrapper_get_byte_array (dw);
	}

	if (!ba || ba->len == 0) {

		if (camel_mime_part_get_filename (part)) {

			if (size >= 0) {
				g_string_append_printf (
					buffer,
					"<img width=\"%d\" src=\"evo-file://%s\" />",
					size, camel_mime_part_get_filename (part));
			} else {
				g_string_append_printf (
					buffer,
					"<img src=\"evo-file://%s\" />",
					camel_mime_part_get_filename (part));
			}
		}

		return;
	}

	b64 = g_base64_encode (ba->data, ba->len);
	ct = camel_mime_part_get_content_type (part);
	content_type = camel_content_type_simple (ct);

	if (size >= 0) {
		g_string_append_printf (
			buffer,
			"<img width=\"%d\" src=\"data:%s;base64,%s\">",
			size, content_type, b64);
	} else {
		g_string_append_printf (
			buffer,
			"<img src=\"data:%s;base64,%s\">",
			content_type, b64);
	}

	g_free (b64);
	g_free (content_type);
}

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;
	CamelSession *session;
	ESourceRegistry *registry;
	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;

	session = em_format_get_session (emf);
	registry = e_mail_session_get_registry (E_MAIL_SESSION (session));

	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) {
		if (!g_ascii_strcasecmp (header->name, "Sender")) {
			struct _camel_header_address *addrs;
			GString *html;

			if (!(addrs = camel_header_address_decode (header->value, hdr_charset)))
				break;

			html = g_string_new("");
			name = efh_format_address (efh, html, addrs, header->name, FALSE);

			header_sender = html->str;
			camel_header_address_list_clear (&addrs);

			g_string_free (html, FALSE);
			g_free (name);
		} else if (!g_ascii_strcasecmp (header->name, "From")) {
			struct _camel_header_address *addrs;
			GString *html;

			if (!(addrs = camel_header_address_decode (header->value, hdr_charset)))
				break;

			html = g_string_new("");
			name = efh_format_address (efh, html, addrs, header->name, FALSE);

			header_from = html->str;
			camel_header_address_list_clear (&addrs);

			g_string_free (html, FALSE);
			g_free (name);
		} else if (!g_ascii_strcasecmp (header->name, "X-Evolution-Mail-From-Delegate")) {
			mail_from_delegate = TRUE;
		}

		header = header->next;
	}

	if (header_sender && header_from && mail_from_delegate) {
		gchar *bold_sender, *bold_from;

		g_string_append (
			buffer,
			"<tr><td><table border=1 width=\"100%%\" "
			"cellspacing=2 cellpadding=2><tr>");
		if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL)
			g_string_append (
				buffer, "<td align=\"right\" width=\"100%%\">");
		else
			g_string_append (
				buffer, "<td align=\"left\" width=\"100%%\">");
		bold_sender = g_strconcat ("<b>", header_sender, "</b>", NULL);
		bold_from = g_strconcat ("<b>", header_from, "</b>", NULL);
		/* Translators: This message suggests to the receipients
		 * that the sender of the mail is different from the one
		 * listed in From field. */
		g_string_append_printf (
			buffer,
			_("This message was sent by %s on behalf of %s"),
			bold_sender, bold_from);
		g_string_append (buffer, "</td></tr></table></td></tr>");
		g_free (bold_sender);
		g_free (bold_from);
	}

	g_free (header_sender);
	g_free (header_from);

	g_string_append (buffer, "<tr><td width=\"100%%\"><table border=0 cellpadding=\"0\">\n");

	g_free (evolution_imagesdir);

	/* dump selected headers */
	if (all_headers) {
		header = ((CamelMimePart *) part)->headers;
		while (header) {
			em_format_html_format_header (
				emf, buffer, part, header,
				EM_FORMAT_HTML_HEADER_NOCOLUMNS, charset);
			header = header->next;
		}
	} else {
		GList *link;
		gint mailer_shown = FALSE;

		link = g_queue_peek_head_link (&emf->header_list);

		while (link != NULL) {
			EMFormatHeader *h = link->data;
			gint mailer, face;

			header = ((CamelMimePart *) part)->headers;
			mailer = !g_ascii_strcasecmp (h->name, "X-Evolution-Mailer");
			face = !g_ascii_strcasecmp (h->name, "Face");

			while (header) {
				if (em_format_html_get_show_sender_photo (efh) &&
					!photo_name && !g_ascii_strcasecmp (header->name, "From"))
					photo_name = header->value;

				if (!mailer_shown && mailer && (
				    !g_ascii_strcasecmp (header->name, "X-Mailer") ||
				    !g_ascii_strcasecmp (header->name, "User-Agent") ||
				    !g_ascii_strcasecmp (header->name, "X-Newsreader") ||
				    !g_ascii_strcasecmp (header->name, "X-MimeOLE"))) {
					struct _camel_header_raw xmailer, *use_header = NULL;

					if (!g_ascii_strcasecmp (header->name, "X-MimeOLE")) {
						for (use_header = header->next; use_header; use_header = use_header->next) {
							if (!g_ascii_strcasecmp (use_header->name, "X-Mailer") ||
							    !g_ascii_strcasecmp (use_header->name, "User-Agent") ||
							    !g_ascii_strcasecmp (use_header->name, "X-Newsreader")) {
								/* even we have X-MimeOLE, then use rather the standard one, when available */
								break;
							}
						}
					}

					if (!use_header)
						use_header = header;

					xmailer.name = (gchar *) "X-Evolution-Mailer";
					xmailer.value = use_header->value;
					mailer_shown = TRUE;

					em_format_html_format_header (
						emf, buffer, part,
						&xmailer, h->flags, charset);
					if (strstr(use_header->value, "Evolution"))
						have_icon = TRUE;
				} else if (!face_decoded && face && !g_ascii_strcasecmp (header->name, "Face")) {
					gchar *cp = header->value;

					/* Skip over spaces */
					while (*cp == ' ')
						cp++;

					face_header_value = g_base64_decode (
						cp, &face_header_len);
					face_header_value = g_realloc (
						face_header_value,
						face_header_len + 1);
					face_header_value[face_header_len] = 0;
					face_decoded = TRUE;
				/* Showing an encoded "Face" header makes little sense */
				} else if (!g_ascii_strcasecmp (header->name, h->name) && !face) {
					em_format_html_format_header (
						emf, buffer, part,
						header, h->flags, charset);
				}

				header = header->next;
			}

			link = g_list_next (link);
		}
	}

	g_string_append (buffer, "</table></td>");

	if (photo_name) {
		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 (
			registry, cia, only_local_photo);

		if (photopart) {
			g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
			write_contact_picture (photopart, -1, buffer);
			g_string_append (buffer, "</td>");
			g_object_unref (photopart);
		}
		g_object_unref (cia);
	}

	if (!contact_has_photo && face_decoded) {
		CamelMimePart *part;

		part = camel_mime_part_new ();
		camel_mime_part_set_content (
			(CamelMimePart *) part,
			(const gchar *) face_header_value,
			face_header_len, "image/png");

		g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
		write_contact_picture (part, 48, buffer);
		g_string_append (buffer, "</td>");

		g_object_unref (part);
		g_free (face_header_value);
	}

	if (have_icon && efh->show_icon) {
		GtkIconInfo *icon_info;
		CamelMimePart *iconpart = NULL;

		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) {
			g_string_append (buffer, "<td align=\"right\" valign=\"top\">");
			write_contact_picture (iconpart, 16, buffer);
			g_string_append (buffer, "</td>");

			g_object_unref (iconpart);
		}
	}

	g_string_append (buffer, "</tr></table>");
}

gboolean
em_format_html_can_load_images (EMFormatHTML *efh)
{
	g_return_val_if_fail (EM_IS_FORMAT_HTML (efh), FALSE);

	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));
}

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);

	if ((anim == NULL) || (anim->data == NULL)) {
		*frame = NULL;
		*len = 0;
		return;
	}

        /* 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;
	}

	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) {

                *frame = g_memdup (anim->data, anim->len);
                *len = anim->len;
		g_object_unref (loader);
		return;
	}

        /* 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;
	}

        /* 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);

	g_object_unref (loader);
}