/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 * 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:
 *		Jeffrey Stedfast <fejj@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

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

#include <string.h>
#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include <libevolution-utils/e-alert-dialog.h>
#include <libevolution-utils/e-alert-sink.h>
#include <e-util/e-util.h>

#include <libemail-utils/mail-mt.h>

#include <libemail-engine/e-mail-folder-utils.h>
#include <libemail-engine/e-mail-session.h>
#include <libemail-engine/e-mail-session-utils.h>
#include <libemail-engine/e-mail-utils.h>
#include <libemail-engine/mail-ops.h>
#include <libemail-engine/mail-tools.h>

#include <em-format/e-mail-parser.h>
#include <em-format/e-mail-formatter-quote.h>

#include <shell/e-shell.h>

#include <composer/e-msg-composer.h>
#include <composer/e-composer-actions.h>
#include <composer/e-composer-post-header.h>

#include "e-mail-printer.h"
#include "em-utils.h"
#include "em-composer-utils.h"
#include "em-folder-selector.h"
#include "em-folder-tree.h"
#include "em-event.h"
#include "mail-send-recv.h"

#ifdef G_OS_WIN32
#ifdef gmtime_r
#undef gmtime_r
#endif

/* The gmtime() in Microsoft's C library is MT-safe */
#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
#endif

typedef struct _AsyncContext AsyncContext;
typedef struct _ForwardData ForwardData;

struct _AsyncContext {
	CamelMimeMessage *message;
	EMailSession *session;
	EMsgComposer *composer;
	EActivity *activity;
	EMailReader *reader;
	GPtrArray *ptr_array;
	EMailForwardStyle style;
	gchar *folder_uri;
	gchar *message_uid;
	gboolean replace;
	GtkWidget *destroy_when_done;
};

struct _ForwardData {
	EShell *shell;
	CamelFolder *folder;
	GPtrArray *uids;
	EMailForwardStyle style;
};

static void
async_context_free (AsyncContext *context)
{
	if (context->message != NULL)
		g_object_unref (context->message);

	if (context->session != NULL)
		g_object_unref (context->session);

	if (context->composer != NULL)
		g_object_unref (context->composer);

	if (context->activity != NULL)
		g_object_unref (context->activity);

	if (context->reader != NULL)
		g_object_unref (context->reader);

	if (context->ptr_array != NULL)
		g_ptr_array_unref (context->ptr_array);

	if (context->destroy_when_done != NULL)
		gtk_widget_destroy (context->destroy_when_done);

	g_free (context->folder_uri);
	g_free (context->message_uid);

	g_slice_free (AsyncContext, context);
}

static void
forward_data_free (ForwardData *data)
{
	if (data->shell != NULL)
		g_object_unref (data->shell);

	if (data->folder != NULL)
		g_object_unref (data->folder);

	if (data->uids != NULL)
		em_utils_uids_free (data->uids);

	g_slice_free (ForwardData, data);
}

static gboolean
ask_confirm_for_unwanted_html_mail (EMsgComposer *composer,
                                    EDestination **recipients)
{
	gboolean res;
	GString *str;
	gint i;

	str = g_string_new ("");
	for (i = 0; recipients[i] != NULL; ++i) {
		if (!e_destination_get_html_mail_pref (recipients[i])) {
			const gchar *name;

			name = e_destination_get_textrep (recipients[i], FALSE);

			g_string_append_printf (str, "     %s\n", name);
		}
	}

	if (str->len)
		res = em_utils_prompt_user (
			GTK_WINDOW (composer),
			"prompt-on-unwanted-html",
			"mail:ask-send-html", str->str, NULL);
	else
		res = TRUE;

	g_string_free (str, TRUE);

	return res;
}

static gboolean
ask_confirm_for_empty_subject (EMsgComposer *composer)
{
	return em_utils_prompt_user (
		GTK_WINDOW (composer),
		"prompt-on-empty-subject",
		"mail:ask-send-no-subject", NULL);
}

static gboolean
ask_confirm_for_only_bcc (EMsgComposer *composer,
                          gboolean hidden_list_case)
{
	/* If the user is mailing a hidden contact list, it is possible for
	 * them to create a message with only Bcc recipients without really
	 * realizing it.  To try to avoid being totally confusing, I've changed
	 * this dialog to provide slightly different text in that case, to
	 * better explain what the hell is going on. */

	return em_utils_prompt_user (
		GTK_WINDOW (composer),
		"prompt-on-only-bcc",
		hidden_list_case ?
		"mail:ask-send-only-bcc-contact" :
		"mail:ask-send-only-bcc", NULL);
}

static gboolean
is_group_definition (const gchar *str)
{
	const gchar *colon;

	if (!str || !*str)
		return FALSE;

	colon = strchr (str, ':');
	return colon > str && strchr (str, ';') > colon;
}

static gboolean
composer_presend_check_recipients (EMsgComposer *composer,
                                   EMailSession *session)
{
	EDestination **recipients;
	EDestination **recipients_bcc;
	CamelInternetAddress *cia;
	EComposerHeaderTable *table;
	EComposerHeader *post_to_header;
	GString *invalid_addrs = NULL;
	gboolean check_passed = FALSE;
	gint hidden = 0;
	gint shown = 0;
	gint num = 0;
	gint num_bcc = 0;
	gint num_post = 0;
	gint ii;

	/* We should do all of the validity checks based on the composer,
	 * and not on the created message, as extra interaction may occur
	 * when we get the message (e.g. passphrase to sign a message). */

	table = e_msg_composer_get_header_table (composer);
	recipients = e_composer_header_table_get_destinations (table);

	cia = camel_internet_address_new ();

	/* See which ones are visible, present, etc. */
	for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) {
		const gchar *addr;
		gint len, j;

		addr = e_destination_get_address (recipients[ii]);
		if (addr == NULL || *addr == '\0')
			continue;

		camel_address_decode (CAMEL_ADDRESS (cia), addr);
		len = camel_address_length (CAMEL_ADDRESS (cia));

		if (len > 0) {
			if (!e_destination_is_evolution_list (recipients[ii])) {
				for (j = 0; j < len; j++) {
					const gchar *name = NULL, *eml = NULL;

					if (!camel_internet_address_get (cia, j, &name, &eml) ||
					    !eml ||
					    strchr (eml, '@') <= eml) {
						if (!invalid_addrs)
							invalid_addrs = g_string_new ("");
						else
							g_string_append (invalid_addrs, ", ");

						if (name)
							g_string_append (invalid_addrs, name);
						if (eml) {
							g_string_append (invalid_addrs, name ? " <" : "");
							g_string_append (invalid_addrs, eml);
							g_string_append (invalid_addrs, name ? ">" : "");
						}
					}
				}
			}

			camel_address_remove (CAMEL_ADDRESS (cia), -1);
			num++;
			if (e_destination_is_evolution_list (recipients[ii])
			    && !e_destination_list_show_addresses (recipients[ii])) {
				hidden++;
			} else {
				shown++;
			}
		} else if (is_group_definition (addr)) {
			/* like an address, it will not claim on only-bcc */
			shown++;
			num++;
		} else if (!invalid_addrs) {
			invalid_addrs = g_string_new (addr);
		} else {
			g_string_append (invalid_addrs, ", ");
			g_string_append (invalid_addrs, addr);
		}
	}

	recipients_bcc = e_composer_header_table_get_destinations_bcc (table);
	if (recipients_bcc) {
		for (ii = 0; recipients_bcc[ii] != NULL; ii++) {
			const gchar *addr;

			addr = e_destination_get_address (recipients_bcc[ii]);
			if (addr == NULL || *addr == '\0')
				continue;

			camel_address_decode (CAMEL_ADDRESS (cia), addr);
			if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) {
				camel_address_remove (CAMEL_ADDRESS (cia), -1);
				num_bcc++;
			}
		}

		e_destination_freev (recipients_bcc);
	}

	g_object_unref (cia);

	post_to_header = e_composer_header_table_get_header (
		table, E_COMPOSER_HEADER_POST_TO);
	if (e_composer_header_get_visible (post_to_header)) {
		GList *postlist;

		postlist = e_composer_header_table_get_post_to (table);
		num_post = g_list_length (postlist);
		g_list_foreach (postlist, (GFunc) g_free, NULL);
		g_list_free (postlist);
	}

	/* I'm sensing a lack of love, er, I mean recipients. */
	if (num == 0 && num_post == 0) {
		e_alert_submit (
			E_ALERT_SINK (composer),
			"mail:send-no-recipients", NULL);
		goto finished;
	}

	if (invalid_addrs) {
		if (!em_utils_prompt_user (
			GTK_WINDOW (composer),
			"prompt-on-invalid-recip",
			strstr (invalid_addrs->str, ", ") ?
				"mail:ask-send-invalid-recip-multi" :
				"mail:ask-send-invalid-recip-one",
			invalid_addrs->str, NULL)) {
			g_string_free (invalid_addrs, TRUE);
			goto finished;
		}

		g_string_free (invalid_addrs, TRUE);
	}

	if (num > 0 && (num == num_bcc || shown == 0)) {
		/* this means that the only recipients are Bcc's */
		if (!ask_confirm_for_only_bcc (composer, shown == 0))
			goto finished;
	}

	check_passed = TRUE;

finished:
	if (recipients != NULL)
		e_destination_freev (recipients);

	return check_passed;
}

static gboolean
composer_presend_check_identity (EMsgComposer *composer,
                                 EMailSession *session)
{
	EComposerHeaderTable *table;
	ESourceRegistry *registry;
	ESource *source;
	const gchar *uid;
	gboolean success = TRUE;

	table = e_msg_composer_get_header_table (composer);
	registry = e_composer_header_table_get_registry (table);
	uid = e_composer_header_table_get_identity_uid (table);
	source = e_source_registry_ref_source (registry, uid);
	g_return_val_if_fail (source != NULL, FALSE);

	if (!e_source_get_enabled (source)) {
		e_alert_submit (
			E_ALERT_SINK (composer),
			"mail:send-no-account-enabled", NULL);
		success = FALSE;
	}

	g_object_unref (source);

	return success;
}

static gboolean
composer_presend_check_downloads (EMsgComposer *composer,
                                  EMailSession *session)
{
	EAttachmentView *view;
	EAttachmentStore *store;
	gboolean check_passed = TRUE;

	view = e_msg_composer_get_attachment_view (composer);
	store = e_attachment_view_get_store (view);

	if (e_attachment_store_get_num_loading (store) > 0) {
		if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL,
		    "mail-composer:ask-send-message-pending-download", NULL))
			check_passed = FALSE;
	}

	return check_passed;
}

