evolution-3.6.4/composer/e-msg-composer.c

No issues found

Incomplete coverage

Tool Failure ID Location Function Message Data
clang-analyzer no-output-found e-msg-composer.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
clang-analyzer no-output-found e-msg-composer.c Message(text='Unable to locate XML output from invoke-clang-analyzer') None
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
Failure running clang-analyzer ('no-output-found')
Message
Unable to locate XML output from invoke-clang-analyzer
   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
   2 /*
   3  * This program is free software; you can redistribute it and/or
   4  * modify it under the terms of the GNU Lesser General Public
   5  * License as published by the Free Software Foundation; either
   6  * version 2 of the License, or (at your option) version 3.
   7  *
   8  * This program is distributed in the hope that it will be useful,
   9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  11  * Lesser General Public License for more details.
  12  *
  13  * You should have received a copy of the GNU Lesser General Public
  14  * License along with the program; if not, see <http://www.gnu.org/licenses/>
  15  *
  16  *
  17  * Authors:
  18  *		Ettore Perazzoli (ettore@ximian.com)
  19  *		Jeffrey Stedfast (fejj@ximian.com)
  20  *		Miguel de Icaza  (miguel@ximian.com)
  21  *		Radek Doulik     (rodo@ximian.com)
  22  *
  23  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
  24  *
  25  */
  26 
  27 #ifdef HAVE_CONFIG_H
  28 #include <config.h>
  29 #endif
  30 
  31 #include <stdlib.h>
  32 #include <string.h>
  33 #include <sys/types.h>
  34 #include <sys/stat.h>
  35 #include <sys/time.h>
  36 #include <unistd.h>
  37 #include <ctype.h>
  38 #include <fcntl.h>
  39 
  40 #include <libevolution-utils/e-alert-dialog.h>
  41 #include <e-util/e-dialog-utils.h>
  42 #include <e-util/e-util-private.h>
  43 
  44 #include "e-composer-private.h"
  45 
  46 #include <em-format/e-mail-part.h>
  47 #include <em-format/e-mail-parser.h>
  48 #include <em-format/e-mail-formatter-quote.h>
  49 
  50 #include <shell/e-shell.h>
  51 
  52 typedef struct _AsyncContext AsyncContext;
  53 
  54 struct _AsyncContext {
  55 	EActivity *activity;
  56 
  57 	CamelMimeMessage *message;
  58 	CamelDataWrapper *top_level_part;
  59 	CamelDataWrapper *text_plain_part;
  60 
  61 	ESource *source;
  62 	CamelSession *session;
  63 	CamelInternetAddress *from;
  64 
  65 	CamelTransferEncoding plain_encoding;
  66 	GtkPrintOperationAction print_action;
  67 
  68 	GPtrArray *recipients;
  69 
  70 	guint skip_content  : 1;
  71 	guint need_thread   : 1;
  72 	guint pgp_sign      : 1;
  73 	guint pgp_encrypt   : 1;
  74 	guint smime_sign    : 1;
  75 	guint smime_encrypt : 1;
  76 };
  77 
  78 /* Flags for building a message. */
  79 typedef enum {
  80 	COMPOSER_FLAG_HTML_CONTENT		= 1 << 0,
  81 	COMPOSER_FLAG_SAVE_OBJECT_DATA		= 1 << 1,
  82 	COMPOSER_FLAG_PRIORITIZE_MESSAGE	= 1 << 2,
  83 	COMPOSER_FLAG_REQUEST_READ_RECEIPT	= 1 << 3,
  84 	COMPOSER_FLAG_PGP_SIGN			= 1 << 4,
  85 	COMPOSER_FLAG_PGP_ENCRYPT		= 1 << 5,
  86 	COMPOSER_FLAG_SMIME_SIGN		= 1 << 6,
  87 	COMPOSER_FLAG_SMIME_ENCRYPT		= 1 << 7
  88 } ComposerFlags;
  89 
  90 enum {
  91 	PROP_0,
  92 	PROP_FOCUS_TRACKER,
  93 	PROP_SHELL
  94 };
  95 
  96 enum {
  97 	PRESEND,
  98 	SEND,
  99 	SAVE_TO_DRAFTS,
 100 	SAVE_TO_OUTBOX,
 101 	PRINT,
 102 	LAST_SIGNAL
 103 };
 104 
 105 static guint signals[LAST_SIGNAL];
 106 
 107 /* used by e_msg_composer_add_message_attachments () */
 108 static void	add_attachments_from_multipart	(EMsgComposer *composer,
 109 						 CamelMultipart *multipart,
 110 						 gboolean just_inlines,
 111 						 gint depth);
 112 
 113 /* used by e_msg_composer_new_with_message () */
 114 static void	handle_multipart		(EMsgComposer *composer,
 115 						 CamelMultipart *multipart,
 116 						 GCancellable *cancellable,
 117 						 gint depth);
 118 static void	handle_multipart_alternative	(EMsgComposer *composer,
 119 						 CamelMultipart *multipart,
 120 						 GCancellable *cancellable,
 121 						 gint depth);
 122 static void	handle_multipart_encrypted	(EMsgComposer *composer,
 123 						 CamelMimePart *multipart,
 124 						 GCancellable *cancellable,
 125 						 gint depth);
 126 static void	handle_multipart_signed		(EMsgComposer *composer,
 127 						 CamelMultipart *multipart,
 128 						 GCancellable *cancellable,
 129 						 gint depth);
 130 
 131 static void	e_msg_composer_alert_sink_init	(EAlertSinkInterface *interface);
 132 
 133 G_DEFINE_TYPE_WITH_CODE (
 134 	EMsgComposer,
 135 	e_msg_composer,
 136 	GTKHTML_TYPE_EDITOR,
 137 	G_IMPLEMENT_INTERFACE (
 138 		E_TYPE_ALERT_SINK, e_msg_composer_alert_sink_init)
 139 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
 140 
 141 static void
 142 async_context_free (AsyncContext *context)
 143 {
 144 	if (context->activity != NULL)
 145 		g_object_unref (context->activity);
 146 
 147 	if (context->message != NULL)
 148 		g_object_unref (context->message);
 149 
 150 	if (context->top_level_part != NULL)
 151 		g_object_unref (context->top_level_part);
 152 
 153 	if (context->text_plain_part != NULL)
 154 		g_object_unref (context->text_plain_part);
 155 
 156 	if (context->source != NULL)
 157 		g_object_unref (context->source);
 158 
 159 	if (context->session != NULL)
 160 		g_object_unref (context->session);
 161 
 162 	if (context->from != NULL)
 163 		g_object_unref (context->from);
 164 
 165 	if (context->recipients != NULL)
 166 		g_ptr_array_free (context->recipients, TRUE);
 167 
 168 	g_slice_free (AsyncContext, context);
 169 }
 170 
 171 /**
 172  * emcu_part_to_html:
 173  * @part:
 174  *
 175  * Converts a mime part's contents into html text.  If @credits is given,
 176  * then it will be used as an attribution string, and the
 177  * content will be cited.  Otherwise no citation or attribution
 178  * will be performed.
 179  *
 180  * Return Value: The part in displayable html format.
 181  **/
 182 static gchar *
 183 emcu_part_to_html (CamelSession *session,
 184                    CamelMimePart *part,
 185                    gssize *len,
 186                    GCancellable *cancellable)
 187 {
 188 	CamelStreamMem *mem;
 189 	GByteArray *buf;
 190 	gchar *text;
 191 	EMailParser *parser;
 192 	EMailFormatter *formatter;
 193 	EMailPartList *part_list;
 194 	GString *part_id;
 195 	EShell *shell;
 196 	GtkWindow *window;
 197 
 198 	shell = e_shell_get_default ();
 199 	window = e_shell_get_active_window (shell);
 200 
 201 	buf = g_byte_array_new ();
 202 	mem = (CamelStreamMem *) camel_stream_mem_new ();
 203 	camel_stream_mem_set_byte_array (mem, buf);
 204 
 205 	part_list = e_mail_part_list_new ();
 206 
 207 	part_id = g_string_sized_new (0);
 208 	parser = e_mail_parser_new (session);
 209 	part_list->list = e_mail_parser_parse_part (parser, part, part_id, cancellable);
 210 	g_string_free (part_id, TRUE);
 211 	g_object_unref (parser);
 212 
 213 	formatter = e_mail_formatter_quote_new (NULL, E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG);
 214 	e_mail_formatter_set_style (formatter,
 215 			gtk_widget_get_style (GTK_WIDGET (window)),
 216 			gtk_widget_get_state (GTK_WIDGET (window)));
 217 
 218 	e_mail_formatter_format_sync (
 219 		formatter, part_list, (CamelStream *) mem,
 220 			0, E_MAIL_FORMATTER_MODE_PRINTING, cancellable);
 221 	g_object_unref (formatter);
 222 	g_object_unref (part_list);
 223 
 224 	camel_stream_write ((CamelStream *) mem, "", 1, cancellable, NULL);
 225 	g_object_unref (mem);
 226 
 227 	text = (gchar *) buf->data;
 228 	if (len)
 229 		*len = buf->len - 1;
 230 	g_byte_array_free (buf, FALSE);
 231 
 232 	return text;
 233 }
 234 
 235 /* copy of mail_tool_remove_xevolution_headers */
 236 static struct _camel_header_raw *
 237 emcu_remove_xevolution_headers (CamelMimeMessage *message)
 238 {
 239 	struct _camel_header_raw *scan, *list = NULL;
 240 
 241 	for (scan = ((CamelMimePart *) message)->headers; scan; scan = scan->next)
 242 		if (!strncmp (scan->name, "X-Evolution", 11))
 243 			camel_header_raw_append (&list, scan->name, scan->value, scan->offset);
 244 
 245 	for (scan = list; scan; scan = scan->next)
 246 		camel_medium_remove_header ((CamelMedium *) message, scan->name);
 247 
 248 	return list;
 249 }
 250 
 251 static EDestination **
 252 destination_list_to_vector_sized (GList *list,
 253                                   gint n)
 254 {
 255 	EDestination **destv;
 256 	gint i = 0;
 257 
 258 	if (n == -1)
 259 		n = g_list_length (list);
 260 
 261 	if (n == 0)
 262 		return NULL;
 263 
 264 	destv = g_new (EDestination *, n + 1);
 265 	while (list != NULL && i < n) {
 266 		destv[i] = E_DESTINATION (list->data);
 267 		list->data = NULL;
 268 		i++;
 269 		list = g_list_next (list);
 270 	}
 271 	destv[i] = NULL;
 272 
 273 	return destv;
 274 }
 275 
 276 static EDestination **
 277 destination_list_to_vector (GList *list)
 278 {
 279 	return destination_list_to_vector_sized (list, -1);
 280 }
 281 
 282 #define LINE_LEN 72
 283 
 284 static gboolean
 285 text_requires_quoted_printable (const gchar *text,
 286                                 gsize len)
 287 {
 288 	const gchar *p;
 289 	gsize pos;
 290 
 291 	if (!text)
 292 		return FALSE;
 293 
 294 	if (len == -1)
 295 		len = strlen (text);
 296 
 297 	if (len >= 5 && strncmp (text, "From ", 5) == 0)
 298 		return TRUE;
 299 
 300 	for (p = text, pos = 0; pos + 6 <= len; pos++, p++) {
 301 		if (*p == '\n' && strncmp (p + 1, "From ", 5) == 0)
 302 			return TRUE;
 303 	}
 304 
 305 	return FALSE;
 306 }
 307 
 308 static CamelTransferEncoding
 309 best_encoding (GByteArray *buf,
 310                const gchar *charset)
 311 {
 312 	gchar *in, *out, outbuf[256], *ch;
 313 	gsize inlen, outlen;
 314 	gint status, count = 0;
 315 	iconv_t cd;
 316 
 317 	if (!charset)
 318 		return -1;
 319 
 320 	cd = camel_iconv_open (charset, "utf-8");
 321 	if (cd == (iconv_t) -1)
 322 		return -1;
 323 
 324 	in = (gchar *) buf->data;
 325 	inlen = buf->len;
 326 	do {
 327 		out = outbuf;
 328 		outlen = sizeof (outbuf);
 329 		status = camel_iconv (cd, (const gchar **) &in, &inlen, &out, &outlen);
 330 		for (ch = out - 1; ch >= outbuf; ch--) {
 331 			if ((guchar) *ch > 127)
 332 				count++;
 333 		}
 334 	} while (status == (gsize) -1 && errno == E2BIG);
 335 	camel_iconv_close (cd);
 336 
 337 	if (status == (gsize) -1 || status > 0)
 338 		return -1;
 339 
 340 	if ((count == 0) && (buf->len < LINE_LEN) &&
 341 		!text_requires_quoted_printable (
 342 		(const gchar *) buf->data, buf->len))
 343 		return CAMEL_TRANSFER_ENCODING_7BIT;
 344 	else if (count <= buf->len * 0.17)
 345 		return CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
 346 	else
 347 		return CAMEL_TRANSFER_ENCODING_BASE64;
 348 }
 349 
 350 static gchar *
 351 best_charset (GByteArray *buf,
 352               const gchar *default_charset,
 353               CamelTransferEncoding *encoding)
 354 {
 355 	const gchar *charset;
 356 
 357 	/* First try US-ASCII */
 358 	*encoding = best_encoding (buf, "US-ASCII");
 359 	if (*encoding == CAMEL_TRANSFER_ENCODING_7BIT)
 360 		return NULL;
 361 
 362 	/* Next try the user-specified charset for this message */
 363 	*encoding = best_encoding (buf, default_charset);
 364 	if (*encoding != -1)
 365 		return g_strdup (default_charset);
 366 
 367 	/* Now try the user's default charset from the mail config */
 368 	charset = e_composer_get_default_charset ();
 369 	*encoding = best_encoding (buf, charset);
 370 	if (*encoding != -1)
 371 		return g_strdup (charset);
 372 
 373 	/* Try to find something that will work */
 374 	charset = camel_charset_best (
 375 		(const gchar *) buf->data, buf->len);
 376 	if (charset == NULL) {
 377 		*encoding = CAMEL_TRANSFER_ENCODING_7BIT;
 378 		return NULL;
 379 	}
 380 
 381 	*encoding = best_encoding (buf, charset);
 382 
 383 	return g_strdup (charset);
 384 }
 385 
 386 static void
 387 clear_current_images (EMsgComposer *composer)
 388 {
 389 	EMsgComposerPrivate *p = composer->priv;
 390 	g_list_free (p->current_images);
 391 	p->current_images = NULL;
 392 }
 393 
 394 void
 395 e_msg_composer_clear_inlined_table (EMsgComposer *composer)
 396 {
 397 	EMsgComposerPrivate *p = composer->priv;
 398 
 399 	g_hash_table_remove_all (p->inline_images);
 400 	g_hash_table_remove_all (p->inline_images_by_url);
 401 }
 402 
 403 static void
 404 add_inlined_images (EMsgComposer *composer,
 405                     CamelMultipart *multipart)
 406 {
 407 	EMsgComposerPrivate *p = composer->priv;
 408 
 409 	GList *d = p->current_images;
 410 	GHashTable *added;
 411 
 412 	added = g_hash_table_new (g_direct_hash, g_direct_equal);
 413 	while (d) {
 414 		CamelMimePart *part = d->data;
 415 
 416 		if (!g_hash_table_lookup (added, part)) {
 417 			camel_multipart_add_part (multipart, part);
 418 			g_hash_table_insert (added, part, part);
 419 		}
 420 		d = d->next;
 421 	}
 422 	g_hash_table_destroy (added);
 423 }
 424 
 425 /* These functions builds a CamelMimeMessage for the message that the user has
 426  * composed in 'composer'.
 427  */
 428 
 429 static void
 430 set_recipients_from_destv (CamelMimeMessage *msg,
 431                            EDestination **to_destv,
 432                            EDestination **cc_destv,
 433                            EDestination **bcc_destv,
 434                            gboolean redirect)
 435 {
 436 	CamelInternetAddress *to_addr;
 437 	CamelInternetAddress *cc_addr;
 438 	CamelInternetAddress *bcc_addr;
 439 	CamelInternetAddress *target;
 440 	const gchar *text_addr, *header;
 441 	gboolean seen_hidden_list = FALSE;
 442 	gint i;
 443 
 444 	to_addr  = camel_internet_address_new ();
 445 	cc_addr  = camel_internet_address_new ();
 446 	bcc_addr = camel_internet_address_new ();
 447 
 448 	for (i = 0; to_destv != NULL && to_destv[i] != NULL; ++i) {
 449 		text_addr = e_destination_get_address (to_destv[i]);
 450 
 451 		if (text_addr && *text_addr) {
 452 			target = to_addr;
 453 			if (e_destination_is_evolution_list (to_destv[i])
 454 			    && !e_destination_list_show_addresses (to_destv[i])) {
 455 				target = bcc_addr;
 456 				seen_hidden_list = TRUE;
 457 			}
 458 
 459 			if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
 460 				camel_internet_address_add (target, "", text_addr);
 461 		}
 462 	}
 463 
 464 	for (i = 0; cc_destv != NULL && cc_destv[i] != NULL; ++i) {
 465 		text_addr = e_destination_get_address (cc_destv[i]);
 466 		if (text_addr && *text_addr) {
 467 			target = cc_addr;
 468 			if (e_destination_is_evolution_list (cc_destv[i])
 469 			    && !e_destination_list_show_addresses (cc_destv[i])) {
 470 				target = bcc_addr;
 471 				seen_hidden_list = TRUE;
 472 			}
 473 
 474 			if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
 475 				camel_internet_address_add (target, "", text_addr);
 476 		}
 477 	}
 478 
 479 	for (i = 0; bcc_destv != NULL && bcc_destv[i] != NULL; ++i) {
 480 		text_addr = e_destination_get_address (bcc_destv[i]);
 481 		if (text_addr && *text_addr) {
 482 			if (camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr) <= 0)
 483 				camel_internet_address_add (bcc_addr, "", text_addr);
 484 		}
 485 	}
 486 
 487 	if (redirect)
 488 		header = CAMEL_RECIPIENT_TYPE_RESENT_TO;
 489 	else
 490 		header = CAMEL_RECIPIENT_TYPE_TO;
 491 
 492 	if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) {
 493 		camel_mime_message_set_recipients (msg, header, to_addr);
 494 	} else if (seen_hidden_list) {
 495 		camel_medium_set_header (
 496 			CAMEL_MEDIUM (msg), header, "Undisclosed-Recipient:;");
 497 	}
 498 
 499 	header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_CC : CAMEL_RECIPIENT_TYPE_CC;
 500 	if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) {
 501 		camel_mime_message_set_recipients (msg, header, cc_addr);
 502 	}
 503 
 504 	header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_BCC : CAMEL_RECIPIENT_TYPE_BCC;
 505 	if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) {
 506 		camel_mime_message_set_recipients (msg, header, bcc_addr);
 507 	}
 508 
 509 	g_object_unref (to_addr);
 510 	g_object_unref (cc_addr);
 511 	g_object_unref (bcc_addr);
 512 }
 513 
 514 static void
 515 build_message_headers (EMsgComposer *composer,
 516                        CamelMimeMessage *message,
 517                        gboolean redirect)
 518 {
 519 	EComposerHeaderTable *table;
 520 	EComposerHeader *header;
 521 	ESourceRegistry *registry;
 522 	ESource *source;
 523 	const gchar *subject;
 524 	const gchar *reply_to;
 525 	const gchar *uid;
 526 
 527 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
 528 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
 529 
 530 	table = e_msg_composer_get_header_table (composer);
 531 
 532 	registry = e_composer_header_table_get_registry (table);
 533 	uid = e_composer_header_table_get_identity_uid (table);
 534 	source = e_source_registry_ref_source (registry, uid);
 535 
 536 	/* Subject: */
 537 	subject = e_composer_header_table_get_subject (table);
 538 	camel_mime_message_set_subject (message, subject);
 539 
 540 	if (source != NULL) {
 541 		CamelMedium *medium;
 542 		CamelInternetAddress *addr;
 543 		ESourceMailIdentity *mi;
 544 		ESourceMailSubmission *ms;
 545 		const gchar *extension_name;
 546 		const gchar *header_name;
 547 		const gchar *name, *address;
 548 		const gchar *transport_uid;
 549 		const gchar *sent_folder;
 550 
 551 		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
 552 		mi = e_source_get_extension (source, extension_name);
 553 
 554 		name = e_source_mail_identity_get_name (mi);
 555 		address = e_source_mail_identity_get_address (mi);
 556 
 557 		extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
 558 		ms = e_source_get_extension (source, extension_name);
 559 
 560 		sent_folder = e_source_mail_submission_get_sent_folder (ms);
 561 		transport_uid = e_source_mail_submission_get_transport_uid (ms);
 562 
 563 		medium = CAMEL_MEDIUM (message);
 564 
 565 		/* From: / Resent-From: */
 566 		addr = camel_internet_address_new ();
 567 		camel_internet_address_add (addr, name, address);
 568 		if (redirect) {
 569 			gchar *value;
 570 
 571 			value = camel_address_encode (CAMEL_ADDRESS (addr));
 572 			camel_medium_set_header (medium, "Resent-From", value);
 573 			g_free (value);
 574 		} else
 575 			camel_mime_message_set_from (message, addr);
 576 		g_object_unref (addr);
 577 
 578 		/* X-Evolution-Identity */
 579 		header_name = "X-Evolution-Identity";
 580 		camel_medium_set_header (medium, header_name, uid);
 581 
 582 		/* X-Evolution-Fcc */
 583 		header_name = "X-Evolution-Fcc";
 584 		camel_medium_set_header (medium, header_name, sent_folder);
 585 
 586 		/* X-Evolution-Transport */
 587 		header_name = "X-Evolution-Transport";
 588 		camel_medium_set_header (medium, header_name, transport_uid);
 589 
 590 		g_object_unref (source);
 591 	}
 592 
 593 	/* Reply-To: */
 594 	reply_to = e_composer_header_table_get_reply_to (table);
 595 	if (reply_to != NULL && *reply_to != '\0') {
 596 		CamelInternetAddress *addr;
 597 
 598 		addr = camel_internet_address_new ();
 599 
 600 		if (camel_address_unformat (CAMEL_ADDRESS (addr), reply_to) > 0)
 601 			camel_mime_message_set_reply_to (message, addr);
 602 
 603 		g_object_unref (addr);
 604 	}
 605 
 606 	/* To:, Cc:, Bcc: */
 607 	header = e_composer_header_table_get_header (
 608 		table, E_COMPOSER_HEADER_TO);
 609 	if (e_composer_header_get_visible (header)) {
 610 		EDestination **to, **cc, **bcc;
 611 
 612 		to = e_composer_header_table_get_destinations_to (table);
 613 		cc = e_composer_header_table_get_destinations_cc (table);
 614 		bcc = e_composer_header_table_get_destinations_bcc (table);
 615 
 616 		set_recipients_from_destv (message, to, cc, bcc, redirect);
 617 
 618 		e_destination_freev (to);
 619 		e_destination_freev (cc);
 620 		e_destination_freev (bcc);
 621 	}
 622 
 623 	/* Date: */
 624 	camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
 625 
 626 	/* X-Evolution-PostTo: */
 627 	header = e_composer_header_table_get_header (
 628 		table, E_COMPOSER_HEADER_POST_TO);
 629 	if (e_composer_header_get_visible (header)) {
 630 		CamelMedium *medium;
 631 		const gchar *name = "X-Evolution-PostTo";
 632 		GList *list, *iter;
 633 
 634 		medium = CAMEL_MEDIUM (message);
 635 		camel_medium_remove_header (medium, name);
 636 
 637 		list = e_composer_header_table_get_post_to (table);
 638 		for (iter = list; iter != NULL; iter = iter->next) {
 639 			gchar *folder = iter->data;
 640 			camel_medium_add_header (medium, name, folder);
 641 			g_free (folder);
 642 		}
 643 		g_list_free (list);
 644 	}
 645 }
 646 
 647 static CamelCipherHash
 648 account_hash_algo_to_camel_hash (const gchar *hash_algo)
 649 {
 650 	CamelCipherHash res = CAMEL_CIPHER_HASH_DEFAULT;
 651 
 652 	if (hash_algo && *hash_algo) {
 653 		if (g_ascii_strcasecmp (hash_algo, "sha1") == 0)
 654 			res = CAMEL_CIPHER_HASH_SHA1;
 655 		else if (g_ascii_strcasecmp (hash_algo, "sha256") == 0)
 656 			res = CAMEL_CIPHER_HASH_SHA256;
 657 		else if (g_ascii_strcasecmp (hash_algo, "sha384") == 0)
 658 			res = CAMEL_CIPHER_HASH_SHA384;
 659 		else if (g_ascii_strcasecmp (hash_algo, "sha512") == 0)
 660 			res = CAMEL_CIPHER_HASH_SHA512;
 661 	}
 662 
 663 	return res;
 664 }
 665 
 666 static void
 667 composer_add_charset_filter (CamelStream *stream,
 668                              const gchar *charset)
 669 {
 670 	CamelMimeFilter *filter;
 671 
 672 	filter = camel_mime_filter_charset_new ("UTF-8", charset);
 673 	camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
 674 	g_object_unref (filter);
 675 }
 676 
 677 static void
 678 composer_add_quoted_printable_filter (CamelStream *stream)
 679 {
 680 	CamelMimeFilter *filter;
 681 
 682 	filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
 683 	camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
 684 	g_object_unref (filter);
 685 
 686 	filter = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_FROM);
 687 	camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
 688 	g_object_unref (filter);
 689 }
 690 
 691 /* Helper for composer_build_message_thread() */
 692 static gboolean
 693 composer_build_message_pgp (AsyncContext *context,
 694                             GCancellable *cancellable,
 695                             GError **error)
 696 {
 697 	ESourceOpenPGP *extension;
 698 	CamelCipherContext *cipher;
 699 	CamelDataWrapper *content;
 700 	CamelMimePart *mime_part;
 701 	const gchar *extension_name;
 702 	const gchar *pgp_key_id;
 703 	const gchar *signing_algorithm;
 704 	gboolean always_trust;
 705 	gboolean encrypt_to_self;
 706 
 707 	/* Return silently if we're not signing or encrypting with PGP. */
 708 	if (!context->pgp_sign && !context->pgp_encrypt)
 709 		return TRUE;
 710 
 711 	extension_name = E_SOURCE_EXTENSION_OPENPGP;
 712 	extension = e_source_get_extension (context->source, extension_name);
 713 
 714 	always_trust = e_source_openpgp_get_always_trust (extension);
 715 	encrypt_to_self = e_source_openpgp_get_encrypt_to_self (extension);
 716 	pgp_key_id = e_source_openpgp_get_key_id (extension);
 717 	signing_algorithm = e_source_openpgp_get_signing_algorithm (extension);
 718 
 719 	mime_part = camel_mime_part_new ();
 720 
 721 	camel_medium_set_content (
 722 		CAMEL_MEDIUM (mime_part),
 723 		context->top_level_part);
 724 
 725 	if (context->top_level_part == context->text_plain_part)
 726 		camel_mime_part_set_encoding (
 727 			mime_part, context->plain_encoding);
 728 
 729 	g_object_unref (context->top_level_part);
 730 	context->top_level_part = NULL;
 731 
 732 	if (pgp_key_id == NULL || *pgp_key_id == '\0')
 733 		camel_internet_address_get (
 734 			context->from, 0, NULL, &pgp_key_id);
 735 
 736 	if (context->pgp_sign) {
 737 		CamelMimePart *npart;
 738 		gboolean success;
 739 
 740 		npart = camel_mime_part_new ();
 741 
 742 		cipher = camel_gpg_context_new (context->session);
 743 		camel_gpg_context_set_always_trust (
 744 			CAMEL_GPG_CONTEXT (cipher), always_trust);
 745 
 746 		success = camel_cipher_context_sign_sync (
 747 			cipher, pgp_key_id,
 748 			account_hash_algo_to_camel_hash (signing_algorithm),
 749 			mime_part, npart, cancellable, error);
 750 
 751 		g_object_unref (cipher);
 752 
 753 		g_object_unref (mime_part);
 754 
 755 		if (!success) {
 756 			g_object_unref (npart);
 757 			return FALSE;
 758 		}
 759 
 760 		mime_part = npart;
 761 	}
 762 
 763 	if (context->pgp_encrypt) {
 764 		CamelMimePart *npart;
 765 		gboolean success;
 766 
 767 		npart = camel_mime_part_new ();
 768 
 769 		/* Check to see if we should encrypt to self.
 770 		 * NB: Gets removed immediately after use. */
 771 		if (encrypt_to_self && pgp_key_id != NULL)
 772 			g_ptr_array_add (
 773 				context->recipients,
 774 				g_strdup (pgp_key_id));
 775 
 776 		cipher = camel_gpg_context_new (context->session);
 777 		camel_gpg_context_set_always_trust (
 778 			CAMEL_GPG_CONTEXT (cipher), always_trust);
 779 
 780 		success = camel_cipher_context_encrypt_sync (
 781 			cipher, pgp_key_id, context->recipients,
 782 			mime_part, npart, cancellable, error);
 783 
 784 		g_object_unref (cipher);
 785 
 786 		if (encrypt_to_self && pgp_key_id != NULL)
 787 			g_ptr_array_set_size (
 788 				context->recipients,
 789 				context->recipients->len - 1);
 790 
 791 		g_object_unref (mime_part);
 792 
 793 		if (!success) {
 794 			g_object_unref (npart);
 795 			return FALSE;
 796 		}
 797 
 798 		mime_part = npart;
 799 	}
 800 
 801 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
 802 	context->top_level_part = g_object_ref (content);
 803 
 804 	g_object_unref (mime_part);
 805 
 806 	return TRUE;
 807 }
 808 
 809 #ifdef HAVE_SSL
 810 static gboolean
 811 composer_build_message_smime (AsyncContext *context,
 812                               GCancellable *cancellable,
 813                               GError **error)
 814 {
 815 	ESourceSMIME *extension;
 816 	CamelCipherContext *cipher;
 817 	CamelMimePart *mime_part;
 818 	const gchar *extension_name;
 819 	const gchar *signing_algorithm;
 820 	const gchar *signing_certificate;
 821 	const gchar *encryption_certificate;
 822 	gboolean encrypt_to_self;
 823 	gboolean have_signing_certificate;
 824 	gboolean have_encryption_certificate;
 825 
 826 	/* Return silently if we're not signing or encrypting with S/MIME. */
 827 	if (!context->smime_sign && !context->smime_encrypt)
 828 		return TRUE;
 829 
 830 	extension_name = E_SOURCE_EXTENSION_SMIME;
 831 	extension = e_source_get_extension (context->source, extension_name);
 832 
 833 	encrypt_to_self =
 834 		e_source_smime_get_encrypt_to_self (extension);
 835 
 836 	signing_algorithm =
 837 		e_source_smime_get_signing_algorithm (extension);
 838 
 839 	signing_certificate =
 840 		e_source_smime_get_signing_certificate (extension);
 841 
 842 	encryption_certificate =
 843 		e_source_smime_get_encryption_certificate (extension);
 844 
 845 	have_signing_certificate =
 846 		(signing_certificate != NULL) &&
 847 		(*signing_certificate != '\0');
 848 
 849 	have_encryption_certificate =
 850 		(encryption_certificate != NULL) &&
 851 		(*encryption_certificate != '\0');
 852 
 853 	if (context->smime_sign && !have_signing_certificate) {
 854 		g_set_error (
 855 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
 856 			_("Cannot sign outgoing message: "
 857 			"No signing certificate set for "
 858 			"this account"));
 859 		return FALSE;
 860 	}
 861 
 862 	if (context->smime_encrypt && !have_encryption_certificate) {
 863 		g_set_error (
 864 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
 865 			_("Cannot encrypt outgoing message: "
 866 			"No encryption certificate set for "
 867 			"this account"));
 868 		return FALSE;
 869 	}
 870 
 871 	mime_part = camel_mime_part_new ();
 872 
 873 	camel_medium_set_content (
 874 		CAMEL_MEDIUM (mime_part),
 875 		context->top_level_part);
 876 
 877 	if (context->top_level_part == context->text_plain_part)
 878 		camel_mime_part_set_encoding (
 879 			mime_part, context->plain_encoding);
 880 
 881 	g_object_unref (context->top_level_part);
 882 	context->top_level_part = NULL;
 883 
 884 	if (context->smime_sign) {
 885 		CamelMimePart *npart;
 886 		gboolean success;
 887 
 888 		npart = camel_mime_part_new ();
 889 
 890 		cipher = camel_smime_context_new (context->session);
 891 
 892 		/* if we're also encrypting, envelope-sign rather than clear-sign */
 893 		if (context->smime_encrypt) {
 894 			camel_smime_context_set_sign_mode (
 895 				(CamelSMIMEContext *) cipher,
 896 				CAMEL_SMIME_SIGN_ENVELOPED);
 897 			camel_smime_context_set_encrypt_key (
 898 				(CamelSMIMEContext *) cipher,
 899 				TRUE, encryption_certificate);
 900 		} else if (have_encryption_certificate) {
 901 			camel_smime_context_set_encrypt_key (
 902 				(CamelSMIMEContext *) cipher,
 903 				TRUE, encryption_certificate);
 904 		}
 905 
 906 		success = camel_cipher_context_sign_sync (
 907 			cipher, signing_certificate,
 908 			account_hash_algo_to_camel_hash (signing_algorithm),
 909 			mime_part, npart, cancellable, error);
 910 
 911 		g_object_unref (cipher);
 912 
 913 		g_object_unref (mime_part);
 914 
 915 		if (!success) {
 916 			g_object_unref (npart);
 917 			return FALSE;
 918 		}
 919 
 920 		mime_part = npart;
 921 	}
 922 
 923 	if (context->smime_encrypt) {
 924 		gboolean success;
 925 
 926 		/* Check to see if we should encrypt to self.
 927 		 * NB: Gets removed immediately after use. */
 928 		if (encrypt_to_self)
 929 			g_ptr_array_add (
 930 				context->recipients, g_strdup (
 931 				encryption_certificate));
 932 
 933 		cipher = camel_smime_context_new (context->session);
 934 		camel_smime_context_set_encrypt_key (
 935 			(CamelSMIMEContext *) cipher, TRUE,
 936 			encryption_certificate);
 937 
 938 		success = camel_cipher_context_encrypt_sync (
 939 			cipher, NULL,
 940 			context->recipients, mime_part,
 941 			CAMEL_MIME_PART (context->message),
 942 			cancellable, error);
 943 
 944 		g_object_unref (cipher);
 945 
 946 		if (!success)
 947 			return FALSE;
 948 
 949 		if (encrypt_to_self)
 950 			g_ptr_array_set_size (
 951 				context->recipients,
 952 				context->recipients->len - 1);
 953 	}
 954 
 955 	/* we replaced the message directly, we don't want to do reparenting foo */
 956 	if (context->smime_encrypt) {
 957 		context->skip_content = TRUE;
 958 	} else {
 959 		CamelDataWrapper *content;
 960 
 961 		content = camel_medium_get_content (
 962 			CAMEL_MEDIUM (mime_part));
 963 		context->top_level_part = g_object_ref (content);
 964 	}
 965 
 966 	g_object_unref (mime_part);
 967 
 968 	return TRUE;
 969 }
 970 #endif
 971 
 972 static void
 973 composer_build_message_thread (GSimpleAsyncResult *simple,
 974                                EMsgComposer *composer,
 975                                GCancellable *cancellable)
 976 {
 977 	AsyncContext *context;
 978 	GError *error = NULL;
 979 
 980 	context = g_simple_async_result_get_op_res_gpointer (simple);
 981 
 982 	/* Setup working recipient list if we're encrypting. */
 983 	if (context->pgp_encrypt || context->smime_encrypt) {
 984 		gint ii, jj;
 985 
 986 		const gchar *types[] = {
 987 			CAMEL_RECIPIENT_TYPE_TO,
 988 			CAMEL_RECIPIENT_TYPE_CC,
 989 			CAMEL_RECIPIENT_TYPE_BCC
 990 		};
 991 
 992 		context->recipients = g_ptr_array_new_with_free_func (
 993 			(GDestroyNotify) g_free);
 994 		for (ii = 0; ii < G_N_ELEMENTS (types); ii++) {
 995 			CamelInternetAddress *addr;
 996 			const gchar *address;
 997 
 998 			addr = camel_mime_message_get_recipients (
 999 				context->message, types[ii]);
1000 			for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++)
1001 				g_ptr_array_add (
1002 					context->recipients,
1003 					g_strdup (address));
1004 		}
1005 	}
1006 
1007 	if (!composer_build_message_pgp (context, cancellable, &error)) {
1008 		g_simple_async_result_take_error (simple, error);
1009 		return;
1010 	}
1011 
1012 #if defined (HAVE_NSS)
1013 	if (!composer_build_message_smime (context, cancellable, &error)) {
1014 		g_simple_async_result_take_error (simple, error);
1015 		return;
1016 	}
1017 #endif /* HAVE_NSS */
1018 }
1019 
1020 static void
1021 composer_add_evolution_format_header (CamelMedium *medium,
1022                                       ComposerFlags flags)
1023 {
1024 	GString *string;
1025 
1026 	string = g_string_sized_new (128);
1027 
1028 	if (flags & COMPOSER_FLAG_HTML_CONTENT)
1029 		g_string_append (string, "text/html");
1030 	else
1031 		g_string_append (string, "text/plain");
1032 
1033 	if (flags & COMPOSER_FLAG_PGP_SIGN)
1034 		g_string_append (string, ", pgp-sign");
1035 
1036 	if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1037 		g_string_append (string, ", pgp-encrypt");
1038 
1039 	if (flags & COMPOSER_FLAG_SMIME_SIGN)
1040 		g_string_append (string, ", smime-sign");
1041 
1042 	if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1043 		g_string_append (string, ", smime-encrypt");
1044 
1045 	camel_medium_add_header (
1046 		medium, "X-Evolution-Format", string->str);
1047 
1048 	g_string_free (string, TRUE);
1049 }
1050 
1051 static void
1052 composer_build_message (EMsgComposer *composer,
1053                         ComposerFlags flags,
1054                         gint io_priority,
1055                         GCancellable *cancellable,
1056                         GAsyncReadyCallback callback,
1057                         gpointer user_data)
1058 {
1059 	EMsgComposerPrivate *priv;
1060 	GSimpleAsyncResult *simple;
1061 	AsyncContext *context;
1062 	GtkhtmlEditor *editor;
1063 	EAttachmentView *view;
1064 	EAttachmentStore *store;
1065 	EComposerHeaderTable *table;
1066 	CamelDataWrapper *html;
1067 	ESourceMailIdentity *mi;
1068 	ESourceRegistry *registry;
1069 	const gchar *extension_name;
1070 	const gchar *iconv_charset = NULL;
1071 	const gchar *identity_uid;
1072 	const gchar *organization;
1073 	CamelMultipart *body = NULL;
1074 	CamelContentType *type;
1075 	CamelSession *session;
1076 	CamelStream *stream;
1077 	CamelStream *mem_stream;
1078 	CamelMimePart *part;
1079 	GByteArray *data;
1080 	ESource *source;
1081 	gchar *charset;
1082 	gint i;
1083 
1084 	priv = composer->priv;
1085 	editor = GTKHTML_EDITOR (composer);
1086 	table = e_msg_composer_get_header_table (composer);
1087 	view = e_msg_composer_get_attachment_view (composer);
1088 	store = e_attachment_view_get_store (view);
1089 	session = e_msg_composer_get_session (composer);
1090 
1091 	registry = e_composer_header_table_get_registry (table);
1092 	identity_uid = e_composer_header_table_get_identity_uid (table);
1093 	source = e_source_registry_ref_source (registry, identity_uid);
1094 	g_return_if_fail (source != NULL);
1095 
1096 	/* Do all the non-blocking work here, and defer
1097 	 * any blocking operations to a separate thread. */
1098 
1099 	context = g_slice_new0 (AsyncContext);
1100 	context->source = source;  /* takes the reference */
1101 	context->session = g_object_ref (session);
1102 	context->from = e_msg_composer_get_from (composer);
1103 
1104 	if (flags & COMPOSER_FLAG_PGP_SIGN)
1105 		context->pgp_sign = TRUE;
1106 
1107 	if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1108 		context->pgp_encrypt = TRUE;
1109 
1110 	if (flags & COMPOSER_FLAG_SMIME_SIGN)
1111 		context->smime_sign = TRUE;
1112 
1113 	if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1114 		context->smime_encrypt = TRUE;
1115 
1116 	context->need_thread =
1117 		context->pgp_sign || context->pgp_encrypt ||
1118 		context->smime_sign || context->smime_encrypt;
1119 
1120 	simple = g_simple_async_result_new (
1121 		G_OBJECT (composer), callback,
1122 		user_data, composer_build_message);
1123 
1124 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1125 
1126 	g_simple_async_result_set_op_res_gpointer (
1127 		simple, context, (GDestroyNotify) async_context_free);
1128 
1129 	/* If this is a redirected message, just tweak the headers. */
1130 	if (priv->redirect) {
1131 		context->skip_content = TRUE;
1132 		context->message = g_object_ref (priv->redirect);
1133 		build_message_headers (composer, context->message, TRUE);
1134 		g_simple_async_result_complete (simple);
1135 		g_object_unref (simple);
1136 		return;
1137 	}
1138 
1139 	context->message = camel_mime_message_new ();
1140 
1141 	/* Explicitly generate a Message-ID header here so it's
1142 	 * consistent for all outbound streams (SMTP, Fcc, etc). */
1143 	camel_mime_message_set_message_id (context->message, NULL);
1144 
1145 	build_message_headers (composer, context->message, FALSE);
1146 	for (i = 0; i < priv->extra_hdr_names->len; i++) {
1147 		camel_medium_add_header (
1148 			CAMEL_MEDIUM (context->message),
1149 			priv->extra_hdr_names->pdata[i],
1150 			priv->extra_hdr_values->pdata[i]);
1151 	}
1152 
1153 	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
1154 	mi = e_source_get_extension (source, extension_name);
1155 	organization = e_source_mail_identity_get_organization (mi);
1156 
1157 	/* Disposition-Notification-To */
1158 	if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) {
1159 		const gchar *mdn_address;
1160 
1161 		mdn_address = e_source_mail_identity_get_reply_to (mi);
1162 		if (mdn_address == NULL)
1163 			mdn_address = e_source_mail_identity_get_address (mi);
1164 		if (mdn_address != NULL)
1165 			camel_medium_add_header (
1166 				CAMEL_MEDIUM (context->message),
1167 				"Disposition-Notification-To", mdn_address);
1168 	}
1169 
1170 	/* X-Priority */
1171 	if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE)
1172 		camel_medium_add_header (
1173 			CAMEL_MEDIUM (context->message),
1174 			"X-Priority", "1");
1175 
1176 	/* Organization */
1177 	if (organization != NULL && *organization != '\0') {
1178 		gchar *encoded_organization;
1179 
1180 		encoded_organization = camel_header_encode_string (
1181 			(const guchar *) organization);
1182 		camel_medium_set_header (
1183 			CAMEL_MEDIUM (context->message),
1184 			"Organization", encoded_organization);
1185 		g_free (encoded_organization);
1186 	}
1187 
1188 	/* X-Evolution-Format */
1189 	composer_add_evolution_format_header (
1190 		CAMEL_MEDIUM (context->message), flags);
1191 
1192 	/* Build the text/plain part. */
1193 
1194 	if (priv->mime_body) {
1195 		if (text_requires_quoted_printable (priv->mime_body, -1)) {
1196 			context->plain_encoding =
1197 				CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1198 		} else {
1199 			context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT;
1200 			for (i = 0; priv->mime_body[i]; i++) {
1201 				if ((guchar) priv->mime_body[i] > 127) {
1202 					context->plain_encoding =
1203 					CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1204 					break;
1205 				}
1206 			}
1207 		}
1208 
1209 		data = g_byte_array_new ();
1210 		g_byte_array_append (
1211 			data, (const guint8 *) priv->mime_body,
1212 			strlen (priv->mime_body));
1213 		type = camel_content_type_decode (priv->mime_type);
1214 
1215 	} else {
1216 		gchar *text;
1217 		gsize length;
1218 
1219 		data = g_byte_array_new ();
1220 		text = gtkhtml_editor_get_text_plain (editor, &length);
1221 		g_byte_array_append (data, (guint8 *) text, (guint) length);
1222 		g_free (text);
1223 
1224 		type = camel_content_type_new ("text", "plain");
1225 		charset = best_charset (
1226 			data, priv->charset, &context->plain_encoding);
1227 		if (charset != NULL) {
1228 			camel_content_type_set_param (type, "charset", charset);
1229 			iconv_charset = camel_iconv_charset_name (charset);
1230 			g_free (charset);
1231 		}
1232 	}
1233 
1234 	mem_stream = camel_stream_mem_new_with_byte_array (data);
1235 	stream = camel_stream_filter_new (mem_stream);
1236 	g_object_unref (mem_stream);
1237 
1238 	/* Convert the stream to the appropriate charset. */
1239 	if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0)
1240 		composer_add_charset_filter (stream, iconv_charset);
1241 
1242 	/* Encode the stream to quoted-printable if necessary. */
1243 	if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1244 		composer_add_quoted_printable_filter (stream);
1245 
1246 	/* Construct the content object.  This does not block since
1247 	 * we're constructing the data wrapper from a memory stream. */
1248 	context->top_level_part = camel_data_wrapper_new ();
1249 	camel_data_wrapper_construct_from_stream_sync (
1250 		context->top_level_part, stream, NULL, NULL);
1251 	g_object_unref (stream);
1252 
1253 	context->text_plain_part = g_object_ref (context->top_level_part);
1254 
1255 	/* Avoid re-encoding the data when adding it to a MIME part. */
1256 	if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1257 		context->top_level_part->encoding = context->plain_encoding;
1258 
1259 	camel_data_wrapper_set_mime_type_field (
1260 		context->top_level_part, type);
1261 
1262 	camel_content_type_unref (type);
1263 
1264 	/* Build the text/html part, and wrap it and the text/plain part
1265 	 * in a multipart/alternative part.  Additionally, if there are
1266 	 * inline images then wrap the multipart/alternative part along
1267 	 * with the images in a multipart/related part.
1268 	 *
1269 	 * So the structure of all this will be:
1270 	 *
1271 	 *    multipart/related
1272 	 *        multipart/alternative
1273 	 *            text/plain
1274 	 *            text/html
1275 	 *        image/<<whatever>>
1276 	 *        image/<<whatever>>
1277 	 *        ...
1278 	 */
1279 
1280 	if (flags & COMPOSER_FLAG_HTML_CONTENT) {
1281 		gchar *text;
1282 		gsize length;
1283 		gboolean pre_encode;
1284 
1285 		clear_current_images (composer);
1286 
1287 		if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA)
1288 			gtkhtml_editor_run_command (editor, "save-data-on");
1289 
1290 		data = g_byte_array_new ();
1291 		text = gtkhtml_editor_get_text_html (editor, &length);
1292 		g_byte_array_append (data, (guint8 *) text, (guint) length);
1293 		pre_encode = text_requires_quoted_printable (text, length);
1294 		g_free (text);
1295 
1296 		if (flags & COMPOSER_FLAG_SAVE_OBJECT_DATA)
1297 			gtkhtml_editor_run_command (editor, "save-data-off");
1298 
1299 		mem_stream = camel_stream_mem_new_with_byte_array (data);
1300 		stream = camel_stream_filter_new (mem_stream);
1301 		g_object_unref (mem_stream);
1302 
1303 		if (pre_encode)
1304 			composer_add_quoted_printable_filter (stream);
1305 
1306 		/* Construct the content object.  This does not block since
1307 		 * we're constructing the data wrapper from a memory stream. */
1308 		html = camel_data_wrapper_new ();
1309 		camel_data_wrapper_construct_from_stream_sync (
1310 			html, stream, NULL, NULL);
1311 		g_object_unref (stream);
1312 
1313 		camel_data_wrapper_set_mime_type (
1314 			html, "text/html; charset=utf-8");
1315 
1316 		/* Avoid re-encoding the data when adding it to a MIME part. */
1317 		if (pre_encode)
1318 			html->encoding =
1319 				CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1320 
1321 		/* Build the multipart/alternative */
1322 		body = camel_multipart_new ();
1323 		camel_data_wrapper_set_mime_type (
1324 			CAMEL_DATA_WRAPPER (body), "multipart/alternative");
1325 		camel_multipart_set_boundary (body, NULL);
1326 
1327 		/* Add the text/plain part. */
1328 		part = camel_mime_part_new ();
1329 		camel_medium_set_content (
1330 			CAMEL_MEDIUM (part), context->top_level_part);
1331 		camel_mime_part_set_encoding (part, context->plain_encoding);
1332 		camel_multipart_add_part (body, part);
1333 		g_object_unref (part);
1334 
1335 		/* Add the text/html part. */
1336 		part = camel_mime_part_new ();
1337 		camel_medium_set_content (CAMEL_MEDIUM (part), html);
1338 		camel_mime_part_set_encoding (
1339 			part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1340 		camel_multipart_add_part (body, part);
1341 		g_object_unref (part);
1342 
1343 		g_object_unref (context->top_level_part);
1344 		g_object_unref (html);
1345 
1346 		/* If there are inlined images, construct a multipart/related
1347 		 * containing the multipart/alternative and the images. */
1348 		if (priv->current_images) {
1349 			CamelMultipart *html_with_images;
1350 
1351 			html_with_images = camel_multipart_new ();
1352 			camel_data_wrapper_set_mime_type (
1353 				CAMEL_DATA_WRAPPER (html_with_images),
1354 				"multipart/related; "
1355 				"type=\"multipart/alternative\"");
1356 			camel_multipart_set_boundary (html_with_images, NULL);
1357 
1358 			part = camel_mime_part_new ();
1359 			camel_medium_set_content (
1360 				CAMEL_MEDIUM (part),
1361 				CAMEL_DATA_WRAPPER (body));
1362 			camel_multipart_add_part (html_with_images, part);
1363 			g_object_unref (part);
1364 
1365 			g_object_unref (body);
1366 
1367 			add_inlined_images (composer, html_with_images);
1368 			clear_current_images (composer);
1369 
1370 			context->top_level_part =
1371 				CAMEL_DATA_WRAPPER (html_with_images);
1372 		} else
1373 			context->top_level_part =
1374 				CAMEL_DATA_WRAPPER (body);
1375 	}
1376 
1377 	/* If there are attachments, wrap what we've built so far
1378 	 * along with the attachments in a multipart/mixed part. */
1379 	if (e_attachment_store_get_num_attachments (store) > 0) {
1380 		CamelMultipart *multipart = camel_multipart_new ();
1381 
1382 		/* Generate a random boundary. */
1383 		camel_multipart_set_boundary (multipart, NULL);
1384 
1385 		part = camel_mime_part_new ();
1386 		camel_medium_set_content (
1387 			CAMEL_MEDIUM (part),
1388 			context->top_level_part);
1389 		if (context->top_level_part == context->text_plain_part)
1390 			camel_mime_part_set_encoding (
1391 				part, context->plain_encoding);
1392 		camel_multipart_add_part (multipart, part);
1393 		g_object_unref (part);
1394 
1395 		e_attachment_store_add_to_multipart (
1396 			store, multipart, priv->charset);
1397 
1398 		g_object_unref (context->top_level_part);
1399 		context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
1400 	}
1401 
1402 	/* Run any blocking operations in a separate thread. */
1403 	if (context->need_thread)
1404 		g_simple_async_result_run_in_thread (
1405 			simple, (GSimpleAsyncThreadFunc)
1406 			composer_build_message_thread,
1407 			io_priority, cancellable);
1408 	else
1409 		g_simple_async_result_complete (simple);
1410 
1411 	g_object_unref (simple);
1412 }
1413 
1414 static CamelMimeMessage *
1415 composer_build_message_finish (EMsgComposer *composer,
1416                                GAsyncResult *result,
1417                                GError **error)
1418 {
1419 	GSimpleAsyncResult *simple;
1420 	AsyncContext *context;
1421 
1422 	g_return_val_if_fail (
1423 		g_simple_async_result_is_valid (
1424 		result, G_OBJECT (composer), composer_build_message), NULL);
1425 
1426 	simple = G_SIMPLE_ASYNC_RESULT (result);
1427 	context = g_simple_async_result_get_op_res_gpointer (simple);
1428 
1429 	if (g_simple_async_result_propagate_error (simple, error))
1430 		return NULL;
1431 
1432 	/* Finalize some details before returning. */
1433 
1434 	if (!context->skip_content)
1435 		camel_medium_set_content (
1436 			CAMEL_MEDIUM (context->message),
1437 			context->top_level_part);
1438 
1439 	if (context->top_level_part == context->text_plain_part)
1440 		camel_mime_part_set_encoding (
1441 			CAMEL_MIME_PART (context->message),
1442 			context->plain_encoding);
1443 
1444 	return g_object_ref (context->message);
1445 }
1446 
1447 /* Signatures */
1448 
1449 static gboolean
1450 use_top_signature (EMsgComposer *composer)
1451 {
1452 	EShell *shell;
1453 	EShellSettings *shell_settings;
1454 	EMsgComposerPrivate *priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
1455 
1456 	g_return_val_if_fail (priv != NULL, FALSE);
1457 
1458 	/* The composer had been created from a stored message, thus the
1459 	 * signature placement is either there already, or pt it at the
1460 	 * bottom regardless of a preferences (which is for reply anyway,
1461 	 * not for Edit as new) */
1462 	if (priv->is_from_message)
1463 		return FALSE;
1464 
1465 	shell = e_msg_composer_get_shell (composer);
1466 	shell_settings = e_shell_get_shell_settings (shell);
1467 
1468 	return e_shell_settings_get_boolean (
1469 		shell_settings, "composer-top-signature");
1470 }
1471 
1472 #define NO_SIGNATURE_TEXT	\
1473 	"<!--+GtkHTML:<DATA class=\"ClueFlow\" " \
1474 	"                     key=\"signature\" " \
1475 	"                   value=\"1\">-->" \
1476 	"<!--+GtkHTML:<DATA class=\"ClueFlow\" " \
1477 	"                     key=\"signature_name\" " \
1478 	"                   value=\"uid:Noname\">--><BR>"
1479 
1480 static void
1481 set_editor_text (EMsgComposer *composer,
1482                  const gchar *text,
1483                  gboolean set_signature)
1484 {
1485 	gchar *body = NULL;
1486 
1487 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1488 	g_return_if_fail (text != NULL);
1489 
1490 	/*
1491 	 *
1492 	 * Keeping Signatures in the beginning of composer
1493 	 * ------------------------------------------------
1494 	 *
1495 	 * Purists are gonna blast me for this.
1496 	 * But there are so many people (read Outlook users) who want this.
1497 	 * And Evo is an exchange-client, Outlook-replacement etc.
1498 	 * So Here it goes :(
1499 	 *
1500 	 * -- Sankar
1501 	 *
1502 	 */
1503 
1504 	/* "Edit as New Message" sets "priv->is_from_message".
1505 	 * Always put the signature at the bottom for that case. */
1506 	if (!composer->priv->is_from_message && use_top_signature (composer)) {
1507 		/* put marker to the top */
1508 		body = g_strdup_printf ("<BR>" NO_SIGNATURE_TEXT "%s", text);
1509 	} else {
1510 		/* no marker => to the bottom */
1511 		body = g_strdup_printf ("%s<BR>", text);
1512 	}
1513 
1514 	gtkhtml_editor_set_text_html (GTKHTML_EDITOR (composer), body, -1);
1515 
1516 	if (set_signature)
1517 		e_composer_update_signature (composer);
1518 
1519 	g_free (body);
1520 }
1521 
1522 /* Miscellaneous callbacks.  */
1523 
1524 static void
1525 attachment_store_changed_cb (EMsgComposer *composer)
1526 {
1527 	GtkhtmlEditor *editor;
1528 
1529 	/* Mark the editor as changed so it prompts about unsaved
1530 	 * changes on close. */
1531 	editor = GTKHTML_EDITOR (composer);
1532 	gtkhtml_editor_set_changed (editor, TRUE);
1533 }
1534 
1535 static void
1536 msg_composer_subject_changed_cb (EMsgComposer *composer)
1537 {
1538 	EComposerHeaderTable *table;
1539 	const gchar *subject;
1540 
1541 	table = e_msg_composer_get_header_table (composer);
1542 	subject = e_composer_header_table_get_subject (table);
1543 
1544 	if (subject == NULL || *subject == '\0')
1545 		subject = _("Compose Message");
1546 
1547 	gtk_window_set_title (GTK_WINDOW (composer), subject);
1548 }
1549 
1550 static void
1551 msg_composer_mail_identity_changed_cb (EMsgComposer *composer)
1552 {
1553 	EMsgComposerPrivate *p = composer->priv;
1554 	EMailSignatureComboBox *combo_box;
1555 	ESourceRegistry *registry;
1556 	ESourceMailComposition *mc;
1557 	ESourceOpenPGP *pgp;
1558 	ESourceSMIME *smime;
1559 	EComposerHeaderTable *table;
1560 	GtkToggleAction *action;
1561 	ESource *source;
1562 	gboolean can_sign;
1563 	gboolean pgp_sign;
1564 	gboolean smime_sign;
1565 	gboolean smime_encrypt;
1566 	const gchar *extension_name;
1567 	const gchar *uid;
1568 
1569 	table = e_msg_composer_get_header_table (composer);
1570 	registry = e_composer_header_table_get_registry (table);
1571 	uid = e_composer_header_table_get_identity_uid (table);
1572 
1573 	/* Silently return if no identity is selected. */
1574 	if (uid == NULL)
1575 		return;
1576 
1577 	source = e_source_registry_ref_source (registry, uid);
1578 	g_return_if_fail (source != NULL);
1579 
1580 	extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
1581 	mc = e_source_get_extension (source, extension_name);
1582 
1583 	extension_name = E_SOURCE_EXTENSION_OPENPGP;
1584 	pgp = e_source_get_extension (source, extension_name);
1585 	pgp_sign = e_source_openpgp_get_sign_by_default (pgp);
1586 
1587 	extension_name = E_SOURCE_EXTENSION_SMIME;
1588 	smime = e_source_get_extension (source, extension_name);
1589 	smime_sign = e_source_smime_get_sign_by_default (smime);
1590 	smime_encrypt = e_source_smime_get_encrypt_by_default (smime);
1591 
1592 	can_sign =
1593 		(p->mime_type == NULL) ||
1594 		e_source_mail_composition_get_sign_imip (mc) ||
1595 		(g_ascii_strncasecmp (p->mime_type, "text/calendar", 13) != 0);
1596 
1597 	action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
1598 	gtk_toggle_action_set_active (action, can_sign && pgp_sign);
1599 
1600 	action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
1601 	gtk_toggle_action_set_active (action, can_sign && smime_sign);
1602 
1603 	action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
1604 	gtk_toggle_action_set_active (action, smime_encrypt);
1605 
1606 	combo_box = e_composer_header_table_get_signature_combo_box (table);
1607 	e_mail_signature_combo_box_set_identity_uid (combo_box, uid);
1608 
1609 	g_object_unref (source);
1610 }
1611 
1612 static void
1613 msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
1614                                          GdkAtom *targets,
1615                                          gint n_targets,
1616                                          EMsgComposer *composer)
1617 {
1618 	GtkhtmlEditor *editor;
1619 	gboolean html_mode;
1620 
1621 	editor = GTKHTML_EDITOR (composer);
1622 	html_mode = gtkhtml_editor_get_html_mode (editor);
1623 
1624 	/* Order is important here to ensure common use cases are
1625 	 * handled correctly.  See GNOME bug #603715 for details. */
1626 
1627 	if (gtk_targets_include_uri (targets, n_targets)) {
1628 		e_composer_paste_uris (composer, clipboard);
1629 		return;
1630 	}
1631 
1632 	/* Only paste HTML content in HTML mode. */
1633 	if (html_mode) {
1634 		if (e_targets_include_html (targets, n_targets)) {
1635 			e_composer_paste_html (composer, clipboard);
1636 			return;
1637 		}
1638 	}
1639 
1640 	if (gtk_targets_include_text (targets, n_targets)) {
1641 		e_composer_paste_text (composer, clipboard);
1642 		return;
1643 	}
1644 
1645 	if (gtk_targets_include_image (targets, n_targets, TRUE)) {
1646 		e_composer_paste_image (composer, clipboard);
1647 		return;
1648 	}
1649 }
1650 
1651 static void
1652 msg_composer_paste_clipboard_cb (EWebViewGtkHTML *web_view,
1653                                  EMsgComposer *composer)
1654 {
1655 	GtkClipboard *clipboard;
1656 
1657 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
1658 
1659 	gtk_clipboard_request_targets (
1660 		clipboard, (GtkClipboardTargetsReceivedFunc)
1661 		msg_composer_paste_clipboard_targets_cb, composer);
1662 
1663 	g_signal_stop_emission_by_name (web_view, "paste-clipboard");
1664 }
1665 
1666 static void
1667 msg_composer_realize_gtkhtml_cb (GtkWidget *widget,
1668                                  EMsgComposer *composer)
1669 {
1670 	EAttachmentView *view;
1671 	GtkTargetList *target_list;
1672 	GtkTargetEntry *targets;
1673 	gint n_targets;
1674 
1675 	/* XXX GtkHTML doesn't set itself up as a drag destination until
1676 	 *     it's realized, and we need to amend to its target list so
1677 	 *     it will accept the same drag targets as the attachment bar.
1678 	 *     Do this any earlier and GtkHTML will just overwrite us. */
1679 
1680 	/* When redirecting a message, the message body is not
1681 	 * editable and therefore cannot be a drag destination. */
1682 	if (!e_web_view_gtkhtml_get_editable (E_WEB_VIEW_GTKHTML (widget)))
1683 		return;
1684 
1685 	view = e_msg_composer_get_attachment_view (composer);
1686 
1687 	target_list = e_attachment_view_get_target_list (view);
1688 	targets = gtk_target_table_new_from_list (target_list, &n_targets);
1689 
1690 	target_list = gtk_drag_dest_get_target_list (widget);
1691 	gtk_target_list_add_table (target_list, targets, n_targets);
1692 
1693 	gtk_target_table_free (targets, n_targets);
1694 }
1695 
1696 static gboolean
1697 msg_composer_drag_motion_cb (GtkWidget *widget,
1698                              GdkDragContext *context,
1699                              gint x,
1700                              gint y,
1701                              guint time,
1702                              EMsgComposer *composer)
1703 {
1704 	EAttachmentView *view;
1705 
1706 	view = e_msg_composer_get_attachment_view (composer);
1707 
1708 	/* Stop the signal from propagating to GtkHtml. */
1709 	g_signal_stop_emission_by_name (widget, "drag-motion");
1710 
1711 	return e_attachment_view_drag_motion (view, context, x, y, time);
1712 }
1713 
1714 static void
1715 msg_composer_drag_data_received_cb (GtkWidget *widget,
1716                                     GdkDragContext *context,
1717                                     gint x,
1718                                     gint y,
1719                                     GtkSelectionData *selection,
1720                                     guint info,
1721                                     guint time,
1722                                     EMsgComposer *composer)
1723 {
1724 	EAttachmentView *view;
1725 
1726 	/* HTML mode has a few special cases for drops... */
1727 	if (gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer))) {
1728 
1729 		/* If we're receiving an image, we want the image to be
1730 		 * inserted in the message body.  Let GtkHtml handle it. */
1731 		if (gtk_selection_data_targets_include_image (selection, TRUE))
1732 			return;
1733 
1734 		/* If we're receiving URIs and -all- the URIs point to
1735 		 * image files, we want the image(s) to be inserted in
1736 		 * the message body.  Let GtkHtml handle it. */
1737 		if (e_composer_selection_is_image_uris (composer, selection))
1738 			return;
1739 	}
1740 
1741 	view = e_msg_composer_get_attachment_view (composer);
1742 
1743 	/* Forward the data to the attachment view.  Note that calling
1744 	 * e_attachment_view_drag_data_received() will not work because
1745 	 * that function only handles the case where all the other drag
1746 	 * handlers have failed. */
1747 	e_attachment_paned_drag_data_received (
1748 		E_ATTACHMENT_PANED (view),
1749 		context, x, y, selection, info, time);
1750 
1751 	/* Stop the signal from propagating to GtkHtml. */
1752 	g_signal_stop_emission_by_name (widget, "drag-data-received");
1753 }
1754 
1755 static void
1756 msg_composer_notify_header_cb (EMsgComposer *composer)
1757 {
1758 	GtkhtmlEditor *editor;
1759 
1760 	editor = GTKHTML_EDITOR (composer);
1761 	gtkhtml_editor_set_changed (editor, TRUE);
1762 }
1763 
1764 static gboolean
1765 msg_composer_delete_event_cb (EMsgComposer *composer)
1766 {
1767 	EShell *shell;
1768 	GtkApplication *application;
1769 	GList *windows;
1770 
1771 	shell = e_msg_composer_get_shell (composer);
1772 
1773 	/* If the "async" action group is insensitive, it means an
1774 	 * asynchronous operation is in progress.  Block the event. */
1775 	if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
1776 		return TRUE;
1777 
1778 	application = GTK_APPLICATION (shell);
1779 	windows = gtk_application_get_windows (application);
1780 
1781 	if (g_list_length (windows) == 1) {
1782 		/* This is the last watched window, use the quit
1783 		 * mechanism to have a draft saved properly */
1784 		e_shell_quit (shell, E_SHELL_QUIT_ACTION);
1785 	} else {
1786 		/* There are more watched windows opened,
1787 		 * invoke only a close action */
1788 		gtk_action_activate (ACTION (CLOSE));
1789 	}
1790 
1791 	return TRUE;
1792 }
1793 
1794 static void
1795 msg_composer_prepare_for_quit_cb (EShell *shell,
1796                                   EActivity *activity,
1797                                   EMsgComposer *composer)
1798 {
1799 	if (e_msg_composer_is_exiting (composer)) {
1800 		/* needs save draft first */
1801 		g_object_ref (activity);
1802 		g_object_weak_ref (
1803 			G_OBJECT (composer), (GWeakNotify)
1804 			g_object_unref, activity);
1805 		gtk_action_activate (ACTION (SAVE_DRAFT));
1806 	}
1807 }
1808 
1809 static void
1810 msg_composer_quit_requested_cb (EShell *shell,
1811                                 EShellQuitReason reason,
1812                                 EMsgComposer *composer)
1813 {
1814 	if (e_msg_composer_is_exiting (composer)) {
1815 		g_signal_handlers_disconnect_by_func (
1816 			shell, msg_composer_quit_requested_cb, composer);
1817 		g_signal_handlers_disconnect_by_func (
1818 			shell, msg_composer_prepare_for_quit_cb, composer);
1819 	} else if (!e_msg_composer_can_close (composer, FALSE) &&
1820 			!e_msg_composer_is_exiting (composer)) {
1821 		e_shell_cancel_quit (shell);
1822 	}
1823 }
1824 
1825 static void
1826 msg_composer_set_shell (EMsgComposer *composer,
1827                         EShell *shell)
1828 {
1829 	g_return_if_fail (E_IS_SHELL (shell));
1830 	g_return_if_fail (composer->priv->shell == NULL);
1831 
1832 	composer->priv->shell = shell;
1833 
1834 	g_object_add_weak_pointer (
1835 		G_OBJECT (shell), &composer->priv->shell);
1836 }
1837 
1838 static void
1839 msg_composer_set_property (GObject *object,
1840                            guint property_id,
1841                            const GValue *value,
1842                            GParamSpec *pspec)
1843 {
1844 	switch (property_id) {
1845 		case PROP_SHELL:
1846 			msg_composer_set_shell (
1847 				E_MSG_COMPOSER (object),
1848 				g_value_get_object (value));
1849 			return;
1850 	}
1851 
1852 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1853 }
1854 
1855 static void
1856 msg_composer_get_property (GObject *object,
1857                            guint property_id,
1858                            GValue *value,
1859                            GParamSpec *pspec)
1860 {
1861 	switch (property_id) {
1862 		case PROP_FOCUS_TRACKER:
1863 			g_value_set_object (
1864 				value, e_msg_composer_get_focus_tracker (
1865 				E_MSG_COMPOSER (object)));
1866 			return;
1867 
1868 		case PROP_SHELL:
1869 			g_value_set_object (
1870 				value, e_msg_composer_get_shell (
1871 				E_MSG_COMPOSER (object)));
1872 			return;
1873 	}
1874 
1875 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1876 }
1877 
1878 static void
1879 msg_composer_finalize (GObject *object)
1880 {
1881 	EMsgComposer *composer = E_MSG_COMPOSER (object);
1882 
1883 	e_composer_private_finalize (composer);
1884 
1885 	/* Chain up to parent's finalize() method. */
1886 	G_OBJECT_CLASS (e_msg_composer_parent_class)->finalize (object);
1887 }
1888 
1889 static void
1890 msg_composer_gallery_drag_data_get (GtkIconView *icon_view,
1891                                     GdkDragContext *context,
1892                                     GtkSelectionData *selection_data,
1893                                     guint target_type,
1894                                     guint time)
1895 {
1896 	GtkTreePath *path;
1897 	GtkCellRenderer *cell;
1898 	GtkTreeModel *model;
1899 	GtkTreeIter iter;
1900 	GdkAtom target;
1901 	gchar *str_data;
1902 
1903 	if (!gtk_icon_view_get_cursor (icon_view, &path, &cell))
1904 		return;
1905 
1906 	target = gtk_selection_data_get_target (selection_data);
1907 
1908 	model = gtk_icon_view_get_model (icon_view);
1909 	gtk_tree_model_get_iter (model, &iter, path);
1910 	gtk_tree_model_get (model, &iter, 1, &str_data, -1);
1911 	gtk_tree_path_free (path);
1912 
1913 	/* only supports "text/uri-list" */
1914 	gtk_selection_data_set (
1915 		selection_data, target, 8,
1916 		(guchar *) str_data, strlen (str_data));
1917 	g_free (str_data);
1918 }
1919 
1920 static void
1921 msg_composer_constructed (GObject *object)
1922 {
1923 	EShell *shell;
1924 	EShellSettings *shell_settings;
1925 	GtkhtmlEditor *editor;
1926 	EMsgComposer *composer;
1927 	EAttachmentView *view;
1928 	EAttachmentStore *store;
1929 	EComposerHeaderTable *table;
1930 	EWebViewGtkHTML *web_view;
1931 	GtkUIManager *ui_manager;
1932 	GtkToggleAction *action;
1933 	const gchar *id;
1934 	gboolean active;
1935 
1936 	editor = GTKHTML_EDITOR (object);
1937 	composer = E_MSG_COMPOSER (object);
1938 
1939 	shell = e_msg_composer_get_shell (composer);
1940 	shell_settings = e_shell_get_shell_settings (shell);
1941 
1942 	if (e_shell_get_express_mode (shell)) {
1943 		GtkWindow *parent = e_shell_get_active_window (shell);
1944 		gtk_window_set_transient_for (GTK_WINDOW (composer), parent);
1945 	}
1946 
1947 	e_composer_private_constructed (composer);
1948 
1949 	web_view = e_msg_composer_get_web_view (composer);
1950 	ui_manager = gtkhtml_editor_get_ui_manager (editor);
1951 	view = e_msg_composer_get_attachment_view (composer);
1952 	table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
1953 
1954 	gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message"));
1955 	gtk_window_set_icon_name (GTK_WINDOW (composer), "mail-message-new");
1956 	gtk_window_set_default_size (GTK_WINDOW (composer), 600, 500);
1957 
1958 	g_signal_connect (
1959 		object, "delete-event",
1960 		G_CALLBACK (msg_composer_delete_event_cb), NULL);
1961 
1962 	e_shell_adapt_window_size (shell, GTK_WINDOW (object));
1963 
1964 	gtk_application_add_window (
1965 		GTK_APPLICATION (shell), GTK_WINDOW (object));
1966 
1967 	g_signal_connect (
1968 		shell, "quit-requested",
1969 		G_CALLBACK (msg_composer_quit_requested_cb), composer);
1970 
1971 	g_signal_connect (
1972 		shell, "prepare-for-quit",
1973 		G_CALLBACK (msg_composer_prepare_for_quit_cb), composer);
1974 
1975 	/* Restore Persistent State */
1976 
1977 	e_restore_window (
1978 		GTK_WINDOW (composer),
1979 		"/org/gnome/evolution/mail/composer-window/",
1980 		E_RESTORE_WINDOW_SIZE);
1981 
1982 	/* Honor User Preferences */
1983 
1984 	action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
1985 	active = e_shell_settings_get_boolean (
1986 		shell_settings, "composer-request-receipt");
1987 	gtk_toggle_action_set_active (action, active);
1988 
1989 	/* Clipboard Support */
1990 
1991 	g_signal_connect (
1992 		web_view, "paste-clipboard",
1993 		G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
1994 
1995 	/* Drag-and-Drop Support */
1996 
1997 	g_signal_connect (
1998 		web_view, "realize",
1999 		G_CALLBACK (msg_composer_realize_gtkhtml_cb), composer);
2000 
2001 	g_signal_connect (
2002 		web_view, "drag-motion",
2003 		G_CALLBACK (msg_composer_drag_motion_cb), composer);
2004 
2005 	g_signal_connect (
2006 		web_view, "drag-data-received",
2007 		G_CALLBACK (msg_composer_drag_data_received_cb), composer);
2008 
2009 	g_signal_connect (
2010 		composer->priv->gallery_icon_view, "drag-data-get",
2011 		G_CALLBACK (msg_composer_gallery_drag_data_get), NULL);
2012 
2013 	/* Configure Headers */
2014 
2015 	g_signal_connect_swapped (
2016 		table, "notify::destinations-bcc",
2017 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2018 	g_signal_connect_swapped (
2019 		table, "notify::destinations-cc",
2020 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2021 	g_signal_connect_swapped (
2022 		table, "notify::destinations-to",
2023 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2024 	g_signal_connect_swapped (
2025 		table, "notify::identity-uid",
2026 		G_CALLBACK (msg_composer_mail_identity_changed_cb), composer);
2027 	g_signal_connect_swapped (
2028 		table, "notify::reply-to",
2029 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2030 	g_signal_connect_swapped (
2031 		table, "notify::signature-uid",
2032 		G_CALLBACK (e_composer_update_signature), composer);
2033 	g_signal_connect_swapped (
2034 		table, "notify::subject",
2035 		G_CALLBACK (msg_composer_subject_changed_cb), composer);
2036 	g_signal_connect_swapped (
2037 		table, "notify::subject",
2038 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2039 
2040 	msg_composer_mail_identity_changed_cb (composer);
2041 
2042 	/* Attachments */
2043 
2044 	store = e_attachment_view_get_store (view);
2045 
2046 	g_signal_connect_swapped (
2047 		store, "row-deleted",
2048 		G_CALLBACK (attachment_store_changed_cb), composer);
2049 
2050 	g_signal_connect_swapped (
2051 		store, "row-inserted",
2052 		G_CALLBACK (attachment_store_changed_cb), composer);
2053 
2054 	/* Initialization may have tripped the "changed" state. */
2055 	gtkhtml_editor_set_changed (editor, FALSE);
2056 
2057 	id = "org.gnome.evolution.composer";
2058 	e_plugin_ui_register_manager (ui_manager, id, composer);
2059 	e_plugin_ui_enable_manager (ui_manager, id);
2060 
2061 	e_extensible_load_extensions (E_EXTENSIBLE (composer));
2062 
2063 	/* Chain up to parent's constructed() method. */
2064 	G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object);
2065 }
2066 
2067 static void
2068 msg_composer_dispose (GObject *object)
2069 {
2070 	EMsgComposer *composer = E_MSG_COMPOSER (object);
2071 	EShell *shell;
2072 
2073 	if (composer->priv->address_dialog != NULL) {
2074 		gtk_widget_destroy (composer->priv->address_dialog);
2075 		composer->priv->address_dialog = NULL;
2076 	}
2077 
2078 	/* FIXME Our EShell is already unreferenced. */
2079 	shell = e_shell_get_default ();
2080 
2081 	g_signal_handlers_disconnect_by_func (
2082 		shell, msg_composer_quit_requested_cb, composer);
2083 	g_signal_handlers_disconnect_by_func (
2084 		shell, msg_composer_prepare_for_quit_cb, composer);
2085 
2086 	e_composer_private_dispose (composer);
2087 
2088 	/* Chain up to parent's dispose() method. */
2089 	G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose (object);
2090 }
2091 
2092 static void
2093 msg_composer_map (GtkWidget *widget)
2094 {
2095 	EComposerHeaderTable *table;
2096 	GtkWidget *input_widget;
2097 	const gchar *text;
2098 
2099 	/* Chain up to parent's map() method. */
2100 	GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget);
2101 
2102 	table = e_msg_composer_get_header_table (E_MSG_COMPOSER (widget));
2103 
2104 	/* If the 'To' field is empty, focus it. */
2105 	input_widget =
2106 		e_composer_header_table_get_header (
2107 		table, E_COMPOSER_HEADER_TO)->input_widget;
2108 	text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2109 	if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2110 		gtk_widget_grab_focus (input_widget);
2111 		return;
2112 	}
2113 
2114 	/* If not, check the 'Subject' field. */
2115 	input_widget =
2116 		e_composer_header_table_get_header (
2117 		table, E_COMPOSER_HEADER_SUBJECT)->input_widget;
2118 	text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2119 	if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2120 		gtk_widget_grab_focus (input_widget);
2121 		return;
2122 	}
2123 
2124 	/* Jump to the editor as a last resort. */
2125 	gtkhtml_editor_run_command (GTKHTML_EDITOR (widget), "grab-focus");
2126 }
2127 
2128 static gboolean
2129 msg_composer_key_press_event (GtkWidget *widget,
2130                               GdkEventKey *event)
2131 {
2132 	EMsgComposer *composer = E_MSG_COMPOSER (widget);
2133 	GtkWidget *input_widget;
2134 	GtkhtmlEditor *editor;
2135 	EWebViewGtkHTML *web_view;
2136 
2137 	editor = GTKHTML_EDITOR (widget);
2138 	composer = E_MSG_COMPOSER (widget);
2139 	web_view = e_msg_composer_get_web_view (composer);
2140 
2141 	input_widget =
2142 		e_composer_header_table_get_header (
2143 		e_msg_composer_get_header_table (composer),
2144 		E_COMPOSER_HEADER_SUBJECT)->input_widget;
2145 
2146 #ifdef HAVE_XFREE
2147 	if (event->keyval == XF86XK_Send) {
2148 		e_msg_composer_send (composer);
2149 		return TRUE;
2150 	}
2151 #endif /* HAVE_XFREE */
2152 
2153 	if (event->keyval == GDK_KEY_Escape) {
2154 		gtk_action_activate (ACTION (CLOSE));
2155 		return TRUE;
2156 	}
2157 
2158 	if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
2159 		gtkhtml_editor_run_command (editor, "grab-focus");
2160 		return TRUE;
2161 	}
2162 
2163 	if (event->keyval == GDK_KEY_ISO_Left_Tab &&
2164 		gtk_widget_is_focus (GTK_WIDGET (web_view))) {
2165 		gtk_widget_grab_focus (input_widget);
2166 		return TRUE;
2167 	}
2168 
2169 	/* Chain up to parent's key_press_event() method. */
2170 	return GTK_WIDGET_CLASS (e_msg_composer_parent_class)->
2171 		key_press_event (widget, event);
2172 }
2173 
2174 static void
2175 msg_composer_cut_clipboard (GtkhtmlEditor *editor)
2176 {
2177 	/* Do nothing.  EFocusTracker handles this. */
2178 }
2179 
2180 static void
2181 msg_composer_copy_clipboard (GtkhtmlEditor *editor)
2182 {
2183 	/* Do nothing.  EFocusTracker handles this. */
2184 }
2185 
2186 static void
2187 msg_composer_paste_clipboard (GtkhtmlEditor *editor)
2188 {
2189 	/* Do nothing.  EFocusTracker handles this. */
2190 }
2191 
2192 static void
2193 msg_composer_select_all (GtkhtmlEditor *editor)
2194 {
2195 	/* Do nothing.  EFocusTracker handles this. */
2196 }
2197 
2198 static void
2199 msg_composer_command_before (GtkhtmlEditor *editor,
2200                              const gchar *command)
2201 {
2202 	EMsgComposer *composer;
2203 	const gchar *data;
2204 
2205 	composer = E_MSG_COMPOSER (editor);
2206 
2207 	if (strcmp (command, "insert-paragraph") != 0)
2208 		return;
2209 
2210 	if (composer->priv->in_signature_insert)
2211 		return;
2212 
2213 	data = gtkhtml_editor_get_paragraph_data (editor, "orig");
2214 	if (data != NULL && *data == '1') {
2215 		gtkhtml_editor_run_command (editor, "text-default-color");
2216 		gtkhtml_editor_run_command (editor, "italic-off");
2217 		return;
2218 	};
2219 
2220 	data = gtkhtml_editor_get_paragraph_data (editor, "signature");
2221 	if (data != NULL && *data == '1') {
2222 		gtkhtml_editor_run_command (editor, "text-default-color");
2223 		gtkhtml_editor_run_command (editor, "italic-off");
2224 	}
2225 }
2226 
2227 static void
2228 msg_composer_command_after (GtkhtmlEditor *editor,
2229                             const gchar *command)
2230 {
2231 	EMsgComposer *composer;
2232 	const gchar *data;
2233 
2234 	composer = E_MSG_COMPOSER (editor);
2235 
2236 	if (strcmp (command, "insert-paragraph") != 0)
2237 		return;
2238 
2239 	if (composer->priv->in_signature_insert)
2240 		return;
2241 
2242 	gtkhtml_editor_run_command (editor, "italic-off");
2243 
2244 	data = gtkhtml_editor_get_paragraph_data (editor, "orig");
2245 	if (data != NULL && *data == '1')
2246 		e_msg_composer_reply_indent (composer);
2247 	gtkhtml_editor_set_paragraph_data (editor, "orig", "0");
2248 
2249 	data = gtkhtml_editor_get_paragraph_data (editor, "signature");
2250 	if (data == NULL || *data != '1')
2251 		return;
2252 
2253 	/* Clear the signature. */
2254 	if (gtkhtml_editor_is_paragraph_empty (editor))
2255 		gtkhtml_editor_set_paragraph_data (editor, "signature" ,"0");
2256 
2257 	else if (gtkhtml_editor_is_previous_paragraph_empty (editor) &&
2258 		gtkhtml_editor_run_command (editor, "cursor-backward")) {
2259 
2260 		gtkhtml_editor_set_paragraph_data (editor, "signature", "0");
2261 		gtkhtml_editor_run_command (editor, "cursor-forward");
2262 	}
2263 
2264 	gtkhtml_editor_run_command (editor, "text-default-color");
2265 	gtkhtml_editor_run_command (editor, "italic-off");
2266 }
2267 
2268 static gchar *
2269 msg_composer_image_uri (GtkhtmlEditor *editor,
2270                         const gchar *uri)
2271 {
2272 	EMsgComposer *composer;
2273 	GHashTable *hash_table;
2274 	CamelMimePart *part;
2275 	const gchar *cid;
2276 
2277 	composer = E_MSG_COMPOSER (editor);
2278 
2279 	hash_table = composer->priv->inline_images_by_url;
2280 	part = g_hash_table_lookup (hash_table, uri);
2281 
2282 	if (part == NULL && g_str_has_prefix (uri, "file:"))
2283 		part = e_msg_composer_add_inline_image_from_file (
2284 			composer, uri + 5);
2285 
2286 	if (part == NULL && g_str_has_prefix (uri, "cid:")) {
2287 		hash_table = composer->priv->inline_images;
2288 		part = g_hash_table_lookup (hash_table, uri);
2289 	}
2290 
2291 	if (part == NULL)
2292 		return NULL;
2293 
2294 	composer->priv->current_images =
2295 		g_list_prepend (composer->priv->current_images, part);
2296 
2297 	cid = camel_mime_part_get_content_id (part);
2298 	if (cid == NULL)
2299 		return NULL;
2300 
2301 	return g_strconcat ("cid:", cid, NULL);
2302 }
2303 
2304 static void
2305 msg_composer_object_deleted (GtkhtmlEditor *editor)
2306 {
2307 	const gchar *data;
2308 
2309 	if (!gtkhtml_editor_is_paragraph_empty (editor))
2310 		return;
2311 
2312 	data = gtkhtml_editor_get_paragraph_data (editor, "orig");
2313 	if (data != NULL && *data == '1') {
2314 		gtkhtml_editor_set_paragraph_data (editor, "orig", "0");
2315 		gtkhtml_editor_run_command (editor, "indent-zero");
2316 		gtkhtml_editor_run_command (editor, "style-normal");
2317 		gtkhtml_editor_run_command (editor, "text-default-color");
2318 		gtkhtml_editor_run_command (editor, "italic-off");
2319 		gtkhtml_editor_run_command (editor, "insert-paragraph");
2320 		gtkhtml_editor_run_command (editor, "delete-back");
2321 	}
2322 
2323 	data = gtkhtml_editor_get_paragraph_data (editor, "signature");
2324 	if (data != NULL && *data == '1')
2325 		gtkhtml_editor_set_paragraph_data (editor, "signature", "0");
2326 }
2327 
2328 static gboolean
2329 msg_composer_presend (EMsgComposer *composer)
2330 {
2331 	/* This keeps the signal accumulator at TRUE. */
2332 	return TRUE;
2333 }
2334 
2335 static void
2336 msg_composer_submit_alert (EAlertSink *alert_sink,
2337                            EAlert *alert)
2338 {
2339 	EMsgComposerPrivate *priv;
2340 	EAlertBar *alert_bar;
2341 	GtkWidget *dialog;
2342 	GtkWindow *parent;
2343 
2344 	priv = E_MSG_COMPOSER_GET_PRIVATE (alert_sink);
2345 
2346 	switch (e_alert_get_message_type (alert)) {
2347 		case GTK_MESSAGE_INFO:
2348 		case GTK_MESSAGE_WARNING:
2349 		case GTK_MESSAGE_ERROR:
2350 			alert_bar = E_ALERT_BAR (priv->alert_bar);
2351 			e_alert_bar_add_alert (alert_bar, alert);
2352 			break;
2353 
2354 		default:
2355 			parent = GTK_WINDOW (alert_sink);
2356 			dialog = e_alert_dialog_new (parent, alert);
2357 			gtk_dialog_run (GTK_DIALOG (dialog));
2358 			gtk_widget_destroy (dialog);
2359 			break;
2360 	}
2361 }
2362 
2363 static gboolean
2364 msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint,
2365                                       GValue *return_accu,
2366                                       const GValue *handler_return,
2367                                       gpointer dummy)
2368 {
2369 	gboolean v_boolean;
2370 
2371 	v_boolean = g_value_get_boolean (handler_return);
2372 	g_value_set_boolean (return_accu, v_boolean);
2373 
2374 	/* FALSE means abort the signal emission. */
2375 	return v_boolean;
2376 }
2377 
2378 static void
2379 e_msg_composer_class_init (EMsgComposerClass *class)
2380 {
2381 	GObjectClass *object_class;
2382 	GtkWidgetClass *widget_class;
2383 	GtkhtmlEditorClass *editor_class;
2384 
2385 	g_type_class_add_private (class, sizeof (EMsgComposerPrivate));
2386 
2387 	object_class = G_OBJECT_CLASS (class);
2388 	object_class->set_property = msg_composer_set_property;
2389 	object_class->get_property = msg_composer_get_property;
2390 	object_class->dispose = msg_composer_dispose;
2391 	object_class->finalize = msg_composer_finalize;
2392 	object_class->constructed = msg_composer_constructed;
2393 
2394 	widget_class = GTK_WIDGET_CLASS (class);
2395 	widget_class->map = msg_composer_map;
2396 	widget_class->key_press_event = msg_composer_key_press_event;
2397 
2398 	editor_class = GTKHTML_EDITOR_CLASS (class);
2399 	editor_class->cut_clipboard = msg_composer_cut_clipboard;
2400 	editor_class->copy_clipboard = msg_composer_copy_clipboard;
2401 	editor_class->paste_clipboard = msg_composer_paste_clipboard;
2402 	editor_class->select_all = msg_composer_select_all;
2403 	editor_class->command_before = msg_composer_command_before;
2404 	editor_class->command_after = msg_composer_command_after;
2405 	editor_class->image_uri = msg_composer_image_uri;
2406 	editor_class->link_clicked = NULL; /* EWebView handles this */
2407 	editor_class->object_deleted = msg_composer_object_deleted;
2408 
2409 	class->presend = msg_composer_presend;
2410 
2411 	g_object_class_install_property (
2412 		object_class,
2413 		PROP_FOCUS_TRACKER,
2414 		g_param_spec_object (
2415 			"focus-tracker",
2416 			NULL,
2417 			NULL,
2418 			E_TYPE_FOCUS_TRACKER,
2419 			G_PARAM_READABLE));
2420 
2421 	g_object_class_install_property (
2422 		object_class,
2423 		PROP_SHELL,
2424 		g_param_spec_object (
2425 			"shell",
2426 			"Shell",
2427 			"The EShell singleton",
2428 			E_TYPE_SHELL,
2429 			G_PARAM_READWRITE |
2430 			G_PARAM_CONSTRUCT_ONLY));
2431 
2432 	signals[PRESEND] = g_signal_new (
2433 		"presend",
2434 		G_OBJECT_CLASS_TYPE (class),
2435 		G_SIGNAL_RUN_LAST,
2436 		G_STRUCT_OFFSET (EMsgComposerClass, presend),
2437 		msg_composer_accumulator_false_abort,
2438 		NULL,
2439 		e_marshal_BOOLEAN__VOID,
2440 		G_TYPE_BOOLEAN, 0);
2441 
2442 	signals[SEND] = g_signal_new (
2443 		"send",
2444 		G_OBJECT_CLASS_TYPE (class),
2445 		G_SIGNAL_RUN_LAST,
2446 		G_STRUCT_OFFSET (EMsgComposerClass, send),
2447 		NULL, NULL,
2448 		e_marshal_VOID__OBJECT_OBJECT,
2449 		G_TYPE_NONE, 2,
2450 		CAMEL_TYPE_MIME_MESSAGE,
2451 		E_TYPE_ACTIVITY);
2452 
2453 	signals[SAVE_TO_DRAFTS] = g_signal_new (
2454 		"save-to-drafts",
2455 		G_OBJECT_CLASS_TYPE (class),
2456 		G_SIGNAL_RUN_LAST,
2457 		G_STRUCT_OFFSET (EMsgComposerClass, save_to_drafts),
2458 		NULL, NULL,
2459 		e_marshal_VOID__OBJECT_OBJECT,
2460 		G_TYPE_NONE, 2,
2461 		CAMEL_TYPE_MIME_MESSAGE,
2462 		E_TYPE_ACTIVITY);
2463 
2464 	signals[SAVE_TO_OUTBOX] = g_signal_new (
2465 		"save-to-outbox",
2466 		G_OBJECT_CLASS_TYPE (class),
2467 		G_SIGNAL_RUN_LAST,
2468 		G_STRUCT_OFFSET (EMsgComposerClass, save_to_outbox),
2469 		NULL, NULL,
2470 		e_marshal_VOID__OBJECT_OBJECT,
2471 		G_TYPE_NONE, 2,
2472 		CAMEL_TYPE_MIME_MESSAGE,
2473 		E_TYPE_ACTIVITY);
2474 
2475 	signals[PRINT] = g_signal_new (
2476 		"print",
2477 		G_OBJECT_CLASS_TYPE (class),
2478 		G_SIGNAL_RUN_LAST,
2479 		0, NULL, NULL,
2480 		e_marshal_VOID__ENUM_OBJECT_OBJECT,
2481 		G_TYPE_NONE, 3,
2482 		GTK_TYPE_PRINT_OPERATION_ACTION,
2483 		CAMEL_TYPE_MIME_MESSAGE,
2484 		E_TYPE_ACTIVITY);
2485 }
2486 
2487 static void
2488 e_msg_composer_alert_sink_init (EAlertSinkInterface *interface)
2489 {
2490 	interface->submit_alert = msg_composer_submit_alert;
2491 }
2492 
2493 static void
2494 e_msg_composer_init (EMsgComposer *composer)
2495 {
2496 	composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2497 }
2498 
2499 /**
2500  * e_msg_composer_new:
2501  * @shell: an #EShell
2502  *
2503  * Create a new message composer widget.
2504  *
2505  * Returns: A pointer to the newly created widget
2506  **/
2507 EMsgComposer *
2508 e_msg_composer_new (EShell *shell)
2509 {
2510 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
2511 
2512 	return g_object_new (
2513 		E_TYPE_MSG_COMPOSER,
2514 		"html", e_web_view_gtkhtml_new (), "shell", shell, NULL);
2515 }
2516 
2517 EFocusTracker *
2518 e_msg_composer_get_focus_tracker (EMsgComposer *composer)
2519 {
2520 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
2521 
2522 	return composer->priv->focus_tracker;
2523 }
2524 
2525 static void
2526 e_msg_composer_set_pending_body (EMsgComposer *composer,
2527                                  gchar *text,
2528                                  gssize length)
2529 {
2530 	g_object_set_data_full (
2531 		G_OBJECT (composer), "body:text",
2532 		text, (GDestroyNotify) g_free);
2533 }
2534 
2535 static void
2536 e_msg_composer_flush_pending_body (EMsgComposer *composer)
2537 {
2538 	const gchar *body;
2539 
2540 	body = g_object_get_data (G_OBJECT (composer), "body:text");
2541 
2542 	if (body != NULL)
2543 		set_editor_text (composer, body, FALSE);
2544 
2545 	g_object_set_data (G_OBJECT (composer), "body:text", NULL);
2546 }
2547 
2548 static void
2549 add_attachments_handle_mime_part (EMsgComposer *composer,
2550                                   CamelMimePart *mime_part,
2551                                   gboolean just_inlines,
2552                                   gboolean related,
2553                                   gint depth)
2554 {
2555 	CamelContentType *content_type;
2556 	CamelDataWrapper *wrapper;
2557 
2558 	if (!mime_part)
2559 		return;
2560 
2561 	content_type = camel_mime_part_get_content_type (mime_part);
2562 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2563 
2564 	if (CAMEL_IS_MULTIPART (wrapper)) {
2565 		/* another layer of multipartness... */
2566 		add_attachments_from_multipart (
2567 			composer, (CamelMultipart *) wrapper,
2568 			just_inlines, depth + 1);
2569 	} else if (just_inlines) {
2570 		if (camel_mime_part_get_content_id (mime_part) ||
2571 		    camel_mime_part_get_content_location (mime_part))
2572 			e_msg_composer_add_inline_image_from_mime_part (
2573 				composer, mime_part);
2574 	} else if (related && camel_content_type_is (content_type, "image", "*")) {
2575 		e_msg_composer_add_inline_image_from_mime_part (composer, mime_part);
2576 	} else if (camel_content_type_is (content_type, "text", "*") &&
2577 		camel_mime_part_get_filename (mime_part) == NULL) {
2578 		/* Do nothing if this is a text/anything without a
2579 		 * filename, otherwise attach it too. */
2580 	} else {
2581 		e_msg_composer_attach (composer, mime_part);
2582 	}
2583 }
2584 
2585 static void
2586 add_attachments_from_multipart (EMsgComposer *composer,
2587                                 CamelMultipart *multipart,
2588                                 gboolean just_inlines,
2589                                 gint depth)
2590 {
2591 	/* find appropriate message attachments to add to the composer */
2592 	CamelMimePart *mime_part;
2593 	gboolean related;
2594 	gint i, nparts;
2595 
2596 	related = camel_content_type_is (
2597 		CAMEL_DATA_WRAPPER (multipart)->mime_type,
2598 		"multipart", "related");
2599 
2600 	if (CAMEL_IS_MULTIPART_SIGNED (multipart)) {
2601 		mime_part = camel_multipart_get_part (
2602 			multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
2603 		add_attachments_handle_mime_part (
2604 			composer, mime_part, just_inlines, related, depth);
2605 	} else if (CAMEL_IS_MULTIPART_ENCRYPTED (multipart)) {
2606 		/* XXX What should we do in this case? */
2607 	} else {
2608 		nparts = camel_multipart_get_number (multipart);
2609 
2610 		for (i = 0; i < nparts; i++) {
2611 			mime_part = camel_multipart_get_part (multipart, i);
2612 			add_attachments_handle_mime_part (
2613 				composer, mime_part, just_inlines,
2614 				related, depth);
2615 		}
2616 	}
2617 }
2618 
2619 /**
2620  * e_msg_composer_add_message_attachments:
2621  * @composer: the composer to add the attachments to.
2622  * @message: the source message to copy the attachments from.
2623  * @just_inlines: whether to attach all attachments or just add
2624  * inline images.
2625  *
2626  * Walk through all the mime parts in @message and add them to the composer
2627  * specified in @composer.
2628  */
2629 void
2630 e_msg_composer_add_message_attachments (EMsgComposer *composer,
2631                                         CamelMimeMessage *message,
2632                                         gboolean just_inlines)
2633 {
2634 	CamelDataWrapper *wrapper;
2635 
2636 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (message));
2637 	if (!CAMEL_IS_MULTIPART (wrapper))
2638 		return;
2639 
2640 	add_attachments_from_multipart (
2641 		composer, (CamelMultipart *) wrapper, just_inlines, 0);
2642 }
2643 
2644 static void
2645 handle_multipart_signed (EMsgComposer *composer,
2646                          CamelMultipart *multipart,
2647                          GCancellable *cancellable,
2648                          gint depth)
2649 {
2650 	CamelContentType *content_type;
2651 	CamelDataWrapper *content;
2652 	CamelMimePart *mime_part;
2653 	CamelSession *session;
2654 	GtkToggleAction *action = NULL;
2655 	const gchar *protocol;
2656 
2657 	session = e_msg_composer_get_session (composer);
2658 
2659 	content = CAMEL_DATA_WRAPPER (multipart);
2660 	content_type = camel_data_wrapper_get_mime_type_field (content);
2661 	protocol = camel_content_type_param (content_type, "protocol");
2662 
2663 	if (protocol == NULL)
2664 		action = NULL;
2665 	else if (g_ascii_strcasecmp (protocol, "application/pgp-signature") == 0)
2666 		action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
2667 	else if (g_ascii_strcasecmp (protocol, "application/x-pkcs7-signature") == 0)
2668 		action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
2669 
2670 	if (action)
2671 		gtk_toggle_action_set_active (action, TRUE);
2672 
2673 	mime_part = camel_multipart_get_part (
2674 		multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
2675 
2676 	if (mime_part == NULL)
2677 		return;
2678 
2679 	content_type = camel_mime_part_get_content_type (mime_part);
2680 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2681 
2682 	if (CAMEL_IS_MULTIPART (content)) {
2683 		multipart = CAMEL_MULTIPART (content);
2684 
2685 		/* Note: depth is preserved here because we're not
2686 		 * counting multipart/signed as a multipart, instead
2687 		 * we want to treat the content part as our mime part
2688 		 * here. */
2689 
2690 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
2691 			/* Handle the signed content and configure
2692 			 * the composer to sign outgoing messages. */
2693 			handle_multipart_signed (
2694 				composer, multipart, cancellable, depth);
2695 
2696 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
2697 			/* Decrypt the encrypted content and configure
2698 			 * the composer to encrypt outgoing messages. */
2699 			handle_multipart_encrypted (
2700 				composer, mime_part, cancellable, depth);
2701 
2702 		} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
2703 			/* This contains the text/plain and text/html
2704 			 * versions of the message body. */
2705 			handle_multipart_alternative (
2706 				composer, multipart, cancellable, depth);
2707 
2708 		} else {
2709 			/* There must be attachments... */
2710 			handle_multipart (
2711 				composer, multipart, cancellable, depth);
2712 		}
2713 
2714 	} else if (camel_content_type_is (content_type, "text", "*")) {
2715 		gchar *html;
2716 		gssize length;
2717 
2718 		html = emcu_part_to_html (
2719 			session, mime_part, &length, cancellable);
2720 		e_msg_composer_set_pending_body (composer, html, length);
2721 	} else {
2722 		e_msg_composer_attach (composer, mime_part);
2723 	}
2724 }
2725 
2726 static void
2727 handle_multipart_encrypted (EMsgComposer *composer,
2728                             CamelMimePart *multipart,
2729                             GCancellable *cancellable,
2730                             gint depth)
2731 {
2732 	CamelContentType *content_type;
2733 	CamelCipherContext *cipher;
2734 	CamelDataWrapper *content;
2735 	CamelMimePart *mime_part;
2736 	CamelSession *session;
2737 	CamelCipherValidity *valid;
2738 	GtkToggleAction *action = NULL;
2739 	const gchar *protocol;
2740 
2741 	content_type = camel_mime_part_get_content_type (multipart);
2742 	protocol = camel_content_type_param (content_type, "protocol");
2743 
2744 	if (protocol && g_ascii_strcasecmp (protocol, "application/pgp-encrypted") == 0)
2745 		action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
2746 	else if (content_type && (
2747 		    camel_content_type_is (content_type, "application", "x-pkcs7-mime")
2748 		 || camel_content_type_is (content_type, "application", "pkcs7-mime")))
2749 		action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
2750 
2751 	if (action)
2752 		gtk_toggle_action_set_active (action, TRUE);
2753 
2754 	session = e_msg_composer_get_session (composer);
2755 	cipher = camel_gpg_context_new (session);
2756 	mime_part = camel_mime_part_new ();
2757 	valid = camel_cipher_context_decrypt_sync (
2758 		cipher, multipart, mime_part, cancellable, NULL);
2759 	g_object_unref (cipher);
2760 
2761 	if (valid == NULL)
2762 		return;
2763 
2764 	camel_cipher_validity_free (valid);
2765 
2766 	content_type = camel_mime_part_get_content_type (mime_part);
2767 
2768 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2769 
2770 	if (CAMEL_IS_MULTIPART (content)) {
2771 		CamelMultipart *content_multipart = CAMEL_MULTIPART (content);
2772 
2773 		/* Note: depth is preserved here because we're not
2774 		 * counting multipart/encrypted as a multipart, instead
2775 		 * we want to treat the content part as our mime part
2776 		 * here. */
2777 
2778 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
2779 			/* Handle the signed content and configure the
2780 			 * composer to sign outgoing messages. */
2781 			handle_multipart_signed (
2782 				composer, content_multipart, cancellable, depth);
2783 
2784 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
2785 			/* Decrypt the encrypted content and configure the
2786 			 * composer to encrypt outgoing messages. */
2787 			handle_multipart_encrypted (
2788 				composer, mime_part, cancellable, depth);
2789 
2790 		} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
2791 			/* This contains the text/plain and text/html
2792 			 * versions of the message body. */
2793 			handle_multipart_alternative (
2794 				composer, content_multipart, cancellable, depth);
2795 
2796 		} else {
2797 			/* There must be attachments... */
2798 			handle_multipart (
2799 				composer, content_multipart, cancellable, depth);
2800 		}
2801 
2802 	} else if (camel_content_type_is (content_type, "text", "*")) {
2803 		gchar *html;
2804 		gssize length;
2805 
2806 		html = emcu_part_to_html (
2807 			session, mime_part, &length, cancellable);
2808 		e_msg_composer_set_pending_body (composer, html, length);
2809 	} else {
2810 		e_msg_composer_attach (composer, mime_part);
2811 	}
2812 
2813 	g_object_unref (mime_part);
2814 }
2815 
2816 static void
2817 handle_multipart_alternative (EMsgComposer *composer,
2818                               CamelMultipart *multipart,
2819                               GCancellable *cancellable,
2820                               gint depth)
2821 {
2822 	/* Find the text/html part and set the composer body to it's contents */
2823 	CamelMimePart *text_part = NULL;
2824 	CamelSession *session;
2825 	gint i, nparts;
2826 
2827 	session = e_msg_composer_get_session (composer);
2828 
2829 	nparts = camel_multipart_get_number (multipart);
2830 
2831 	for (i = 0; i < nparts; i++) {
2832 		CamelContentType *content_type;
2833 		CamelDataWrapper *content;
2834 		CamelMimePart *mime_part;
2835 
2836 		mime_part = camel_multipart_get_part (multipart, i);
2837 
2838 		if (!mime_part)
2839 			continue;
2840 
2841 		content_type = camel_mime_part_get_content_type (mime_part);
2842 		content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2843 
2844 		if (CAMEL_IS_MULTIPART (content)) {
2845 			CamelMultipart *mp;
2846 
2847 			mp = CAMEL_MULTIPART (content);
2848 
2849 			if (CAMEL_IS_MULTIPART_SIGNED (content)) {
2850 				/* Handle the signed content and configure
2851 				 * the composer to sign outgoing messages. */
2852 				handle_multipart_signed (
2853 					composer, mp, cancellable, depth + 1);
2854 
2855 			} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
2856 				/* Decrypt the encrypted content and configure
2857 				 * the composer to encrypt outgoing messages. */
2858 				handle_multipart_encrypted (
2859 					composer, mime_part,
2860 					cancellable, depth + 1);
2861 
2862 			} else {
2863 				/* Depth doesn't matter so long as we
2864 				 * don't pass 0. */
2865 				handle_multipart (
2866 					composer, mp, cancellable, depth + 1);
2867 			}
2868 
2869 		} else if (camel_content_type_is (content_type, "text", "html")) {
2870 			/* text/html is preferable, so once we find it we're done... */
2871 			text_part = mime_part;
2872 			break;
2873 		} else if (camel_content_type_is (content_type, "text", "*")) {
2874 			/* anyt text part not text/html is second rate so the first
2875 			 * text part we find isn't necessarily the one we'll use. */
2876 			if (!text_part)
2877 				text_part = mime_part;
2878 		} else {
2879 			e_msg_composer_attach (composer, mime_part);
2880 		}
2881 	}
2882 
2883 	if (text_part) {
2884 		gchar *html;
2885 		gssize length;
2886 
2887 		html = emcu_part_to_html (
2888 			session, text_part, &length, cancellable);
2889 		e_msg_composer_set_pending_body (composer, html, length);
2890 	}
2891 }
2892 
2893 static void
2894 handle_multipart (EMsgComposer *composer,
2895                   CamelMultipart *multipart,
2896                   GCancellable *cancellable,
2897                   gint depth)
2898 {
2899 	CamelSession *session;
2900 	gint i, nparts;
2901 
2902 	session = e_msg_composer_get_session (composer);
2903 
2904 	nparts = camel_multipart_get_number (multipart);
2905 
2906 	for (i = 0; i < nparts; i++) {
2907 		CamelContentType *content_type;
2908 		CamelDataWrapper *content;
2909 		CamelMimePart *mime_part;
2910 
2911 		mime_part = camel_multipart_get_part (multipart, i);
2912 
2913 		if (!mime_part)
2914 			continue;
2915 
2916 		content_type = camel_mime_part_get_content_type (mime_part);
2917 		content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2918 
2919 		if (CAMEL_IS_MULTIPART (content)) {
2920 			CamelMultipart *mp;
2921 
2922 			mp = CAMEL_MULTIPART (content);
2923 
2924 			if (CAMEL_IS_MULTIPART_SIGNED (content)) {
2925 				/* Handle the signed content and configure
2926 				 * the composer to sign outgoing messages. */
2927 				handle_multipart_signed (
2928 					composer, mp, cancellable, depth + 1);
2929 
2930 			} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
2931 				/* Decrypt the encrypted content and configure
2932 				 * the composer to encrypt outgoing messages. */
2933 				handle_multipart_encrypted (
2934 					composer, mime_part,
2935 					cancellable, depth + 1);
2936 
2937 			} else if (camel_content_type_is (
2938 				content_type, "multipart", "alternative")) {
2939 				handle_multipart_alternative (
2940 					composer, mp, cancellable, depth + 1);
2941 
2942 			} else {
2943 				/* Depth doesn't matter so long as we
2944 				 * don't pass 0. */
2945 				handle_multipart (
2946 					composer, mp, cancellable, depth + 1);
2947 			}
2948 
2949 		} else if (depth == 0 && i == 0) {
2950 			gchar *html;
2951 			gssize length;
2952 
2953 			/* Since the first part is not multipart/alternative,
2954 			 * this must be the body. */
2955 			html = emcu_part_to_html (
2956 				session, mime_part, &length, cancellable);
2957 			e_msg_composer_set_pending_body (composer, html, length);
2958 		} else if (camel_mime_part_get_content_id (mime_part) ||
2959 			   camel_mime_part_get_content_location (mime_part)) {
2960 			/* special in-line attachment */
2961 			e_msg_composer_add_inline_image_from_mime_part (
2962 				composer, mime_part);
2963 		} else {
2964 			/* normal attachment */
2965 			e_msg_composer_attach (composer, mime_part);
2966 		}
2967 	}
2968 }
2969 
2970 static void
2971 set_signature_gui (EMsgComposer *composer)
2972 {
2973 	GtkhtmlEditor *editor;
2974 	EComposerHeaderTable *table;
2975 	EMailSignatureComboBox *combo_box;
2976 	const gchar *data;
2977 	gchar *uid;
2978 
2979 	editor = GTKHTML_EDITOR (composer);
2980 	table = e_msg_composer_get_header_table (composer);
2981 	combo_box = e_composer_header_table_get_signature_combo_box (table);
2982 
2983 	if (!gtkhtml_editor_search_by_data (editor, 1, "ClueFlow", "signature", "1"))
2984 		return;
2985 
2986 	data = gtkhtml_editor_get_paragraph_data (editor, "signature_name");
2987 
2988 	if (!g_str_has_prefix (data, "uid:"))
2989 		return;
2990 
2991 	/* The combo box active ID is the signature's ESource UID. */
2992 	uid = e_composer_decode_clue_value (data + 4);
2993 	gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
2994 	g_free (uid);
2995 }
2996 
2997 static void
2998 composer_add_auto_recipients (ESource *source,
2999                               const gchar *property_name,
3000                               GHashTable *hash_table)
3001 {
3002 	ESourceMailComposition *extension;
3003 	CamelInternetAddress *inet_addr;
3004 	const gchar *extension_name;
3005 	gchar *comma_separated_addrs;
3006 	gchar **addr_array = NULL;
3007 	gint length, ii;
3008 	gint retval;
3009 
3010 	extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
3011 	extension = e_source_get_extension (source, extension_name);
3012 
3013 	g_object_get (extension, property_name, &addr_array, NULL);
3014 
3015 	if (addr_array == NULL)
3016 		return;
3017 
3018 	inet_addr = camel_internet_address_new ();
3019 	comma_separated_addrs = g_strjoinv (", ", addr_array);
3020 
3021 	retval = camel_address_decode (
3022 		CAMEL_ADDRESS (inet_addr), comma_separated_addrs);
3023 
3024 	g_free (comma_separated_addrs);
3025 	g_strfreev (addr_array);
3026 
3027 	if (retval == -1)
3028 		return;
3029 
3030 	length = camel_address_length (CAMEL_ADDRESS (inet_addr));
3031 
3032 	for (ii = 0; ii < length; ii++) {
3033 		const gchar *name;
3034 		const gchar *addr;
3035 
3036 		if (camel_internet_address_get (inet_addr, ii, &name, &addr))
3037 			g_hash_table_insert (
3038 				hash_table,
3039 				g_strdup (addr),
3040 				GINT_TO_POINTER (1));
3041 	}
3042 
3043 	g_object_unref (inet_addr);
3044 }
3045 
3046 /**
3047  * e_msg_composer_new_with_message:
3048  * @shell: an #EShell
3049  * @message: The message to use as the source
3050  *
3051  * Create a new message composer widget.
3052  *
3053  * Note: Designed to work only for messages constructed using Evolution.
3054  *
3055  * Returns: A pointer to the newly created widget
3056  **/
3057 EMsgComposer *
3058 e_msg_composer_new_with_message (EShell *shell,
3059                                  CamelMimeMessage *message,
3060                                  GCancellable *cancellable)
3061 {
3062 	CamelInternetAddress *to, *cc, *bcc;
3063 	GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL;
3064 	const gchar *format, *subject;
3065 	EDestination **Tov, **Ccv, **Bccv;
3066 	GHashTable *auto_cc, *auto_bcc;
3067 	CamelContentType *content_type;
3068 	struct _camel_header_raw *headers;
3069 	CamelDataWrapper *content;
3070 	CamelSession *session;
3071 	EMsgComposer *composer;
3072 	EMsgComposerPrivate *priv;
3073 	EComposerHeaderTable *table;
3074 	ESourceRegistry *registry;
3075 	ESource *source = NULL;
3076 	GtkToggleAction *action;
3077 	struct _camel_header_raw *xev;
3078 	gchar *identity_uid;
3079 	gint len, i;
3080 
3081 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
3082 
3083 	headers = CAMEL_MIME_PART (message)->headers;
3084 	while (headers != NULL) {
3085 		gchar *value;
3086 
3087 		if (strcmp (headers->name, "X-Evolution-PostTo") == 0) {
3088 			value = g_strstrip (g_strdup (headers->value));
3089 			postto = g_list_append (postto, value);
3090 		}
3091 
3092 		headers = headers->next;
3093 	}
3094 
3095 	composer = e_msg_composer_new (shell);
3096 	priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
3097 	session = e_msg_composer_get_session (composer);
3098 	table = e_msg_composer_get_header_table (composer);
3099 	registry = e_composer_header_table_get_registry (table);
3100 
3101 	if (postto) {
3102 		e_composer_header_table_set_post_to_list (table, postto);
3103 		g_list_foreach (postto, (GFunc) g_free, NULL);
3104 		g_list_free (postto);
3105 		postto = NULL;
3106 	}
3107 
3108 	/* Restore the mail identity preference. */
3109 	identity_uid = (gchar *) camel_medium_get_header (
3110 		CAMEL_MEDIUM (message), "X-Evolution-Identity");
3111 	if (!identity_uid) {
3112 		/* for backward compatibility */
3113 		identity_uid = (gchar *) camel_medium_get_header (
3114 			CAMEL_MEDIUM (message), "X-Evolution-Account");
3115 	}
3116 	if (identity_uid != NULL) {
3117 		identity_uid = g_strstrip (g_strdup (identity_uid));
3118 		source = e_source_registry_ref_source (registry, identity_uid);
3119 	}
3120 
3121 	if (postto == NULL) {
3122 		auto_cc = g_hash_table_new_full (
3123 			camel_strcase_hash, camel_strcase_equal,
3124 			(GDestroyNotify) g_free,
3125 			(GDestroyNotify) NULL);
3126 
3127 		auto_bcc = g_hash_table_new_full (
3128 			camel_strcase_hash, camel_strcase_equal,
3129 			(GDestroyNotify) g_free,
3130 			(GDestroyNotify) NULL);
3131 
3132 		if (source != NULL) {
3133 			composer_add_auto_recipients (source, "cc", auto_cc);
3134 			composer_add_auto_recipients (source, "bcc", auto_bcc);
3135 		}
3136 
3137 		to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
3138 		cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
3139 		bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
3140 
3141 		len = CAMEL_ADDRESS (to)->addresses->len;
3142 		for (i = 0; i < len; i++) {
3143 			const gchar *name, *addr;
3144 
3145 			if (camel_internet_address_get (to, i, &name, &addr)) {
3146 				EDestination *dest = e_destination_new ();
3147 				e_destination_set_name (dest, name);
3148 				e_destination_set_email (dest, addr);
3149 				To = g_list_append (To, dest);
3150 			}
3151 		}
3152 
3153 		Tov = destination_list_to_vector (To);
3154 		g_list_free (To);
3155 
3156 		len = CAMEL_ADDRESS (cc)->addresses->len;
3157 		for (i = 0; i < len; i++) {
3158 			const gchar *name, *addr;
3159 
3160 			if (camel_internet_address_get (cc, i, &name, &addr)) {
3161 				EDestination *dest = e_destination_new ();
3162 				e_destination_set_name (dest, name);
3163 				e_destination_set_email (dest, addr);
3164 
3165 				if (g_hash_table_lookup (auto_cc, addr))
3166 					e_destination_set_auto_recipient (dest, TRUE);
3167 
3168 				Cc = g_list_append (Cc, dest);
3169 			}
3170 		}
3171 
3172 		Ccv = destination_list_to_vector (Cc);
3173 		g_hash_table_destroy (auto_cc);
3174 		g_list_free (Cc);
3175 
3176 		len = CAMEL_ADDRESS (bcc)->addresses->len;
3177 		for (i = 0; i < len; i++) {
3178 			const gchar *name, *addr;
3179 
3180 			if (camel_internet_address_get (bcc, i, &name, &addr)) {
3181 				EDestination *dest = e_destination_new ();
3182 				e_destination_set_name (dest, name);
3183 				e_destination_set_email (dest, addr);
3184 
3185 				if (g_hash_table_lookup (auto_bcc, addr))
3186 					e_destination_set_auto_recipient (dest, TRUE);
3187 
3188 				Bcc = g_list_append (Bcc, dest);
3189 			}
3190 		}
3191 
3192 		Bccv = destination_list_to_vector (Bcc);
3193 		g_hash_table_destroy (auto_bcc);
3194 		g_list_free (Bcc);
3195 	} else {
3196 		Tov = NULL;
3197 		Ccv = NULL;
3198 		Bccv = NULL;
3199 	}
3200 
3201 	if (source != NULL)
3202 		g_object_unref (source);
3203 
3204 	subject = camel_mime_message_get_subject (message);
3205 
3206 	e_composer_header_table_set_identity_uid (table, identity_uid);
3207 	e_composer_header_table_set_destinations_to (table, Tov);
3208 	e_composer_header_table_set_destinations_cc (table, Ccv);
3209 	e_composer_header_table_set_destinations_bcc (table, Bccv);
3210 	e_composer_header_table_set_subject (table, subject);
3211 
3212 	g_free (identity_uid);
3213 
3214 	e_destination_freev (Tov);
3215 	e_destination_freev (Ccv);
3216 	e_destination_freev (Bccv);
3217 
3218 	/* Restore the format editing preference */
3219 	format = camel_medium_get_header (
3220 		CAMEL_MEDIUM (message), "X-Evolution-Format");
3221 	if (format != NULL) {
3222 		gchar **flags;
3223 
3224 		while (*format && camel_mime_is_lwsp (*format))
3225 			format++;
3226 
3227 		flags = g_strsplit (format, ", ", 0);
3228 		for (i = 0; flags[i]; i++) {
3229 			if (g_ascii_strcasecmp (flags[i], "text/html") == 0)
3230 				gtkhtml_editor_set_html_mode (
3231 					GTKHTML_EDITOR (composer), TRUE);
3232 			else if (g_ascii_strcasecmp (flags[i], "text/plain") == 0)
3233 				gtkhtml_editor_set_html_mode (
3234 					GTKHTML_EDITOR (composer), FALSE);
3235 			else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) {
3236 				action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3237 				gtk_toggle_action_set_active (action, TRUE);
3238 			} else if (g_ascii_strcasecmp (flags[i], "pgp-encrypt") == 0) {
3239 				action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3240 				gtk_toggle_action_set_active (action, TRUE);
3241 			} else if (g_ascii_strcasecmp (flags[i], "smime-sign") == 0) {
3242 				action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3243 				gtk_toggle_action_set_active (action, TRUE);
3244 			} else if (g_ascii_strcasecmp (flags[i], "smime-encrypt") == 0) {
3245 				action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3246 				gtk_toggle_action_set_active (action, TRUE);
3247 			}
3248 		}
3249 		g_strfreev (flags);
3250 	}
3251 
3252 	/* Remove any other X-Evolution-* headers that may have been set */
3253 	xev = emcu_remove_xevolution_headers (message);
3254 	camel_header_raw_clear (&xev);
3255 
3256 	/* Check for receipt request */
3257 	if (camel_medium_get_header (
3258 		CAMEL_MEDIUM (message), "Disposition-Notification-To")) {
3259 		action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
3260 		gtk_toggle_action_set_active (action, TRUE);
3261 	}
3262 
3263 	/* Check for mail priority */
3264 	if (camel_medium_get_header (CAMEL_MEDIUM (message), "X-Priority")) {
3265 		action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE));
3266 		gtk_toggle_action_set_active (action, TRUE);
3267 	}
3268 
3269 	/* set extra headers */
3270 	headers = CAMEL_MIME_PART (message)->headers;
3271 	while (headers) {
3272 		if (g_ascii_strcasecmp (headers->name, "References") == 0 ||
3273 		    g_ascii_strcasecmp (headers->name, "In-Reply-To") == 0) {
3274 			g_ptr_array_add (
3275 				composer->priv->extra_hdr_names,
3276 				g_strdup (headers->name));
3277 			g_ptr_array_add (
3278 				composer->priv->extra_hdr_values,
3279 				g_strdup (headers->value));
3280 		}
3281 
3282 		headers = headers->next;
3283 	}
3284 
3285 	/* Restore the attachments and body text */
3286 	content = camel_medium_get_content (CAMEL_MEDIUM (message));
3287 	if (CAMEL_IS_MULTIPART (content)) {
3288 		CamelMimePart *mime_part;
3289 		CamelMultipart *multipart;
3290 
3291 		multipart = CAMEL_MULTIPART (content);
3292 		mime_part = CAMEL_MIME_PART (message);
3293 		content_type = camel_mime_part_get_content_type (mime_part);
3294 
3295 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3296 			/* Handle the signed content and configure the
3297 			 * composer to sign outgoing messages. */
3298 			handle_multipart_signed (
3299 				composer, multipart, cancellable, 0);
3300 
3301 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3302 			/* Decrypt the encrypted content and configure the
3303 			 * composer to encrypt outgoing messages. */
3304 			handle_multipart_encrypted (
3305 				composer, mime_part, cancellable, 0);
3306 
3307 		} else if (camel_content_type_is (
3308 			content_type, "multipart", "alternative")) {
3309 			/* This contains the text/plain and text/html
3310 			 * versions of the message body. */
3311 			handle_multipart_alternative (
3312 				composer, multipart, cancellable, 0);
3313 
3314 		} else {
3315 			/* There must be attachments... */
3316 			handle_multipart (
3317 				composer, multipart, cancellable, 0);
3318 		}
3319 	} else {
3320 		CamelMimePart *mime_part;
3321 		gchar *html;
3322 		gssize length;
3323 
3324 		mime_part = CAMEL_MIME_PART (message);
3325 		content_type = camel_mime_part_get_content_type (mime_part);
3326 
3327 		if (content_type != NULL && (
3328 			camel_content_type_is (
3329 				content_type, "application", "x-pkcs7-mime") ||
3330 			camel_content_type_is (
3331 				content_type, "application", "pkcs7-mime")))
3332 			gtk_toggle_action_set_active (
3333 				GTK_TOGGLE_ACTION (
3334 				ACTION (SMIME_ENCRYPT)), TRUE);
3335 
3336 		html = emcu_part_to_html (
3337 			session, CAMEL_MIME_PART (message),
3338 			&length, cancellable);
3339 		e_msg_composer_set_pending_body (composer, html, length);
3340 	}
3341 
3342 	priv->is_from_message = TRUE;
3343 
3344 	/* We wait until now to set the body text because we need to
3345 	 * ensure that the attachment bar has all the attachments before
3346 	 * we request them. */
3347 	e_msg_composer_flush_pending_body (composer);
3348 
3349 	set_signature_gui (composer);
3350 
3351 	return composer;
3352 }
3353 
3354 /**
3355  * e_msg_composer_new_redirect:
3356  * @shell: an #EShell
3357  * @message: The message to use as the source
3358  *
3359  * Create a new message composer widget.
3360  *
3361  * Returns: A pointer to the newly created widget
3362  **/
3363 EMsgComposer *
3364 e_msg_composer_new_redirect (EShell *shell,
3365                              CamelMimeMessage *message,
3366                              const gchar *identity_uid,
3367                              GCancellable *cancellable)
3368 {
3369 	EMsgComposer *composer;
3370 	EComposerHeaderTable *table;
3371 	EWebViewGtkHTML *web_view;
3372 	const gchar *subject;
3373 
3374 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
3375 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
3376 
3377 	composer = e_msg_composer_new_with_message (
3378 		shell, message, cancellable);
3379 	table = e_msg_composer_get_header_table (composer);
3380 
3381 	subject = camel_mime_message_get_subject (message);
3382 
3383 	composer->priv->redirect = message;
3384 	g_object_ref (message);
3385 
3386 	e_composer_header_table_set_identity_uid (table, identity_uid);
3387 	e_composer_header_table_set_subject (table, subject);
3388 
3389 	web_view = e_msg_composer_get_web_view (composer);
3390 	e_web_view_gtkhtml_set_editable (web_view, FALSE);
3391 
3392 	return composer;
3393 }
3394 
3395 /**
3396  * e_msg_composer_get_session:
3397  * @composer: an #EMsgComposer
3398  *
3399  * Returns the mail module's global #CamelSession instance.  Calling
3400  * this function will load the mail module if it isn't already loaded.
3401  *
3402  * Returns: the mail module's #CamelSession
3403  **/
3404 CamelSession *
3405 e_msg_composer_get_session (EMsgComposer *composer)
3406 {
3407 	EShell *shell;
3408 	EShellSettings *shell_settings;
3409 	CamelSession *session;
3410 
3411 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3412 
3413 	shell = e_msg_composer_get_shell (composer);
3414 	shell_settings = e_shell_get_shell_settings (shell);
3415 
3416 	session = e_shell_settings_get_pointer (shell_settings, "mail-session");
3417 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
3418 
3419 	return session;
3420 }
3421 
3422 /**
3423  * e_msg_composer_get_shell:
3424  * @composer: an #EMsgComposer
3425  *
3426  * Returns the #EShell that was passed to e_msg_composer_new().
3427  *
3428  * Returns: the #EShell
3429  **/
3430 EShell *
3431 e_msg_composer_get_shell (EMsgComposer *composer)
3432 {
3433 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3434 
3435 	return E_SHELL (composer->priv->shell);
3436 }
3437 
3438 /**
3439  * e_msg_composer_get_web_view:
3440  * @composer: an #EMsgComposer
3441  *
3442  * Returns the #EWebView widget in @composer.
3443  *
3444  * Returns: the #EWebView
3445  **/
3446 EWebViewGtkHTML *
3447 e_msg_composer_get_web_view (EMsgComposer *composer)
3448 {
3449 	GtkHTML *html;
3450 	GtkhtmlEditor *editor;
3451 
3452 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3453 
3454 	/* This is a convenience function to avoid
3455 	 * repeating this awkwardness everywhere */
3456 	editor = GTKHTML_EDITOR (composer);
3457 	html = gtkhtml_editor_get_html (editor);
3458 
3459 	return E_WEB_VIEW_GTKHTML (html);
3460 }
3461 
3462 static void
3463 msg_composer_send_cb (EMsgComposer *composer,
3464                       GAsyncResult *result,
3465                       AsyncContext *context)
3466 {
3467 	CamelMimeMessage *message;
3468 	EAlertSink *alert_sink;
3469 	GtkhtmlEditor *editor;
3470 	GError *error = NULL;
3471 
3472 	alert_sink = e_activity_get_alert_sink (context->activity);
3473 
3474 	message = e_msg_composer_get_message_finish (composer, result, &error);
3475 
3476 	if (e_activity_handle_cancellation (context->activity, error)) {
3477 		g_warn_if_fail (message == NULL);
3478 		async_context_free (context);
3479 		g_error_free (error);
3480 
3481 		gtk_window_present (GTK_WINDOW (composer));
3482 		return;
3483 	}
3484 
3485 	if (error != NULL) {
3486 		g_warn_if_fail (message == NULL);
3487 		e_alert_submit (
3488 			alert_sink,
3489 			"mail-composer:no-build-message",
3490 			error->message, NULL);
3491 		async_context_free (context);
3492 		g_error_free (error);
3493 
3494 		gtk_window_present (GTK_WINDOW (composer));
3495 		return;
3496 	}
3497 
3498 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3499 
3500 	/* The callback can set editor 'changed' if anything failed. */
3501 	editor = GTKHTML_EDITOR (composer);
3502 	gtkhtml_editor_set_changed (editor, FALSE);
3503 
3504 	g_signal_emit (
3505 		composer, signals[SEND], 0,
3506 		message, context->activity);
3507 
3508 	g_object_unref (message);
3509 
3510 	async_context_free (context);
3511 }
3512 
3513 /**
3514  * e_msg_composer_send:
3515  * @composer: an #EMsgComposer
3516  *
3517  * Send the message in @composer.
3518  **/
3519 void
3520 e_msg_composer_send (EMsgComposer *composer)
3521 {
3522 	AsyncContext *context;
3523 	EAlertSink *alert_sink;
3524 	EActivityBar *activity_bar;
3525 	GCancellable *cancellable;
3526 	gboolean proceed_with_send = TRUE;
3527 
3528 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3529 
3530 	/* This gives the user a chance to abort the send. */
3531 	g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
3532 
3533 	if (!proceed_with_send) {
3534 		gtk_window_present (GTK_WINDOW (composer));
3535 		return;
3536 	}
3537 
3538 	context = g_slice_new0 (AsyncContext);
3539 	context->activity = e_composer_activity_new (composer);
3540 
3541 	alert_sink = E_ALERT_SINK (composer);
3542 	e_activity_set_alert_sink (context->activity, alert_sink);
3543 
3544 	cancellable = camel_operation_new ();
3545 	e_activity_set_cancellable (context->activity, cancellable);
3546 	g_object_unref (cancellable);
3547 
3548 	activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
3549 	e_activity_bar_set_activity (activity_bar, context->activity);
3550 
3551 	e_msg_composer_get_message (
3552 		composer, G_PRIORITY_DEFAULT, cancellable,
3553 		(GAsyncReadyCallback) msg_composer_send_cb,
3554 		context);
3555 }
3556 
3557 static void
3558 msg_composer_save_to_drafts_cb (EMsgComposer *composer,
3559                                 GAsyncResult *result,
3560                                 AsyncContext *context)
3561 {
3562 	CamelMimeMessage *message;
3563 	EAlertSink *alert_sink;
3564 	GtkhtmlEditor *editor;
3565 	GError *error = NULL;
3566 
3567 	alert_sink = e_activity_get_alert_sink (context->activity);
3568 
3569 	message = e_msg_composer_get_message_draft_finish (
3570 		composer, result, &error);
3571 
3572 	if (e_activity_handle_cancellation (context->activity, error)) {
3573 		g_warn_if_fail (message == NULL);
3574 		async_context_free (context);
3575 		g_error_free (error);
3576 
3577 		if (e_msg_composer_is_exiting (composer)) {
3578 			gtk_window_present (GTK_WINDOW (composer));
3579 			composer->priv->application_exiting = FALSE;
3580 		}
3581 
3582 		return;
3583 	}
3584 
3585 	if (error != NULL) {
3586 		g_warn_if_fail (message == NULL);
3587 		e_alert_submit (
3588 			alert_sink,
3589 			"mail-composer:no-build-message",
3590 			error->message, NULL);
3591 		async_context_free (context);
3592 		g_error_free (error);
3593 
3594 		if (e_msg_composer_is_exiting (composer)) {
3595 			gtk_window_present (GTK_WINDOW (composer));
3596 			composer->priv->application_exiting = FALSE;
3597 		}
3598 
3599 		return;
3600 	}
3601 
3602 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3603 
3604 	/* The callback can set editor 'changed' if anything failed. */
3605 	editor = GTKHTML_EDITOR (composer);
3606 	gtkhtml_editor_set_changed (editor, FALSE);
3607 
3608 	g_signal_emit (
3609 		composer, signals[SAVE_TO_DRAFTS],
3610 		0, message, context->activity);
3611 
3612 	g_object_unref (message);
3613 
3614 	if (e_msg_composer_is_exiting (composer))
3615 		g_object_weak_ref (
3616 			G_OBJECT (context->activity),
3617 			(GWeakNotify) gtk_widget_destroy, composer);
3618 
3619 	async_context_free (context);
3620 }
3621 
3622 /**
3623  * e_msg_composer_save_to_drafts:
3624  * @composer: an #EMsgComposer
3625  *
3626  * Save the message in @composer to the selected account's Drafts folder.
3627  **/
3628 void
3629 e_msg_composer_save_to_drafts (EMsgComposer *composer)
3630 {
3631 	AsyncContext *context;
3632 	EAlertSink *alert_sink;
3633 	EActivityBar *activity_bar;
3634 	GCancellable *cancellable;
3635 
3636 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3637 
3638 	context = g_slice_new0 (AsyncContext);
3639 	context->activity = e_composer_activity_new (composer);
3640 
3641 	alert_sink = E_ALERT_SINK (composer);
3642 	e_activity_set_alert_sink (context->activity, alert_sink);
3643 
3644 	cancellable = camel_operation_new ();
3645 	e_activity_set_cancellable (context->activity, cancellable);
3646 	g_object_unref (cancellable);
3647 
3648 	activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
3649 	e_activity_bar_set_activity (activity_bar, context->activity);
3650 
3651 	e_msg_composer_get_message_draft (
3652 		composer, G_PRIORITY_DEFAULT, cancellable,
3653 		(GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
3654 		context);
3655 }
3656 
3657 static void
3658 msg_composer_save_to_outbox_cb (EMsgComposer *composer,
3659                                 GAsyncResult *result,
3660                                 AsyncContext *context)
3661 {
3662 	CamelMimeMessage *message;
3663 	EAlertSink *alert_sink;
3664 	GtkhtmlEditor *editor;
3665 	GError *error = NULL;
3666 
3667 	alert_sink = e_activity_get_alert_sink (context->activity);
3668 
3669 	message = e_msg_composer_get_message_finish (composer, result, &error);
3670 
3671 	if (e_activity_handle_cancellation (context->activity, error)) {
3672 		g_warn_if_fail (message == NULL);
3673 		async_context_free (context);
3674 		g_error_free (error);
3675 		return;
3676 	}
3677 
3678 	if (error != NULL) {
3679 		g_warn_if_fail (message == NULL);
3680 		e_alert_submit (
3681 			alert_sink,
3682 			"mail-composer:no-build-message",
3683 			error->message, NULL);
3684 		async_context_free (context);
3685 		g_error_free (error);
3686 		return;
3687 	}
3688 
3689 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3690 
3691 	g_signal_emit (
3692 		composer, signals[SAVE_TO_OUTBOX],
3693 		0, message, context->activity);
3694 
3695 	g_object_unref (message);
3696 
3697 	async_context_free (context);
3698 
3699 	/* XXX This should be elsewhere. */
3700 	editor = GTKHTML_EDITOR (composer);
3701 	gtkhtml_editor_set_changed (editor, FALSE);
3702 }
3703 
3704 /**
3705  * e_msg_composer_save_to_outbox:
3706  * @composer: an #EMsgComposer
3707  *
3708  * Save the message in @composer to the local Outbox folder.
3709  **/
3710 void
3711 e_msg_composer_save_to_outbox (EMsgComposer *composer)
3712 {
3713 	AsyncContext *context;
3714 	EAlertSink *alert_sink;
3715 	EActivityBar *activity_bar;
3716 	GCancellable *cancellable;
3717 	gboolean proceed_with_save = TRUE;
3718 
3719 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3720 
3721 	/* This gives the user a chance to abort the save. */
3722 	g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
3723 
3724 	if (!proceed_with_save)
3725 		return;
3726 
3727 	context = g_slice_new0 (AsyncContext);
3728 	context->activity = e_composer_activity_new (composer);
3729 
3730 	alert_sink = E_ALERT_SINK (composer);
3731 	e_activity_set_alert_sink (context->activity, alert_sink);
3732 
3733 	cancellable = camel_operation_new ();
3734 	e_activity_set_cancellable (context->activity, cancellable);
3735 	g_object_unref (cancellable);
3736 
3737 	activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
3738 	e_activity_bar_set_activity (activity_bar, context->activity);
3739 
3740 	e_msg_composer_get_message (
3741 		composer, G_PRIORITY_DEFAULT, cancellable,
3742 		(GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
3743 		context);
3744 }
3745 
3746 static void
3747 msg_composer_print_cb (EMsgComposer *composer,
3748                        GAsyncResult *result,
3749                        AsyncContext *context)
3750 {
3751 	CamelMimeMessage *message;
3752 	EAlertSink *alert_sink;
3753 	GError *error = NULL;
3754 
3755 	alert_sink = e_activity_get_alert_sink (context->activity);
3756 
3757 	message = e_msg_composer_get_message_print_finish (
3758 		composer, result, &error);
3759 
3760 	if (e_activity_handle_cancellation (context->activity, error)) {
3761 		g_warn_if_fail (message == NULL);
3762 		async_context_free (context);
3763 		g_error_free (error);
3764 		return;
3765 	}
3766 
3767 	if (error != NULL) {
3768 		g_warn_if_fail (message == NULL);
3769 		async_context_free (context);
3770 		e_alert_submit (
3771 			alert_sink,
3772 			"mail-composer:no-build-message",
3773 			error->message, NULL);
3774 		g_error_free (error);
3775 		return;
3776 	}
3777 
3778 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
3779 
3780 	g_signal_emit (
3781 		composer, signals[PRINT], 0,
3782 		context->print_action, message, context->activity);
3783 
3784 	g_object_unref (message);
3785 
3786 	async_context_free (context);
3787 }
3788 
3789 /**
3790  * e_msg_composer_print:
3791  * @composer: an #EMsgComposer
3792  * @print_action: the print action to start
3793  *
3794  * Print the message in @composer.
3795  **/
3796 void
3797 e_msg_composer_print (EMsgComposer *composer,
3798                       GtkPrintOperationAction print_action)
3799 {
3800 	AsyncContext *context;
3801 	EAlertSink *alert_sink;
3802 	EActivityBar *activity_bar;
3803 	GCancellable *cancellable;
3804 
3805 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3806 
3807 	context = g_slice_new0 (AsyncContext);
3808 	context->activity = e_composer_activity_new (composer);
3809 	context->print_action = print_action;
3810 
3811 	alert_sink = E_ALERT_SINK (composer);
3812 	e_activity_set_alert_sink (context->activity, alert_sink);
3813 
3814 	cancellable = camel_operation_new ();
3815 	e_activity_set_cancellable (context->activity, cancellable);
3816 	g_object_unref (cancellable);
3817 
3818 	activity_bar = E_ACTIVITY_BAR (composer->priv->activity_bar);
3819 	e_activity_bar_set_activity (activity_bar, context->activity);
3820 
3821 	e_msg_composer_get_message_print (
3822 		composer, G_PRIORITY_DEFAULT, cancellable,
3823 		(GAsyncReadyCallback) msg_composer_print_cb,
3824 		context);
3825 }
3826 
3827 static GList *
3828 add_recipients (GList *list,
3829                 const gchar *recips)
3830 {
3831 	CamelInternetAddress *cia;
3832 	const gchar *name, *addr;
3833 	gint num, i;
3834 
3835 	cia = camel_internet_address_new ();
3836 	num = camel_address_decode (CAMEL_ADDRESS (cia), recips);
3837 
3838 	for (i = 0; i < num; i++) {
3839 		if (camel_internet_address_get (cia, i, &name, &addr)) {
3840 			EDestination *dest = e_destination_new ();
3841 			e_destination_set_name (dest, name);
3842 			e_destination_set_email (dest, addr);
3843 
3844 			list = g_list_append (list, dest);
3845 		}
3846 	}
3847 
3848 	return list;
3849 }
3850 
3851 static gboolean
3852 list_contains_addr (const GList *list,
3853                     EDestination *dest)
3854 {
3855 	g_return_val_if_fail (dest != NULL, FALSE);
3856 
3857 	while (list != NULL) {
3858 		if (e_destination_equal (dest, list->data))
3859 			return TRUE;
3860 
3861 		list = list->next;
3862 	}
3863 
3864 	return FALSE;
3865 }
3866 
3867 static void
3868 merge_cc_bcc (EDestination **addrv,
3869               GList **merge_into,
3870               const GList *to,
3871               const GList *cc,
3872               const GList *bcc)
3873 {
3874 	gint ii;
3875 
3876 	for (ii = 0; addrv && addrv[ii]; ii++) {
3877 		if (!list_contains_addr (to, addrv[ii]) &&
3878 		    !list_contains_addr (cc, addrv[ii]) &&
3879 		    !list_contains_addr (bcc, addrv[ii]))
3880 			*merge_into = g_list_append (
3881 				*merge_into, g_object_ref (addrv[ii]));
3882 	}
3883 }
3884 
3885 static void
3886 merge_always_cc_and_bcc (EComposerHeaderTable *table,
3887                          const GList *to,
3888                          GList **cc,
3889                          GList **bcc)
3890 {
3891 	EDestination **addrv;
3892 
3893 	g_return_if_fail (table != NULL);
3894 	g_return_if_fail (cc != NULL);
3895 	g_return_if_fail (bcc != NULL);
3896 
3897 	addrv = e_composer_header_table_get_destinations_cc (table);
3898 	merge_cc_bcc (addrv, cc, to, *cc, *bcc);
3899 	e_destination_freev (addrv);
3900 
3901 	addrv = e_composer_header_table_get_destinations_bcc (table);
3902 	merge_cc_bcc (addrv, bcc, to, *cc, *bcc);
3903 	e_destination_freev (addrv);
3904 }
3905 
3906 static const gchar *blacklist[] = { ".", "etc", ".." };
3907 
3908 static gboolean
3909 file_is_blacklisted (const gchar *argument)
3910 {
3911 	GFile *file;
3912 	gboolean blacklisted = FALSE;
3913 	guint ii, jj, n_parts;
3914 	gchar *filename;
3915 	gchar **parts;
3916 
3917 	/* The "attach" argument may be a URI or local path.  Normalize
3918 	 * it to a local path if we can.  We only blacklist local files. */
3919 	file = g_file_new_for_commandline_arg (argument);
3920 	filename = g_file_get_path (file);
3921 	g_object_unref (file);
3922 
3923 	if (filename == NULL)
3924 		return FALSE;
3925 
3926 	parts = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
3927 	n_parts = g_strv_length (parts);
3928 
3929 	for (ii = 0; ii < G_N_ELEMENTS (blacklist); ii++) {
3930 		for (jj = 0; jj < n_parts; jj++) {
3931 			if (g_str_has_prefix (parts[jj], blacklist[ii])) {
3932 				blacklisted = TRUE;
3933 				break;
3934 			}
3935 		}
3936 	}
3937 
3938 	if (blacklisted) {
3939 		gchar *base_dir;
3940 
3941 		/* Don't blacklist files in trusted base directories. */
3942 		if (g_str_has_prefix (filename, g_get_user_data_dir ()))
3943 			blacklisted = FALSE;
3944 		if (g_str_has_prefix (filename, g_get_user_cache_dir ()))
3945 			blacklisted = FALSE;
3946 		if (g_str_has_prefix (filename, g_get_user_config_dir ()))
3947 			blacklisted = FALSE;
3948 
3949 		/* Apparently KDE still uses ~/.kde heavily, and some
3950 		 * distributions use ~/.kde4 to distinguish KDE4 data
3951 		 * from KDE3 data.  Trust these directories as well. */
3952 
3953 		base_dir = g_build_filename (g_get_home_dir (), ".kde", NULL);
3954 		if (g_str_has_prefix (filename, base_dir))
3955 			blacklisted = FALSE;
3956 		g_free (base_dir);
3957 
3958 		base_dir = g_build_filename (g_get_home_dir (), ".kde4", NULL);
3959 		if (g_str_has_prefix (filename, base_dir))
3960 			blacklisted = FALSE;
3961 		g_free (base_dir);
3962 	}
3963 
3964 	g_strfreev (parts);
3965 	g_free (filename);
3966 
3967 	return blacklisted;
3968 }
3969 
3970 static void
3971 handle_mailto (EMsgComposer *composer,
3972                const gchar *mailto)
3973 {
3974 	EAttachmentView *view;
3975 	EAttachmentStore *store;
3976 	EComposerHeaderTable *table;
3977 	GList *to = NULL, *cc = NULL, *bcc = NULL;
3978 	EDestination **tov, **ccv, **bccv;
3979 	gchar *subject = NULL, *body = NULL;
3980 	gchar *header, *content, *buf;
3981 	gsize nread, nwritten;
3982 	const gchar *p;
3983 	gint len, clen;
3984 
3985 	table = e_msg_composer_get_header_table (composer);
3986 	view = e_msg_composer_get_attachment_view (composer);
3987 	store = e_attachment_view_get_store (view);
3988 
3989 	buf = g_strdup (mailto);
3990 
3991 	/* Parse recipients (everything after ':' until '?' or eos). */
3992 	p = buf + 7;
3993 	len = strcspn (p, "?");
3994 	if (len) {
3995 		content = g_strndup (p, len);
3996 		camel_url_decode (content);
3997 		to = add_recipients (to, content);
3998 		g_free (content);
3999 	}
4000 
4001 	p += len;
4002 	if (*p == '?') {
4003 		p++;
4004 
4005 		while (*p) {
4006 			len = strcspn (p, "=&");
4007 
4008 			/* If it's malformed, give up. */
4009 			if (p[len] != '=')
4010 				break;
4011 
4012 			header = (gchar *) p;
4013 			header[len] = '\0';
4014 			p += len + 1;
4015 
4016 			clen = strcspn (p, "&");
4017 
4018 			content = g_strndup (p, clen);
4019 
4020 			if (!g_ascii_strcasecmp (header, "to")) {
4021 				camel_url_decode (content);
4022 				to = add_recipients (to, content);
4023 			} else if (!g_ascii_strcasecmp (header, "cc")) {
4024 				camel_url_decode (content);
4025 				cc = add_recipients (cc, content);
4026 			} else if (!g_ascii_strcasecmp (header, "bcc")) {
4027 				camel_url_decode (content);
4028 				bcc = add_recipients (bcc, content);
4029 			} else if (!g_ascii_strcasecmp (header, "subject")) {
4030 				g_free (subject);
4031 				camel_url_decode (content);
4032 				if (g_utf8_validate (content, -1, NULL)) {
4033 					subject = content;
4034 					content = NULL;
4035 				} else {
4036 					subject = g_locale_to_utf8 (
4037 						content, clen, &nread,
4038 						&nwritten, NULL);
4039 					if (subject) {
4040 						subject = g_realloc (subject, nwritten + 1);
4041 						subject[nwritten] = '\0';
4042 					}
4043 				}
4044 			} else if (!g_ascii_strcasecmp (header, "body")) {
4045 				g_free (body);
4046 				camel_url_decode (content);
4047 				if (g_utf8_validate (content, -1, NULL)) {
4048 					body = content;
4049 					content = NULL;
4050 				} else {
4051 					body = g_locale_to_utf8 (
4052 						content, clen, &nread,
4053 						&nwritten, NULL);
4054 					if (body) {
4055 						body = g_realloc (body, nwritten + 1);
4056 						body[nwritten] = '\0';
4057 					}
4058 				}
4059 			} else if (!g_ascii_strcasecmp (header, "attach") ||
4060 				   !g_ascii_strcasecmp (header, "attachment")) {
4061 				EAttachment *attachment;
4062 
4063 				camel_url_decode (content);
4064 				if (file_is_blacklisted (content))
4065 					e_alert_submit (
4066 						E_ALERT_SINK (composer),
4067 						"mail:blacklisted-file",
4068 						content, NULL);
4069 				if (g_ascii_strncasecmp (content, "file:", 5) == 0)
4070 					attachment = e_attachment_new_for_uri (content);
4071 				else
4072 					attachment = e_attachment_new_for_path (content);
4073 				e_attachment_store_add_attachment (store, attachment);
4074 				e_attachment_load_async (
4075 					attachment, (GAsyncReadyCallback)
4076 					e_attachment_load_handle_error, composer);
4077 				g_object_unref (attachment);
4078 			} else if (!g_ascii_strcasecmp (header, "from")) {
4079 				/* Ignore */
4080 			} else if (!g_ascii_strcasecmp (header, "reply-to")) {
4081 				/* ignore */
4082 			} else {
4083 				/* add an arbitrary header? */
4084 				camel_url_decode (content);
4085 				e_msg_composer_add_header (composer, header, content);
4086 			}
4087 
4088 			g_free (content);
4089 
4090 			p += clen;
4091 			if (*p == '&') {
4092 				p++;
4093 				if (!g_ascii_strncasecmp (p, "amp;", 4))
4094 					p += 4;
4095 			}
4096 		}
4097 	}
4098 
4099 	g_free (buf);
4100 
4101 	merge_always_cc_and_bcc (table, to, &cc, &bcc);
4102 
4103 	tov  = destination_list_to_vector (to);
4104 	ccv  = destination_list_to_vector (cc);
4105 	bccv = destination_list_to_vector (bcc);
4106 
4107 	g_list_free (to);
4108 	g_list_free (cc);
4109 	g_list_free (bcc);
4110 
4111 	e_composer_header_table_set_destinations_to (table, tov);
4112 	e_composer_header_table_set_destinations_cc (table, ccv);
4113 	e_composer_header_table_set_destinations_bcc (table, bccv);
4114 
4115 	e_destination_freev (tov);
4116 	e_destination_freev (ccv);
4117 	e_destination_freev (bccv);
4118 
4119 	e_composer_header_table_set_subject (table, subject);
4120 	g_free (subject);
4121 
4122 	if (body) {
4123 		gchar *htmlbody;
4124 
4125 		htmlbody = camel_text_to_html (body, CAMEL_MIME_FILTER_TOHTML_PRE, 0);
4126 		set_editor_text (composer, htmlbody, TRUE);
4127 		g_free (htmlbody);
4128 	}
4129 }
4130 
4131 /**
4132  * e_msg_composer_new_from_url:
4133  * @shell: an #EShell
4134  * @url: a mailto URL
4135  *
4136  * Create a new message composer widget, and fill in fields as
4137  * defined by the provided URL.
4138  **/
4139 EMsgComposer *
4140 e_msg_composer_new_from_url (EShell *shell,
4141                              const gchar *url)
4142 {
4143 	EMsgComposer *composer;
4144 
4145 	g_return_val_if_fail (E_IS_SHELL (shell), NULL);
4146 	g_return_val_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0, NULL);
4147 
4148 	composer = e_msg_composer_new (shell);
4149 
4150 	handle_mailto (composer, url);
4151 
4152 	return composer;
4153 }
4154 
4155 /**
4156  * e_msg_composer_set_body_text:
4157  * @composer: a composer object
4158  * @text: the HTML text to initialize the editor with
4159  * @update_signature: whether update signature in the text after setting it;
4160  *    Might be usually called with TRUE.
4161  *
4162  * Loads the given HTML text into the editor.
4163  **/
4164 void
4165 e_msg_composer_set_body_text (EMsgComposer *composer,
4166                               const gchar *text,
4167                               gboolean update_signature)
4168 {
4169 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4170 	g_return_if_fail (text != NULL);
4171 
4172 	set_editor_text (composer, text, update_signature);
4173 }
4174 
4175 /**
4176  * e_msg_composer_set_body:
4177  * @composer: a composer object
4178  * @body: the data to initialize the composer with
4179  * @mime_type: the MIME type of data
4180  *
4181  * Loads the given data into the composer as the message body.
4182  **/
4183 void
4184 e_msg_composer_set_body (EMsgComposer *composer,
4185                          const gchar *body,
4186                          const gchar *mime_type)
4187 {
4188 	EMsgComposerPrivate *priv = composer->priv;
4189 	EComposerHeaderTable *table;
4190 	EWebViewGtkHTML *web_view;
4191 	ESourceRegistry *registry;
4192 	ESource *source;
4193 	const gchar *identity_uid;
4194 	gchar *buff;
4195 
4196 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4197 
4198 	table = e_msg_composer_get_header_table (composer);
4199 	registry = e_composer_header_table_get_registry (table);
4200 
4201 	identity_uid = e_composer_header_table_get_identity_uid (table);
4202 	source = e_source_registry_ref_source (registry, identity_uid);
4203 
4204 	buff = g_markup_printf_escaped (
4205 		"<b>%s</b>",
4206 		_("The composer contains a non-text "
4207 		"message body, which cannot be edited."));
4208 	set_editor_text (composer, buff, FALSE);
4209 	g_free (buff);
4210 
4211 	gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (composer), FALSE);
4212 
4213 	web_view = e_msg_composer_get_web_view (composer);
4214 	e_web_view_gtkhtml_set_editable (web_view, FALSE);
4215 
4216 	g_free (priv->mime_body);
4217 	priv->mime_body = g_strdup (body);
4218 	g_free (priv->mime_type);
4219 	priv->mime_type = g_strdup (mime_type);
4220 
4221 	if (g_ascii_strncasecmp (priv->mime_type, "text/calendar", 13) == 0) {
4222 		ESourceMailComposition *extension;
4223 		const gchar *extension_name;
4224 
4225 		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
4226 		extension = e_source_get_extension (source, extension_name);
4227 
4228 		if (!e_source_mail_composition_get_sign_imip (extension)) {
4229 			GtkToggleAction *action;
4230 
4231 			action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
4232 			gtk_toggle_action_set_active (action, FALSE);
4233 
4234 			action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
4235 			gtk_toggle_action_set_active (action, FALSE);
4236 		}
4237 	}
4238 
4239 	g_object_unref (source);
4240 }
4241 
4242 /**
4243  * e_msg_composer_add_header:
4244  * @composer: an #EMsgComposer
4245  * @name: the header's name
4246  * @value: the header's value
4247  *
4248  * Adds a new custom header created from @name and @value.  The header
4249  * is not shown in the user interface but will be added to the resulting
4250  * MIME message when sending or saving.
4251  **/
4252 void
4253 e_msg_composer_add_header (EMsgComposer *composer,
4254                            const gchar *name,
4255                            const gchar *value)
4256 {
4257 	EMsgComposerPrivate *priv;
4258 
4259 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4260 	g_return_if_fail (name != NULL);
4261 	g_return_if_fail (value != NULL);
4262 
4263 	priv = composer->priv;
4264 
4265 	g_ptr_array_add (priv->extra_hdr_names, g_strdup (name));
4266 	g_ptr_array_add (priv->extra_hdr_values, g_strdup (value));
4267 }
4268 
4269 /**
4270  * e_msg_composer_set_header:
4271  * @composer: an #EMsgComposer
4272  * @name: the header's name
4273  * @value: the header's value
4274  *
4275  * Replaces all custom headers matching @name that were added with
4276  * e_msg_composer_add_header() or e_msg_composer_set_header(), with
4277  * a new custom header created from @name and @value.  The header is
4278  * not shown in the user interface but will be added to the resulting
4279  * MIME message when sending or saving.
4280  **/
4281 void
4282 e_msg_composer_set_header (EMsgComposer *composer,
4283                            const gchar *name,
4284                            const gchar *value)
4285 {
4286 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4287 	g_return_if_fail (name != NULL);
4288 	g_return_if_fail (value != NULL);
4289 
4290 	e_msg_composer_remove_header (composer, name);
4291 	e_msg_composer_add_header (composer, name, value);
4292 }
4293 
4294 /**
4295  * e_msg_composer_remove_header:
4296  * @composer: an #EMsgComposer
4297  * @name: the header's name
4298  *
4299  * Removes all custom headers matching @name that were added with
4300  * e_msg_composer_add_header() or e_msg_composer_set_header().
4301  **/
4302 void
4303 e_msg_composer_remove_header (EMsgComposer *composer,
4304                               const gchar *name)
4305 {
4306 	EMsgComposerPrivate *priv;
4307 	guint ii;
4308 
4309 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4310 	g_return_if_fail (name != NULL);
4311 
4312 	priv = composer->priv;
4313 
4314 	for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
4315 		if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
4316 			g_free (priv->extra_hdr_names->pdata[ii]);
4317 			g_free (priv->extra_hdr_values->pdata[ii]);
4318 			g_ptr_array_remove_index (priv->extra_hdr_names, ii);
4319 			g_ptr_array_remove_index (priv->extra_hdr_values, ii);
4320 		}
4321 	}
4322 }
4323 
4324 /**
4325  * e_msg_composer_set_draft_headers:
4326  * @composer: an #EMsgComposer
4327  * @folder_uri: folder URI of the last saved draft
4328  * @message_uid: message UID of the last saved draft
4329  *
4330  * Add special X-Evolution-Draft headers to remember the most recently
4331  * saved draft message, even across Evolution sessions.  These headers
4332  * can be used to mark the draft message for deletion after saving a
4333  * newer draft or sending the composed message.
4334  **/
4335 void
4336 e_msg_composer_set_draft_headers (EMsgComposer *composer,
4337                                   const gchar *folder_uri,
4338                                   const gchar *message_uid)
4339 {
4340 	const gchar *header_name;
4341 
4342 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4343 	g_return_if_fail (folder_uri != NULL);
4344 	g_return_if_fail (message_uid != NULL);
4345 
4346 	header_name = "X-Evolution-Draft-Folder";
4347 	e_msg_composer_set_header (composer, header_name, folder_uri);
4348 
4349 	header_name = "X-Evolution-Draft-Message";
4350 	e_msg_composer_set_header (composer, header_name, message_uid);
4351 }
4352 
4353 /**
4354  * e_msg_composer_set_source_headers:
4355  * @composer: an #EMsgComposer
4356  * @folder_uri: folder URI of the source message
4357  * @message_uid: message UID of the source message
4358  * @flags: flags to set on the source message after sending
4359  *
4360  * Add special X-Evolution-Source headers to remember the message being
4361  * forwarded or replied to, even across Evolution sessions.  These headers
4362  * can be used to set appropriate flags on the source message after sending
4363  * the composed message.
4364  **/
4365 void
4366 e_msg_composer_set_source_headers (EMsgComposer *composer,
4367                                    const gchar *folder_uri,
4368                                    const gchar *message_uid,
4369                                    CamelMessageFlags flags)
4370 {
4371 	GString *buffer;
4372 	const gchar *header_name;
4373 
4374 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4375 	g_return_if_fail (folder_uri != NULL);
4376 	g_return_if_fail (message_uid != NULL);
4377 
4378 	buffer = g_string_sized_new (32);
4379 
4380 	if (flags & CAMEL_MESSAGE_ANSWERED)
4381 		g_string_append (buffer, "ANSWERED ");
4382 	if (flags & CAMEL_MESSAGE_ANSWERED_ALL)
4383 		g_string_append (buffer, "ANSWERED_ALL ");
4384 	if (flags & CAMEL_MESSAGE_FORWARDED)
4385 		g_string_append (buffer, "FORWARDED ");
4386 	if (flags & CAMEL_MESSAGE_SEEN)
4387 		g_string_append (buffer, "SEEN ");
4388 
4389 	header_name = "X-Evolution-Source-Folder";
4390 	e_msg_composer_set_header (composer, header_name, folder_uri);
4391 
4392 	header_name = "X-Evolution-Source-Message";
4393 	e_msg_composer_set_header (composer, header_name, message_uid);
4394 
4395 	header_name = "X-Evolution-Source-Flags";
4396 	e_msg_composer_set_header (composer, header_name, buffer->str);
4397 
4398 	g_string_free (buffer, TRUE);
4399 }
4400 
4401 /**
4402  * e_msg_composer_attach:
4403  * @composer: a composer object
4404  * @mime_part: the #CamelMimePart to attach
4405  *
4406  * Attaches @attachment to the message being composed in the composer.
4407  **/
4408 void
4409 e_msg_composer_attach (EMsgComposer *composer,
4410                        CamelMimePart *mime_part)
4411 {
4412 	EAttachmentV