evolution-3.6.4/mail/em-composer-utils.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
   2 
   3 /*
   4  * This program is free software; you can redistribute it and/or
   5  * modify it under the terms of the GNU Lesser General Public
   6  * License as published by the Free Software Foundation; either
   7  * version 2 of the License, or (at your option) version 3.
   8  *
   9  * This program is distributed in the hope that it will be useful,
  10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  12  * Lesser General Public License for more details.
  13  *
  14  * You should have received a copy of the GNU Lesser General Public
  15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16  *
  17  *
  18  * Authors:
  19  *		Jeffrey Stedfast <fejj@ximian.com>
  20  *
  21  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  22  *
  23  */
  24 
  25 #ifdef HAVE_CONFIG_H
  26 #include <config.h>
  27 #endif
  28 
  29 #include <string.h>
  30 #include <gtk/gtk.h>
  31 #include <glib/gi18n.h>
  32 
  33 #include <libevolution-utils/e-alert-dialog.h>
  34 #include <libevolution-utils/e-alert-sink.h>
  35 #include <e-util/e-util.h>
  36 
  37 #include <libemail-utils/mail-mt.h>
  38 
  39 #include <libemail-engine/e-mail-folder-utils.h>
  40 #include <libemail-engine/e-mail-session.h>
  41 #include <libemail-engine/e-mail-session-utils.h>
  42 #include <libemail-engine/e-mail-utils.h>
  43 #include <libemail-engine/mail-ops.h>
  44 #include <libemail-engine/mail-tools.h>
  45 
  46 #include <em-format/e-mail-parser.h>
  47 #include <em-format/e-mail-formatter-quote.h>
  48 
  49 #include <shell/e-shell.h>
  50 
  51 #include <composer/e-msg-composer.h>
  52 #include <composer/e-composer-actions.h>
  53 #include <composer/e-composer-post-header.h>
  54 
  55 #include "e-mail-printer.h"
  56 #include "e-mail-ui-session.h"
  57 #include "em-utils.h"
  58 #include "em-composer-utils.h"
  59 #include "em-folder-selector.h"
  60 #include "em-folder-tree.h"
  61 #include "em-event.h"
  62 #include "mail-send-recv.h"
  63 
  64 #ifdef G_OS_WIN32
  65 #ifdef gmtime_r
  66 #undef gmtime_r
  67 #endif
  68 
  69 /* The gmtime() in Microsoft's C library is MT-safe */
  70 #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0)
  71 #endif
  72 
  73 typedef struct _AsyncContext AsyncContext;
  74 typedef struct _ForwardData ForwardData;
  75 
  76 struct _AsyncContext {
  77 	CamelMimeMessage *message;
  78 	EMailSession *session;
  79 	EMsgComposer *composer;
  80 	EActivity *activity;
  81 	EMailReader *reader;
  82 	GPtrArray *ptr_array;
  83 	EMailForwardStyle style;
  84 	gchar *folder_uri;
  85 	gchar *message_uid;
  86 	gboolean replace;
  87 	GtkWidget *destroy_when_done;
  88 };
  89 
  90 struct _ForwardData {
  91 	EShell *shell;
  92 	CamelFolder *folder;
  93 	GPtrArray *uids;
  94 	EMailForwardStyle style;
  95 };
  96 
  97 static void
  98 async_context_free (AsyncContext *context)
  99 {
 100 	if (context->message != NULL)
 101 		g_object_unref (context->message);
 102 
 103 	if (context->session != NULL)
 104 		g_object_unref (context->session);
 105 
 106 	if (context->composer != NULL)
 107 		g_object_unref (context->composer);
 108 
 109 	if (context->activity != NULL)
 110 		g_object_unref (context->activity);
 111 
 112 	if (context->reader != NULL)
 113 		g_object_unref (context->reader);
 114 
 115 	if (context->ptr_array != NULL)
 116 		g_ptr_array_unref (context->ptr_array);
 117 
 118 	if (context->destroy_when_done != NULL)
 119 		gtk_widget_destroy (context->destroy_when_done);
 120 
 121 	g_free (context->folder_uri);
 122 	g_free (context->message_uid);
 123 
 124 	g_slice_free (AsyncContext, context);
 125 }
 126 
 127 static void
 128 forward_data_free (ForwardData *data)
 129 {
 130 	if (data->shell != NULL)
 131 		g_object_unref (data->shell);
 132 
 133 	if (data->folder != NULL)
 134 		g_object_unref (data->folder);
 135 
 136 	if (data->uids != NULL)
 137 		em_utils_uids_free (data->uids);
 138 
 139 	g_slice_free (ForwardData, data);
 140 }
 141 
 142 static gboolean
 143 ask_confirm_for_unwanted_html_mail (EMsgComposer *composer,
 144                                     EDestination **recipients)
 145 {
 146 	gboolean res;
 147 	GString *str;
 148 	gint i;
 149 
 150 	str = g_string_new ("");
 151 	for (i = 0; recipients[i] != NULL; ++i) {
 152 		if (!e_destination_get_html_mail_pref (recipients[i])) {
 153 			const gchar *name;
 154 
 155 			name = e_destination_get_textrep (recipients[i], FALSE);
 156 
 157 			g_string_append_printf (str, "     %s\n", name);
 158 		}
 159 	}
 160 
 161 	if (str->len)
 162 		res = em_utils_prompt_user (
 163 			GTK_WINDOW (composer),
 164 			"prompt-on-unwanted-html",
 165 			"mail:ask-send-html", str->str, NULL);
 166 	else
 167 		res = TRUE;
 168 
 169 	g_string_free (str, TRUE);
 170 
 171 	return res;
 172 }
 173 
 174 static gboolean
 175 ask_confirm_for_empty_subject (EMsgComposer *composer)
 176 {
 177 	return em_utils_prompt_user (
 178 		GTK_WINDOW (composer),
 179 		"prompt-on-empty-subject",
 180 		"mail:ask-send-no-subject", NULL);
 181 }
 182 
 183 static gboolean
 184 ask_confirm_for_only_bcc (EMsgComposer *composer,
 185                           gboolean hidden_list_case)
 186 {
 187 	/* If the user is mailing a hidden contact list, it is possible for
 188 	 * them to create a message with only Bcc recipients without really
 189 	 * realizing it.  To try to avoid being totally confusing, I've changed
 190 	 * this dialog to provide slightly different text in that case, to
 191 	 * better explain what the hell is going on. */
 192 
 193 	return em_utils_prompt_user (
 194 		GTK_WINDOW (composer),
 195 		"prompt-on-only-bcc",
 196 		hidden_list_case ?
 197 		"mail:ask-send-only-bcc-contact" :
 198 		"mail:ask-send-only-bcc", NULL);
 199 }
 200 
 201 static gboolean
 202 is_group_definition (const gchar *str)
 203 {
 204 	const gchar *colon;
 205 
 206 	if (!str || !*str)
 207 		return FALSE;
 208 
 209 	colon = strchr (str, ':');
 210 	return colon > str && strchr (str, ';') > colon;
 211 }
 212 
 213 static gboolean
 214 composer_presend_check_recipients (EMsgComposer *composer,
 215                                    EMailSession *session)
 216 {
 217 	EDestination **recipients;
 218 	EDestination **recipients_bcc;
 219 	CamelInternetAddress *cia;
 220 	EComposerHeaderTable *table;
 221 	EComposerHeader *post_to_header;
 222 	GString *invalid_addrs = NULL;
 223 	gboolean check_passed = FALSE;
 224 	gint hidden = 0;
 225 	gint shown = 0;
 226 	gint num = 0;
 227 	gint num_bcc = 0;
 228 	gint num_post = 0;
 229 	gint ii;
 230 
 231 	/* We should do all of the validity checks based on the composer,
 232 	 * and not on the created message, as extra interaction may occur
 233 	 * when we get the message (e.g. passphrase to sign a message). */
 234 
 235 	table = e_msg_composer_get_header_table (composer);
 236 	recipients = e_composer_header_table_get_destinations (table);
 237 
 238 	cia = camel_internet_address_new ();
 239 
 240 	/* See which ones are visible, present, etc. */
 241 	for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) {
 242 		const gchar *addr;
 243 		gint len, j;
 244 
 245 		addr = e_destination_get_address (recipients[ii]);
 246 		if (addr == NULL || *addr == '\0')
 247 			continue;
 248 
 249 		camel_address_decode (CAMEL_ADDRESS (cia), addr);
 250 		len = camel_address_length (CAMEL_ADDRESS (cia));
 251 
 252 		if (len > 0) {
 253 			if (!e_destination_is_evolution_list (recipients[ii])) {
 254 				for (j = 0; j < len; j++) {
 255 					const gchar *name = NULL, *eml = NULL;
 256 
 257 					if (!camel_internet_address_get (cia, j, &name, &eml) ||
 258 					    !eml ||
 259 					    strchr (eml, '@') <= eml) {
 260 						if (!invalid_addrs)
 261 							invalid_addrs = g_string_new ("");
 262 						else
 263 							g_string_append (invalid_addrs, ", ");
 264 
 265 						if (name)
 266 							g_string_append (invalid_addrs, name);
 267 						if (eml) {
 268 							g_string_append (invalid_addrs, name ? " <" : "");
 269 							g_string_append (invalid_addrs, eml);
 270 							g_string_append (invalid_addrs, name ? ">" : "");
 271 						}
 272 					}
 273 				}
 274 			}
 275 
 276 			camel_address_remove (CAMEL_ADDRESS (cia), -1);
 277 			num++;
 278 			if (e_destination_is_evolution_list (recipients[ii])
 279 			    && !e_destination_list_show_addresses (recipients[ii])) {
 280 				hidden++;
 281 			} else {
 282 				shown++;
 283 			}
 284 		} else if (is_group_definition (addr)) {
 285 			/* like an address, it will not claim on only-bcc */
 286 			shown++;
 287 			num++;
 288 		} else if (!invalid_addrs) {
 289 			invalid_addrs = g_string_new (addr);
 290 		} else {
 291 			g_string_append (invalid_addrs, ", ");
 292 			g_string_append (invalid_addrs, addr);
 293 		}
 294 	}
 295 
 296 	recipients_bcc = e_composer_header_table_get_destinations_bcc (table);
 297 	if (recipients_bcc) {
 298 		for (ii = 0; recipients_bcc[ii] != NULL; ii++) {
 299 			const gchar *addr;
 300 
 301 			addr = e_destination_get_address (recipients_bcc[ii]);
 302 			if (addr == NULL || *addr == '\0')
 303 				continue;
 304 
 305 			camel_address_decode (CAMEL_ADDRESS (cia), addr);
 306 			if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) {
 307 				camel_address_remove (CAMEL_ADDRESS (cia), -1);
 308 				num_bcc++;
 309 			}
 310 		}
 311 
 312 		e_destination_freev (recipients_bcc);
 313 	}
 314 
 315 	g_object_unref (cia);
 316 
 317 	post_to_header = e_composer_header_table_get_header (
 318 		table, E_COMPOSER_HEADER_POST_TO);
 319 	if (e_composer_header_get_visible (post_to_header)) {
 320 		GList *postlist;
 321 
 322 		postlist = e_composer_header_table_get_post_to (table);
 323 		num_post = g_list_length (postlist);
 324 		g_list_foreach (postlist, (GFunc) g_free, NULL);
 325 		g_list_free (postlist);
 326 	}
 327 
 328 	/* I'm sensing a lack of love, er, I mean recipients. */
 329 	if (num == 0 && num_post == 0) {
 330 		e_alert_submit (
 331 			E_ALERT_SINK (composer),
 332 			"mail:send-no-recipients", NULL);
 333 		goto finished;
 334 	}
 335 
 336 	if (invalid_addrs) {
 337 		if (!em_utils_prompt_user (
 338 			GTK_WINDOW (composer),
 339 			"prompt-on-invalid-recip",
 340 			strstr (invalid_addrs->str, ", ") ?
 341 				"mail:ask-send-invalid-recip-multi" :
 342 				"mail:ask-send-invalid-recip-one",
 343 			invalid_addrs->str, NULL)) {
 344 			g_string_free (invalid_addrs, TRUE);
 345 			goto finished;
 346 		}
 347 
 348 		g_string_free (invalid_addrs, TRUE);
 349 	}
 350 
 351 	if (num > 0 && (num == num_bcc || shown == 0)) {
 352 		/* this means that the only recipients are Bcc's */
 353 		if (!ask_confirm_for_only_bcc (composer, shown == 0))
 354 			goto finished;
 355 	}
 356 
 357 	check_passed = TRUE;
 358 
 359 finished:
 360 	if (recipients != NULL)
 361 		e_destination_freev (recipients);
 362 
 363 	return check_passed;
 364 }
 365 
 366 static gboolean
 367 composer_presend_check_identity (EMsgComposer *composer,
 368                                  EMailSession *session)
 369 {
 370 	EComposerHeaderTable *table;
 371 	ESourceRegistry *registry;
 372 	ESource *source;
 373 	const gchar *uid;
 374 	gboolean success = TRUE;
 375 
 376 	table = e_msg_composer_get_header_table (composer);
 377 	registry = e_composer_header_table_get_registry (table);
 378 	uid = e_composer_header_table_get_identity_uid (table);
 379 	source = e_source_registry_ref_source (registry, uid);
 380 	g_return_val_if_fail (source != NULL, FALSE);
 381 
 382 	if (!e_source_get_enabled (source)) {
 383 		e_alert_submit (
 384 			E_ALERT_SINK (composer),
 385 			"mail:send-no-account-enabled", NULL);
 386 		success = FALSE;
 387 	}
 388 
 389 	g_object_unref (source);
 390 
 391 	return success;
 392 }
 393 
 394 static gboolean
 395 composer_presend_check_downloads (EMsgComposer *composer,
 396                                   EMailSession *session)
 397 {
 398 	EAttachmentView *view;
 399 	EAttachmentStore *store;
 400 	gboolean check_passed = TRUE;
 401 
 402 	view = e_msg_composer_get_attachment_view (composer);
 403 	store = e_attachment_view_get_store (view);
 404 
 405 	if (e_attachment_store_get_num_loading (store) > 0) {
 406 		if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL,
 407 		    "mail-composer:ask-send-message-pending-download", NULL))
 408 			check_passed = FALSE;
 409 	}
 410 
 411 	return check_passed;
 412 }
 413 
 414 static gboolean
 415 composer_presend_check_plugins (EMsgComposer *composer,
 416                                 EMailSession *session)
 417 {
 418 	EMEvent *eme;
 419 	EMEventTargetComposer *target;
 420 	gpointer data;
 421 
 422 	/** @Event: composer.presendchecks
 423 	 * @Title: Composer PreSend Checks
 424 	 * @Target: EMEventTargetMessage
 425 	 *
 426 	 * composer.presendchecks is emitted during pre-checks for the
 427 	 * message just before sending.  Since the e-plugin framework
 428 	 * doesn't provide a way to return a value from the plugin,
 429 	 * use 'presend_check_status' to set whether the check passed.
 430 	 */
 431 	eme = em_event_peek ();
 432 	target = em_event_target_new_composer (eme, composer, 0);
 433 
 434 	e_event_emit (
 435 		(EEvent *) eme, "composer.presendchecks",
 436 		(EEventTarget *) target);
 437 
 438 	/* A non-NULL value for this key means the check failed. */
 439 	data = g_object_get_data (G_OBJECT (composer), "presend_check_status");
 440 
 441 	/* Clear the value in case we have to run these checks again. */
 442 	g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL);
 443 
 444 	return (data == NULL);
 445 }
 446 
 447 static gboolean
 448 composer_presend_check_subject (EMsgComposer *composer,
 449                                 EMailSession *session)
 450 {
 451 	EComposerHeaderTable *table;
 452 	const gchar *subject;
 453 	gboolean check_passed = TRUE;
 454 
 455 	table = e_msg_composer_get_header_table (composer);
 456 	subject = e_composer_header_table_get_subject (table);
 457 
 458 	if (subject == NULL || subject[0] == '\0') {
 459 		if (!ask_confirm_for_empty_subject (composer))
 460 			check_passed = FALSE;
 461 	}
 462 
 463 	return check_passed;
 464 }
 465 
 466 static gboolean
 467 composer_presend_check_unwanted_html (EMsgComposer *composer,
 468                                       EMailSession *session)
 469 {
 470 	EDestination **recipients;
 471 	EComposerHeaderTable *table;
 472 	GSettings *settings;
 473 	gboolean check_passed = TRUE;
 474 	gboolean html_mode;
 475 	gboolean send_html;
 476 	gboolean confirm_html;
 477 	gint ii;
 478 
 479 	settings = g_settings_new ("org.gnome.evolution.mail");
 480 
 481 	table = e_msg_composer_get_header_table (composer);
 482 	recipients = e_composer_header_table_get_destinations (table);
 483 	html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer));
 484 
 485 	send_html = g_settings_get_boolean (settings, "composer-send-html");
 486 	confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");
 487 
 488 	/* Only show this warning if our default is to send html.  If it
 489 	 * isn't, we've manually switched into html mode in the composer
 490 	 * and (presumably) had a good reason for doing this. */
 491 	if (html_mode && send_html && confirm_html && recipients != NULL) {
 492 		gboolean html_problem = FALSE;
 493 
 494 		for (ii = 0; recipients[ii] != NULL; ii++) {
 495 			if (!e_destination_get_html_mail_pref (recipients[ii]))
 496 				html_problem = TRUE;
 497 				break;
 498 		}
 499 
 500 		if (html_problem) {
 501 			if (!ask_confirm_for_unwanted_html_mail (
 502 				composer, recipients))
 503 				check_passed = FALSE;
 504 		}
 505 	}
 506 
 507 	if (recipients != NULL)
 508 		e_destination_freev (recipients);
 509 
 510 	g_object_unref (settings);
 511 
 512 	return check_passed;
 513 }
 514 
 515 static void
 516 composer_send_completed (EMailSession *session,
 517                          GAsyncResult *result,
 518                          AsyncContext *context)
 519 {
 520 	GError *error = NULL;
 521 	gboolean set_changed = FALSE;
 522 
 523 	e_mail_session_send_to_finish (session, result, &error);
 524 
 525 	if (e_activity_handle_cancellation (context->activity, error)) {
 526 		g_error_free (error);
 527 		set_changed = TRUE;
 528 		goto exit;
 529 	}
 530 
 531 	/* Post-processing errors are shown in the shell window. */
 532 	if (g_error_matches (error, E_MAIL_ERROR, E_MAIL_ERROR_POST_PROCESSING)) {
 533 		EAlert *alert;
 534 		EShell *shell;
 535 
 536 		shell = e_msg_composer_get_shell (context->composer);
 537 
 538 		alert = e_alert_new (
 539 			"mail-composer:send-post-processing-error",
 540 			error->message, NULL);
 541 		e_shell_submit_alert (shell, alert);
 542 		g_object_unref (alert);
 543 
 544 	/* All other errors are shown in the composer window. */
 545 	} else if (error != NULL) {
 546 		gint response;
 547 
 548 		/* Clear the activity bar before
 549 		 * presenting the error dialog. */
 550 		g_object_unref (context->activity);
 551 		context->activity = NULL;
 552 
 553 		response = e_alert_run_dialog_for_args (
 554 			GTK_WINDOW (context->composer),
 555 			"mail-composer:send-error",
 556 			error->message, NULL);
 557 		if (response == GTK_RESPONSE_OK)  /* Try Again */
 558 			e_msg_composer_send (context->composer);
 559 		if (response == GTK_RESPONSE_ACCEPT)  /* Save to Outbox */
 560 			e_msg_composer_save_to_outbox (context->composer);
 561 		g_error_free (error);
 562 		set_changed = TRUE;
 563 		goto exit;
 564 	}
 565 
 566 	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
 567 
 568 	/* Wait for the EActivity's completion message to
 569 	 * time out and then destroy the composer window. */
 570 	g_object_weak_ref (
 571 		G_OBJECT (context->activity), (GWeakNotify)
 572 		gtk_widget_destroy, context->composer);
 573 
 574 exit:
 575 	if (set_changed) {
 576 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 577 		gtk_window_present (GTK_WINDOW (context->composer));
 578 	}
 579 
 580 	async_context_free (context);
 581 }
 582 
 583 static void
 584 em_utils_composer_send_cb (EMsgComposer *composer,
 585                            CamelMimeMessage *message,
 586                            EActivity *activity,
 587                            EMailSession *session)
 588 {
 589 	AsyncContext *context;
 590 	GCancellable *cancellable;
 591 
 592 	context = g_slice_new0 (AsyncContext);
 593 	context->message = g_object_ref (message);
 594 	context->composer = g_object_ref (composer);
 595 	context->activity = g_object_ref (activity);
 596 
 597 	cancellable = e_activity_get_cancellable (activity);
 598 
 599 	e_mail_session_send_to (
 600 		session, message,
 601 		G_PRIORITY_DEFAULT, cancellable, NULL, NULL,
 602 		(GAsyncReadyCallback) composer_send_completed,
 603 		context);
 604 }
 605 
 606 static void
 607 composer_set_no_change (EMsgComposer *composer)
 608 {
 609 	GtkhtmlEditor *editor;
 610 
 611 	g_return_if_fail (composer != NULL);
 612 
 613 	editor = GTKHTML_EDITOR (composer);
 614 
 615 	gtkhtml_editor_drop_undo (editor);
 616 	gtkhtml_editor_set_changed (editor, FALSE);
 617 }
 618 
 619 /* delete original messages from Outbox folder */
 620 static void
 621 manage_x_evolution_replace_outbox (EMsgComposer *composer,
 622                                    EMailSession *session,
 623                                    CamelMimeMessage *message,
 624                                    GCancellable *cancellable)
 625 {
 626 	const gchar *message_uid;
 627 	const gchar *header;
 628 	CamelFolder *outbox;
 629 
 630 	g_return_if_fail (composer != NULL);
 631 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
 632 
 633 	header = "X-Evolution-Replace-Outbox-UID";
 634 	message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header);
 635 	e_msg_composer_remove_header (composer, header);
 636 
 637 	if (!message_uid)
 638 		return;
 639 
 640 	outbox = e_mail_session_get_local_folder (
 641 		session, E_MAIL_LOCAL_FOLDER_OUTBOX);
 642 	g_return_if_fail (outbox != NULL);
 643 
 644 	camel_folder_set_message_flags (
 645 		outbox, message_uid,
 646 		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN,
 647 		CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN);
 648 
 649 	/* ignore errors here */
 650 	camel_folder_synchronize_message_sync (
 651 		outbox, message_uid, cancellable, NULL);
 652 }
 653 
 654 static void
 655 composer_save_to_drafts_complete (EMailSession *session,
 656                                   GAsyncResult *result,
 657                                   AsyncContext *context)
 658 {
 659 	GError *error = NULL;
 660 
 661 	/* We don't really care if this failed.  If something other than
 662 	 * cancellation happened, emit a runtime warning so the error is
 663 	 * not completely lost. */
 664 
 665 	e_mail_session_handle_draft_headers_finish (session, result, &error);
 666 
 667 	if (e_activity_handle_cancellation (context->activity, error)) {
 668 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 669 		g_error_free (error);
 670 
 671 	} else if (error != NULL) {
 672 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 673 		g_warning ("%s", error->message);
 674 		g_error_free (error);
 675 
 676 	} else
 677 		e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
 678 
 679 	/* Encode the draft message we just saved into the EMsgComposer
 680 	 * as X-Evolution-Draft headers.  The message will be marked for
 681 	 * deletion if the user saves a newer draft message or sends the
 682 	 * composed message. */
 683 	e_msg_composer_set_draft_headers (
 684 		context->composer, context->folder_uri,
 685 		context->message_uid);
 686 
 687 	async_context_free (context);
 688 }
 689 
 690 static void
 691 composer_save_to_drafts_cleanup (CamelFolder *drafts_folder,
 692                                  GAsyncResult *result,
 693                                  AsyncContext *context)
 694 {
 695 	CamelSession *session;
 696 	EAlertSink *alert_sink;
 697 	GCancellable *cancellable;
 698 	GError *error = NULL;
 699 
 700 	session = e_msg_composer_get_session (context->composer);
 701 	alert_sink = e_activity_get_alert_sink (context->activity);
 702 	cancellable = e_activity_get_cancellable (context->activity);
 703 
 704 	e_mail_folder_append_message_finish (
 705 		drafts_folder, result, &context->message_uid, &error);
 706 
 707 	if (e_activity_handle_cancellation (context->activity, error)) {
 708 		g_warn_if_fail (context->message_uid == NULL);
 709 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 710 		async_context_free (context);
 711 		g_error_free (error);
 712 		return;
 713 
 714 	} else if (error != NULL) {
 715 		g_warn_if_fail (context->message_uid == NULL);
 716 		e_alert_submit (
 717 			alert_sink,
 718 			"mail-composer:save-to-drafts-error",
 719 			error->message, NULL);
 720 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 721 		async_context_free (context);
 722 		g_error_free (error);
 723 		return;
 724 	}
 725 
 726 	/* Mark the previously saved draft message for deletion.
 727 	 * Note: This is just a nice-to-have; ignore failures. */
 728 	e_mail_session_handle_draft_headers (
 729 		E_MAIL_SESSION (session), context->message,
 730 		G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback)
 731 		composer_save_to_drafts_complete, context);
 732 }
 733 
 734 static void
 735 composer_save_to_drafts_append_mail (AsyncContext *context,
 736                                      CamelFolder *drafts_folder)
 737 {
 738 	CamelFolder *local_drafts_folder;
 739 	GCancellable *cancellable;
 740 	CamelMessageInfo *info;
 741 
 742 	local_drafts_folder =
 743 		e_mail_session_get_local_folder (
 744 		context->session, E_MAIL_LOCAL_FOLDER_DRAFTS);
 745 
 746 	if (drafts_folder == NULL)
 747 		drafts_folder = g_object_ref (local_drafts_folder);
 748 
 749 	cancellable = e_activity_get_cancellable (context->activity);
 750 
 751 	info = camel_message_info_new (NULL);
 752 
 753 	camel_message_info_set_flags (
 754 		info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0);
 755 
 756 	camel_medium_remove_header (
 757 		CAMEL_MEDIUM (context->message),
 758 		"X-Evolution-Replace-Outbox-UID");
 759 
 760 	e_mail_folder_append_message (
 761 		drafts_folder, context->message,
 762 		info, G_PRIORITY_DEFAULT, cancellable,
 763 		(GAsyncReadyCallback) composer_save_to_drafts_cleanup,
 764 		context);
 765 
 766 	camel_message_info_free (info);
 767 
 768 	g_object_unref (drafts_folder);
 769 }
 770 
 771 static void
 772 composer_save_to_drafts_got_folder (EMailSession *session,
 773                                     GAsyncResult *result,
 774                                     AsyncContext *context)
 775 {
 776 	CamelFolder *drafts_folder;
 777 	GError *error = NULL;
 778 
 779 	drafts_folder = e_mail_session_uri_to_folder_finish (
 780 		session, result, &error);
 781 
 782 	if (e_activity_handle_cancellation (context->activity, error)) {
 783 		g_warn_if_fail (drafts_folder == NULL);
 784 		gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 785 		async_context_free (context);
 786 		g_error_free (error);
 787 		return;
 788 
 789 	} else if (error != NULL) {
 790 		gint response;
 791 
 792 		g_warn_if_fail (drafts_folder == NULL);
 793 
 794 		/* XXX Not showing the error message in the dialog? */
 795 		g_error_free (error);
 796 
 797 		/* If we can't retrieve the Drafts folder for the
 798 		 * selected account, ask the user if he wants to
 799 		 * save to the local Drafts folder instead. */
 800 		response = e_alert_run_dialog_for_args (
 801 			GTK_WINDOW (context->composer),
 802 			"mail:ask-default-drafts", NULL);
 803 		if (response != GTK_RESPONSE_YES) {
 804 			gtkhtml_editor_set_changed (GTKHTML_EDITOR (context->composer), TRUE);
 805 			async_context_free (context);
 806 			return;
 807 		}
 808 	}
 809 
 810 	composer_save_to_drafts_append_mail (context, drafts_folder);
 811 }
 812 
 813 static void
 814 em_utils_composer_save_to_drafts_cb (EMsgComposer *composer,
 815                                      CamelMimeMessage *message,
 816                                      EActivity *activity,
 817                                      EMailSession *session)
 818 {
 819 	AsyncContext *context;
 820 	EComposerHeaderTable *table;
 821 	ESourceRegistry *registry;
 822 	ESource *source;
 823 	const gchar *local_drafts_folder_uri;
 824 	const gchar *identity_uid;
 825 	gchar *drafts_folder_uri = NULL;
 826 
 827 	context = g_slice_new0 (AsyncContext);
 828 	context->message = g_object_ref (message);
 829 	context->session = g_object_ref (session);
 830 	context->composer = g_object_ref (composer);
 831 	context->activity = g_object_ref (activity);
 832 
 833 	table = e_msg_composer_get_header_table (composer);
 834 
 835 	registry = e_composer_header_table_get_registry (table);
 836 	identity_uid = e_composer_header_table_get_identity_uid (table);
 837 	source = e_source_registry_ref_source (registry, identity_uid);
 838 
 839 	/* Get the selected identity's preferred Drafts folder. */
 840 	if (source != NULL) {
 841 		ESourceMailComposition *extension;
 842 		const gchar *extension_name;
 843 		gchar *uri;
 844 
 845 		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
 846 		extension = e_source_get_extension (source, extension_name);
 847 		uri = e_source_mail_composition_dup_drafts_folder (extension);
 848 
 849 		drafts_folder_uri = uri;
 850 
 851 		g_object_unref (source);
 852 	}
 853 
 854 	local_drafts_folder_uri =
 855 		e_mail_session_get_local_folder_uri (
 856 		session, E_MAIL_LOCAL_FOLDER_DRAFTS);
 857 
 858 	if (drafts_folder_uri == NULL) {
 859 		composer_save_to_drafts_append_mail (context, NULL);
 860 		context->folder_uri = g_strdup (local_drafts_folder_uri);
 861 	} else {
 862 		GCancellable *cancellable;
 863 
 864 		cancellable = e_activity_get_cancellable (activity);
 865 		context->folder_uri = g_strdup (drafts_folder_uri);
 866 
 867 		e_mail_session_uri_to_folder (
 868 			session, drafts_folder_uri, 0,
 869 			G_PRIORITY_DEFAULT, cancellable,
 870 			(GAsyncReadyCallback)
 871 			composer_save_to_drafts_got_folder, context);
 872 
 873 		g_free (drafts_folder_uri);
 874 	}
 875 }
 876 
 877 static void
 878 composer_save_to_outbox_completed (EMailSession *session,
 879                                    GAsyncResult *result,
 880                                    AsyncContext *context)
 881 {
 882 	EAlertSink *alert_sink;
 883 	GError *error = NULL;
 884 
 885 	alert_sink = e_activity_get_alert_sink (context->activity);
 886 
 887 	e_mail_session_append_to_local_folder_finish (
 888 		session, result, NULL, &error);
 889 
 890 	if (e_activity_handle_cancellation (context->activity, error)) {
 891 		g_error_free (error);
 892 		goto exit;
 893 
 894 	} else if (error != NULL) {
 895 		e_alert_submit (
 896 			alert_sink,
 897 			"mail-composer:append-to-outbox-error",
 898 			error->message, NULL);
 899 		g_error_free (error);
 900 		goto exit;
 901 	}
 902 
 903 	/* special processing for Outbox folder */
 904 	manage_x_evolution_replace_outbox (
 905 		context->composer, session, context->message,
 906 		e_activity_get_cancellable (context->activity));
 907 
 908 	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
 909 
 910 	/* Wait for the EActivity's completion message to
 911 	 * time out and then destroy the composer window. */
 912 	g_object_weak_ref (
 913 		G_OBJECT (context->activity), (GWeakNotify)
 914 		gtk_widget_destroy, context->composer);
 915 
 916 exit:
 917 	async_context_free (context);
 918 }
 919 
 920 static void
 921 em_utils_composer_save_to_outbox_cb (EMsgComposer *composer,
 922                                      CamelMimeMessage *message,
 923                                      EActivity *activity,
 924                                      EMailSession *session)
 925 {
 926 	AsyncContext *context;
 927 	CamelMessageInfo *info;
 928 	GCancellable *cancellable;
 929 
 930 	context = g_slice_new0 (AsyncContext);
 931 	context->message = g_object_ref (message);
 932 	context->composer = g_object_ref (composer);
 933 	context->activity = g_object_ref (activity);
 934 
 935 	cancellable = e_activity_get_cancellable (activity);
 936 
 937 	info = camel_message_info_new (NULL);
 938 	camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0);
 939 
 940 	e_mail_session_append_to_local_folder (
 941 		session, E_MAIL_LOCAL_FOLDER_OUTBOX,
 942 		message, info, G_PRIORITY_DEFAULT, cancellable,
 943 		(GAsyncReadyCallback) composer_save_to_outbox_completed,
 944 		context);
 945 
 946 	camel_message_info_free (info);
 947 }
 948 
 949 static void
 950 composer_print_done_cb (EMailPrinter *emp,
 951                         GtkPrintOperation *operation,
 952                         GtkPrintOperationResult result,
 953                         gpointer user_data)
 954 {
 955 	g_object_unref (emp);
 956 }
 957 
 958 static void
 959 em_utils_composer_print_cb (EMsgComposer *composer,
 960                             GtkPrintOperationAction action,
 961                             CamelMimeMessage *message,
 962                             EActivity *activity,
 963                             EMailSession *session)
 964 {
 965 	EMailPrinter *emp;
 966 	EMailParser *parser;
 967 	EMailPartList *parts;
 968 	const gchar *message_id;
 969 
 970 	parser = e_mail_parser_new (CAMEL_SESSION (session));
 971 
 972 	message_id = camel_mime_message_get_message_id (message);
 973 	parts = e_mail_parser_parse_sync (parser, NULL, g_strdup (message_id), message, NULL);
 974 
 975         /* Use EMailPrinter and WebKit to print the message */
 976 	emp = e_mail_printer_new (parts);
 977 	g_signal_connect (
 978 		emp, "done",
 979 		G_CALLBACK (composer_print_done_cb), NULL);
 980 
 981 	e_mail_printer_print (emp, action, NULL, NULL);
 982 
 983 	g_object_unref (parts);
 984 }
 985 
 986 /* Composing messages... */
 987 
 988 static EMsgComposer *
 989 create_new_composer (EShell *shell,
 990                      const gchar *subject,
 991                      CamelFolder *folder)
 992 {
 993 	EMsgComposer *composer;
 994 	ESourceRegistry *registry;
 995 	EComposerHeaderTable *table;
 996 	ESource *source = NULL;
 997 	gchar *identity = NULL;
 998 
 999 	composer = e_msg_composer_new (shell);
1000 
1001 	table = e_msg_composer_get_header_table (composer);
1002 	registry = e_composer_header_table_get_registry (table);
1003 
1004 	if (folder != NULL) {
1005 		CamelStore *store;
1006 		gchar *folder_uri;
1007 		GList *list;
1008 
1009 		store = camel_folder_get_parent_store (folder);
1010 		source = em_utils_ref_mail_identity_for_store (registry, store);
1011 
1012 		folder_uri = e_mail_folder_uri_from_folder (folder);
1013 
1014 		list = g_list_prepend (NULL, folder_uri);
1015 		e_composer_header_table_set_post_to_list (table, list);
1016 		g_list_free (list);
1017 
1018 		g_free (folder_uri);
1019 	}
1020 
1021 	if (source != NULL) {
1022 		identity = e_source_dup_uid (source);
1023 		g_object_unref (source);
1024 	}
1025 
1026 	e_composer_header_table_set_subject (table, subject);
1027 	e_composer_header_table_set_identity_uid (table, identity);
1028 
1029 	g_free (identity);
1030 
1031 	return composer;
1032 }
1033 
1034 /**
1035  * em_utils_compose_new_message:
1036  * @shell: an #EShell
1037  * @folder: a #CamelFolder, or %NULL
1038  *
1039  * Opens a new composer window as a child window of @parent's toplevel
1040  * window.
1041  **/
1042 void
1043 em_utils_compose_new_message (EShell *shell,
1044                               CamelFolder *folder)
1045 {
1046 	EMsgComposer *composer;
1047 
1048 	g_return_if_fail (E_IS_SHELL (shell));
1049 
1050 	if (folder != NULL)
1051 		g_return_if_fail (CAMEL_IS_FOLDER (folder));
1052 
1053 	composer = create_new_composer (shell, "", folder);
1054 	composer_set_no_change (composer);
1055 
1056 	gtk_widget_show (GTK_WIDGET (composer));
1057 }
1058 
1059 /**
1060  * em_utils_compose_new_message_with_mailto:
1061  * @shell: an #EShell
1062  * @mailto: a mailto URL
1063  * @folder: a #CamelFolder, or %NULL
1064  *
1065  * Opens a new composer window as a child window of @parent's toplevel
1066  * window. If @mailto is non-NULL, the composer fields will be filled in
1067  * according to the values in the mailto URL.
1068  **/
1069 EMsgComposer *
1070 em_utils_compose_new_message_with_mailto (EShell *shell,
1071                                           const gchar *mailto,
1072                                           CamelFolder *folder)
1073 {
1074 	EMsgComposer *composer;
1075 	ESourceRegistry *registry;
1076 	EComposerHeaderTable *table;
1077 
1078 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
1079 
1080 	if (folder != NULL)
1081 		g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
1082 
1083 	if (mailto != NULL)
1084 		composer = e_msg_composer_new_from_url (shell, mailto);
1085 	else
1086 		composer = e_msg_composer_new (shell);
1087 
1088 	table = e_msg_composer_get_header_table (composer);
1089 	registry = e_composer_header_table_get_registry (table);
1090 
1091 	composer_set_no_change (composer);
1092 
1093 	gtk_window_present (GTK_WINDOW (composer));
1094 
1095 	/* If a CamelFolder was given, we need to backtrack and find
1096 	 * the corresponding ESource with a Mail Identity extension. */
1097 
1098 	if (folder != NULL) {
1099 		ESource *source;
1100 		CamelStore *store;
1101 
1102 		store = camel_folder_get_parent_store (folder);
1103 		source = em_utils_ref_mail_identity_for_store (registry, store);
1104 
1105 		if (source != NULL) {
1106 			const gchar *uid = e_source_get_uid (source);
1107 			e_composer_header_table_set_identity_uid (table, uid);
1108 			g_object_unref (source);
1109 		}
1110 	}
1111 
1112 	return composer;
1113 }
1114 
1115 static gboolean
1116 replace_variables (GSList *clues,
1117                    CamelMimeMessage *message,
1118                    gchar **pstr)
1119 {
1120 	gint i;
1121 	gboolean string_changed = FALSE, count1 = FALSE;
1122 	gchar *str;
1123 
1124 	g_return_val_if_fail (pstr != NULL, FALSE);
1125 	g_return_val_if_fail (*pstr != NULL, FALSE);
1126 	g_return_val_if_fail (message != NULL, FALSE);
1127 
1128 	str = *pstr;
1129 
1130 	for (i = 0; i < strlen (str); i++) {
1131 		const gchar *cur = str + i;
1132 		if (!g_ascii_strncasecmp (cur, "$", 1)) {
1133 			const gchar *end = cur + 1;
1134 			gchar *out;
1135 			gchar **temp_str;
1136 			GSList *list;
1137 
1138 			while (*end && (g_unichar_isalnum (*end) || *end == '_'))
1139 				end++;
1140 
1141 			out = g_strndup ((const gchar *) cur, end - cur);
1142 
1143 			temp_str = g_strsplit (str, out, 2);
1144 
1145 			for (list = clues; list; list = g_slist_next (list)) {
1146 				gchar **temp = g_strsplit (list->data, "=", 2);
1147 				if (!g_ascii_strcasecmp (temp[0], out + 1)) {
1148 					g_free (str);
1149 					str = g_strconcat (temp_str[0], temp[1], temp_str[1], NULL);
1150 					count1 = TRUE;
1151 					string_changed = TRUE;
1152 				} else
1153 					count1 = FALSE;
1154 				g_strfreev (temp);
1155 			}
1156 
1157 			if (!count1) {
1158 				if (getenv (out + 1)) {
1159 					g_free (str);
1160 					str = g_strconcat (
1161 						temp_str[0],
1162 						getenv (out + 1),
1163 						temp_str[1], NULL);
1164 					count1 = TRUE;
1165 					string_changed = TRUE;
1166 				} else
1167 					count1 = FALSE;
1168 			}
1169 
1170 			if (!count1) {
1171 				CamelInternetAddress *to;
1172 				const gchar *name, *addr;
1173 
1174 				to = camel_mime_message_get_recipients (
1175 					message, CAMEL_RECIPIENT_TYPE_TO);
1176 				if (!camel_internet_address_get (to, 0, &name, &addr))
1177 					continue;
1178 
1179 				if (name && g_ascii_strcasecmp ("sender_name", out + 1) == 0) {
1180 					g_free (str);
1181 					str = g_strconcat (temp_str[0], name, temp_str[1], NULL);
1182 					count1 = TRUE;
1183 					string_changed = TRUE;
1184 				} else if (addr && g_ascii_strcasecmp ("sender_email", out + 1) == 0) {
1185 					g_free (str);
1186 					str = g_strconcat (temp_str[0], addr, temp_str[1], NULL);
1187 					count1 = TRUE;
1188 					string_changed = TRUE;
1189 				}
1190 			}
1191 
1192 			g_strfreev (temp_str);
1193 			g_free (out);
1194 		}
1195 	}
1196 
1197 	*pstr = str;
1198 
1199 	return string_changed;
1200 }
1201 
1202 static void
1203 traverse_parts (GSList *clues,
1204                 CamelMimeMessage *message,
1205                 CamelDataWrapper *content)
1206 {
1207 	g_return_if_fail (message != NULL);
1208 
1209 	if (!content)
1210 		return;
1211 
1212 	if (CAMEL_IS_MULTIPART (content)) {
1213 		guint i, n;
1214 		CamelMultipart *multipart = CAMEL_MULTIPART (content);
1215 		CamelMimePart *part;
1216 
1217 		n = camel_multipart_get_number (multipart);
1218 		for (i = 0; i < n; i++) {
1219 			part = camel_multipart_get_part (multipart, i);
1220 			if (!part)
1221 				continue;
1222 
1223 			traverse_parts (clues, message, CAMEL_DATA_WRAPPER (part));
1224 		}
1225 	} else if (CAMEL_IS_MIME_PART (content)) {
1226 		CamelMimePart *part = CAMEL_MIME_PART (content);
1227 		CamelContentType *type;
1228 		CamelStream *stream;
1229 		GByteArray *byte_array;
1230 		gchar *str;
1231 
1232 		content = camel_medium_get_content (CAMEL_MEDIUM (part));
1233 		if (!content)
1234 			return;
1235 
1236 		if (CAMEL_IS_MULTIPART (content)) {
1237 			traverse_parts (clues, message, CAMEL_DATA_WRAPPER (content));
1238 			return;
1239 		}
1240 
1241 		type = camel_mime_part_get_content_type (part);
1242 		if (!camel_content_type_is (type, "text", "*"))
1243 			return;
1244 
1245 		byte_array = g_byte_array_new ();
1246 		stream = camel_stream_mem_new_with_byte_array (byte_array);
1247 		camel_data_wrapper_decode_to_stream_sync (
1248 			content, stream, NULL, NULL);
1249 
1250 		str = g_strndup ((gchar *) byte_array->data, byte_array->len);
1251 		g_object_unref (stream);
1252 
1253 		if (replace_variables (clues, message, &str)) {
1254 			stream = camel_stream_mem_new_with_buffer (str, strlen (str));
1255 			camel_data_wrapper_construct_from_stream_sync (
1256 				content, stream, NULL, NULL);
1257 			g_object_unref (stream);
1258 		}
1259 
1260 		g_free (str);
1261 	}
1262 }
1263 
1264 /* Editing messages... */
1265 
1266 typedef enum {
1267 	QUOTING_ATTRIBUTION,
1268 	QUOTING_FORWARD,
1269 	QUOTING_ORIGINAL
1270 } QuotingTextEnum;
1271 
1272 static struct {
1273 	const gchar * conf_key;
1274 	const gchar * message;
1275 } conf_messages[] = {
1276 	[QUOTING_ATTRIBUTION] =
1277 		{ "composer-message-attribution",
1278 		/* Note to translators: this is the attribution string used
1279 		 * when quoting messages.  Each ${Variable} gets replaced
1280 		 * with a value.  To see a full list of available variables,
1281 		 * see mail/em-composer-utils.c:attribvars array. */
1282 		  N_("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at "
1283 		     "${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:")
1284 		},
1285 
1286 	[QUOTING_FORWARD] =
1287 		{ "composer-message-forward",
1288 		  N_("-------- Forwarded Message --------")
1289 		},
1290 
1291 	[QUOTING_ORIGINAL] =
1292 		{ "composer-message-original",
1293 		  N_("-----Original Message-----")
1294 		}
1295 };
1296 
1297 static gchar *
1298 quoting_text (QuotingTextEnum type)
1299 {
1300 	GSettings *settings;
1301 	gchar *text;
1302 
1303 	settings = g_settings_new ("org.gnome.evolution.mail");
1304 	text = g_settings_get_string (settings, conf_messages[type].conf_key);
1305 	g_object_unref (settings);
1306 
1307 	if (text && *text)
1308 		return text;
1309 
1310 	g_free (text);
1311 
1312 	return g_strdup (_(conf_messages[type].message));
1313 }
1314 
1315 /**
1316  * em_utils_edit_message:
1317  * @shell: an #EShell
1318  * @folder: a #CamelFolder
1319  * @message: a #CamelMimeMessage
1320  * @message_uid: UID of @message, or %NULL
1321  *
1322  * Opens a composer filled in with the headers/mime-parts/etc of
1323  * @message.
1324  **/
1325 GtkWidget *
1326 em_utils_edit_message (EShell *shell,
1327                        CamelFolder *folder,
1328                        CamelMimeMessage *message,
1329                        const gchar *message_uid)
1330 {
1331 	EMsgComposer *composer;
1332 	ESourceRegistry *registry;
1333 	gboolean folder_is_drafts;
1334 	gboolean folder_is_outbox;
1335 	gboolean folder_is_templates;
1336 
1337 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
1338 	g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL);
1339 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1340 
1341 	registry = e_shell_get_registry (shell);
1342 	folder_is_drafts = em_utils_folder_is_drafts (registry, folder);
1343 	folder_is_outbox = em_utils_folder_is_outbox (registry, folder);
1344 	folder_is_templates = em_utils_folder_is_templates (registry, folder);
1345 
1346 	/* Template specific code follows. */
1347 	if (folder_is_templates) {
1348 		CamelDataWrapper *content;
1349 		GSettings *settings;
1350 		gchar **strv;
1351 		gint i;
1352 		GSList *clue_list = NULL;
1353 
1354 		settings = g_settings_new ("org.gnome.evolution.plugin.templates");
1355 
1356 		/* Get the list from GSettings */
1357 		strv = g_settings_get_strv (settings, "template-placeholders");
1358 		for (i = 0; strv[i] != NULL; i++)
1359 			clue_list = g_slist_append (clue_list, g_strdup (strv[i]));
1360 		g_object_unref (settings);
1361 		g_strfreev (strv);
1362 
1363 		content = camel_medium_get_content (CAMEL_MEDIUM (message));
1364 		traverse_parts (clue_list, message, content);
1365 
1366 		g_slist_foreach (clue_list, (GFunc) g_free, NULL);
1367 		g_slist_free (clue_list);
1368 	}
1369 
1370 	composer = e_msg_composer_new_with_message (shell, message, NULL);
1371 	if (!folder_is_templates) {
1372 		EComposerHeaderTable *table;
1373 		ESource *source;
1374 		CamelStore *store;
1375 		gchar *folder_uri;
1376 		GList *list;
1377 
1378 		table = e_msg_composer_get_header_table (composer);
1379 
1380 		store = camel_folder_get_parent_store (folder);
1381 		source = em_utils_ref_mail_identity_for_store (registry, store);
1382 
1383 		if (source != NULL) {
1384 			const gchar *uid = e_source_get_uid (source);
1385 			e_composer_header_table_set_identity_uid (table, uid);
1386 			g_object_unref (source);
1387 		}
1388 
1389 		folder_uri = e_mail_folder_uri_from_folder (folder);
1390 
1391 		list = g_list_prepend (NULL, folder_uri);
1392 		e_composer_header_table_set_post_to_list (table, list);
1393 		g_list_free (list);
1394 
1395 		g_free (folder_uri);
1396 	}
1397 
1398 	e_msg_composer_remove_header (
1399 		composer, "X-Evolution-Replace-Outbox-UID");
1400 
1401 	if (message_uid != NULL && folder_is_drafts) {
1402 		gchar *folder_uri;
1403 
1404 		folder_uri = e_mail_folder_uri_from_folder (folder);
1405 
1406 		e_msg_composer_set_draft_headers (
1407 			composer, folder_uri, message_uid);
1408 
1409 		g_free (folder_uri);
1410 
1411 	} else if (message_uid != NULL && folder_is_outbox) {
1412 		e_msg_composer_set_header (
1413 			composer, "X-Evolution-Replace-Outbox-UID",
1414 			message_uid);
1415 	}
1416 
1417 	composer_set_no_change (composer);
1418 
1419 	gtk_widget_show (GTK_WIDGET (composer));
1420 
1421 	return GTK_WIDGET (composer);
1422 }
1423 
1424 static void
1425 edit_messages_cb (CamelFolder *folder,
1426                   GAsyncResult *result,
1427                   AsyncContext *context)
1428 {
1429 	EShell *shell;
1430 	EMailBackend *backend;
1431 	EAlertSink *alert_sink;
1432 	GHashTable *hash_table;
1433 	GHashTableIter iter;
1434 	gpointer key, value;
1435 	GError *error = NULL;
1436 
1437 	alert_sink = e_mail_reader_get_alert_sink (context->reader);
1438 
1439 	hash_table = e_mail_folder_get_multiple_messages_finish (
1440 		folder, result, &error);
1441 
1442 	if (e_activity_handle_cancellation (context->activity, error)) {
1443 		g_warn_if_fail (hash_table == NULL);
1444 		async_context_free (context);
1445 		g_error_free (error);
1446 		return;
1447 
1448 	} else if (error != NULL) {
1449 		g_warn_if_fail (hash_table == NULL);
1450 		e_alert_submit (
1451 			alert_sink,
1452 			"mail:get-multiple-messages",
1453 			error->message, NULL);
1454 		async_context_free (context);
1455 		g_error_free (error);
1456 		return;
1457 	}
1458 
1459 	g_return_if_fail (hash_table != NULL);
1460 
1461 	backend = e_mail_reader_get_backend (context->reader);
1462 	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
1463 
1464 	/* Open each message in its own composer window. */
1465 
1466 	g_hash_table_iter_init (&iter, hash_table);
1467 
1468 	while (g_hash_table_iter_next (&iter, &key, &value)) {
1469 		CamelMimeMessage *message;
1470 
1471 		if (!context->replace)
1472 			key = NULL;
1473 
1474 		message = CAMEL_MIME_MESSAGE (value);
1475 		camel_medium_remove_header (CAMEL_MEDIUM (value), "X-Mailer");
1476 		em_utils_edit_message (shell, folder, message, key);
1477 	}
1478 
1479 	g_hash_table_unref (hash_table);
1480 
1481 	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
1482 
1483 	async_context_free (context);
1484 }
1485 
1486 /**
1487  * em_utils_edit_messages:
1488  * @reader: an #EMailReader
1489  * @folder: folder containing messages to edit
1490  * @uids: uids of messages to edit
1491  * @replace: replace the existing message(s) when sent or saved.
1492  *
1493  * Opens a composer for each message to be edited.
1494  **/
1495 void
1496 em_utils_edit_messages (EMailReader *reader,
1497                         CamelFolder *folder,
1498                         GPtrArray *uids,
1499                         gboolean replace)
1500 {
1501 	EActivity *activity;
1502 	AsyncContext *context;
1503 	GCancellable *cancellable;
1504 
1505 	g_return_if_fail (E_IS_MAIL_READER (reader));
1506 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1507 	g_return_if_fail (uids != NULL);
1508 
1509 	activity = e_mail_reader_new_activity (reader);
1510 	cancellable = e_activity_get_cancellable (activity);
1511 
1512 	context = g_slice_new0 (AsyncContext);
1513 	context->activity = activity;
1514 	context->reader = g_object_ref (reader);
1515 	context->ptr_array = g_ptr_array_ref (uids);
1516 	context->replace = replace;
1517 
1518 	e_mail_folder_get_multiple_messages (
1519 		folder, uids, G_PRIORITY_DEFAULT,
1520 		cancellable, (GAsyncReadyCallback)
1521 		edit_messages_cb, context);
1522 }
1523 
1524 static void
1525 emu_update_composers_security (EMsgComposer *composer,
1526                                guint32 validity_found)
1527 {
1528 	EShell *shell;
1529 	EShellSettings *shell_settings;
1530 	GtkAction *action;
1531 	gboolean sign_by_default;
1532 
1533 	g_return_if_fail (composer != NULL);
1534 
1535 	shell = e_msg_composer_get_shell (composer);
1536 	shell_settings = e_shell_get_shell_settings (shell);
1537 
1538 	sign_by_default =
1539 		(validity_found & E_MAIL_PART_VALIDITY_SIGNED) != 0 &&
1540 		e_shell_settings_get_boolean (
1541 		shell_settings, "composer-sign-reply-if-signed");
1542 
1543 	/* Pre-set only for encrypted messages, not for signed */
1544 	if (sign_by_default) {
1545 		if (validity_found & E_MAIL_PART_VALIDITY_SMIME)
1546 			action = E_COMPOSER_ACTION_SMIME_SIGN (composer);
1547 		else
1548 			action = E_COMPOSER_ACTION_PGP_SIGN (composer);
1549 
1550 		gtk_toggle_action_set_active (
1551 			GTK_TOGGLE_ACTION (action), TRUE);
1552 	}
1553 
1554 	if (validity_found & E_MAIL_PART_VALIDITY_ENCRYPTED) {
1555 		if (validity_found & E_MAIL_PART_VALIDITY_SMIME)
1556 			action = E_COMPOSER_ACTION_SMIME_ENCRYPT (composer);
1557 		else
1558 			action = E_COMPOSER_ACTION_PGP_ENCRYPT (composer);
1559 
1560 		gtk_toggle_action_set_active (
1561 			GTK_TOGGLE_ACTION (action), TRUE);
1562 	}
1563 }
1564 
1565 static void
1566 real_update_forwarded_flag (gpointer uid,
1567                             gpointer folder)
1568 {
1569 	if (uid && folder)
1570 		camel_folder_set_message_flags (
1571 			folder, uid, CAMEL_MESSAGE_FORWARDED,
1572 			CAMEL_MESSAGE_FORWARDED);
1573 }
1574 
1575 static void
1576 update_forwarded_flags_cb (EMsgComposer *composer,
1577                            ForwardData *data)
1578 {
1579 	if (data && data->uids && data->folder)
1580 		g_ptr_array_foreach (
1581 			data->uids, real_update_forwarded_flag, data->folder);
1582 }
1583 
1584 static void
1585 setup_forward_attached_callbacks (EMsgComposer *composer,
1586                                   CamelFolder *folder,
1587                                   GPtrArray *uids)
1588 {
1589 	ForwardData *data;
1590 
1591 	if (!composer || !folder || !uids || !uids->len)
1592 		return;
1593 
1594 	g_object_ref (folder);
1595 
1596 	data = g_slice_new0 (ForwardData);
1597 	data->folder = g_object_ref (folder);
1598 	data->uids = em_utils_uids_copy (uids);
1599 
1600 	g_signal_connect (
1601 		composer, "send",
1602 		G_CALLBACK (update_forwarded_flags_cb), data);
1603 	g_signal_connect (
1604 		composer, "save-to-drafts",
1605 		G_CALLBACK (update_forwarded_flags_cb), data);
1606 
1607 	g_object_set_data_full (
1608 		G_OBJECT (composer), "forward-data", data,
1609 		(GDestroyNotify) forward_data_free);
1610 }
1611 
1612 static EMsgComposer *
1613 forward_attached (EShell *shell,
1614                   CamelFolder *folder,
1615                   GPtrArray *uids,
1616                   CamelMimePart *part,
1617                   gchar *subject)
1618 {
1619 	EMsgComposer *composer;
1620 
1621 	composer = create_new_composer (shell, subject, folder);
1622 
1623 	e_msg_composer_attach (composer, part);
1624 
1625 	if (uids)
1626 		setup_forward_attached_callbacks (composer, folder, uids);
1627 
1628 	composer_set_no_change (composer);
1629 
1630 	gtk_widget_show (GTK_WIDGET (composer));
1631 
1632 	return composer;
1633 }
1634 
1635 static void
1636 forward_attached_cb (CamelFolder *folder,
1637                      GAsyncResult *result,
1638                      AsyncContext *context)
1639 {
1640 	EShell *shell;
1641 	EMailBackend *backend;
1642 	EAlertSink *alert_sink;
1643 	CamelMimePart *part;
1644 	gchar *subject = NULL;
1645 	GError *error = NULL;
1646 
1647 	alert_sink = e_mail_reader_get_alert_sink (context->reader);
1648 
1649 	part = e_mail_folder_build_attachment_finish (
1650 		folder, result, &subject, &error);
1651 
1652 	if (e_activity_handle_cancellation (context->activity, error)) {
1653 		g_warn_if_fail (part == NULL);
1654 		g_warn_if_fail (subject == NULL);
1655 		async_context_free (context);
1656 		g_error_free (error);
1657 		return;
1658 
1659 	} else if (error != NULL) {
1660 		g_warn_if_fail (part == NULL);
1661 		g_warn_if_fail (subject == NULL);
1662 		e_alert_submit (
1663 			alert_sink,
1664 			"mail:get-multiple-messages",
1665 			error->message, NULL);
1666 		async_context_free (context);
1667 		g_error_free (error);
1668 		return;
1669 	}
1670 
1671 	g_return_if_fail (CAMEL_IS_MIME_PART (part));
1672 
1673 	backend = e_mail_reader_get_backend (context->reader);
1674 	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
1675 
1676 	forward_attached (shell, folder, context->ptr_array, part, subject);
1677 
1678 	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
1679 
1680 	g_object_unref (part);
1681 	g_free (subject);
1682 
1683 	async_context_free (context);
1684 }
1685 
1686 static EMsgComposer *
1687 forward_non_attached (EShell *shell,
1688                       CamelSession *session,
1689                       CamelFolder *folder,
1690                       const gchar *uid,
1691                       CamelMimeMessage *message,
1692                       EMailForwardStyle style)
1693 {
1694 	EMsgComposer *composer = NULL;
1695 	gchar *text, *forward;
1696 	guint32 validity_found = 0;
1697 	guint32 flags;
1698 
1699 	flags = E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS | E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
1700 	if (style == E_MAIL_FORWARD_STYLE_QUOTED)
1701 		flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE;
1702 
1703 	forward = quoting_text (QUOTING_FORWARD);
1704 	text = em_utils_message_to_html (
1705 		session, message, forward, flags, NULL, NULL, &validity_found);
1706 
1707 	if (text != NULL) {
1708 		CamelDataWrapper *content;
1709 		gchar *subject;
1710 
1711 		subject = mail_tool_generate_forward_subject (message);
1712 		composer = create_new_composer (shell, subject, folder);
1713 		g_free (subject);
1714 
1715 		content = camel_medium_get_content (CAMEL_MEDIUM (message));
1716 
1717 		if (CAMEL_IS_MULTIPART (content))
1718 			e_msg_composer_add_message_attachments (
1719 				composer, message, FALSE);
1720 
1721 		e_msg_composer_set_body_text (composer, text, TRUE);
1722 
1723 		if (uid != NULL) {
1724 			gchar *folder_uri;
1725 
1726 			folder_uri = e_mail_folder_uri_from_folder (folder);
1727 
1728 			e_msg_composer_set_source_headers (
1729 				composer, folder_uri, uid,
1730 				CAMEL_MESSAGE_FORWARDED);
1731 
1732 			g_free (folder_uri);
1733 		}
1734 
1735 		emu_update_composers_security (
1736 			composer, validity_found);
1737 		composer_set_no_change (composer);
1738 		gtk_widget_show (GTK_WIDGET (composer));
1739 
1740 		g_free (text);
1741 	}
1742 
1743 	g_free (forward);
1744 
1745 	return composer;
1746 }
1747 
1748 /**
1749  * em_utils_forward_message:
1750  * @shell: an #EShell
1751  * @session: a #CamelSession
1752  * @message: a #CamelMimeMessage to forward
1753  * @style: the forward style to use
1754  * @folder: a #CamelFolder, or %NULL
1755  * @uid: the UID of %message, or %NULL
1756  *
1757  * Forwards a message in the given style.  See em_utils_forward_messages()
1758  * for more details about forwarding styles.
1759  **/
1760 EMsgComposer *
1761 em_utils_forward_message (EShell *shell,
1762                           CamelSession *session,
1763                           CamelMimeMessage *message,
1764                           EMailForwardStyle style,
1765                           CamelFolder *folder,
1766                           const gchar *uid)
1767 {
1768 	CamelMimePart *part;
1769 	gchar *subject;
1770 	EMsgComposer *composer = NULL;
1771 
1772 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
1773 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
1774 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
1775 
1776 	switch (style) {
1777 		case E_MAIL_FORWARD_STYLE_ATTACHED:
1778 		default:
1779 			part = mail_tool_make_message_attachment (message);
1780 			subject = mail_tool_generate_forward_subject (message);
1781 
1782 			composer = forward_attached (
1783 				shell, NULL, NULL, part, subject);
1784 
1785 			g_object_unref (part);
1786 			g_free (subject);
1787 			break;
1788 
1789 		case E_MAIL_FORWARD_STYLE_INLINE:
1790 		case E_MAIL_FORWARD_STYLE_QUOTED:
1791 			composer = forward_non_attached (
1792 				shell, session, folder, uid, message, style);
1793 			break;
1794 	}
1795 
1796 	return composer;
1797 }
1798 
1799 static void
1800 forward_got_messages_cb (CamelFolder *folder,
1801                          GAsyncResult *result,
1802                          AsyncContext *context)
1803 {
1804 	EShell *shell;
1805 	EMailBackend *backend;
1806 	EMailSession *session;
1807 	EAlertSink *alert_sink;
1808 	GHashTable *hash_table;
1809 	GHashTableIter iter;
1810 	gpointer key, value;
1811 	GError *error = NULL;
1812 
1813 	alert_sink = e_mail_reader_get_alert_sink (context->reader);
1814 
1815 	hash_table = e_mail_folder_get_multiple_messages_finish (
1816 		folder, result, &error);
1817 
1818 	if (e_activity_handle_cancellation (context->activity, error)) {
1819 		g_warn_if_fail (hash_table == NULL);
1820 		context->destroy_when_done = NULL;
1821 		async_context_free (context);
1822 		g_error_free (error);
1823 		return;
1824 
1825 	} else if (error != NULL) {
1826 		g_warn_if_fail (hash_table == NULL);
1827 		e_alert_submit (
1828 			alert_sink,
1829 			"mail:get-multiple-messages",
1830 			error->message, NULL);
1831 		context->destroy_when_done = NULL;
1832 		async_context_free (context);
1833 		g_error_free (error);
1834 		return;
1835 	}
1836 
1837 	g_return_if_fail (hash_table != NULL);
1838 
1839 	backend = e_mail_reader_get_backend (context->reader);
1840 	session = e_mail_backend_get_session (backend);
1841 	shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
1842 
1843 	/* Create a new composer window for each message. */
1844 
1845 	g_hash_table_iter_init (&iter, hash_table);
1846 
1847 	while (g_hash_table_iter_next (&iter, &key, &value))
1848 		em_utils_forward_message (
1849 			shell, CAMEL_SESSION (session),
1850 			value, context->style, folder, key);
1851 
1852 	g_hash_table_unref (hash_table);
1853 
1854 	e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
1855 
1856 	async_context_free (context);
1857 }
1858 
1859 /**
1860  * em_utils_forward_messages:
1861  * @shell: an #EShell
1862  * @folder: folder containing messages to forward
1863  * @uids: uids of messages to forward
1864  * @style: the forward style to use
1865  * @destroy_when_done: a #GtkWidget to destroy with gtk_widget_destroy()
1866  * when done; can be NULL
1867  *
1868  * Forwards a group of messages in the given style.
1869  *
1870  * If @style is #E_MAIL_FORWARD_STYLE_ATTACHED, the new message is
1871  * created as follows.  If there is more than a single message in @uids,
1872  * a multipart/digest will be constructed and attached to a new composer
1873  * window preset with the appropriate header defaults for forwarding the
1874  * first message in the list.  If only one message is to be forwarded,
1875  * it is forwarded as a simple message/rfc822 attachment.
1876  *
1877  * If @style is #E_MAIL_FORWARD_STYLE_INLINE, each message is forwarded
1878  * in its own composer window in 'inline' form.
1879  *
1880  * If @style is #E_MAIL_FORWARD_STYLE_QUOTED, each message is forwarded
1881  * in its own composer window in 'quoted' form (each line starting with
1882  * a "> ").
1883  **/
1884 void
1885 em_utils_forward_messages (EMailReader *reader,
1886                            CamelFolder *folder,
1887                            GPtrArray *uids,
1888                            EMailForwardStyle style,
1889                            GtkWidget *destroy_when_done)
1890 {
1891 	EActivity *activity;
1892 	AsyncContext *context;
1893 	GCancellable *cancellable;
1894 
1895 	g_return_if_fail (E_IS_MAIL_READER (reader));
1896 	g_return_if_fail (CAMEL_IS_FOLDER (folder));
1897 	g_return_if_fail (uids != NULL);
1898 
1899 	activity = e_mail_reader_new_activity (reader);
1900 	cancellable = e_activity_get_cancellable (activity);
1901 
1902 	context = g_slice_new0 (AsyncContext);
1903 	context->activity = activity;
1904 	context->reader = g_object_ref (reader);
1905 	context->ptr_array = g_ptr_array_ref (uids);
1906 	context->style = style;
1907 	context->destroy_when_done = destroy_when_done;
1908 
1909 	switch (style) {
1910 		case E_MAIL_FORWARD_STYLE_ATTACHED:
1911 			e_mail_folder_build_attachment (
1912 				folder, uids, G_PRIORITY_DEFAULT,
1913 				cancellable, (GAsyncReadyCallback)
1914 				forward_attached_cb, context);
1915 			break;
1916 
1917 		case E_MAIL_FORWARD_STYLE_INLINE:
1918 		case E_MAIL_FORWARD_STYLE_QUOTED:
1919 			e_mail_folder_get_multiple_messages (
1920 				folder, uids, G_PRIORITY_DEFAULT,
1921 				cancellable, (GAsyncReadyCallback)
1922 				forward_got_messages_cb, context);
1923 			break;
1924 
1925 		default:
1926 			g_warn_if_reached ();
1927 	}
1928 }
1929 
1930 static gint
1931 compare_sources_with_uids_order_cb (gconstpointer a,
1932 				    gconstpointer b,
1933 				    gpointer user_data)
1934 {
1935 	ESource *asource = (ESource *) a;
1936 	ESource *bsource = (ESource *) b;
1937 	GHashTable *uids_order = user_data;
1938 	gint aindex, bindex;
1939 
1940 	aindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (asource)));
1941 	bindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (bsource)));
1942 
1943 	if (aindex <= 0)
1944 		aindex = g_hash_table_size (uids_order);
1945 	if (bindex <= 0)
1946 		bindex = g_hash_table_size (uids_order);
1947 
1948 	return aindex - bindex;
1949 }
1950 
1951 static void
1952 sort_sources_by_ui (GList **psources,
1953 		    gpointer user_data)
1954 {
1955 	EShell *shell = user_data;
1956 	EShellBackend *shell_backend;
1957 	EMailSession *mail_session;
1958 	EMailAccountStore *account_store;
1959 	GtkTreeModel *model;
1960 	GtkTreeIter iter;
1961 	GHashTable *uids_order;
1962 	gint index = 0;
1963 
1964 	g_return_if_fail (psources != NULL);
1965 	g_return_if_fail (E_IS_SHELL (shell));
1966 
1967 	/* nothing to sort */
1968 	if (!*psources || !g_list_next (*psources))
1969 		return;
1970 
1971 	shell_backend = e_shell_get_backend_by_name (shell, "mail");
1972 	g_return_if_fail (shell_backend != NULL);
1973 
1974 	mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend));
1975 	g_return_if_fail (mail_session != NULL);
1976 
1977 	account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session));
1978 	g_return_if_fail (account_store != NULL);
1979 
1980 	model = GTK_TREE_MODEL (account_store);
1981 	if (!gtk_tree_model_get_iter_first (model, &iter))
1982 		return;
1983 
1984 	uids_order = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1985 
1986 	do {
1987 		CamelService *service = NULL;
1988 
1989 		gtk_tree_model_get (model, &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &service, -1);
1990 
1991 		if (service) {
1992 			index++;
1993 			g_hash_table_insert (uids_order, g_strdup (camel_service_get_uid (service)), GINT_TO_POINTER (index));
1994 			g_object_unref (service);
1995 		}
1996 	} while (gtk_tree_model_iter_next (model, &iter));
1997 
1998 	*psources = g_list_sort_with_data (*psources, compare_sources_with_uids_order_cb, uids_order);
1999 
2000 	g_hash_table_destroy (uids_order);
2001 }
2002 
2003 /* Redirecting messages... */
2004 
2005 static EMsgComposer *
2006 redirect_get_composer (EShell *shell,
2007                        CamelMimeMessage *message)
2008 {
2009 	EMsgComposer *composer;
2010 	ESourceRegistry *registry;
2011 	CamelMedium *medium;
2012 	ESource *source;
2013 	gchar *identity_uid = NULL;
2014 
2015 	medium = CAMEL_MEDIUM (message);
2016 
2017 	/* QMail will refuse to send a message if it finds one of
2018 	 * it's Delivered-To headers in the message, so remove all
2019 	 * Delivered-To headers. Fixes bug #23635. */
2020 	while (camel_medium_get_header (medium, "Delivered-To"))
2021 		camel_medium_remove_header (medium, "Delivered-To");
2022 
2023 	while (camel_medium_get_header (medium, "Bcc"))
2024 		camel_medium_remove_header (medium, "Bcc");
2025 
2026 	while (camel_medium_get_header (medium, "Resent-Bcc"))
2027 		camel_medium_remove_header (medium, "Resent-Bcc");
2028 
2029 	registry = e_shell_get_registry (shell);
2030 
2031 	/* This returns a new ESource reference. */
2032 	source = em_utils_guess_mail_identity_with_recipients_and_sort (
2033 		registry, message, NULL, NULL, sort_sources_by_ui, shell);
2034 
2035 	if (source != NULL) {
2036 		identity_uid = e_source_dup_uid (source);
2037 		g_object_unref (source);
2038 	}
2039 
2040 	composer = e_msg_composer_new_redirect (
2041 		shell, message, identity_uid, NULL);
2042 
2043 	g_free (identity_uid);
2044 
2045 	return composer;
2046 }
2047 
2048 /**
2049  * em_utils_redirect_message:
2050  * @shell: an #EShell
2051  * @message: message to redirect
2052  *
2053  * Opens a composer to redirect @message (Note: only headers will be
2054  * editable). Adds Resent-From/Resent-To/etc headers.
2055  **/
2056 void
2057 em_utils_redirect_message (EShell *shell,
2058                            CamelMimeMessage *message)
2059 {
2060 	EMsgComposer *composer;
2061 
2062 	g_return_if_fail (E_IS_SHELL (shell));
2063 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2064 
2065 	composer = redirect_get_composer (shell, message);
2066 
2067 	gtk_widget_show (GTK_WIDGET (composer));
2068 
2069 	composer_set_no_change (composer);
2070 }
2071 
2072 /* Replying to messages... */
2073 
2074 EDestination **
2075 em_utils_camel_address_to_destination (CamelInternetAddress *iaddr)
2076 {
2077 	EDestination *dest, **destv;
2078 	gint n, i, j;
2079 
2080 	if (iaddr == NULL)
2081 		return NULL;
2082 
2083 	if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0)
2084 		return NULL;
2085 
2086 	destv = g_malloc (sizeof (EDestination *) * (n + 1));
2087 	for (i = 0, j = 0; i < n; i++) {
2088 		const gchar *name, *addr;
2089 
2090 		if (camel_internet_address_get (iaddr, i, &name, &addr)) {
2091 			dest = e_destination_new ();
2092 			e_destination_set_name (dest, name);
2093 			e_destination_set_email (dest, addr);
2094 
2095 			destv[j++] = dest;
2096 		}
2097 	}
2098 
2099 	if (j == 0) {
2100 		g_free (destv);
2101 		return NULL;
2102 	}
2103 
2104 	destv[j] = NULL;
2105 
2106 	return destv;
2107 }
2108 
2109 static EMsgComposer *
2110 reply_get_composer (EShell *shell,
2111                     CamelMimeMessage *message,
2112                     const gchar *identity_uid,
2113                     CamelInternetAddress *to,
2114                     CamelInternetAddress *cc,
2115                     CamelFolder *folder,
2116                     CamelNNTPAddress *postto)
2117 {
2118 	const gchar *message_id, *references;
2119 	EDestination **tov, **ccv;
2120 	EMsgComposer *composer;
2121 	EComposerHeaderTable *table;
2122 	CamelMedium *medium;
2123 	gchar *subject;
2124 
2125 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2126 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
2127 
2128 	if (to != NULL)
2129 		g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (to), NULL);
2130 
2131 	if (cc != NULL)
2132 		g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc), NULL);
2133 
2134 	composer = e_msg_composer_new (shell);
2135 
2136 	/* construct the tov/ccv */
2137 	tov = em_utils_camel_address_to_destination (to);
2138 	ccv = em_utils_camel_address_to_destination (cc);
2139 
2140 	/* Set the subject of the new message. */
2141 	if ((subject = (gchar *) camel_mime_message_get_subject (message))) {
2142 		gboolean skip_len = -1;
2143 
2144 		if (em_utils_is_re_in_subject (shell, subject, &skip_len) && skip_len > 0)
2145 			subject = subject + skip_len;
2146 
2147 		subject = g_strdup_printf ("Re: %s", subject);
2148 	} else {
2149 		subject = g_strdup ("");
2150 	}
2151 
2152 	table = e_msg_composer_get_header_table (composer);
2153 	e_composer_header_table_set_subject (table, subject);
2154 	e_composer_header_table_set_destinations_to (table, tov);
2155 	e_composer_header_table_set_identity_uid (table, identity_uid);
2156 
2157 	/* Add destinations instead of setting, so we don't remove
2158 	 * automatic CC addresses that have already been added. */
2159 	e_composer_header_table_add_destinations_cc (table, ccv);
2160 
2161 	e_destination_freev (tov);
2162 	e_destination_freev (ccv);
2163 	g_free (subject);
2164 
2165 	/* add post-to, if nessecary */
2166 	if (postto && camel_address_length ((CamelAddress *) postto)) {
2167 		gchar *store_url = NULL;
2168 		gchar *post;
2169 
2170 		if (folder) {
2171 			CamelStore *parent_store;
2172 			CamelService *service;
2173 			CamelURL *url;
2174 
2175 			parent_store = camel_folder_get_parent_store (folder);
2176 
2177 			service = CAMEL_SERVICE (parent_store);
2178 			url = camel_service_new_camel_url (service);
2179 
2180 			store_url = camel_url_to_string (
2181 				url, CAMEL_URL_HIDE_ALL);
2182 			if (store_url[strlen (store_url) - 1] == '/')
2183 				store_url[strlen (store_url) - 1] = '\0';
2184 
2185 			camel_url_free (url);
2186 		}
2187 
2188 		post = camel_address_encode ((CamelAddress *) postto);
2189 		e_composer_header_table_set_post_to_base (
2190 			table, store_url ? store_url : "", post);
2191 		g_free (post);
2192 		g_free (store_url);
2193 	}
2194 
2195 	/* Add In-Reply-To and References. */
2196 
2197 	medium = CAMEL_MEDIUM (message);
2198 	message_id = camel_medium_get_header (medium, "Message-ID");
2199 	references = camel_medium_get_header (medium, "References");
2200 
2201 	if (message_id != NULL) {
2202 		gchar *reply_refs;
2203 
2204 		e_msg_composer_add_header (
2205 			composer, "In-Reply-To", message_id);
2206 
2207 		if (references)
2208 			reply_refs = g_strdup_printf (
2209 				"%s %s", references, message_id);
2210 		else
2211 			reply_refs = g_strdup (message_id);
2212 
2213 		e_msg_composer_add_header (
2214 			composer, "References", reply_refs);
2215 		g_free (reply_refs);
2216 
2217 	} else if (references != NULL) {
2218 		e_msg_composer_add_header (
2219 			composer, "References", references);
2220 	}
2221 
2222 	return composer;
2223 }
2224 
2225 static gboolean
2226 get_reply_list (CamelMimeMessage *message,
2227                 CamelInternetAddress *to)
2228 {
2229 	const gchar *header, *p;
2230 	gchar *addr;
2231 
2232 	/* Examples:
2233 	 *
2234 	 * List-Post: <mailto:list@host.com>
2235 	 * List-Post: <mailto:moderator@host.com?subject=list%20posting>
2236 	 * List-Post: NO (posting not allowed on this list)
2237 	 */
2238 	if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post")))
2239 		return FALSE;
2240 
2241 	while (*header == ' ' || *header == '\t')
2242 		header++;
2243 
2244 	/* check for NO */
2245 	if (!g_ascii_strncasecmp (header, "NO", 2))
2246 		return FALSE;
2247 
2248 	/* Search for the first mailto angle-bracket enclosed URL.
2249 	 * (See rfc2369, Section 2, paragraph 3 for details) */
2250 	if (!(header = camel_strstrcase (header, "<mailto:")))
2251 		return FALSE;
2252 
2253 	header += 8;
2254 
2255 	p = header;
2256 	while (*p && !strchr ("?>", *p))
2257 		p++;
2258 
2259 	addr = g_strndup (header, p - header);
2260 	camel_internet_address_add (to, NULL, addr);
2261 	g_free (addr);
2262 
2263 	return TRUE;
2264 }
2265 
2266 gboolean
2267 em_utils_is_munged_list_message (CamelMimeMessage *message)
2268 {
2269 	CamelInternetAddress *reply_to, *list;
2270 	gboolean result = FALSE;
2271 
2272 	reply_to = camel_mime_message_get_reply_to (message);
2273 	if (reply_to) {
2274 		list = camel_internet_address_new ();
2275 
2276 		if (get_reply_list (message, list) &&
2277 		    camel_address_length (CAMEL_ADDRESS (list)) ==
2278 		    camel_address_length (CAMEL_ADDRESS (reply_to))) {
2279 			gint i;
2280 			const gchar *r_name, *r_addr;
2281 			const gchar *l_name, *l_addr;
2282 
2283 			for (i = 0; i < camel_address_length (CAMEL_ADDRESS (list)); i++) {
2284 				if (!camel_internet_address_get (reply_to, i, &r_name, &r_addr))
2285 					break;
2286 				if (!camel_internet_address_get (list, i, &l_name, &l_addr))
2287 					break;
2288 				if (strcmp (l_addr, r_addr))
2289 					break;
2290 			}
2291 			if (i == camel_address_length (CAMEL_ADDRESS (list)))
2292 				result = TRUE;
2293 		}
2294 		g_object_unref (list);
2295 	}
2296 	return result;
2297 }
2298 
2299 static CamelInternetAddress *
2300 get_reply_to (CamelMimeMessage *message)
2301 {
2302 	CamelInternetAddress *reply_to;
2303 
2304 	reply_to = camel_mime_message_get_reply_to (message);
2305 	if (reply_to) {
2306 		GSettings *settings;
2307 		gboolean ignore_list_reply_to;
2308 
2309 		settings = g_settings_new ("org.gnome.evolution.mail");
2310 		ignore_list_reply_to = g_settings_get_boolean (
2311 			settings, "composer-ignore-list-reply-to");
2312 		g_object_unref (settings);
2313 
2314 		if (ignore_list_reply_to && em_utils_is_munged_list_message (message))
2315 			reply_to = NULL;
2316 	}
2317 	if (!reply_to)
2318 		reply_to = camel_mime_message_get_from (message);
2319 
2320 	return reply_to;
2321 }
2322 
2323 static void
2324 get_reply_sender (CamelMimeMessage *message,
2325                   CamelInternetAddress *to,
2326                   CamelNNTPAddress *postto)
2327 {
2328 	CamelInternetAddress *reply_to;
2329 	CamelMedium *medium;
2330 	const gchar *posthdr = NULL;
2331 
2332 	medium = CAMEL_MEDIUM (message);
2333 
2334 	/* check whether there is a 'Newsgroups: ' header in there */
2335 	if (postto != NULL && posthdr == NULL)
2336 		posthdr = camel_medium_get_header (medium, "Followup-To");
2337 
2338 	if (postto != NULL && posthdr == NULL)
2339 		posthdr = camel_medium_get_header (medium, "Newsgroups");
2340 
2341 	if (postto != NULL && posthdr != NULL) {
2342 		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
2343 		return;
2344 	}
2345 
2346 	reply_to = get_reply_to (message);
2347 
2348 	if (reply_to != NULL) {
2349 		const gchar *name;
2350 		const gchar *addr;
2351 		gint ii = 0;
2352 
2353 		while (camel_internet_address_get (reply_to, ii++, &name, &addr))
2354 			camel_internet_address_add (to, name, addr);
2355 	}
2356 }
2357 
2358 void
2359 em_utils_get_reply_sender (CamelMimeMessage *message,
2360                            CamelInternetAddress *to,
2361                            CamelNNTPAddress *postto)
2362 {
2363 	get_reply_sender (message, to, postto);
2364 }
2365 
2366 static void
2367 get_reply_from (CamelMimeMessage *message,
2368                 CamelInternetAddress *to,
2369                 CamelNNTPAddress *postto)
2370 {
2371 	CamelInternetAddress *from;
2372 	CamelMedium *medium;
2373 	const gchar *name, *addr;
2374 	const gchar *posthdr = NULL;
2375 
2376 	medium = CAMEL_MEDIUM (message);
2377 
2378 	/* check whether there is a 'Newsgroups: ' header in there */
2379 	if (postto != NULL && posthdr == NULL)
2380 		posthdr = camel_medium_get_header (medium, "Followup-To");
2381 
2382 	if (postto != NULL && posthdr == NULL)
2383 		posthdr = camel_medium_get_header (medium, "Newsgroups");
2384 
2385 	if (postto != NULL && posthdr != NULL) {
2386 		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
2387 		return;
2388 	}
2389 
2390 	from = camel_mime_message_get_from (message);
2391 
2392 	if (from != NULL) {
2393 		gint ii = 0;
2394 
2395 		while (camel_internet_address_get (from, ii++, &name, &addr))
2396 			camel_internet_address_add (to, name, addr);
2397 	}
2398 }
2399 
2400 static void
2401 get_reply_recipient (CamelMimeMessage *message,
2402                      CamelInternetAddress *to,
2403                      CamelNNTPAddress *postto,
2404                      CamelInternetAddress *address)
2405 {
2406 	CamelMedium *medium;
2407 	const gchar *posthdr =  NULL;
2408 
2409 	medium = CAMEL_MEDIUM (message);
2410 
2411 	/* check whether there is a 'Newsgroups: ' header in there */
2412 	if (postto != NULL && posthdr == NULL)
2413 		posthdr = camel_medium_get_header (medium, "Followup-To");
2414 
2415 	if (postto != NULL && posthdr == NULL)
2416 		 posthdr = camel_medium_get_header (medium, "Newsgroups");
2417 
2418 	if (postto != NULL && posthdr != NULL) {
2419 		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
2420 		return;
2421 	}
2422 
2423 	if (address != NULL) {
2424 		const gchar *name;
2425 		const gchar *addr;
2426 		gint ii = 0;
2427 
2428 		while (camel_internet_address_get (address, ii++, &name, &addr))
2429 			camel_internet_address_add (to, name, addr);
2430 	}
2431 
2432 }
2433 
2434 static void
2435 concat_unique_addrs (CamelInternetAddress *dest,
2436                      CamelInternetAddress *src,
2437                      GHashTable *rcpt_hash)
2438 {
2439 	const gchar *name, *addr;
2440 	gint i;
2441 
2442 	for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) {
2443 		if (!g_hash_table_lookup (rcpt_hash, addr)) {
2444 			camel_internet_address_add (dest, name, addr);
2445 			g_hash_table_insert (rcpt_hash, (gchar *) addr, GINT_TO_POINTER (1));
2446 		}
2447 	}
2448 }
2449 
2450 static GHashTable *
2451 generate_recipient_hash (ESourceRegistry *registry)
2452 {
2453 	GHashTable *rcpt_hash;
2454 	ESource *default_source;
2455 	GList *list, *link;
2456 	const gchar *extension_name;
2457 
2458 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
2459 
2460 	rcpt_hash = g_hash_table_new (
2461 		(GHashFunc) camel_strcase_hash,
2462 		(GEqualFunc) camel_strcase_equal);
2463 
2464 	default_source = e_source_registry_ref_default_mail_identity (registry);
2465 
2466 	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
2467 	list = e_source_registry_list_sources (registry, extension_name);
2468 
2469 	for (link = list; link != NULL; link = g_list_next (link)) {
2470 		ESource *source = E_SOURCE (link->data);
2471 		ESource *cached_source;
2472 		ESourceMailIdentity *extension;
2473 		const gchar *address;
2474 		gboolean insert_source;
2475 		gboolean cached_is_default;
2476 		gboolean cached_is_enabled;
2477 		gboolean source_is_default;
2478 		gboolean source_is_enabled;
2479 
2480 		/* No default mail identity implies there are no mail
2481 		 * identities at all and so we should never get here. */
2482 		g_warn_if_fail (default_source != NULL);
2483 
2484 		source_is_default = e_source_equal (source, default_source);
2485 		source_is_enabled = e_source_get_enabled (source);
2486 
2487 		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
2488 		extension = e_source_get_extension (source, extension_name);
2489 
2490 		address = e_source_mail_identity_get_address (extension);
2491 
2492 		if (address == NULL)
2493 			continue;
2494 
2495 		cached_source = g_hash_table_lookup (rcpt_hash, address);
2496 
2497 		if (cached_source != NULL) {
2498 			cached_is_default = e_source_equal (
2499 				cached_source, default_source);
2500 			cached_is_enabled =
2501 				e_source_get_enabled (cached_source);
2502 		} else {
2503 			cached_is_default = FALSE;
2504 			cached_is_enabled = FALSE;
2505 		}
2506 
2507 		/* Accounts with identical email addresses that are enabled
2508 		 * take precedence over disabled accounts.  If all accounts
2509 		 * with matching email addresses are disabled, the first
2510 		 * one in the list takes precedence.  The default account
2511 		 * always takes precedence no matter what. */
2512 		insert_source =
2513 			source_is_default ||
2514 			cached_source == NULL ||
2515 			(source_is_enabled &&
2516 			 !cached_is_enabled &&
2517 			 !cached_is_default);
2518 
2519 		if (insert_source)
2520 			g_hash_table_insert (
2521 				rcpt_hash, (gchar *) address, source);
2522 	}
2523 
2524 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
2525 
2526 	if (default_source != NULL)
2527 		g_object_unref (default_source);
2528 
2529 	return rcpt_hash;
2530 }
2531 
2532 void
2533 em_utils_get_reply_all (ESourceRegistry *registry,
2534                         CamelMimeMessage *message,
2535                         CamelInternetAddress *to,
2536                         CamelInternetAddress *cc,
2537                         CamelNNTPAddress *postto)
2538 {
2539 	CamelInternetAddress *reply_to;
2540 	CamelInternetAddress *to_addrs;
2541 	CamelInternetAddress *cc_addrs;
2542 	CamelMedium *medium;
2543 	const gchar *name, *addr;
2544 	const gchar *posthdr = NULL;
2545 	GHashTable *rcpt_hash;
2546 
2547 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
2548 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
2549 	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to));
2550 	g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc));
2551 
2552 	medium = CAMEL_MEDIUM (message);
2553 
2554 	/* check whether there is a 'Newsgroups: ' header in there */
2555 	if (postto != NULL && posthdr == NULL)
2556 		posthdr = camel_medium_get_header (medium, "Followup-To");
2557 
2558 	if (postto != NULL && posthdr == NULL)
2559 		posthdr = camel_medium_get_header (medium, "Newsgroups");
2560 
2561 	if (postto != NULL && posthdr != NULL)
2562 		camel_address_decode (CAMEL_ADDRESS (postto), posthdr);
2563 
2564 	rcpt_hash = generate_recipient_hash (registry);
2565 
2566 	reply_to = get_reply_to (message);
2567 	to_addrs = camel_mime_message_get_recipients (
2568 		message, CAMEL_RECIPIENT_TYPE_TO);
2569 	cc_addrs = camel_mime_message_get_recipients (
2570 		message, CAMEL_RECIPIENT_TYPE_CC);
2571 
2572 	if (reply_to != NULL) {
2573 		gint ii = 0;
2574 
2575 		while (camel_internet_address_get (reply_to, ii++, &name, &addr)) {
2576 			/* Ignore references to the Reply-To address
2577 			 * in the To and Cc lists. */
2578 			if (addr && !g_hash_table_lookup (rcpt_hash, addr)) {
2579 				/* In the case we are doing a Reply-To-All,
2580 				 * we do not want to include the user's email
2581 				 * address because replying to oneself is
2582 				 * kinda silly. */
2583 				camel_internet_address_add (to, name, addr);
2584 				g_hash_table_insert (
2585 					rcpt_hash, (gchar *) addr,
2586 					GINT_TO_POINTER (1));
2587 			}
2588 		}
2589 	}
2590 
2591 	concat_unique_addrs (cc, to_addrs, rcpt_hash);
2592 	concat_unique_addrs (cc, cc_addrs, rcpt_hash);
2593 
2594 	/* Promote the first Cc: address to To: if To: is empty. */
2595 	if (camel_address_length ((CamelAddress *) to) == 0 &&
2596 			camel_address_length ((CamelAddress *) cc) > 0) {
2597 		camel_internet_address_get (cc, 0, &name, &addr);
2598 		camel_internet_address_add (to, name, addr);
2599 		camel_address_remove ((CamelAddress *) cc, 0);
2600 	}
2601 
2602 	/* If To: is still empty, may we removed duplicates (i.e. ourself),
2603 	 * so add the original To if it was set. */
2604 	if (camel_address_length ((CamelAddress *) to) == 0
2605 	    && (camel_internet_address_get (to_addrs, 0, &name, &addr)
2606 		|| camel_internet_address_get (cc_addrs, 0, &name, &addr))) {
2607 		camel_internet_address_add (to, name, addr);
2608 	}
2609 
2610 	g_hash_table_destroy (rcpt_hash);
2611 }
2612 
2613 enum {
2614 	ATTRIB_UNKNOWN,
2615 	ATTRIB_CUSTOM,
2616 	ATTRIB_TIMEZONE,
2617 	ATTRIB_STRFTIME,
2618 	ATTRIB_TM_SEC,
2619 	ATTRIB_TM_MIN,
2620 	ATTRIB_TM_24HOUR,
2621 	ATTRIB_TM_12HOUR,
2622 	ATTRIB_TM_MDAY,
2623 	ATTRIB_TM_MON,
2624 	ATTRIB_TM_YEAR,
2625 	ATTRIB_TM_2YEAR,
2626 	ATTRIB_TM_WDAY, /* not actually used */
2627 	ATTRIB_TM_YDAY
2628 };
2629 
2630 typedef void		(*AttribFormatter)	(GString *str,
2631 						 const gchar *attr,
2632 						 CamelMimeMessage *message);
2633 
2634 static void
2635 format_sender (GString *str,
2636                const gchar *attr,
2637                CamelMimeMessage *message)
2638 {
2639 	CamelInternetAddress *sender;
2640 	const gchar *name, *addr = NULL;
2641 
2642 	sender = camel_mime_message_get_from (message);
2643 	if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) {
2644 		camel_internet_address_get (sender, 0, &name, &addr);
2645 	} else {
2646 		name = _("an unknown sender");
2647 	}
2648 
2649 	if (name && !strcmp (attr, "{SenderName}")) {
2650 		g_string_append (str, name);
2651 	} else if (addr && !strcmp (attr, "{SenderEMail}")) {
2652 		g_string_append (str, addr);
2653 	} else if (name && *name) {
2654 		g_string_append (str, name);
2655 	} else if (addr) {
2656 		g_string_append (str, addr);
2657 	}
2658 }
2659 
2660 static struct {
2661 	const gchar *name;
2662 	gint type;
2663 	struct {
2664 		const gchar *format;         /* strftime or printf format */
2665 		AttribFormatter formatter;  /* custom formatter */
2666 	} v;
2667 } attribvars[] = {
2668 	{ "{Sender}", ATTRIB_CUSTOM, { NULL, format_sender } },
2669 	{ "{SenderName}", ATTRIB_CUSTOM, { NULL, format_sender } },
2670 	{ "{SenderEMail}", ATTRIB_CUSTOM, { NULL, format_sender } },
2671 	{ "{AbbrevWeekdayName}", ATTRIB_STRFTIME, { "%a", NULL } },
2672 	{ "{WeekdayName}", ATTRIB_STRFTIME, { "%A", NULL } },
2673 	{ "{AbbrevMonthName}", ATTRIB_STRFTIME, { "%b", NULL } },
2674 	{ "{MonthName}", ATTRIB_STRFTIME, { "%B", NULL } },
2675 	{ "{AmPmUpper}", ATTRIB_STRFTIME, { "%p", NULL } },
2676 	{ "{AmPmLower}", ATTRIB_STRFTIME, { "%P", NULL } },
2677 	{ "{Day}", ATTRIB_TM_MDAY, { "%02d", NULL } },  /* %d  01-31 */
2678 	{ "{ Day}", ATTRIB_TM_MDAY, { "% 2d", NULL } },  /* %e   1-31 */
2679 	{ "{24Hour}", ATTRIB_TM_24HOUR, { "%02d", NULL } },  /* %H  00-23 */
2680 	{ "{12Hour}", ATTRIB_TM_12HOUR, { "%02d", NULL } },  /* %I  00-12 */
2681 	{ "{DayOfYear}", ATTRIB_TM_YDAY, { "%d", NULL } },  /* %j  1-366 */
2682 	{ "{Month}", ATTRIB_TM_MON, { "%02d", NULL } },  /* %m  01-12 */
2683 	{ "{Minute}", ATTRIB_TM_MIN, { "%02d", NULL } },  /* %M  00-59 */
2684 	{ "{Seconds}", ATTRIB_TM_SEC, { "%02d", NULL } },  /* %S  00-61 */
2685 	{ "{2DigitYear}", ATTRIB_TM_2YEAR, { "%02d", NULL } },  /* %y */
2686 	{ "{Year}", ATTRIB_TM_YEAR, { "%04d", NULL } },  /* %Y */
2687 	{ "{TimeZone}", ATTRIB_TIMEZONE, { "%+05d", NULL } }
2688 };
2689 
2690 static gchar *
2691 attribution_format (CamelMimeMessage *message)
2692 {
2693 	register const gchar *inptr;
2694 	const gchar *start;
2695 	gint tzone, len, i;
2696 	gchar buf[64], *s;
2697 	GString *str;
2698 	struct tm tm;
2699 	time_t date;
2700 	gint type;
2701 	gchar *format = quoting_text (QUOTING_ATTRIBUTION);
2702 
2703 	str = g_string_new ("");
2704 
2705 	date = camel_mime_message_get_date (message, &tzone);
2706 
2707 	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
2708 		/* The message has no Date: header, look at Received: */
2709 		date = camel_mime_message_get_date_received (message, &tzone);
2710 	}
2711 	if (date == CAMEL_MESSAGE_DATE_CURRENT) {
2712 		/* That didn't work either, use current time */
2713 		time (&date);
2714 		tzone = 0;
2715 	}
2716 
2717 	/* Convert to UTC */
2718 	date += (tzone / 100) * 60 * 60;
2719 	date += (tzone % 100) * 60;
2720 
2721 	gmtime_r (&date, &tm);
2722 
2723 	inptr = format;
2724 	while (*inptr != '\0') {
2725 		start = inptr;
2726 		while (*inptr && strncmp (inptr, "${", 2) != 0)
2727 			inptr++;
2728 
2729 		g_string_append_len (str, start, inptr - start);
2730 
2731 		if (*inptr == '\0')
2732 			break;
2733 
2734 		start = ++inptr;
2735 		while (*inptr && *inptr != '}')
2736 			inptr++;
2737 
2738 		if (*inptr != '}') {
2739 			/* broken translation */
2740 			g_string_append_len (str, "${", 2);
2741 			inptr = start + 1;
2742 			continue;
2743 		}
2744 
2745 		inptr++;
2746 		len = inptr - start;
2747 		type = ATTRIB_UNKNOWN;
2748 		for (i = 0; i < G_N_ELEMENTS (attribvars); i++) {
2749 			if (!strncmp (attribvars[i].name, start, len)) {
2750 				type = attribvars[i].type;
2751 				break;
2752 			}
2753 		}
2754 
2755 		switch (type) {
2756 		case ATTRIB_CUSTOM:
2757 			attribvars[i].v.formatter (
2758 				str, attribvars[i].name, message);
2759 			break;
2760 		case ATTRIB_TIMEZONE:
2761 			g_string_append_printf (
2762 				str, attribvars[i].v.format, tzone);
2763 			break;
2764 		case ATTRIB_STRFTIME:
2765 			e_utf8_strftime (
2766 				buf, sizeof (buf), attribvars[i].v.format, &tm);
2767 			g_string_append (str, buf);
2768 			break;
2769 		case ATTRIB_TM_SEC:
2770 			g_string_append_printf (
2771 				str, attribvars[i].v.format, tm.tm_sec);
2772 			break;
2773 		case ATTRIB_TM_MIN:
2774 			g_string_append_printf (
2775 				str, attribvars[i].v.format, tm.tm_min);
2776 			break;
2777 		case ATTRIB_TM_24HOUR:
2778 			g_string_append_printf (
2779 				str, attribvars[i].v.format, tm.tm_hour);
2780 			break;
2781 		case ATTRIB_TM_12HOUR:
2782 			g_string_append_printf (
2783 				str, attribvars[i].v.format,
2784 				(tm.tm_hour + 1) % 13);
2785 			break;
2786 		case ATTRIB_TM_MDAY:
2787 			g_string_append_printf (
2788 				str, attribvars[i].v.format, tm.tm_mday);
2789 			break;
2790 		case ATTRIB_TM_MON:
2791 			g_string_append_printf (
2792 				str, attribvars[i].v.format, tm.tm_mon + 1);
2793 			break;
2794 		case ATTRIB_TM_YEAR:
2795 			g_string_append_printf (
2796 				str, attribvars[i].v.format, tm.tm_year + 1900);
2797 			break;
2798 		case ATTRIB_TM_2YEAR:
2799 			g_string_append_printf (
2800 				str, attribvars[i].v.format, tm.tm_year % 100);
2801 			break;
2802 		case ATTRIB_TM_WDAY:
2803 			/* not actually used */
2804 			g_string_append_printf (
2805 				str, attribvars[i].v.format, tm.tm_wday);
2806 			break;
2807 		case ATTRIB_TM_YDAY:
2808 			g_string_append_printf (
2809 				str, attribvars[i].v.format, tm.tm_yday + 1);
2810 			break;
2811 		default:
2812 			/* Misspelled variable?  Drop the
2813 			 * format argument and continue. */
2814 			break;
2815 		}
2816 	}
2817 
2818 	s = str->str;
2819 	g_string_free (str, FALSE);
2820 	g_free (format);
2821 
2822 	return s;
2823 }
2824 
2825 static void
2826 composer_set_body (EMsgComposer *composer,
2827                    CamelMimeMessage *message,
2828                    EMailReplyStyle style,
2829                    EMailPartList *parts_list)
2830 {
2831 	gchar *text, *credits, *original;
2832 	CamelMimePart *part;
2833 	CamelSession *session;
2834 	GSettings *settings;
2835 	gboolean start_bottom, has_body_text = FALSE;
2836 	guint32 validity_found = 0;
2837 
2838 	session = e_msg_composer_get_session (composer);
2839 
2840 	settings = g_settings_new ("org.gnome.evolution.mail");
2841 
2842 	start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
2843 
2844 	switch (style) {
2845 	case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE:
2846 		/* do nothing */
2847 		break;
2848 	case E_MAIL_REPLY_STYLE_ATTACH:
2849 		/* attach the original message as an attachment */
2850 		part = mail_tool_make_message_attachment (message);
2851 		e_msg_composer_attach (composer, part);
2852 		g_object_unref (part);
2853 		break;
2854 	case E_MAIL_REPLY_STYLE_OUTLOOK:
2855 		original = quoting_text (QUOTING_ORIGINAL);
2856 		text = em_utils_message_to_html (
2857 			session, message, original, E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS,
2858 			parts_list, start_bottom ? "<BR>" : NULL, &validity_found);
2859 		e_msg_composer_set_body_text (composer, text, TRUE);
2860 		has_body_text = text && *text;
2861 		g_free (text);
2862 		g_free (original);
2863 		emu_update_composers_security (composer, validity_found);
2864 		break;
2865 
2866 	case E_MAIL_REPLY_STYLE_QUOTED:
2867 	default:
2868 		/* do what any sane user would want when replying... */
2869 		credits = attribution_format (message);
2870 		text = em_utils_message_to_html (
2871 			session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE,
2872 			parts_list, start_bottom ? "<BR>" : NULL, &validity_found);
2873 		g_free (credits);
2874 		e_msg_composer_set_body_text (composer, text, TRUE);
2875 		has_body_text = text && *text;
2876 		g_free (text);
2877 		emu_update_composers_security (composer, validity_found);
2878 		break;
2879 	}
2880 
2881 	if (has_body_text && start_bottom) {
2882 		GtkhtmlEditor *editor = GTKHTML_EDITOR (composer);
2883 		gboolean move_cursor_to_end;
2884 		gboolean top_signature;
2885 
2886 		/* If we are placing signature on top, then move cursor to the end,
2887 		 * otherwise try to find the signature place and place cursor just
2888 		 * before the signature. We added there an empty line already. */
2889 		gtkhtml_editor_run_command (editor, "block-selection");
2890 		gtkhtml_editor_run_command (editor, "cursor-bod");
2891 
2892 		top_signature = g_settings_get_boolean (settings, "composer-top-signature");
2893 
2894 		move_cursor_to_end = top_signature ||
2895 			!gtkhtml_editor_search_by_data (
2896 				editor, 1, "ClueFlow", "signature", "1");
2897 
2898 		if (move_cursor_to_end)
2899 			gtkhtml_editor_run_command (editor, "cursor-eod");
2900 		else
2901 			gtkhtml_editor_run_command (editor, "selection-move-left");
2902 		gtkhtml_editor_run_command (editor, "unblock-selection");
2903 	}
2904 
2905 	g_object_unref (settings);
2906 }
2907 
2908 gchar *
2909 em_utils_construct_composer_text (CamelSession *session,
2910                                   CamelMimeMessage *message,
2911                                   EMailPartList *parts_list)
2912 {
2913 	gchar *text, *credits;
2914 	gboolean start_bottom = 0;
2915 
2916 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
2917 
2918 	credits = attribution_format (message);
2919 	text = em_utils_message_to_html (
2920 		session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE,
2921 		parts_list, start_bottom ? "<BR>" : NULL, NULL);
2922 	g_free (credits);
2923 
2924 	return text;
2925 }
2926 
2927 /**
2928  * em_utils_reply_to_message:
2929  * @shell: an #EShell
2930  * @message: a #CamelMimeMessage
2931  * @folder: a #CamelFolder, or %NULL
2932  * @message_uid: the UID of @message, or %NULL
2933  * @type: the type of reply to create
2934  * @style: the reply style to use
2935  * @source: source to inherit view settings from
2936  * @address: used for E_MAIL_REPLY_TO_RECIPIENT @type
2937  *
2938  * Creates a new composer ready to reply to @message.
2939  *
2940  * @folder and @message_uid may be supplied in order to update the message
2941  * flags once it has been replied to.
2942  **/
2943 EMsgComposer *
2944 em_utils_reply_to_message (EShell *shell,
2945                            CamelMimeMessage *message,
2946                            CamelFolder *folder,
2947                            const gchar *message_uid,
2948                            EMailReplyType type,
2949                            EMailReplyStyle style,
2950                            EMailPartList *parts_list,
2951                            CamelInternetAddress *address)
2952 {
2953 	ESourceRegistry *registry;
2954 	CamelInternetAddress *to, *cc;
2955 	CamelNNTPAddress *postto = NULL;
2956 	EMsgComposer *composer;
2957 	ESource *source;
2958 	gchar *identity_uid = NULL;
2959 	guint32 flags;
2960 
2961 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2962 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
2963 
2964 	to = camel_internet_address_new ();
2965 	cc = camel_internet_address_new ();
2966 
2967 	registry = e_shell_get_registry (shell);
2968 
2969 	/* This returns a new ESource reference. */
2970 	source = em_utils_guess_mail_identity_with_recipients_and_sort (
2971 		registry, message, folder, message_uid, sort_sources_by_ui, shell);
2972 	if (source != NULL) {
2973 		identity_uid = e_source_dup_uid (source);
2974 		g_object_unref (source);
2975 	}
2976 
2977 	flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN;
2978 
2979 	if (!address && (type == E_MAIL_REPLY_TO_FROM || type == E_MAIL_REPLY_TO_SENDER) &&
2980 	    folder && em_utils_folder_is_sent (registry, folder))
2981 		type = E_MAIL_REPLY_TO_ALL;
2982 
2983 	switch (type) {
2984 	case E_MAIL_REPLY_TO_FROM:
2985 		if (folder)
2986 			postto = camel_nntp_address_new ();
2987 
2988 		get_reply_from (message, to, postto);
2989 		break;
2990 	case E_MAIL_REPLY_TO_RECIPIENT:
2991 		if (folder)
2992 			postto = camel_nntp_address_new ();
2993 
2994 		get_reply_recipient (message, to, postto, address);
2995 		break;
2996 	case E_MAIL_REPLY_TO_SENDER:
2997 		if (folder)
2998 			postto = camel_nntp_address_new ();
2999 
3000 		get_reply_sender (message, to, postto);
3001 		break;
3002 	case E_MAIL_REPLY_TO_LIST:
3003 		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
3004 		if (get_reply_list (message, to))
3005 			break;
3006 		/* falls through */
3007 	case E_MAIL_REPLY_TO_ALL:
3008 		flags |= CAMEL_MESSAGE_ANSWERED_ALL;
3009 		if (folder)
3010 			postto = camel_nntp_address_new ();
3011 
3012 		em_utils_get_reply_all (registry, message, to, cc, postto);
3013 		break;
3014 	}
3015 
3016 	composer = reply_get_composer (
3017 		shell, message, identity_uid, to, cc, folder, postto);
3018 	e_msg_composer_add_message_attachments (composer, message, TRUE);
3019 
3020 	if (postto)
3021 		g_object_unref (postto);
3022 	g_object_unref (to);
3023 	g_object_unref (cc);
3024 
3025 	composer_set_body (composer, message, style, parts_list);
3026 
3027 	if (folder != NULL) {
3028 		gchar *folder_uri;
3029 
3030 		folder_uri = e_mail_folder_uri_from_folder (folder);
3031 
3032 		e_msg_composer_set_source_headers (
3033 			composer, folder_uri, message_uid, flags);
3034 
3035 		g_free (folder_uri);
3036 	}
3037 
3038 	composer_set_no_change (composer);
3039 
3040 	gtk_widget_show (GTK_WIDGET (composer));
3041 
3042 	g_free (identity_uid);
3043 
3044 	return composer;
3045 }
3046 
3047 static void
3048 post_header_clicked_cb (EComposerPostHeader *header,
3049                         EMailSession *session)
3050 {
3051 	GtkTreeSelection *selection;
3052 	EMFolderSelector *selector;
3053 	EMFolderTreeModel *model;
3054 	EMFolderTree *folder_tree;
3055 	GtkWidget *dialog;
3056 	GList *list;
3057 
3058 	/* FIXME Limit the folder tree to the NNTP account? */
3059 	model = em_folder_tree_model_get_default ();
3060 
3061 	dialog = em_folder_selector_new (
3062 		/* FIXME GTK_WINDOW (composer) */ NULL,
3063 		model, EM_FOLDER_SELECTOR_CAN_CREATE,
3064 		_("Posting destination"),
3065 		_("Choose folders to post the message to."),
3066 		NULL);
3067 
3068 	selector = EM_FOLDER_SELECTOR (dialog);
3069 	folder_tree = em_folder_selector_get_folder_tree (selector);
3070 
3071 	em_folder_tree_set_excluded (
3072 		folder_tree,
3073 		EMFT_EXCLUDE_NOSELECT |
3074 		EMFT_EXCLUDE_VIRTUAL |
3075 		EMFT_EXCLUDE_VTRASH);
3076 
3077 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree));
3078 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
3079 
3080 	list = e_composer_post_header_get_folders (header);
3081 	em_folder_tree_set_selected_list (folder_tree, list, FALSE);
3082 	g_list_foreach (list, (GFunc) g_free, NULL);
3083 	g_list_free (list);
3084 
3085 	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) {
3086 		/* Prevent the header's "custom" flag from being reset,
3087 		 * which is what the default method will do next. */
3088 		g_signal_stop_emission_by_name (header, "clicked");
3089 		goto exit;
3090 	}
3091 
3092 	list = em_folder_tree_get_selected_uris (folder_tree);
3093 	e_composer_post_header_set_folders (header, list);
3094 	g_list_foreach (list, (GFunc) g_free, NULL);
3095 	g_list_free (list);
3096 
3097 exit:
3098 	gtk_widget_destroy (dialog);
3099 }
3100 
3101 /**
3102  * em_configure_new_composer:
3103  * @composer: a newly created #EMsgComposer
3104  *
3105  * Integrates a newly created #EMsgComposer into the mail backend.  The
3106  * composer can't link directly to the mail backend without introducing
3107  * circular library dependencies, so this function finishes configuring
3108  * things the #EMsgComposer instance can't do itself.
3109  **/
3110 void
3111 em_configure_new_composer (EMsgComposer *composer,
3112                            EMailSession *session)
3113 {
3114 	EComposerHeaderTable *table;
3115 	EComposerHeaderType header_type;
3116 	EComposerHeader *header;
3117 
3118 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3119 	g_return_if_fail (E_IS_MAIL_SESSION (session));
3120 
3121 	header_type = E_COMPOSER_HEADER_POST_TO;
3122 	table = e_msg_composer_get_header_table (composer);
3123 	header = e_composer_header_table_get_header (table, header_type);
3124 
3125 	g_signal_connect (
3126 		composer, "presend",
3127 		G_CALLBACK (composer_presend_check_recipients), session);
3128 
3129 	g_signal_connect (
3130 		composer, "presend",
3131 		G_CALLBACK (composer_presend_check_identity), session);
3132 
3133 	g_signal_connect (
3134 		composer, "presend",
3135 		G_CALLBACK (composer_presend_check_downloads), session);
3136 
3137 	g_signal_connect (
3138 		composer, "presend",
3139 		G_CALLBACK (composer_presend_check_plugins), session);
3140 
3141 	g_signal_connect (
3142 		composer, "presend",
3143 		G_CALLBACK (composer_presend_check_subject), session);
3144 
3145 	g_signal_connect (
3146 		composer, "presend",
3147 		G_CALLBACK (composer_presend_check_unwanted_html), session);
3148 
3149 	g_signal_connect (
3150 		composer, "send",
3151 		G_CALLBACK (em_utils_composer_send_cb), session);
3152 
3153 	g_signal_connect (
3154 		composer, "save-to-drafts",
3155 		G_CALLBACK (em_utils_composer_save_to_drafts_cb), session);
3156 
3157 	g_signal_connect (
3158 		composer, "save-to-outbox",
3159 		G_CALLBACK (em_utils_composer_save_to_outbox_cb), session);
3160 
3161 	g_signal_connect (
3162 		composer, "print",
3163 		G_CALLBACK (em_utils_composer_print_cb), session);
3164 
3165 	/* Handle "Post To:" button clicks, which displays a folder tree
3166 	 * widget.  The composer doesn't know about folder tree widgets,
3167 	 * so it can't handle this itself.
3168 	 *
3169 	 * Note: This is a G_SIGNAL_RUN_LAST signal, which allows us to
3170 	 *       stop the signal emission if the user cancels or closes
3171 	 *       the folder selector dialog.  See the handler function. */
3172 	g_signal_connect (
3173 		header, "clicked",
3174 		G_CALLBACK (post_header_clicked_cb), session);
3175 }