static gboolean
composer_presend_check_plugins (EMsgComposer *composer,
                                EMailSession *session)
{
	EMEvent *eme;
	EMEventTargetComposer *target;
	gpointer data;

	/** @Event: composer.presendchecks
	 * @Title: Composer PreSend Checks
	 * @Target: EMEventTargetMessage
	 *
	 * composer.presendchecks is emitted during pre-checks for the
	 * message just before sending.  Since the e-plugin framework
	 * doesn't provide a way to return a value from the plugin,
	 * use 'presend_check_status' to set whether the check passed.
	 */
	eme = em_event_peek ();
	target = em_event_target_new_composer (eme, composer, 0);

	e_event_emit (
		(EEvent *) eme, "composer.presendchecks",
		(EEventTarget *) target);

	/* A non-NULL value for this key means the check failed. */
	data = g_object_get_data (G_OBJECT (composer), "presend_check_status");

	/* Clear the value in case we have to run these checks again. */
	g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL);

	return (data == NULL);
}

static gboolean
composer_presend_check_subject (EMsgComposer *composer,
                                EMailSession *session)
{
	EComposerHeaderTable *table;
	const gchar *subject;
	gboolean check_passed = TRUE;

	table = e_msg_composer_get_header_table (composer);
	subject = e_composer_header_table_get_subject (table);

	if (subject == NULL || subject[0] == '\0') {
		if (!ask_confirm_for_empty_subject (composer))
			check_passed = FALSE;
	}

	return check_passed;
}

static gboolean
composer_presend_check_unwanted_html (EMsgComposer *composer,
                                      EMailSession *session)
{
	EDestination **recipients;
	EComposerHeaderTable *table;
	GSettings *settings;
	gboolean check_passed = TRUE;
	gboolean html_mode;
	gboolean send_html;
	gboolean confirm_html;
	gint ii;

	settings = g_settings_new ("org.gnome.evolution.mail");

	table = e_msg_composer_get_header_table (composer);
	recipients = e_composer_header_table_get_destinations (table);
	html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer));

	send_html = g_settings_get_boolean (settings, "composer-send-html");
	confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");

	/* Only show this warning if our default is to send html.  If it
	 * isn't, we've manually switched into html mode in the composer
	 * and (presumably) had a good reason for doing this. */
	if (html_mode && send_html && confirm_html && recipients != NULL) {
		gboolean html_problem = FALSE;

		for (ii = 0; recipients[ii] != NULL; ii++) {
			if (!e_destination_get_html_mail_pref (recipients[ii]))
				html_problem = TRUE;
				break;
		}

		if (html_problem) {
			if (!ask_confirm_for_unwanted_html_mail (
				composer, recipients))
				check_passed = FALSE;
		}
	}

	if (recipients != NULL)
		e_destination_freev (recipients);

	g_object_unref (settings);

	return check_passed;
}

static void
composer_send_completed (EMailSession *session,
                         GAsyncResult *result,
                         AsyncContext *context)
{
	GError *error = NULL;
	gboolean set_changed = FALSE;

	e_mail_session_send_to_finish (session, result, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_error_free (error);
		set_changed = TRUE;
		goto exit;
	}

	/* Post-processing errors are shown in the shell window. */
	if (g_error_matches (error, E_MAIL_ERROR, E_MAIL_ERROR_POST_PROCESSING)) {
		EAlert *alert;
		EShell *shell;

		shell = e_msg_composer_get_shell (context->composer);

		alert = e_alert_new (
			"mail-composer:send-post-processing-error",
			error->message, NULL);
		e_shell_submit_alert (shell, alert);
		g_object_unref (alert);

	/* All other errors are shown in the composer window. */
	} else if (error != NULL) {
		gint response;

		/* Clear the activity bar before
		 * presenting the error dialog. */
		g_object_unref (context->activity);
		context->activity = NULL;

		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (context->composer),
			"mail-composer:send-error",
			error->message, NULL);
		if (response == GTK_RESPONSE_OK)  /* Try Again */
			e_msg_composer_send (context->composer);
		if (response == GTK_RESPONSE_ACCEPT)  /* Save to Outbox */
			e_msg_composer_save_to_outbox (context->composer);
		g_error_free (error);
		set_changed = TRUE;
		goto exit;
	}

	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (context->activity), (GWeakNotify)
		gtk_widget_destroy, context->composer);

exit:
	if (set_changed) {
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		gtk_window_present (GTK_WINDOW (context->composer));
	}

	async_context_free (context);
}

static void
em_utils_composer_send_cb (EMsgComposer *composer,
                           CamelMimeMessage *message,
                           EActivity *activity,
                           EMailSession *session)
{
	AsyncContext *context;
	GCancellable *cancellable;

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);

	e_mail_session_send_to (
		session, message,
		G_PRIORITY_DEFAULT, cancellable, NULL, NULL,
		(GAsyncReadyCallback) composer_send_completed,
		context);
}

static void
composer_set_no_change (EMsgComposer *composer)
{
	GtkhtmlEditor *editor;

	g_return_if_fail (composer != NULL);

	editor = GTKHTML_EDITOR (composer);

	gtkhtml_editor_drop_undo (editor);
	gtkhtml_editor_set_changed (editor, FALSE);
}

/* delete original messages from Outbox folder */
static void
manage_x_evolution_replace_outbox (EMsgComposer *composer,
                                   EMailSession *session,
                                   CamelMimeMessage *message,
                                   GCancellable *cancellable)
{
	const gchar *message_uid;
	const gchar *header;
	CamelFolder *outbox;

	g_return_if_fail (composer != NULL);
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	header = "X-Evolution-Replace-Outbox-UID";
	message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header);
	e_msg_composer_remove_header (composer, header);

	if (!message_uid)
		return;

	outbox = e_mail_session_get_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX);
	g_return_if_fail (outbox != NULL);

	camel_folder_set_message_flags (
		outbox, message_uid,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);

	/* ignore errors here */
	camel_folder_synchronize_message_sync (
		outbox, message_uid, cancellable, NULL);
}

static void
composer_save_to_drafts_complete (EMailSession *session,
                                  GAsyncResult *result,
                                  AsyncContext *context)
{
	GError *error = NULL;

	/* We don't really care if this failed.  If something other than
	 * cancellation happened, emit a runtime warning so the error is
	 * not completely lost. */

	e_mail_session_handle_draft_headers_finish (session, result, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		g_error_free (error);

	} else if (error != NULL) {
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		g_warning ("%s", error->message);
		g_error_free (error);

	} else
		e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	/* Encode the draft message we just saved into the EMsgComposer
	 * as X-Evolution-Draft headers.  The message will be marked for
	 * deletion if the user saves a newer draft message or sends the
	 * composed message. */
	e_msg_composer_set_draft_headers (
		context->composer, context->folder_uri,
		context->message_uid);

	async_context_free (context);
}

static void
composer_save_to_drafts_cleanup (CamelFolder *drafts_folder,
                                 GAsyncResult *result,
                                 AsyncContext *context)
{
	CamelSession *session;
	EAlertSink *alert_sink;
	GCancellable *cancellable;
	GError *error = NULL;

	session = e_msg_composer_get_session (context->composer);
	alert_sink = e_activity_get_alert_sink (context->activity);
	cancellable = e_activity_get_cancellable (context->activity);

	e_mail_folder_append_message_finish (
		drafts_folder, result, &context->message_uid, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_warn_if_fail (context->message_uid == NULL);
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		async_context_free (context);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (context->message_uid == NULL);
		e_alert_submit (
			alert_sink,
			"mail-composer:save-to-drafts-error",
			error->message, NULL);
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		async_context_free (context);
		g_error_free (error);
		return;
	}

	/* Mark the previously saved draft message for deletion.
	 * Note: This is just a nice-to-have; ignore failures. */
	e_mail_session_handle_draft_headers (
		E_MAIL_SESSION (session), context->message,
		G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
		composer_save_to_drafts_complete, context);
}

static void
composer_save_to_drafts_append_mail (AsyncContext *context,
                                     CamelFolder *drafts_folder)
{
	CamelFolder *local_drafts_folder;
	GCancellable *cancellable;
	CamelMessageInfo *info;

	local_drafts_folder =
		e_mail_session_get_local_folder (
		context->session, E_MAIL_LOCAL_FOLDER_DRAFTS);

	if (drafts_folder == NULL)
		drafts_folder = g_object_ref (local_drafts_folder);

	cancellable = e_activity_get_cancellable (context->activity);

	info = camel_message_info_new (NULL);

	camel_message_info_set_flags (
		info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0);

	camel_medium_remove_header (
		CAMEL_MEDIUM (context->message),
		"X-Evolution-Replace-Outbox-UID");

	e_mail_folder_append_message (
		drafts_folder, context->message,
		info, G_PRIORITY_DEFAULT, cancellable,
		(GAsyncReadyCallback) composer_save_to_drafts_cleanup,
		context);

	camel_message_info_free (info);

	g_object_unref (drafts_folder);
}

static void
composer_save_to_drafts_got_folder (EMailSession *session,
                                    GAsyncResult *result,
                                    AsyncContext *context)
{
	CamelFolder *drafts_folder;
	GError *error = NULL;

	drafts_folder = e_mail_session_uri_to_folder_finish (
		session, result, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_warn_if_fail (drafts_folder == NULL);
		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
		async_context_free (context);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		gint response;

		g_warn_if_fail (drafts_folder == NULL);

		/* XXX Not showing the error message in the dialog? */
		g_error_free (error);

		/* If we can't retrieve the Drafts folder for the
		 * selected account, ask the user if he wants to
		 * save to the local Drafts folder instead. */
		response = e_alert_run_dialog_for_args (
			GTK_WINDOW (context->composer),
			"mail:ask-default-drafts", NULL);
		if (response != GTK_RESPONSE_YES) {
			gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
			async_context_free (context);
			return;
		}
	}

	composer_save_to_drafts_append_mail (context, drafts_folder);
}

static void
em_utils_composer_save_to_drafts_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
                                     EActivity *activity,
                                     EMailSession *session)
{
	AsyncContext *context;
	EComposerHeaderTable *table;
	ESourceRegistry *registry;
	ESource *source;
	const gchar *local_drafts_folder_uri;
	const gchar *identity_uid;
	gchar *drafts_folder_uri = NULL;

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
	context->session = g_object_ref (session);
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

	table = e_msg_composer_get_header_table (composer);

	registry = e_composer_header_table_get_registry (table);
	identity_uid = e_composer_header_table_get_identity_uid (table);
	source = e_source_registry_ref_source (registry, identity_uid);

	/* Get the selected identity's preferred Drafts folder. */
	if (source != NULL) {
		ESourceMailComposition *extension;
		const gchar *extension_name;
		gchar *uri;

		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
		extension = e_source_get_extension (source, extension_name);
		uri = e_source_mail_composition_dup_drafts_folder (extension);

		drafts_folder_uri = uri;

		g_object_unref (source);
	}

	local_drafts_folder_uri =
		e_mail_session_get_local_folder_uri (
		session, E_MAIL_LOCAL_FOLDER_DRAFTS);

	if (drafts_folder_uri == NULL) {
		composer_save_to_drafts_append_mail (context, NULL);
		context->folder_uri = g_strdup (local_drafts_folder_uri);
	} else {
		GCancellable *cancellable;

		cancellable = e_activity_get_cancellable (activity);
		context->folder_uri = g_strdup (drafts_folder_uri);

		e_mail_session_uri_to_folder (
			session, drafts_folder_uri, 0,
			G_PRIORITY_DEFAULT, cancellable,
			(GAsyncReadyCallback)
			composer_save_to_drafts_got_folder, context);

		g_free (drafts_folder_uri);
	}
}

static void
composer_save_to_outbox_completed (CamelFolder *outbox_folder,
                                   GAsyncResult *result,
                                   AsyncContext *context)
{
	EAlertSink *alert_sink;
	GError *error = NULL;

	alert_sink = e_activity_get_alert_sink (context->activity);

	e_mail_folder_append_message_finish (
		outbox_folder, result, NULL, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_error_free (error);
		goto exit;

	} else if (error != NULL) {
		e_alert_submit (
			alert_sink,
			"mail-composer:append-to-outbox-error",
			error->message, NULL);
		g_error_free (error);
		goto exit;
	}

	/* special processing for Outbox folder */
	manage_x_evolution_replace_outbox (
		context->composer, context->session, context->message,
		e_activity_get_cancellable (context->activity));

	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	/* Wait for the EActivity's completion message to
	 * time out and then destroy the composer window. */
	g_object_weak_ref (
		G_OBJECT (context->activity), (GWeakNotify)
		gtk_widget_destroy, context->composer);

exit:
	async_context_free (context);
}

static void
em_utils_composer_save_to_outbox_cb (EMsgComposer *composer,
                                     CamelMimeMessage *message,
                                     EActivity *activity,
                                     EMailSession *session)
{
	AsyncContext *context;
	CamelFolder *outbox_folder;
	CamelMessageInfo *info;
	GCancellable *cancellable;

	context = g_slice_new0 (AsyncContext);
	context->message = g_object_ref (message);
	context->session = g_object_ref (session);
	context->composer = g_object_ref (composer);
	context->activity = g_object_ref (activity);

	cancellable = e_activity_get_cancellable (activity);

	outbox_folder =
		e_mail_session_get_local_folder (
		session, E_MAIL_LOCAL_FOLDER_OUTBOX);

	info = camel_message_info_new (NULL);
	camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);

	e_mail_folder_append_message (
		outbox_folder, message, info,
		G_PRIORITY_DEFAULT, cancellable,
		(GAsyncReadyCallback) composer_save_to_outbox_completed,
		context);

	camel_message_info_free (info);
}

static void
composer_print_done_cb (EMailPrinter *emp,
                        GtkPrintOperation *operation,
                        GtkPrintOperationResult result,
                        gpointer user_data)
{
	g_object_unref (emp);
}

static void
em_utils_composer_print_cb (EMsgComposer *composer,
                            GtkPrintOperationAction action,
                            CamelMimeMessage *message,
                            EActivity *activity,
                            EMailSession *session)
{
	EMailPrinter *emp;
	EMailParser *parser;
	EMailPartList *parts;
	const gchar *message_id;

	parser = e_mail_parser_new (CAMEL_SESSION (session));

	message_id = camel_mime_message_get_message_id (message);
	parts = e_mail_parser_parse_sync (parser, NULL, g_strdup (message_id), message, NULL);

        /* Use EMailPrinter and WebKit to print the message */
	emp = e_mail_printer_new (parts);
	g_signal_connect (emp, "done",
		G_CALLBACK (composer_print_done_cb), NULL);

	e_mail_printer_print (
		emp, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL);

	g_object_unref (parts);
}

/* Composing messages... */

static EMsgComposer *
create_new_composer (EShell *shell,
                     const gchar *subject,
                     CamelFolder *folder)
{
	EMsgComposer *composer;
	ESourceRegistry *registry;
	EComposerHeaderTable *table;
	ESource *source = NULL;
	gchar *identity = NULL;

	composer = e_msg_composer_new (shell);

	table = e_msg_composer_get_header_table (composer);
	registry = e_composer_header_table_get_registry (table);

	if (folder != NULL) {
		CamelStore *store;
		gchar *folder_uri;
		GList *list;

		store = camel_folder_get_parent_store (folder);
		source = em_utils_ref_mail_identity_for_store (registry, store);

		folder_uri = e_mail_folder_uri_from_folder (folder);

		list = g_list_prepend (NULL, folder_uri);
		e_composer_header_table_set_post_to_list (table, list);
		g_list_free (list);

		g_free (folder_uri);
	}

	if (source != NULL) {
		identity = e_source_dup_uid (source);
		g_object_unref (source);
	}

	e_composer_header_table_set_subject (table, subject);
	e_composer_header_table_set_identity_uid (table, identity);

	g_free (identity);

	return composer;
}

/**
 * em_utils_compose_new_message:
 * @shell: an #EShell
 * @folder: a #CamelFolder, or %NULL
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window.
 **/
void
em_utils_compose_new_message (EShell *shell,
                              CamelFolder *folder)
{
	EMsgComposer *composer;

	g_return_if_fail (E_IS_SHELL (shell));

	if (folder != NULL)
		g_return_if_fail (CAMEL_IS_FOLDER (folder));

	composer = create_new_composer (shell, "", folder);
	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));
}

/**
 * em_utils_compose_new_message_with_mailto:
 * @shell: an #EShell
 * @mailto: a mailto URL
 * @folder: a #CamelFolder, or %NULL
 *
 * Opens a new composer window as a child window of @parent's toplevel
 * window. If @mailto is non-NULL, the composer fields will be filled in
 * according to the values in the mailto URL.
 **/
EMsgComposer *
em_utils_compose_new_message_with_mailto (EShell *shell,
                                          const gchar *mailto,
                                          CamelFolder *folder)
{
	EMsgComposer *composer;
	ESourceRegistry *registry;
	EComposerHeaderTable *table;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);

	if (folder != NULL)
		g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);

	if (mailto != NULL)
		composer = e_msg_composer_new_from_url (shell, mailto);
	else
		composer = e_msg_composer_new (shell);

	table = e_msg_composer_get_header_table (composer);
	registry = e_composer_header_table_get_registry (table);

	composer_set_no_change (composer);

	gtk_window_present (GTK_WINDOW (composer));

	/* If a CamelFolder was given, we need to backtrack and find
	 * the corresponding ESource with a Mail Identity extension. */

	if (folder != NULL) {
		ESource *source;
		CamelStore *store;

		store = camel_folder_get_parent_store (folder);
		source = em_utils_ref_mail_identity_for_store (registry, store);

		if (source != NULL) {
			const gchar *uid = e_source_get_uid (source);
			e_composer_header_table_set_identity_uid (table, uid);
			g_object_unref (source);
		}
	}

	return composer;
}

static gboolean
replace_variables (GSList *clues,
                   CamelMimeMessage *message,
                   gchar **pstr)
{
	gint i;
	gboolean string_changed = FALSE, count1 = FALSE;
	gchar *str;

	g_return_val_if_fail (pstr != NULL, FALSE);
	g_return_val_if_fail (*pstr != NULL, FALSE);
	g_return_val_if_fail (message != NULL, FALSE);

	str = *pstr;

	for (i = 0; i < strlen (str); i++) {
		const gchar *cur = str + i;
		if (!g_ascii_strncasecmp (cur, "$", 1)) {
			const gchar *end = cur + 1;
			gchar *out;
			gchar **temp_str;
			GSList *list;

			while (*end && (g_unichar_isalnum (*end) || *end == '_'))
				end++;

			out = g_strndup ((const gchar *) cur, end - cur);

			temp_str = g_strsplit (str, out, 2);

			for (list = clues; list; list = g_slist_next (list)) {
				gchar **temp = g_strsplit (list->data, "=", 2);
				if (!g_ascii_strcasecmp (temp[0], out + 1)) {
					g_free (str);
					str = g_strconcat (temp_str[0], temp[1], temp_str[1], NULL);
					count1 = TRUE;
					string_changed = TRUE;
				} else
					count1 = FALSE;
				g_strfreev (temp);
			}

			if (!count1) {
				if (getenv (out + 1)) {
					g_free (str);
					str = g_strconcat (
						temp_str[0],
						getenv (out + 1),
						temp_str[1], NULL);
					count1 = TRUE;
					string_changed = TRUE;
				} else
					count1 = FALSE;
			}

			if (!count1) {
				CamelInternetAddress *to;
				const gchar *name, *addr;

				to = camel_mime_message_get_recipients (
					message, CAMEL_RECIPIENT_TYPE_TO);
				if (!camel_internet_address_get (to, 0, &name, &addr))
					continue;

				if (name && g_ascii_strcasecmp ("sender_name", out + 1) == 0) {
					g_free (str);
					str = g_strconcat (temp_str[0], name, temp_str[1], NULL);
					count1 = TRUE;
					string_changed = TRUE;
				} else if (addr && g_ascii_strcasecmp ("sender_email", out + 1) == 0) {
					g_free (str);
					str = g_strconcat (temp_str[0], addr, temp_str[1], NULL);
					count1 = TRUE;
					string_changed = TRUE;
				}
			}

			g_strfreev (temp_str);
			g_free (out);
		}
	}

	*pstr = str;

	return string_changed;
}

static void
traverse_parts (GSList *clues,
                CamelMimeMessage *message,
                CamelDataWrapper *content)
{
	g_return_if_fail (message != NULL);

	if (!content)
		return;

	if (CAMEL_IS_MULTIPART (content)) {
		guint i, n;
		CamelMultipart *multipart = CAMEL_MULTIPART (content);
		CamelMimePart *part;

		n = camel_multipart_get_number (multipart);
		for (i = 0; i < n; i++) {
			part = camel_multipart_get_part (multipart, i);
			if (!part)
				continue;

			traverse_parts (clues, message, CAMEL_DATA_WRAPPER (part));
		}
	} else if (CAMEL_IS_MIME_PART (content)) {
		CamelMimePart *part = CAMEL_MIME_PART (content);
		CamelContentType *type;
		CamelStream *stream;
		GByteArray *byte_array;
		gchar *str;

		content = camel_medium_get_content (CAMEL_MEDIUM (part));
		if (!content)
			return;

		if (CAMEL_IS_MULTIPART (content)) {
			traverse_parts (clues, message, CAMEL_DATA_WRAPPER (content));
			return;
		}

		type = camel_mime_part_get_content_type (part);
		if (!camel_content_type_is (type, "text", "*"))
			return;

		byte_array = g_byte_array_new ();
		stream = camel_stream_mem_new_with_byte_array (byte_array);
		camel_data_wrapper_decode_to_stream_sync (
			content, stream, NULL, NULL);

		str = g_strndup ((gchar *) byte_array->data, byte_array->len);
		g_object_unref (stream);

		if (replace_variables (clues, message, &str)) {
			stream = camel_stream_mem_new_with_buffer (str, strlen (str));
			camel_data_wrapper_construct_from_stream_sync (
				content, stream, NULL, NULL);
			g_object_unref (stream);
		}

		g_free (str);
	}
}

/* Editing messages... */

typedef enum {
	QUOTING_ATTRIBUTION,
	QUOTING_FORWARD,
	QUOTING_ORIGINAL
} QuotingTextEnum;

static struct {
	const gchar * conf_key;
	const gchar * message;
} conf_messages[] = {
	[QUOTING_ATTRIBUTION] =
		{ "composer-message-attribution",
		/* Note to translators: this is the attribution string used
		 * when quoting messages.  Each ${Variable} gets replaced
		 * with a value.  To see a full list of available variables,
		 * see mail/em-composer-utils.c:attribvars array. */
		  N_("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at "
		     "${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:") 
		},

	[QUOTING_FORWARD] =
		{ "composer-message-forward",
		  N_("-------- Forwarded Message --------")
		},

	[QUOTING_ORIGINAL] =
		{ "composer-message-original",
		  N_("-----Original Message-----")
		}
};

static gchar *
quoting_text (QuotingTextEnum type)
{
	GSettings *settings;
	gchar *text;

	settings = g_settings_new ("org.gnome.evolution.mail");
	text = g_settings_get_string (settings, conf_messages[type].conf_key);
	g_object_unref (settings);

	if (text && *text)
		return text;

	g_free (text);

	return g_strdup (_(conf_messages[type].message));
}

/**
 * em_utils_edit_message:
 * @shell: an #EShell
 * @folder: a #CamelFolder
 * @message: a #CamelMimeMessage
 * @message_uid: UID of @message, or %NULL
 *
 * Opens a composer filled in with the headers/mime-parts/etc of
 * @message.
 **/
GtkWidget *
em_utils_edit_message (EShell *shell,
                       CamelFolder *folder,
                       CamelMimeMessage *message,
                       const gchar *message_uid)
{
	EMsgComposer *composer;
	ESourceRegistry *registry;
	gboolean folder_is_drafts;
	gboolean folder_is_outbox;
	gboolean folder_is_templates;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);

	registry = e_shell_get_registry (shell);
	folder_is_drafts = em_utils_folder_is_drafts (registry, folder);
	folder_is_outbox = em_utils_folder_is_outbox (registry, folder);
	folder_is_templates = em_utils_folder_is_templates (registry, folder);

	/* Template specific code follows. */
	if (folder_is_templates) {
		CamelDataWrapper *content;
		GSettings *settings;
		gchar **strv;
		gint i;
		GSList *clue_list = NULL;

		settings = g_settings_new ("org.gnome.evolution.plugin.templates");

		/* Get the list from GSettings */
		strv = g_settings_get_strv (settings, "template-placeholders");
		for (i = 0; strv[i] != NULL; i++)
			clue_list = g_slist_append (clue_list, g_strdup (strv[i]));
		g_object_unref (settings);
		g_strfreev (strv);

		content = camel_medium_get_content (CAMEL_MEDIUM (message));
		traverse_parts (clue_list, message, content);

		g_slist_foreach (clue_list, (GFunc) g_free, NULL);
		g_slist_free (clue_list);
	}

	composer = e_msg_composer_new_with_message (shell, message, NULL);
	if (!folder_is_templates) {
		EComposerHeaderTable *table;
		ESource *source;
		CamelStore *store;
		gchar *folder_uri;
		GList *list;

		table = e_msg_composer_get_header_table (composer);

		store = camel_folder_get_parent_store (folder);
		source = em_utils_ref_mail_identity_for_store (registry, store);

		if (source != NULL) {
			const gchar *uid = e_source_get_uid (source);
			e_composer_header_table_set_identity_uid (table, uid);
			g_object_unref (source);
		}

		folder_uri = e_mail_folder_uri_from_folder (folder);

		list = g_list_prepend (NULL, folder_uri);
		e_composer_header_table_set_post_to_list (table, list);
		g_list_free (list);

		g_free (folder_uri);
	}

	e_msg_composer_remove_header (
		composer, "X-Evolution-Replace-Outbox-UID");

	if (message_uid != NULL && folder_is_drafts) {
		gchar *folder_uri;

		folder_uri = e_mail_folder_uri_from_folder (folder);

		e_msg_composer_set_draft_headers (
			composer, folder_uri, message_uid);

		g_free (folder_uri);

	} else if (message_uid != NULL && folder_is_outbox) {
		e_msg_composer_set_header (
			composer, "X-Evolution-Replace-Outbox-UID",
			message_uid);
	}

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));

	return GTK_WIDGET (composer);
}

static void
edit_messages_cb (CamelFolder *folder,
                  GAsyncResult *result,
                  AsyncContext *context)
{
	EShell *shell;
	EMailBackend *backend;
	EAlertSink *alert_sink;
	GHashTable *hash_table;
	GHashTableIter iter;
	gpointer key, value;
	GError *error = NULL;

	alert_sink = e_mail_reader_get_alert_sink (context->reader);

	hash_table = e_mail_folder_get_multiple_messages_finish (
		folder, result, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_warn_if_fail (hash_table == NULL);
		async_context_free (context);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (hash_table == NULL);
		e_alert_submit (
			alert_sink,
			"mail:get-multiple-messages",
			error->message, NULL);
		async_context_free (context);
		g_error_free (error);
		return;
	}

	g_return_if_fail (hash_table != NULL);

	backend = e_mail_reader_get_backend (context->reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

	/* Open each message in its own composer window. */

	g_hash_table_iter_init (&iter, hash_table);

	while (g_hash_table_iter_next (&iter, &key, &value)) {
		CamelMimeMessage *message;

		if (!context->replace)
			key = NULL;

		message = CAMEL_MIME_MESSAGE (value);
		camel_medium_remove_header (CAMEL_MEDIUM (value), "X-Mailer");
		em_utils_edit_message (shell, folder, message, key);
	}

	g_hash_table_unref (hash_table);

	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	async_context_free (context);
}

/**
 * em_utils_edit_messages:
 * @reader: an #EMailReader
 * @folder: folder containing messages to edit
 * @uids: uids of messages to edit
 * @replace: replace the existing message(s) when sent or saved.
 *
 * Opens a composer for each message to be edited.
 **/
void
em_utils_edit_messages (EMailReader *reader,
                        CamelFolder *folder,
                        GPtrArray *uids,
                        gboolean replace)
{
	EActivity *activity;
	AsyncContext *context;
	GCancellable *cancellable;

	g_return_if_fail (E_IS_MAIL_READER (reader));
	g_return_if_fail (CAMEL_IS_FOLDER (folder));
	g_return_if_fail (uids != NULL);

	activity = e_mail_reader_new_activity (reader);
	cancellable = e_activity_get_cancellable (activity);

	context = g_slice_new0 (AsyncContext);
	context->activity = activity;
	context->reader = g_object_ref (reader);
	context->ptr_array = g_ptr_array_ref (uids);
	context->replace = replace;

	e_mail_folder_get_multiple_messages (
		folder, uids, G_PRIORITY_DEFAULT,
		cancellable, (GAsyncReadyCallback)
		edit_messages_cb, context);
}

static void
emu_update_composers_security (EMsgComposer *composer,
                               guint32 validity_found)
{
	EShell *shell;
	EShellSettings *shell_settings;
	GtkAction *action;
	gboolean sign_by_default;

	g_return_if_fail (composer != NULL);

	shell = e_msg_composer_get_shell (composer);
	shell_settings = e_shell_get_shell_settings (shell);

	sign_by_default =
		(validity_found & E_MAIL_PART_VALIDITY_SIGNED) != 0 &&
		e_shell_settings_get_boolean (
		shell_settings, "composer-sign-reply-if-signed");

	/* Pre-set only for encrypted messages, not for signed */
	if (sign_by_default) {
		if (validity_found & E_MAIL_PART_VALIDITY_SMIME)
			action = E_COMPOSER_ACTION_SMIME_SIGN (composer);
		else
			action = E_COMPOSER_ACTION_PGP_SIGN (composer);

		gtk_toggle_action_set_active (
			GTK_TOGGLE_ACTION (action), TRUE);
	}

	if (validity_found & E_MAIL_PART_VALIDITY_ENCRYPTED) {
		if (validity_found & E_MAIL_PART_VALIDITY_SMIME)
			action = E_COMPOSER_ACTION_SMIME_ENCRYPT (composer);
		else
			action = E_COMPOSER_ACTION_PGP_ENCRYPT (composer);

		gtk_toggle_action_set_active (
			GTK_TOGGLE_ACTION (action), TRUE);
	}
}

static void
real_update_forwarded_flag (gpointer uid,
                            gpointer folder)
{
	if (uid && folder)
		camel_folder_set_message_flags (
			folder, uid, CAMEL_MESSAGE_FORWARDED,
			CAMEL_MESSAGE_FORWARDED);
}

static void
update_forwarded_flags_cb (EMsgComposer *composer,
                           ForwardData *data)
{
	if (data && data->uids && data->folder)
		g_ptr_array_foreach (
			data->uids, real_update_forwarded_flag, data->folder);
}

static void
setup_forward_attached_callbacks (EMsgComposer *composer,
                                  CamelFolder *folder,
                                  GPtrArray *uids)
{
	ForwardData *data;

	if (!composer || !folder || !uids || !uids->len)
		return;

	g_object_ref (folder);

	data = g_slice_new0 (ForwardData);
	data->folder = g_object_ref (folder);
	data->uids = em_utils_uids_copy (uids);

	g_signal_connect (
		composer, "send",
		G_CALLBACK (update_forwarded_flags_cb), data);
	g_signal_connect (
		composer, "save-to-drafts",
		G_CALLBACK (update_forwarded_flags_cb), data);

	g_object_set_data_full (
		G_OBJECT (composer), "forward-data", data,
		(GDestroyNotify) forward_data_free);
}

static EMsgComposer *
forward_attached (EShell *shell,
                  CamelFolder *folder,
                  GPtrArray *uids,
                  CamelMimePart *part,
                  gchar *subject)
{
	EMsgComposer *composer;

	composer = create_new_composer (shell, subject, folder);

	e_msg_composer_attach (composer, part);

	if (uids)
		setup_forward_attached_callbacks (composer, folder, uids);

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));

	return composer;
}

static void
forward_attached_cb (CamelFolder *folder,
                     GAsyncResult *result,
                     AsyncContext *context)
{
	EShell *shell;
	EMailBackend *backend;
	EAlertSink *alert_sink;
	CamelMimePart *part;
	gchar *subject = NULL;
	GError *error = NULL;

	alert_sink = e_mail_reader_get_alert_sink (context->reader);

	part = e_mail_folder_build_attachment_finish (
		folder, result, &subject, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_warn_if_fail (part == NULL);
		g_warn_if_fail (subject == NULL);
		async_context_free (context);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (part == NULL);
		g_warn_if_fail (subject == NULL);
		e_alert_submit (
			alert_sink,
			"mail:get-multiple-messages",
			error->message, NULL);
		async_context_free (context);
		g_error_free (error);
		return;
	}

	g_return_if_fail (CAMEL_IS_MIME_PART (part));

	backend = e_mail_reader_get_backend (context->reader);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

	forward_attached (shell, folder, context->ptr_array, part, subject);

	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	g_object_unref (part);
	g_free (subject);

	async_context_free (context);
}

static EMsgComposer *
forward_non_attached (EShell *shell,
                      CamelSession *session,
                      CamelFolder *folder,
                      const gchar *uid,
                      CamelMimeMessage *message,
                      EMailForwardStyle style)
{
	EMsgComposer *composer = NULL;
	gchar *text, *forward;
	guint32 validity_found = 0;
	guint32 flags;

	flags = E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS | E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
	if (style == E_MAIL_FORWARD_STYLE_QUOTED)
		flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE;

	forward = quoting_text (QUOTING_FORWARD);
	text = em_utils_message_to_html (
		session, message, forward, flags, NULL, NULL, &validity_found);

	if (text != NULL) {
		CamelDataWrapper *content;
		gchar *subject;

		subject = mail_tool_generate_forward_subject (message);
		composer = create_new_composer (shell, subject, folder);
		g_free (subject);

		content = camel_medium_get_content (CAMEL_MEDIUM (message));

		if (CAMEL_IS_MULTIPART (content))
			e_msg_composer_add_message_attachments (
				composer, message, FALSE);

		e_msg_composer_set_body_text (composer, text, TRUE);

		if (uid != NULL) {
			gchar *folder_uri;

			folder_uri = e_mail_folder_uri_from_folder (folder);

			e_msg_composer_set_source_headers (
				composer, folder_uri, uid,
				CAMEL_MESSAGE_FORWARDED);

			g_free (folder_uri);
		}

		emu_update_composers_security (
			composer, validity_found);
		composer_set_no_change (composer);
		gtk_widget_show (GTK_WIDGET (composer));

		g_free (text);
	}

	g_free (forward);

	return composer;
}

/**
 * em_utils_forward_message:
 * @shell: an #EShell
 * @session: a #CamelSession
 * @message: a #CamelMimeMessage to forward
 * @style: the forward style to use
 * @folder: a #CamelFolder, or %NULL
 * @uid: the UID of %message, or %NULL
 *
 * Forwards a message in the given style.  See em_utils_forward_messages()
 * for more details about forwarding styles.
 **/
EMsgComposer *
em_utils_forward_message (EShell *shell,
                          CamelSession *session,
                          CamelMimeMessage *message,
                          EMailForwardStyle style,
                          CamelFolder *folder,
                          const gchar *uid)
{
	CamelMimePart *part;
	gchar *subject;
	EMsgComposer *composer = NULL;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);

	switch (style) {
		case E_MAIL_FORWARD_STYLE_ATTACHED:
		default:
			part = mail_tool_make_message_attachment (message);
			subject = mail_tool_generate_forward_subject (message);

			composer = forward_attached (
				shell, NULL, NULL, part, subject);

			g_object_unref (part);
			g_free (subject);
			break;

		case E_MAIL_FORWARD_STYLE_INLINE:
		case E_MAIL_FORWARD_STYLE_QUOTED:
			composer = forward_non_attached (
				shell, session, folder, uid, message, style);
			break;
	}

	return composer;
}

static void
forward_got_messages_cb (CamelFolder *folder,
                         GAsyncResult *result,
                         AsyncContext *context)
{
	EShell *shell;
	EMailBackend *backend;
	EMailSession *session;
	EAlertSink *alert_sink;
	GHashTable *hash_table;
	GHashTableIter iter;
	gpointer key, value;
	GError *error = NULL;

	alert_sink = e_mail_reader_get_alert_sink (context->reader);

	hash_table = e_mail_folder_get_multiple_messages_finish (
		folder, result, &error);

	if (e_activity_handle_cancellation (context->activity, error)) {
		g_warn_if_fail (hash_table == NULL);
		context->destroy_when_done = NULL;
		async_context_free (context);
		g_error_free (error);
		return;

	} else if (error != NULL) {
		g_warn_if_fail (hash_table == NULL);
		e_alert_submit (
			alert_sink,
			"mail:get-multiple-messages",
			error->message, NULL);
		context->destroy_when_done = NULL;
		async_context_free (context);
		g_error_free (error);
		return;
	}

	g_return_if_fail (hash_table != NULL);

	backend = e_mail_reader_get_backend (context->reader);
	session = e_mail_backend_get_session (backend);
	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));

	/* Create a new composer window for each message. */

	g_hash_table_iter_init (&iter, hash_table);

	while (g_hash_table_iter_next (&iter, &key, &value))
		em_utils_forward_message (
			shell, CAMEL_SESSION (session),
			value, context->style, folder, key);

	g_hash_table_unref (hash_table);

	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);

	async_context_free (context);
}

/**
 * em_utils_forward_messages:
 * @shell: an #EShell
 * @folder: folder containing messages to forward
 * @uids: uids of messages to forward
 * @style: the forward style to use
 * @destroy_when_done: a #GtkWidget to destroy with gtk_widget_destroy()
 * when done; can be NULL
 *
 * Forwards a group of messages in the given style.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_ATTACHED, the new message is
 * created as follows.  If there is more than a single message in @uids,
 * a multipart/digest will be constructed and attached to a new composer
 * window preset with the appropriate header defaults for forwarding the
 * first message in the list.  If only one message is to be forwarded,
 * it is forwarded as a simple message/rfc822 attachment.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_INLINE, each message is forwarded
 * in its own composer window in 'inline' form.
 *
 * If @style is #E_MAIL_FORWARD_STYLE_QUOTED, each message is forwarded
 * in its own composer window in 'quoted' form (each line starting with
 * a "> ").
 **/
void
em_utils_forward_messages (EMailReader *reader,
                           CamelFolder *folder,
                           GPtrArray *uids,
                           EMailForwardStyle style,
                           GtkWidget *destroy_when_done)
{
	EActivity *activity;
	AsyncContext *context;
	GCancellable *cancellable;

	g_return_if_fail (E_IS_MAIL_READER (reader));
	g_return_if_fail (CAMEL_IS_FOLDER (folder));
	g_return_if_fail (uids != NULL);

	activity = e_mail_reader_new_activity (reader);
	cancellable = e_activity_get_cancellable (activity);

	context = g_slice_new0 (AsyncContext);
	context->activity = activity;
	context->reader = g_object_ref (reader);
	context->ptr_array = g_ptr_array_ref (uids);
	context->style = style;
	context->destroy_when_done = destroy_when_done;

	switch (style) {
		case E_MAIL_FORWARD_STYLE_ATTACHED:
			e_mail_folder_build_attachment (
				folder, uids, G_PRIORITY_DEFAULT,
				cancellable, (GAsyncReadyCallback)
				forward_attached_cb, context);
			break;

		case E_MAIL_FORWARD_STYLE_INLINE:
		case E_MAIL_FORWARD_STYLE_QUOTED:
			e_mail_folder_get_multiple_messages (
				folder, uids, G_PRIORITY_DEFAULT,
				cancellable, (GAsyncReadyCallback)
				forward_got_messages_cb, context);
			break;

		default:
			g_warn_if_reached ();
	}
}

/* Redirecting messages... */

static EMsgComposer *
redirect_get_composer (EShell *shell,
                       CamelMimeMessage *message)
{
	EMsgComposer *composer;
	ESourceRegistry *registry;
	CamelMedium *medium;
	ESource *source;
	gchar *identity_uid = NULL;

	medium = CAMEL_MEDIUM (message);

	/* QMail will refuse to send a message if it finds one of
	 * it's Delivered-To headers in the message, so remove all
	 * Delivered-To headers. Fixes bug #23635. */
	while (camel_medium_get_header (medium, "Delivered-To"))
		camel_medium_remove_header (medium, "Delivered-To");

	while (camel_medium_get_header (medium, "Bcc"))
		camel_medium_remove_header (medium, "Bcc");

	while (camel_medium_get_header (medium, "Resent-Bcc"))
		camel_medium_remove_header (medium, "Resent-Bcc");

	registry = e_shell_get_registry (shell);

	/* This returns a new ESource reference. */
	source = em_utils_guess_mail_identity_with_recipients (
		registry, message, NULL, NULL);

	if (source != NULL) {
		identity_uid = e_source_dup_uid (source);
		g_object_unref (source);
	}

	composer = e_msg_composer_new_redirect (
		shell, message, identity_uid, NULL);

	g_free (identity_uid);

	return composer;
}

/**
 * em_utils_redirect_message:
 * @shell: an #EShell
 * @message: message to redirect
 *
 * Opens a composer to redirect @message (Note: only headers will be
 * editable). Adds Resent-From/Resent-To/etc headers.
 **/
void
em_utils_redirect_message (EShell *shell,
                           CamelMimeMessage *message)
{
	EMsgComposer *composer;

	g_return_if_fail (E_IS_SHELL (shell));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));

	composer = redirect_get_composer (shell, message);

	gtk_widget_show (GTK_WIDGET (composer));

	composer_set_no_change (composer);
}

/* Replying to messages... */

EDestination **
em_utils_camel_address_to_destination (CamelInternetAddress *iaddr)
{
	EDestination *dest, **destv;
	gint n, i, j;

	if (iaddr == NULL)
		return NULL;

	if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0)
		return NULL;

	destv = g_malloc (sizeof (EDestination *) * (n + 1));
	for (i = 0, j = 0; i < n; i++) {
		const gchar *name, *addr;

		if (camel_internet_address_get (iaddr, i, &name, &addr)) {
			dest = e_destination_new ();
			e_destination_set_name (dest, name);
			e_destination_set_email (dest, addr);

			destv[j++] = dest;
		}
	}

	if (j == 0) {
		g_free (destv);
		return NULL;
	}

	destv[j] = NULL;

	return destv;
}

static EMsgComposer *
reply_get_composer (EShell *shell,
                    CamelMimeMessage *message,
                    const gchar *identity_uid,
                    CamelInternetAddress *to,
                    CamelInternetAddress *cc,
                    CamelFolder *folder,
                    CamelNNTPAddress *postto)
{
	const gchar *message_id, *references;
	EDestination **tov, **ccv;
	EMsgComposer *composer;
	EComposerHeaderTable *table;
	CamelMedium *medium;
	gchar *subject;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);

	if (to != NULL)
		g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (to), NULL);

	if (cc != NULL)
		g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc), NULL);

	composer = e_msg_composer_new (shell);

	/* construct the tov/ccv */
	tov = em_utils_camel_address_to_destination (to);
	ccv = em_utils_camel_address_to_destination (cc);

	/* Set the subject of the new message. */
	if ((subject = (gchar *) camel_mime_message_get_subject (message))) {
		gboolean skip_len = -1;

		if (em_utils_is_re_in_subject (shell, subject, &skip_len) && skip_len > 0)
			subject = subject + skip_len;

		subject = g_strdup_printf ("Re: %s", subject);
	} else {
		subject = g_strdup ("");
	}

	table = e_msg_composer_get_header_table (composer);
	e_composer_header_table_set_subject (table, subject);
	e_composer_header_table_set_destinations_to (table, tov);
	e_composer_header_table_set_identity_uid (table, identity_uid);

	/* Add destinations instead of setting, so we don't remove
	 * automatic CC addresses that have already been added. */
	e_composer_header_table_add_destinations_cc (table, ccv);

	e_destination_freev (tov);
	e_destination_freev (ccv);
	g_free (subject);

	/* add post-to, if nessecary */
	if (postto && camel_address_length ((CamelAddress *) postto)) {
		gchar *store_url = NULL;
		gchar *post;

		if (folder) {
			CamelStore *parent_store;
			CamelService *service;
			CamelURL *url;

			parent_store = camel_folder_get_parent_store (folder);

			service = CAMEL_SERVICE (parent_store);
			url = camel_service_new_camel_url (service);

			store_url = camel_url_to_string (
				url, CAMEL_URL_HIDE_ALL);
			if (store_url[strlen (store_url) - 1] == '/')
				store_url[strlen (store_url) - 1] = '\0';

			camel_url_free (url);
		}

		post = camel_address_encode ((CamelAddress *) postto);
		e_composer_header_table_set_post_to_base (
			table, store_url ? store_url : "", post);
		g_free (post);
		g_free (store_url);
	}

	/* Add In-Reply-To and References. */

	medium = CAMEL_MEDIUM (message);
	message_id = camel_medium_get_header (medium, "Message-ID");
	references = camel_medium_get_header (medium, "References");

	if (message_id != NULL) {
		gchar *reply_refs;

		e_msg_composer_add_header (
			composer, "In-Reply-To", message_id);

		if (references)
			reply_refs = g_strdup_printf (
				"%s %s", references, message_id);
		else
			reply_refs = g_strdup (message_id);

		e_msg_composer_add_header (
			composer, "References", reply_refs);
		g_free (reply_refs);

	} else if (references != NULL) {
		e_msg_composer_add_header (
			composer, "References", references);
	}

	return composer;
}

static gboolean
get_reply_list (CamelMimeMessage *message,
                CamelInternetAddress *to)
{
	const gchar *header, *p;
	gchar *addr;

	/* Examples:
	 *
	 * List-Post: <mailto:list@host.com>
	 * List-Post: <mailto:moderator@host.com?subject=list%20posting>
	 * List-Post: NO (posting not allowed on this list)
	 */
	if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post")))
		return FALSE;

	while (*header == ' ' || *header == '\t')
		header++;

	/* check for NO */
	if (!g_ascii_strncasecmp (header, "NO", 2))
		return FALSE;

	/* Search for the first mailto angle-bracket enclosed URL.
	 * (See rfc2369, Section 2, paragraph 3 for details) */
	if (!(header = camel_strstrcase (header, "<mailto:")))
		return FALSE;

	header += 8;

	p = header;
	while (*p && !strchr ("?>", *p))
		p++;

	addr = g_strndup (header, p - header);
	camel_internet_address_add (to, NULL, addr);
	g_free (addr);

	return TRUE;
}

gboolean
em_utils_is_munged_list_message (CamelMimeMessage *message)
{
	CamelInternetAddress *reply_to, *list;
	gboolean result = FALSE;

	reply_to = camel_mime_message_get_reply_to (message);
	if (reply_to) {
		list = camel_internet_address_new ();

		if (get_reply_list (message, list) &&
		    camel_address_length (CAMEL_ADDRESS (list)) ==
		    camel_address_length (CAMEL_ADDRESS (reply_to))) {
			gint i;
			const gchar *r_name, *r_addr;
			const gchar *l_name, *l_addr;

			for (i = 0; i < camel_address_length (CAMEL_ADDRESS (list)); i++) {
				if (!camel_internet_address_get (reply_to, i, &r_name, &r_addr))
					break;
				if (!camel_internet_address_get (list, i, &l_name, &l_addr))
					break;
				if (strcmp (l_addr, r_addr))
					break;
			}
			if (i == camel_address_length (CAMEL_ADDRESS (list)))
				result = TRUE;
		}
		g_object_unref (list);
	}
	return result;
}

static CamelInternetAddress *
get_reply_to (CamelMimeMessage *message)
{
	CamelInternetAddress *reply_to;

	reply_to = camel_mime_message_get_reply_to (message);
	if (reply_to) {
		GSettings *settings;
		gboolean ignore_list_reply_to;

		settings = g_settings_new ("org.gnome.evolution.mail");
		ignore_list_reply_to = g_settings_get_boolean (
			settings, "composer-ignore-list-reply-to");
		g_object_unref (settings);

		if (ignore_list_reply_to && em_utils_is_munged_list_message (message))
			reply_to = NULL;
	}
	if (!reply_to)
		reply_to = camel_mime_message_get_from (message);

	return reply_to;
}

static void
get_reply_sender (CamelMimeMessage *message,
                  CamelInternetAddress *to,
                  CamelNNTPAddress *postto)
{
	CamelInternetAddress *reply_to;
	CamelMedium *medium;
	const gchar *posthdr = NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	reply_to = get_reply_to (message);

	if (reply_to != NULL) {
		const gchar *name;
		const gchar *addr;
		gint ii = 0;

		while (camel_internet_address_get (reply_to, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}
}

void
em_utils_get_reply_sender (CamelMimeMessage *message,
                           CamelInternetAddress *to,
                           CamelNNTPAddress *postto)
{
	get_reply_sender (message, to, postto);
}

static void
get_reply_from (CamelMimeMessage *message,
                CamelInternetAddress *to,
                CamelNNTPAddress *postto)
{
	CamelInternetAddress *from;
	CamelMedium *medium;
	const gchar *name, *addr;
	const gchar *posthdr = NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	from = camel_mime_message_get_from (message);

	if (from != NULL) {
		gint ii = 0;

		while (camel_internet_address_get (from, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}
}

static void
get_reply_recipient (CamelMimeMessage *message,
                     CamelInternetAddress *to,
                     CamelNNTPAddress *postto,
                     CamelInternetAddress *address)
{
	CamelMedium *medium;
	const gchar *posthdr =  NULL;

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		 posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL) {
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
		return;
	}

	if (address != NULL) {
		const gchar *name;
		const gchar *addr;
		gint ii = 0;

		while (camel_internet_address_get (address, ii++, &name, &addr))
			camel_internet_address_add (to, name, addr);
	}

}

static void
concat_unique_addrs (CamelInternetAddress *dest,
                     CamelInternetAddress *src,
                     GHashTable *rcpt_hash)
{
	const gchar *name, *addr;
	gint i;

	for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) {
		if (!g_hash_table_lookup (rcpt_hash, addr)) {
			camel_internet_address_add (dest, name, addr);
			g_hash_table_insert (rcpt_hash, (gchar *) addr, GINT_TO_POINTER (1));
		}
	}
}

static GHashTable *
generate_recipient_hash (ESourceRegistry *registry)
{
	GHashTable *rcpt_hash;
	ESource *default_source;
	GList *list, *link;
	const gchar *extension_name;

	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);

	rcpt_hash = g_hash_table_new (
		(GHashFunc) camel_strcase_hash,
		(GEqualFunc) camel_strcase_equal);

	default_source = e_source_registry_ref_default_mail_identity (registry);

	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
	list = e_source_registry_list_sources (registry, extension_name);

	for (link = list; link != NULL; link = g_list_next (link)) {
		ESource *source = E_SOURCE (link->data);
		ESource *cached_source;
		ESourceMailIdentity *extension;
		const gchar *address;
		gboolean insert_source;
		gboolean cached_is_default;
		gboolean cached_is_enabled;
		gboolean source_is_default;
		gboolean source_is_enabled;

		/* No default mail identity implies there are no mail
		 * identities at all and so we should never get here. */
		g_warn_if_fail (default_source != NULL);

		source_is_default = e_source_equal (source, default_source);
		source_is_enabled = e_source_get_enabled (source);

		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
		extension = e_source_get_extension (source, extension_name);

		address = e_source_mail_identity_get_address (extension);

		if (address == NULL)
			continue;

		cached_source = g_hash_table_lookup (rcpt_hash, address);

		if (cached_source != NULL) {
			cached_is_default = e_source_equal (
				cached_source, default_source);
			cached_is_enabled =
				e_source_get_enabled (cached_source);
		} else {
			cached_is_default = FALSE;
			cached_is_enabled = FALSE;
		}

		/* Accounts with identical email addresses that are enabled
		 * take precedence over disabled accounts.  If all accounts
		 * with matching email addresses are disabled, the first
		 * one in the list takes precedence.  The default account
		 * always takes precedence no matter what. */
		insert_source =
			source_is_default ||
			cached_source == NULL ||
			(source_is_enabled &&
			 !cached_is_enabled &&
			 !cached_is_default);

		if (insert_source)
			g_hash_table_insert (
				rcpt_hash, (gchar *) address, source);
	}

	g_list_free_full (list, (GDestroyNotify) g_object_unref);

	if (default_source != NULL)
		g_object_unref (default_source);

	return rcpt_hash;
}

void
em_utils_get_reply_all (ESourceRegistry *registry,
                        CamelMimeMessage *message,
                        CamelInternetAddress *to,
                        CamelInternetAddress *cc,
                        CamelNNTPAddress *postto)
{
	CamelInternetAddress *reply_to;
	CamelInternetAddress *to_addrs;
	CamelInternetAddress *cc_addrs;
	CamelMedium *medium;
	const gchar *name, *addr;
	const gchar *posthdr = NULL;
	GHashTable *rcpt_hash;

	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));

	medium = CAMEL_MEDIUM (message);

	/* check whether there is a 'Newsgroups: ' header in there */
	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Followup-To");

	if (postto != NULL && posthdr == NULL)
		posthdr = camel_medium_get_header (medium, "Newsgroups");

	if (postto != NULL && posthdr != NULL)
		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);

	rcpt_hash = generate_recipient_hash (registry);

	reply_to = get_reply_to (message);
	to_addrs = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_TO);
	cc_addrs = camel_mime_message_get_recipients (
		message, CAMEL_RECIPIENT_TYPE_CC);

	if (reply_to != NULL) {
		gint ii = 0;

		while (camel_internet_address_get (reply_to, ii++, &name, &addr)) {
			/* Ignore references to the Reply-To address
			 * in the To and Cc lists. */
			if (addr && !g_hash_table_lookup (rcpt_hash, addr)) {
				/* In the case we are doing a Reply-To-All,
				 * we do not want to include the user's email
				 * address because replying to oneself is
				 * kinda silly. */
				camel_internet_address_add (to, name, addr);
				g_hash_table_insert (
					rcpt_hash, (gchar *) addr,
					GINT_TO_POINTER (1));
			}
		}
	}

	concat_unique_addrs (cc, to_addrs, rcpt_hash);
	concat_unique_addrs (cc, cc_addrs, rcpt_hash);

	/* Promote the first Cc: address to To: if To: is empty. */
	if (camel_address_length ((CamelAddress *) to) == 0 &&
			camel_address_length ((CamelAddress *) cc) > 0) {
		camel_internet_address_get (cc, 0, &name, &addr);
		camel_internet_address_add (to, name, addr);
		camel_address_remove ((CamelAddress *) cc, 0);
	}

	/* If To: is still empty, may we removed duplicates (i.e. ourself),
	 * so add the original To if it was set. */
	if (camel_address_length ((CamelAddress *) to) == 0
	    && (camel_internet_address_get (to_addrs, 0, &name, &addr)
		|| camel_internet_address_get (cc_addrs, 0, &name, &addr))) {
		camel_internet_address_add (to, name, addr);
	}

	g_hash_table_destroy (rcpt_hash);
}

enum {
	ATTRIB_UNKNOWN,
	ATTRIB_CUSTOM,
	ATTRIB_TIMEZONE,
	ATTRIB_STRFTIME,
	ATTRIB_TM_SEC,
	ATTRIB_TM_MIN,
	ATTRIB_TM_24HOUR,
	ATTRIB_TM_12HOUR,
	ATTRIB_TM_MDAY,
	ATTRIB_TM_MON,
	ATTRIB_TM_YEAR,
	ATTRIB_TM_2YEAR,
	ATTRIB_TM_WDAY, /* not actually used */
	ATTRIB_TM_YDAY
};

typedef void		(*AttribFormatter)	(GString *str,
						 const gchar *attr,
						 CamelMimeMessage *message);

static void
format_sender (GString *str,
               const gchar *attr,
               CamelMimeMessage *message)
{
	CamelInternetAddress *sender;
	const gchar *name, *addr = NULL;

	sender = camel_mime_message_get_from (message);
	if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) {
		camel_internet_address_get (sender, 0, &name, &addr);
	} else {
		name = _("an unknown sender");
	}

	if (name && !strcmp (attr, "{SenderName}")) {
		g_string_append (str, name);
	} else if (addr && !strcmp (attr, "{SenderEMail}")) {
		g_string_append (str, addr);
	} else if (name && *name) {
		g_string_append (str, name);
	} else if (addr) {
		g_string_append (str, addr);
	}
}

static struct {
	const gchar *name;
	gint type;
	struct {
		const gchar *format;         /* strftime or printf format */
		AttribFormatter formatter;  /* custom formatter */
	} v;
} attribvars[] = {
	{ "{Sender}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{SenderName}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{SenderEMail}", ATTRIB_CUSTOM, { NULL, format_sender } },
	{ "{AbbrevWeekdayName}", ATTRIB_STRFTIME, { "%a", NULL } },
	{ "{WeekdayName}", ATTRIB_STRFTIME, { "%A", NULL } },
	{ "{AbbrevMonthName}", ATTRIB_STRFTIME, { "%b", NULL } },
	{ "{MonthName}", ATTRIB_STRFTIME, { "%B", NULL } },
	{ "{AmPmUpper}", ATTRIB_STRFTIME, { "%p", NULL } },
	{ "{AmPmLower}", ATTRIB_STRFTIME, { "%P", NULL } },
	{ "{Day}", ATTRIB_TM_MDAY, { "%02d", NULL } },  /* %d  01-31 */
	{ "{ Day}", ATTRIB_TM_MDAY, { "% 2d", NULL } },  /* %e   1-31 */
	{ "{24Hour}", ATTRIB_TM_24HOUR, { "%02d", NULL } },  /* %H  00-23 */
	{ "{12Hour}", ATTRIB_TM_12HOUR, { "%02d", NULL } },  /* %I  00-12 */
	{ "{DayOfYear}", ATTRIB_TM_YDAY, { "%d", NULL } },  /* %j  1-366 */
	{ "{Month}", ATTRIB_TM_MON, { "%02d", NULL } },  /* %m  01-12 */
	{ "{Minute}", ATTRIB_TM_MIN, { "%02d", NULL } },  /* %M  00-59 */
	{ "{Seconds}", ATTRIB_TM_SEC, { "%02d", NULL } },  /* %S  00-61 */
	{ "{2DigitYear}", ATTRIB_TM_2YEAR, { "%02d", NULL } },  /* %y */
	{ "{Year}", ATTRIB_TM_YEAR, { "%04d", NULL } },  /* %Y */
	{ "{TimeZone}", ATTRIB_TIMEZONE, { "%+05d", NULL } }
};

static gchar *
attribution_format (CamelMimeMessage *message)
{
	register const gchar *inptr;
	const gchar *start;
	gint tzone, len, i;
	gchar buf[64], *s;
	GString *str;
	struct tm tm;
	time_t date;
	gint type;
	gchar *format = quoting_text (QUOTING_ATTRIBUTION);

	str = g_string_new ("");

	date = camel_mime_message_get_date (message, &tzone);

	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
		/* The message has no Date: header, look at Received: */
		date = camel_mime_message_get_date_received (message, &tzone);
	}
	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
		/* That didn't work either, use current time */
		time (&date);
		tzone = 0;
	}

	/* Convert to UTC */
	date += (tzone / 100) * 60 * 60;
	date += (tzone % 100) * 60;

	gmtime_r (&date, &tm);

	inptr = format;
	while (*inptr != '\0') {
		start = inptr;
		while (*inptr && strncmp (inptr, "${", 2) != 0)
			inptr++;

		g_string_append_len (str, start, inptr - start);

		if (*inptr == '\0')
			break;

		start = ++inptr;
		while (*inptr && *inptr != '}')
			inptr++;

		if (*inptr != '}') {
			/* broken translation */
			g_string_append_len (str, "${", 2);
			inptr = start + 1;
			continue;
		}

		inptr++;
		len = inptr - start;
		type = ATTRIB_UNKNOWN;
		for (i = 0; i < G_N_ELEMENTS (attribvars); i++) {
			if (!strncmp (attribvars[i].name, start, len)) {
				type = attribvars[i].type;
				break;
			}
		}

		switch (type) {
		case ATTRIB_CUSTOM:
			attribvars[i].v.formatter (
				str, attribvars[i].name, message);
			break;
		case ATTRIB_TIMEZONE:
			g_string_append_printf (
				str, attribvars[i].v.format, tzone);
			break;
		case ATTRIB_STRFTIME:
			e_utf8_strftime (
				buf, sizeof (buf), attribvars[i].v.format, &tm);
			g_string_append (str, buf);
			break;
		case ATTRIB_TM_SEC:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_sec);
			break;
		case ATTRIB_TM_MIN:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_min);
			break;
		case ATTRIB_TM_24HOUR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_hour);
			break;
		case ATTRIB_TM_12HOUR:
			g_string_append_printf (
				str, attribvars[i].v.format,
				(tm.tm_hour + 1) % 13);
			break;
		case ATTRIB_TM_MDAY:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_mday);
			break;
		case ATTRIB_TM_MON:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_mon + 1);
			break;
		case ATTRIB_TM_YEAR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_year + 1900);
			break;
		case ATTRIB_TM_2YEAR:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_year % 100);
			break;
		case ATTRIB_TM_WDAY:
			/* not actually used */
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_wday);
			break;
		case ATTRIB_TM_YDAY:
			g_string_append_printf (
				str, attribvars[i].v.format, tm.tm_yday + 1);
			break;
		default:
			/* Misspelled variable?  Drop the
			 * format argument and continue. */
			break;
		}
	}

	s = str->str;
	g_string_free (str, FALSE);
	g_free (format);

	return s;
}

static void
composer_set_body (EMsgComposer *composer,
                   CamelMimeMessage *message,
                   EMailReplyStyle style,
                   EMailPartList *parts_list)
{
	gchar *text, *credits, *original;
	CamelMimePart *part;
	CamelSession *session;
	GSettings *settings;
	gboolean start_bottom, has_body_text = FALSE;
	guint32 validity_found = 0;

	session = e_msg_composer_get_session (composer);

	settings = g_settings_new ("org.gnome.evolution.mail");

	start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");

	switch (style) {
	case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
		/* do nothing */
		break;
	case E_MAIL_REPLY_STYLE_ATTACH:
		/* attach the original message as an attachment */
		part = mail_tool_make_message_attachment (message);
		e_msg_composer_attach (composer, part);
		g_object_unref (part);
		break;
	case E_MAIL_REPLY_STYLE_OUTLOOK:
		original = quoting_text (QUOTING_ORIGINAL);
		text = em_utils_message_to_html (
			session, message, original, E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS,
			parts_list, start_bottom ? "<BR>" : NULL, &validity_found);
		e_msg_composer_set_body_text (composer, text, TRUE);
		has_body_text = text && *text;
		g_free (text);
		g_free (original);
		emu_update_composers_security (composer, validity_found);
		break;

	case E_MAIL_REPLY_STYLE_QUOTED:
	default:
		/* do what any sane user would want when replying... */
		credits = attribution_format (message);
		text = em_utils_message_to_html (
			session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE,
			parts_list, start_bottom ? "<BR>" : NULL, &validity_found);
		g_free (credits);
		e_msg_composer_set_body_text (composer, text, TRUE);
		has_body_text = text && *text;
		g_free (text);
		emu_update_composers_security (composer, validity_found);
		break;
	}

	if (has_body_text && start_bottom) {
		GtkhtmlEditor *editor = GTKHTML_EDITOR (composer);
		gboolean move_cursor_to_end;
		gboolean top_signature;

		/* If we are placing signature on top, then move cursor to the end,
		 * otherwise try to find the signature place and place cursor just
		 * before the signature. We added there an empty line already. */
		gtkhtml_editor_run_command (editor, "block-selection");
		gtkhtml_editor_run_command (editor, "cursor-bod");

		top_signature = g_settings_get_boolean (settings, "composer-top-signature");

		move_cursor_to_end = top_signature ||
			!gtkhtml_editor_search_by_data (
				editor, 1, "ClueFlow", "signature", "1");

		if (move_cursor_to_end)
			gtkhtml_editor_run_command (editor, "cursor-eod");
		else
			gtkhtml_editor_run_command (editor, "selection-move-left");
		gtkhtml_editor_run_command (editor, "unblock-selection");
	}

	g_object_unref (settings);
}

gchar *
em_utils_construct_composer_text (CamelSession *session,
                                  CamelMimeMessage *message,
                                  EMailPartList *parts_list)
{
	gchar *text, *credits;
	gboolean start_bottom = 0;

	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);

	credits = attribution_format (message);
	text = em_utils_message_to_html (
		session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE,
		parts_list, start_bottom ? "<BR>" : NULL, NULL);
	g_free (credits);

	return text;
}

/**
 * em_utils_reply_to_message:
 * @shell: an #EShell
 * @message: a #CamelMimeMessage
 * @folder: a #CamelFolder, or %NULL
 * @message_uid: the UID of @message, or %NULL
 * @type: the type of reply to create
 * @style: the reply style to use
 * @source: source to inherit view settings from
 * @address: used for E_MAIL_REPLY_TO_RECIPIENT @type
 *
 * Creates a new composer ready to reply to @message.
 *
 * @folder and @message_uid may be supplied in order to update the message
 * flags once it has been replied to.
 **/
EMsgComposer *
em_utils_reply_to_message (EShell *shell,
                           CamelMimeMessage *message,
                           CamelFolder *folder,
                           const gchar *message_uid,
                           EMailReplyType type,
                           EMailReplyStyle style,
                           EMailPartList *parts_list,
                           CamelInternetAddress *address)
{
	ESourceRegistry *registry;
	CamelInternetAddress *to, *cc;
	CamelNNTPAddress *postto = NULL;
	EMsgComposer *composer;
	ESource *source;
	gchar *identity_uid = NULL;
	guint32 flags;

	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);

	to = camel_internet_address_new ();
	cc = camel_internet_address_new ();

	registry = e_shell_get_registry (shell);

	/* This returns a new ESource reference. */
	source = em_utils_guess_mail_identity_with_recipients (
		registry, message, folder, message_uid);
	if (source != NULL) {
		identity_uid = e_source_dup_uid (source);
		g_object_unref (source);
	}

	flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;

	if (!address && (type == E_MAIL_REPLY_TO_FROM || type == E_MAIL_REPLY_TO_SENDER) &&
	    folder && em_utils_folder_is_sent (registry, folder))
		type = E_MAIL_REPLY_TO_ALL;

	switch (type) {
	case E_MAIL_REPLY_TO_FROM:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_from (message, to, postto);
		break;
	case E_MAIL_REPLY_TO_RECIPIENT:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_recipient (message, to, postto, address);
		break;
	case E_MAIL_REPLY_TO_SENDER:
		if (folder)
			postto = camel_nntp_address_new ();

		get_reply_sender (message, to, postto);
		break;
	case E_MAIL_REPLY_TO_LIST:
		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
		if (get_reply_list (message, to))
			break;
		/* falls through */
	case E_MAIL_REPLY_TO_ALL:
		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
		if (folder)
			postto = camel_nntp_address_new ();

		em_utils_get_reply_all (registry, message, to, cc, postto);
		break;
	}

	composer = reply_get_composer (
		shell, message, identity_uid, to, cc, folder, postto);
	e_msg_composer_add_message_attachments (composer, message, TRUE);

	if (postto)
		g_object_unref (postto);
	g_object_unref (to);
	g_object_unref (cc);

	composer_set_body (composer, message, style, parts_list);

	if (folder != NULL) {
		gchar *folder_uri;

		folder_uri = e_mail_folder_uri_from_folder (folder);

		e_msg_composer_set_source_headers (
			composer, folder_uri, message_uid, flags);

		g_free (folder_uri);
	}

	composer_set_no_change (composer);

	gtk_widget_show (GTK_WIDGET (composer));

	g_free (identity_uid);

	return composer;
}

static void
post_header_clicked_cb (EComposerPostHeader *header,
                        EMailSession *session)
{
	GtkTreeSelection *selection;
	EMFolderSelector *selector;
	EMFolderTreeModel *model;
	EMFolderTree *folder_tree;
	GtkWidget *dialog;
	GList *list;

	/* FIXME Limit the folder tree to the NNTP account? */
	model = em_folder_tree_model_get_default ();

	dialog = em_folder_selector_new (
		/* FIXME GTK_WINDOW (composer) */ NULL,
		model, EM_FOLDER_SELECTOR_CAN_CREATE,
		_("Posting destination"),
		_("Choose folders to post the message to."),
		NULL);

	selector = EM_FOLDER_SELECTOR (dialog);
	folder_tree = em_folder_selector_get_folder_tree (selector);

	em_folder_tree_set_excluded (
		folder_tree,
		EMFT_EXCLUDE_NOSELECT |
		EMFT_EXCLUDE_VIRTUAL |
		EMFT_EXCLUDE_VTRASH);

	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);

	list = e_composer_post_header_get_folders (header);
	em_folder_tree_set_selected_list (folder_tree, list, FALSE);
	g_list_foreach (list, (GFunc) g_free, NULL);
	g_list_free (list);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) {
		/* Prevent the header's "custom" flag from being reset,
		 * which is what the default method will do next. */
		g_signal_stop_emission_by_name (header, "clicked");
		goto exit;
	}

	list = em_folder_tree_get_selected_uris (folder_tree);
	e_composer_post_header_set_folders (header, list);
	g_list_foreach (list, (GFunc) g_free, NULL);
	g_list_free (list);

exit:
	gtk_widget_destroy (dialog);
}

/**
 * em_configure_new_composer:
 * @composer: a newly created #EMsgComposer
 *
 * Integrates a newly created #EMsgComposer into the mail backend.  The
 * composer can't link directly to the mail backend without introducing
 * circular library dependencies, so this function finishes configuring
 * things the #EMsgComposer instance can't do itself.
 **/
void
em_configure_new_composer (EMsgComposer *composer,
                           EMailSession *session)
{
	EComposerHeaderTable *table;
	EComposerHeaderType header_type;
	EComposerHeader *header;

	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
	g_return_if_fail (E_IS_MAIL_SESSION (session));

	header_type = E_COMPOSER_HEADER_POST_TO;
	table = e_msg_composer_get_header_table (composer);
	header = e_composer_header_table_get_header (table, header_type);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_recipients), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_identity), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_downloads), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_plugins), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_subject), session);

	g_signal_connect (
		composer, "presend",
		G_CALLBACK (composer_presend_check_unwanted_html), session);

	g_signal_connect (
		composer, "send",
		G_CALLBACK (em_utils_composer_send_cb), session);

	g_signal_connect (
		composer, "save-to-drafts",
		G_CALLBACK (em_utils_composer_save_to_drafts_cb), session);

	g_signal_connect (
		composer, "save-to-outbox",
		G_CALLBACK (em_utils_composer_save_to_outbox_cb), session);

	g_signal_connect (
		composer, "print",
		G_CALLBACK (em_utils_composer_print_cb), session);

	/* Handle "Post To:" button clicks, which displays a folder tree
	 * widget.  The composer doesn't know about folder tree widgets,
	 * so it can't handle this itself.
	 *
	 * Note: This is a G_SIGNAL_RUN_LAST signal, which allows us to
	 *       stop the signal emission if the user cancels or closes
	 *       the folder selector dialog.  See the handler function. */
	g_signal_connect (
		header, "clicked",
		G_CALLBACK (post_header_clicked_cb), session);
}