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 modify it
4  * under the terms of the GNU Lesser General Public License as published by
5  * the Free Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful, but
8  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10  * for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public License
13  * along with this program; if not, see <http://www.gnu.org/licenses/>.
14  *
15  *
16  * Authors:
17  *		Ettore Perazzoli (ettore@ximian.com)
18  *		Jeffrey Stedfast (fejj@ximian.com)
19  *		Miguel de Icaza  (miguel@ximian.com)
20  *		Radek Doulik     (rodo@ximian.com)
21  *
22  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23  *
24  */
25 
26 #include "evolution-config.h"
27 
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <fcntl.h>
36 #include <enchant.h>
37 
38 #include "e-composer-from-header.h"
39 #include "e-composer-text-header.h"
40 #include "e-composer-private.h"
41 
42 #include <em-format/e-mail-part.h>
43 #include <em-format/e-mail-parser.h>
44 #include <em-format/e-mail-formatter-quote.h>
45 
46 #include <shell/e-shell.h>
47 
48 #include <libemail-engine/libemail-engine.h>
49 
50 typedef struct _AsyncContext AsyncContext;
51 
52 struct _AsyncContext {
53 	EActivity *activity;
54 
55 	CamelMimeMessage *message;
56 	CamelDataWrapper *top_level_part;
57 	CamelDataWrapper *text_plain_part;
58 
59 	ESource *source;
60 	CamelSession *session;
61 	CamelInternetAddress *from;
62 
63 	CamelTransferEncoding plain_encoding;
64 	GtkPrintOperationAction print_action;
65 
66 	GPtrArray *recipients;
67 	GSList *recipients_with_certificate; /* EContact * */
68 
69 	guint skip_content : 1;
70 	guint is_redirect : 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 	guint is_draft : 1;
77 };
78 
79 /* Flags for building a message. */
80 typedef enum {
81 	COMPOSER_FLAG_HTML_CONTENT		= 1 << 0,
82 	COMPOSER_FLAG_SAVE_OBJECT_DATA		= 1 << 1,
83 	COMPOSER_FLAG_PRIORITIZE_MESSAGE	= 1 << 2,
84 	COMPOSER_FLAG_REQUEST_READ_RECEIPT	= 1 << 3,
85 	COMPOSER_FLAG_PGP_SIGN			= 1 << 4,
86 	COMPOSER_FLAG_PGP_ENCRYPT		= 1 << 5,
87 	COMPOSER_FLAG_SMIME_SIGN		= 1 << 6,
88 	COMPOSER_FLAG_SMIME_ENCRYPT		= 1 << 7,
89 	COMPOSER_FLAG_HTML_MODE			= 1 << 8,
90 	COMPOSER_FLAG_SAVE_DRAFT		= 1 << 9
91 } ComposerFlags;
92 
93 enum {
94 	PROP_0,
95 	PROP_BUSY,
96 	PROP_SOFT_BUSY,
97 	PROP_EDITOR,
98 	PROP_FOCUS_TRACKER,
99 	PROP_SHELL,
100 	PROP_IS_REPLY_OR_FORWARD
101 };
102 
103 enum {
104 	PRESEND,
105 	SEND,
106 	SAVE_TO_DRAFTS,
107 	SAVE_TO_OUTBOX,
108 	PRINT,
109 	BEFORE_DESTROY,
110 	LAST_SIGNAL
111 };
112 
113 static GtkTargetEntry drag_dest_targets[] = {
114 	{ (gchar *) "text/uri-list", 0, E_DND_TARGET_TYPE_TEXT_URI_LIST },
115 	{ (gchar *) "_NETSCAPE_URL", 0, E_DND_TARGET_TYPE_MOZILLA_URL },
116 	{ (gchar *) "text/html", 0, E_DND_TARGET_TYPE_TEXT_HTML },
117 	{ (gchar *) "UTF8_STRING", 0, E_DND_TARGET_TYPE_UTF8_STRING },
118 	{ (gchar *) "text/plain", 0, E_DND_TARGET_TYPE_TEXT_PLAIN },
119 	{ (gchar *) "STRING", 0, E_DND_TARGET_TYPE_STRING },
120 	{ (gchar *) "text/plain;charset=utf-8", 0, E_DND_TARGET_TYPE_TEXT_PLAIN_UTF8 },
121 };
122 
123 static guint signals[LAST_SIGNAL];
124 
125 /* used by e_msg_composer_add_message_attachments () */
126 static void	add_attachments_from_multipart	(EMsgComposer *composer,
127 						 CamelMultipart *multipart,
128 						 gboolean just_inlines,
129 						 gint depth);
130 
131 /* used by e_msg_composer_setup_with_message () */
132 static void	handle_multipart		(EMsgComposer *composer,
133 						 CamelMultipart *multipart,
134 						 CamelMimePart *parent_part,
135 						 gboolean keep_signature,
136 						 GCancellable *cancellable,
137 						 gint depth);
138 static void	handle_multipart_alternative	(EMsgComposer *composer,
139 						 CamelMultipart *multipart,
140 						 CamelMimePart *parent_part,
141 						 gboolean keep_signature,
142 						 GCancellable *cancellable,
143 						 gint depth);
144 static void	handle_multipart_encrypted	(EMsgComposer *composer,
145 						 CamelMimePart *multipart,
146 						 CamelMimePart *parent_part,
147 						 gboolean keep_signature,
148 						 GCancellable *cancellable,
149 						 gint depth);
150 static void	handle_multipart_signed		(EMsgComposer *composer,
151 						 CamelMultipart *multipart,
152 						 CamelMimePart *parent_part,
153 						 gboolean keep_signature,
154 						 GCancellable *cancellable,
155 						 gint depth);
156 
G_DEFINE_TYPE_WITH_CODE(EMsgComposer,e_msg_composer,GTK_TYPE_WINDOW,G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE,NULL))157 G_DEFINE_TYPE_WITH_CODE (
158 	EMsgComposer,
159 	e_msg_composer,
160 	GTK_TYPE_WINDOW,
161 	G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
162 
163 static void
164 async_context_free (AsyncContext *context)
165 {
166 	g_clear_object (&context->activity);
167 	g_clear_object (&context->message);
168 	g_clear_object (&context->top_level_part);
169 	g_clear_object (&context->text_plain_part);
170 	g_clear_object (&context->source);
171 	g_clear_object (&context->session);
172 	g_clear_object (&context->from);
173 
174 	if (context->recipients != NULL)
175 		g_ptr_array_free (context->recipients, TRUE);
176 
177 	if (context->recipients_with_certificate)
178 		g_slist_free_full (context->recipients_with_certificate, g_object_unref);
179 
180 	g_slice_free (AsyncContext, context);
181 }
182 
183 static void
e_msg_composer_unref_content_hash(EMsgComposer * composer)184 e_msg_composer_unref_content_hash (EMsgComposer *composer)
185 {
186 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
187 	g_return_if_fail (composer->priv->content_hash_ref_count > 0);
188 
189 	composer->priv->content_hash_ref_count--;
190 
191 	if (!composer->priv->content_hash_ref_count) {
192 		g_clear_pointer (&composer->priv->content_hash, e_content_editor_util_free_content_hash);
193 	}
194 }
195 
196 static void
e_msg_composer_inc_soft_busy(EMsgComposer * composer)197 e_msg_composer_inc_soft_busy (EMsgComposer *composer)
198 {
199 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
200 	g_return_if_fail (composer->priv->soft_busy_count + 1 > composer->priv->soft_busy_count);
201 
202 	composer->priv->soft_busy_count++;
203 
204 	if (composer->priv->soft_busy_count == 1)
205 		g_object_notify (G_OBJECT (composer), "soft-busy");
206 }
207 
208 static void
e_msg_composer_dec_soft_busy(EMsgComposer * composer)209 e_msg_composer_dec_soft_busy (EMsgComposer *composer)
210 {
211 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
212 	g_return_if_fail (composer->priv->soft_busy_count > 0);
213 
214 	composer->priv->soft_busy_count--;
215 
216 	if (composer->priv->soft_busy_count == 0)
217 		g_object_notify (G_OBJECT (composer), "soft-busy");
218 }
219 
220 /**
221  * emcu_part_to_html:
222  * @part:
223  *
224  * Converts a mime part's contents into html text.  If @credits is given,
225  * then it will be used as an attribution string, and the
226  * content will be cited.  Otherwise no citation or attribution
227  * will be performed.
228  *
229  * Return Value: The part in displayable html format.
230  **/
231 static gchar *
emcu_part_to_html(EMsgComposer * composer,CamelMimePart * part,gssize * len,gboolean keep_signature,GCancellable * cancellable)232 emcu_part_to_html (EMsgComposer *composer,
233                    CamelMimePart *part,
234                    gssize *len,
235                    gboolean keep_signature,
236                    GCancellable *cancellable)
237 {
238 	CamelSession *session;
239 	GOutputStream *stream;
240 	gchar *text;
241 	EMailParser *parser;
242 	EMailFormatter *formatter;
243 	EMailPartList *part_list;
244 	GString *part_id;
245 	EShell *shell;
246 	GtkWindow *window;
247 	gsize n_bytes_written = 0;
248 	GQueue queue = G_QUEUE_INIT;
249 
250 	shell = e_shell_get_default ();
251 	window = e_shell_get_active_window (shell);
252 
253 	session = e_msg_composer_ref_session (composer);
254 
255 	part_list = e_mail_part_list_new (NULL, NULL, NULL);
256 
257 	part_id = g_string_sized_new (0);
258 	parser = e_mail_parser_new (session);
259 	e_mail_parser_parse_part (
260 		parser, part, part_id, cancellable, &queue);
261 	while (!g_queue_is_empty (&queue)) {
262 		EMailPart *mail_part = g_queue_pop_head (&queue);
263 
264 		if (!e_mail_part_get_is_attachment (mail_part) &&
265 		    !mail_part->is_hidden)
266 			e_mail_part_list_add_part (part_list, mail_part);
267 
268 		g_object_unref (mail_part);
269 	}
270 	g_string_free (part_id, TRUE);
271 	g_object_unref (parser);
272 	g_object_unref (session);
273 
274 	if (e_mail_part_list_is_empty (part_list)) {
275 		g_object_unref (part_list);
276 		return NULL;
277 	}
278 
279 	stream = g_memory_output_stream_new_resizable ();
280 
281 	formatter = e_mail_formatter_quote_new (
282 		NULL, keep_signature ? E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG : 0);
283 	e_mail_formatter_update_style (
284 		formatter,
285 		gtk_widget_get_state_flags (GTK_WIDGET (window)));
286 
287 	e_mail_formatter_format_sync (
288 		formatter, part_list, stream,
289 		0, E_MAIL_FORMATTER_MODE_PRINTING, cancellable);
290 
291 	g_object_unref (formatter);
292 	g_object_unref (part_list);
293 
294 	g_output_stream_write_all (stream, "", 1, &n_bytes_written, NULL, NULL);
295 
296 	g_output_stream_close (stream, NULL, NULL);
297 
298 	text = g_memory_output_stream_steal_data (
299 		G_MEMORY_OUTPUT_STREAM (stream));
300 
301 	if (len != NULL)
302 		*len = strlen (text);
303 
304 	g_object_unref (stream);
305 
306 	return text;
307 }
308 
309 static EDestination **
destination_list_to_vector_sized(GList * list,gint n)310 destination_list_to_vector_sized (GList *list,
311                                   gint n)
312 {
313 	EDestination **destv;
314 	gint i = 0;
315 
316 	if (n == -1)
317 		n = g_list_length (list);
318 
319 	if (n == 0)
320 		return NULL;
321 
322 	destv = g_new (EDestination *, n + 1);
323 	while (list != NULL && i < n) {
324 		destv[i] = E_DESTINATION (list->data);
325 		list->data = NULL;
326 		i++;
327 		list = g_list_next (list);
328 	}
329 	destv[i] = NULL;
330 
331 	return destv;
332 }
333 
334 static EDestination **
destination_list_to_vector(GList * list)335 destination_list_to_vector (GList *list)
336 {
337 	return destination_list_to_vector_sized (list, -1);
338 }
339 
340 #define LINE_LEN 72
341 
342 static gboolean
text_requires_quoted_printable(const gchar * text,gsize len)343 text_requires_quoted_printable (const gchar *text,
344                                 gsize len)
345 {
346 	const gchar *p;
347 	gsize pos;
348 
349 	if (!text)
350 		return FALSE;
351 
352 	if (len == -1)
353 		len = strlen (text);
354 
355 	if (len >= 5 && strncmp (text, "From ", 5) == 0)
356 		return TRUE;
357 
358 	for (p = text, pos = 0; pos + 6 <= len; pos++, p++) {
359 		if (*p == '\n' && strncmp (p + 1, "From ", 5) == 0)
360 			return TRUE;
361 	}
362 
363 	return FALSE;
364 }
365 
366 static gboolean
best_encoding(GByteArray * buf,const gchar * charset,CamelTransferEncoding * encoding)367 best_encoding (GByteArray *buf,
368                const gchar *charset,
369 	       CamelTransferEncoding *encoding)
370 {
371 	gchar *in, *out, outbuf[256], *ch;
372 	gsize inlen, outlen;
373 	gint status, count = 0;
374 	iconv_t cd;
375 
376 	if (!charset)
377 		return FALSE;
378 
379 	cd = camel_iconv_open (charset, "utf-8");
380 	if (cd == (iconv_t) -1)
381 		return FALSE;
382 
383 	in = (gchar *) buf->data;
384 	inlen = buf->len;
385 	do {
386 		out = outbuf;
387 		outlen = sizeof (outbuf);
388 		status = camel_iconv (cd, (const gchar **) &in, &inlen, &out, &outlen);
389 		for (ch = out - 1; ch >= outbuf; ch--) {
390 			if ((guchar) *ch > 127)
391 				count++;
392 		}
393 	} while (status == (gsize) -1 && errno == E2BIG);
394 	camel_iconv_close (cd);
395 
396 	if (status == (gsize) -1 || status > 0)
397 		return FALSE;
398 
399 	if ((count == 0) && (buf->len < LINE_LEN) &&
400 		!text_requires_quoted_printable (
401 		(const gchar *) buf->data, buf->len))
402 		*encoding = CAMEL_TRANSFER_ENCODING_7BIT;
403 	else if (count <= buf->len * 0.17)
404 		*encoding = CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
405 	else
406 		*encoding = CAMEL_TRANSFER_ENCODING_BASE64;
407 
408 	return TRUE;
409 }
410 
411 static gchar *
best_charset(GByteArray * buf,const gchar * default_charset,CamelTransferEncoding * encoding)412 best_charset (GByteArray *buf,
413               const gchar *default_charset,
414               CamelTransferEncoding *encoding)
415 {
416 	const gchar *charset;
417 
418 	/* First try US-ASCII */
419 	if (best_encoding (buf, "US-ASCII", encoding) &&
420 	    *encoding == CAMEL_TRANSFER_ENCODING_7BIT)
421 		return NULL;
422 
423 	/* Next try the user-specified charset for this message */
424 	if (best_encoding (buf, default_charset, encoding))
425 		return g_strdup (default_charset);
426 
427 	/* Now try the user's default charset from the mail config */
428 	charset = e_composer_get_default_charset ();
429 	if (best_encoding (buf, charset, encoding))
430 		return g_strdup (charset);
431 
432 	/* Try to find something that will work */
433 	charset = camel_charset_best (
434 		(const gchar *) buf->data, buf->len);
435 	if (charset == NULL) {
436 		*encoding = CAMEL_TRANSFER_ENCODING_7BIT;
437 		return NULL;
438 	}
439 
440 	if (!best_encoding (buf, charset, encoding))
441 		*encoding = CAMEL_TRANSFER_ENCODING_BASE64;
442 
443 	return g_strdup (charset);
444 }
445 
446 /* These functions builds a CamelMimeMessage for the message that the user has
447  * composed in 'composer'.
448  */
449 
450 static void
set_recipients_from_destv(CamelMimeMessage * msg,EDestination ** to_destv,EDestination ** cc_destv,EDestination ** bcc_destv,gboolean redirect)451 set_recipients_from_destv (CamelMimeMessage *msg,
452                            EDestination **to_destv,
453                            EDestination **cc_destv,
454                            EDestination **bcc_destv,
455                            gboolean redirect)
456 {
457 	CamelInternetAddress *to_addr;
458 	CamelInternetAddress *cc_addr;
459 	CamelInternetAddress *bcc_addr;
460 	CamelInternetAddress *target;
461 	const gchar *text_addr, *header;
462 	gboolean seen_hidden_list = FALSE;
463 	gint i;
464 
465 	to_addr = camel_internet_address_new ();
466 	cc_addr = camel_internet_address_new ();
467 	bcc_addr = camel_internet_address_new ();
468 
469 	for (i = 0; to_destv != NULL && to_destv[i] != NULL; ++i) {
470 		text_addr = e_destination_get_address (to_destv[i]);
471 
472 		if (text_addr && *text_addr) {
473 			target = to_addr;
474 			if (e_destination_is_evolution_list (to_destv[i])
475 			    && !e_destination_list_show_addresses (to_destv[i])) {
476 				target = bcc_addr;
477 				seen_hidden_list = TRUE;
478 			}
479 
480 			if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
481 				camel_internet_address_add (target, "", text_addr);
482 		}
483 	}
484 
485 	for (i = 0; cc_destv != NULL && cc_destv[i] != NULL; ++i) {
486 		text_addr = e_destination_get_address (cc_destv[i]);
487 		if (text_addr && *text_addr) {
488 			target = cc_addr;
489 			if (e_destination_is_evolution_list (cc_destv[i])
490 			    && !e_destination_list_show_addresses (cc_destv[i])) {
491 				target = bcc_addr;
492 				seen_hidden_list = TRUE;
493 			}
494 
495 			if (camel_address_decode (CAMEL_ADDRESS (target), text_addr) <= 0)
496 				camel_internet_address_add (target, "", text_addr);
497 		}
498 	}
499 
500 	for (i = 0; bcc_destv != NULL && bcc_destv[i] != NULL; ++i) {
501 		text_addr = e_destination_get_address (bcc_destv[i]);
502 		if (text_addr && *text_addr) {
503 			if (camel_address_decode (CAMEL_ADDRESS (bcc_addr), text_addr) <= 0)
504 				camel_internet_address_add (bcc_addr, "", text_addr);
505 		}
506 	}
507 
508 	if (redirect)
509 		header = CAMEL_RECIPIENT_TYPE_RESENT_TO;
510 	else
511 		header = CAMEL_RECIPIENT_TYPE_TO;
512 
513 	if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) {
514 		camel_mime_message_set_recipients (msg, header, to_addr);
515 	} else if (seen_hidden_list) {
516 		camel_medium_set_header (
517 			CAMEL_MEDIUM (msg), header, "Undisclosed-Recipient:;");
518 	}
519 
520 	header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_CC : CAMEL_RECIPIENT_TYPE_CC;
521 	if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) {
522 		camel_mime_message_set_recipients (msg, header, cc_addr);
523 	}
524 
525 	header = redirect ? CAMEL_RECIPIENT_TYPE_RESENT_BCC : CAMEL_RECIPIENT_TYPE_BCC;
526 	if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) {
527 		camel_mime_message_set_recipients (msg, header, bcc_addr);
528 	}
529 
530 	g_object_unref (to_addr);
531 	g_object_unref (cc_addr);
532 	g_object_unref (bcc_addr);
533 }
534 
535 static void
build_message_headers(EMsgComposer * composer,CamelMimeMessage * message,gboolean redirect)536 build_message_headers (EMsgComposer *composer,
537                        CamelMimeMessage *message,
538                        gboolean redirect)
539 {
540 	EComposerHeaderTable *table;
541 	EComposerHeader *header;
542 	ESource *source;
543 	gchar *alias_name = NULL, *alias_address = NULL, *uid;
544 	const gchar *subject;
545 	const gchar *reply_to;
546 
547 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
548 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
549 
550 	table = e_msg_composer_get_header_table (composer);
551 
552 	uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
553 	if (uid)
554 		source = e_composer_header_table_ref_source (table, uid);
555 	else
556 		source = NULL;
557 
558 	/* Subject: */
559 	subject = e_composer_header_table_get_subject (table);
560 	if (!redirect || g_strcmp0 (subject, camel_mime_message_get_subject (message)) != 0)
561 		camel_mime_message_set_subject (message, subject);
562 
563 	if (source != NULL) {
564 		CamelMedium *medium;
565 		CamelInternetAddress *addr;
566 		ESourceMailSubmission *ms;
567 		EComposerHeader *composer_header;
568 		const gchar *extension_name;
569 		const gchar *header_name;
570 		const gchar *name = NULL, *address = NULL;
571 		const gchar *transport_uid;
572 		const gchar *sent_folder = NULL;
573 		gboolean is_from_override = FALSE;
574 
575 		composer_header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
576 		if (e_composer_from_header_get_override_visible (E_COMPOSER_FROM_HEADER (composer_header))) {
577 			name = e_composer_header_table_get_from_name (table);
578 			address = e_composer_header_table_get_from_address (table);
579 
580 			if (address && !*address) {
581 				name = NULL;
582 				address = NULL;
583 			}
584 
585 			is_from_override = address != NULL;
586 		}
587 
588 		if (!address) {
589 			if (alias_name)
590 				name = alias_name;
591 			if (alias_address)
592 				address = alias_address;
593 		}
594 
595 		if (!is_from_override && (!address || !name || !*name)) {
596 			ESourceMailIdentity *mail_identity;
597 
598 			mail_identity = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
599 
600 			if (!name || !*name)
601 				name = e_source_mail_identity_get_name (mail_identity);
602 
603 			if (!address)
604 				address = e_source_mail_identity_get_address (mail_identity);
605 		}
606 
607 		extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
608 		ms = e_source_get_extension (source, extension_name);
609 
610 		if (e_source_mail_submission_get_use_sent_folder (ms))
611 			sent_folder = e_source_mail_submission_get_sent_folder (ms);
612 		transport_uid = e_source_mail_submission_get_transport_uid (ms);
613 
614 		medium = CAMEL_MEDIUM (message);
615 
616 		/* From: / Resent-From: */
617 		addr = camel_internet_address_new ();
618 		camel_internet_address_add (addr, name, address);
619 		if (redirect) {
620 			gchar *value;
621 
622 			value = camel_address_encode (CAMEL_ADDRESS (addr));
623 			camel_medium_set_header (medium, "Resent-From", value);
624 			g_free (value);
625 		} else {
626 			camel_mime_message_set_from (message, addr);
627 		}
628 		g_object_unref (addr);
629 
630 		/* X-Evolution-Identity */
631 		header_name = "X-Evolution-Identity";
632 		camel_medium_set_header (medium, header_name, uid);
633 
634 		/* X-Evolution-Fcc */
635 		header_name = "X-Evolution-Fcc";
636 		camel_medium_set_header (medium, header_name, sent_folder);
637 
638 		/* X-Evolution-Transport */
639 		header_name = "X-Evolution-Transport";
640 		camel_medium_set_header (medium, header_name, transport_uid);
641 
642 		g_object_unref (source);
643 	}
644 
645 	if (redirect)
646 		camel_medium_set_header (CAMEL_MEDIUM (message), "X-Evolution-Is-Redirect", "1");
647 
648 	/* Reply-To: */
649 	reply_to = e_composer_header_table_get_reply_to (table);
650 	if (reply_to != NULL && *reply_to != '\0') {
651 		CamelInternetAddress *addr;
652 
653 		addr = camel_internet_address_new ();
654 
655 		if (camel_address_unformat (CAMEL_ADDRESS (addr), reply_to) > 0)
656 			camel_mime_message_set_reply_to (message, addr);
657 
658 		g_object_unref (addr);
659 	}
660 
661 	/* To:, Cc:, Bcc: */
662 	header = e_composer_header_table_get_header (
663 		table, E_COMPOSER_HEADER_TO);
664 	if (e_composer_header_get_visible (header)) {
665 		EDestination **to, **cc, **bcc;
666 
667 		to = e_composer_header_table_get_destinations_to (table);
668 		cc = e_composer_header_table_get_destinations_cc (table);
669 		bcc = e_composer_header_table_get_destinations_bcc (table);
670 
671 		set_recipients_from_destv (message, to, cc, bcc, redirect);
672 
673 		e_destination_freev (to);
674 		e_destination_freev (cc);
675 		e_destination_freev (bcc);
676 	}
677 
678 	/* Date: */
679 	if (redirect) {
680 		struct tm local;
681 		gint tz, offset;
682 		time_t date;
683 		gchar *datestr;
684 
685 		date = time (NULL);
686 		camel_localtime_with_offset (date, &local, &tz);
687 		offset = (((tz / 60 / 60) * 100) + (tz / 60 % 60));
688 
689 		datestr = camel_header_format_date (date, offset);
690 		camel_medium_set_header (CAMEL_MEDIUM (message), "Resent-Date", datestr);
691 		g_free (datestr);
692 	} else {
693 		camel_mime_message_set_date (message, CAMEL_MESSAGE_DATE_CURRENT, 0);
694 	}
695 
696 	/* X-Evolution-PostTo: */
697 	header = e_composer_header_table_get_header (
698 		table, E_COMPOSER_HEADER_POST_TO);
699 	if (e_composer_header_get_visible (header)) {
700 		CamelMedium *medium;
701 		const gchar *name = "X-Evolution-PostTo";
702 		GList *list, *iter;
703 
704 		medium = CAMEL_MEDIUM (message);
705 		camel_medium_remove_header (medium, name);
706 
707 		list = e_composer_header_table_get_post_to (table);
708 		for (iter = list; iter != NULL; iter = iter->next) {
709 			gchar *folder = iter->data;
710 			camel_medium_add_header (medium, name, folder);
711 			g_free (folder);
712 		}
713 		g_list_free (list);
714 	}
715 
716 	g_free (uid);
717 	g_free (alias_name);
718 	g_free (alias_address);
719 }
720 
721 static CamelCipherHash
account_hash_algo_to_camel_hash(const gchar * hash_algo)722 account_hash_algo_to_camel_hash (const gchar *hash_algo)
723 {
724 	CamelCipherHash res = CAMEL_CIPHER_HASH_DEFAULT;
725 
726 	if (hash_algo && *hash_algo) {
727 		if (g_ascii_strcasecmp (hash_algo, "sha1") == 0)
728 			res = CAMEL_CIPHER_HASH_SHA1;
729 		else if (g_ascii_strcasecmp (hash_algo, "sha256") == 0)
730 			res = CAMEL_CIPHER_HASH_SHA256;
731 		else if (g_ascii_strcasecmp (hash_algo, "sha384") == 0)
732 			res = CAMEL_CIPHER_HASH_SHA384;
733 		else if (g_ascii_strcasecmp (hash_algo, "sha512") == 0)
734 			res = CAMEL_CIPHER_HASH_SHA512;
735 	}
736 
737 	return res;
738 }
739 
740 static void
composer_add_charset_filter(CamelStream * stream,const gchar * charset)741 composer_add_charset_filter (CamelStream *stream,
742                              const gchar *charset)
743 {
744 	CamelMimeFilter *filter;
745 
746 	filter = camel_mime_filter_charset_new ("UTF-8", charset);
747 	camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
748 	g_object_unref (filter);
749 }
750 
751 static void
composer_add_quoted_printable_filter(CamelStream * stream)752 composer_add_quoted_printable_filter (CamelStream *stream)
753 {
754 	CamelMimeFilter *filter;
755 
756 	filter = camel_mime_filter_basic_new (CAMEL_MIME_FILTER_BASIC_QP_ENC);
757 	camel_stream_filter_add (CAMEL_STREAM_FILTER (stream), filter);
758 	g_object_unref (filter);
759 }
760 
761 /* Extracts auto-completed contacts which have X.509 or PGP certificate set.
762    This should be called in the GUI thread, because it accesses GtkWidget-s. */
763 static GSList * /* EContact * */
composer_get_completed_recipients_with_certificate(EMsgComposer * composer)764 composer_get_completed_recipients_with_certificate (EMsgComposer *composer)
765 {
766 	EComposerHeaderTable *table;
767 	GSList *contacts = NULL;
768 	EDestination **to, **cc, **bcc;
769 	gint ii;
770 
771 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
772 
773 	table = e_msg_composer_get_header_table (composer);
774 	to = e_composer_header_table_get_destinations_to (table);
775 	cc = e_composer_header_table_get_destinations_cc (table);
776 	bcc = e_composer_header_table_get_destinations_bcc (table);
777 
778 	#define traverse_destv(x) \
779 		for (ii = 0; x && x[ii]; ii++) { \
780 			EDestination *dest = x[ii]; \
781 			EContactCert *x509cert, *pgpcert; \
782 			EContact *contact; \
783 			 \
784 			contact = e_destination_get_contact (dest); \
785 			 \
786 			/* Get certificates only for individuals, not for lists */ \
787 			if (!contact || e_destination_is_evolution_list (dest)) \
788 				continue; \
789 			 \
790 			x509cert = e_contact_get (contact, E_CONTACT_X509_CERT); \
791 			pgpcert = e_contact_get (contact, E_CONTACT_PGP_CERT); \
792 			 \
793 			if (x509cert || pgpcert) \
794 				contacts = g_slist_prepend (contacts, e_contact_duplicate (contact)); \
795 			 \
796 			e_contact_cert_free (x509cert); \
797 			e_contact_cert_free (pgpcert); \
798 		}
799 
800 	traverse_destv (to);
801 	traverse_destv (cc);
802 	traverse_destv (bcc);
803 
804 	#undef traverse_destv
805 
806 	e_destination_freev (to);
807 	e_destination_freev (cc);
808 	e_destination_freev (bcc);
809 
810 	return contacts;
811 }
812 
813 static gchar *
composer_get_recipient_certificate_cb(EMailSession * session,guint32 flags,const gchar * email_address,gpointer user_data)814 composer_get_recipient_certificate_cb (EMailSession *session,
815 				       guint32 flags, /* bit-or of CamelRecipientCertificateFlags */
816 				       const gchar *email_address,
817 				       gpointer user_data)
818 {
819 	AsyncContext *context = user_data;
820 	EContactField field_id;
821 	GSList *link;
822 	gchar *base64_cert = NULL;
823 
824 	g_return_val_if_fail (context != NULL, NULL);
825 
826 	if (!email_address || !*email_address)
827 		return NULL;
828 
829 	if ((flags & CAMEL_RECIPIENT_CERTIFICATE_SMIME) != 0)
830 		field_id = E_CONTACT_X509_CERT;
831 	else
832 		field_id = E_CONTACT_PGP_CERT;
833 
834 	for (link = context->recipients_with_certificate; link && !base64_cert; link = g_slist_next (link)) {
835 		EContact *contact = link->data;
836 		GList *emails, *elink;
837 		EContactCert *cert;
838 
839 		cert = e_contact_get (contact, field_id);
840 		if (!cert || !cert->data || !cert->length) {
841 			e_contact_cert_free (cert);
842 			continue;
843 		}
844 
845 		emails = e_contact_get (contact, E_CONTACT_EMAIL);
846 
847 		for (elink = emails; elink && !base64_cert; elink = g_list_next (elink)) {
848 			const gchar *contact_email = elink->data;
849 
850 			if (contact_email && g_ascii_strcasecmp (contact_email, email_address) == 0) {
851 				base64_cert = g_base64_encode ((const guchar *) cert->data, cert->length);
852 			}
853 		}
854 
855 		g_list_free_full (emails, g_free);
856 		e_contact_cert_free (cert);
857 	}
858 
859 	return base64_cert;
860 }
861 
862 /* Helper for composer_build_message_thread() */
863 static gboolean
composer_build_message_pgp(AsyncContext * context,GCancellable * cancellable,GError ** error)864 composer_build_message_pgp (AsyncContext *context,
865                             GCancellable *cancellable,
866                             GError **error)
867 {
868 	ESourceOpenPGP *extension;
869 	CamelCipherContext *cipher;
870 	CamelDataWrapper *content;
871 	CamelMimePart *mime_part;
872 	const gchar *extension_name;
873 	const gchar *pgp_key_id;
874 	const gchar *signing_algorithm;
875 	gboolean always_trust;
876 	gboolean encrypt_to_self;
877 	gboolean prefer_inline;
878 
879 	/* Return silently if we're not signing or encrypting with PGP. */
880 	if (!context->pgp_sign && !context->pgp_encrypt)
881 		return TRUE;
882 
883 	extension_name = E_SOURCE_EXTENSION_OPENPGP;
884 	extension = e_source_get_extension (context->source, extension_name);
885 
886 	always_trust = e_source_openpgp_get_always_trust (extension);
887 	encrypt_to_self = context->is_draft || e_source_openpgp_get_encrypt_to_self (extension);
888 	prefer_inline = e_source_openpgp_get_prefer_inline (extension);
889 	pgp_key_id = e_source_openpgp_get_key_id (extension);
890 	signing_algorithm = e_source_openpgp_get_signing_algorithm (extension);
891 
892 	mime_part = camel_mime_part_new ();
893 
894 	camel_medium_set_content (
895 		CAMEL_MEDIUM (mime_part),
896 		context->top_level_part);
897 
898 	if (context->top_level_part == context->text_plain_part)
899 		camel_mime_part_set_encoding (
900 			mime_part, context->plain_encoding);
901 
902 	g_object_unref (context->top_level_part);
903 	context->top_level_part = NULL;
904 
905 	if ((pgp_key_id == NULL || *pgp_key_id == '\0') &&
906 	    !camel_internet_address_get (context->from, 0, NULL, &pgp_key_id))
907 		pgp_key_id = NULL;
908 
909 	if (context->pgp_sign) {
910 		CamelMimePart *npart;
911 		gboolean success;
912 
913 		npart = camel_mime_part_new ();
914 
915 		cipher = camel_gpg_context_new (context->session);
916 		camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (cipher), always_trust);
917 		camel_gpg_context_set_prefer_inline (CAMEL_GPG_CONTEXT (cipher), prefer_inline);
918 
919 		success = camel_cipher_context_sign_sync (
920 			cipher, pgp_key_id,
921 			account_hash_algo_to_camel_hash (signing_algorithm),
922 			mime_part, npart, cancellable, error);
923 
924 		g_object_unref (cipher);
925 
926 		g_object_unref (mime_part);
927 
928 		if (!success) {
929 			g_object_unref (npart);
930 			return FALSE;
931 		}
932 
933 		mime_part = npart;
934 	}
935 
936 	if (context->pgp_encrypt) {
937 		CamelMimePart *npart;
938 		gulong handler_id;
939 		gboolean success;
940 
941 		npart = camel_mime_part_new ();
942 
943 		/* Check to see if we should encrypt to self.
944 		 * NB: Gets removed immediately after use. */
945 		if (encrypt_to_self && pgp_key_id != NULL)
946 			g_ptr_array_add (
947 				context->recipients,
948 				g_strdup (pgp_key_id));
949 
950 		cipher = camel_gpg_context_new (context->session);
951 		camel_gpg_context_set_always_trust (CAMEL_GPG_CONTEXT (cipher), always_trust);
952 		camel_gpg_context_set_prefer_inline (CAMEL_GPG_CONTEXT (cipher), prefer_inline);
953 
954 		handler_id = g_signal_connect (context->session, "get-recipient-certificate",
955 			G_CALLBACK (composer_get_recipient_certificate_cb), context);
956 
957 		success = camel_cipher_context_encrypt_sync (
958 			cipher, pgp_key_id, context->recipients,
959 			mime_part, npart, cancellable, error);
960 
961 		if (handler_id)
962 			g_signal_handler_disconnect (context->session, handler_id);
963 
964 		g_object_unref (cipher);
965 
966 		if (encrypt_to_self && pgp_key_id != NULL)
967 			g_ptr_array_set_size (
968 				context->recipients,
969 				context->recipients->len - 1);
970 
971 		g_object_unref (mime_part);
972 
973 		if (!success) {
974 			g_object_unref (npart);
975 			return FALSE;
976 		}
977 
978 		mime_part = npart;
979 	}
980 
981 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
982 	context->top_level_part = g_object_ref (content);
983 
984 	g_object_unref (mime_part);
985 
986 	return TRUE;
987 }
988 
989 #ifdef ENABLE_SMIME
990 static gboolean
composer_build_message_smime(AsyncContext * context,GCancellable * cancellable,GError ** error)991 composer_build_message_smime (AsyncContext *context,
992                               GCancellable *cancellable,
993                               GError **error)
994 {
995 	ESourceSMIME *extension;
996 	CamelCipherContext *cipher;
997 	CamelMimePart *mime_part;
998 	const gchar *extension_name;
999 	const gchar *signing_algorithm;
1000 	const gchar *signing_certificate;
1001 	const gchar *encryption_certificate;
1002 	gboolean encrypt_to_self;
1003 	gboolean have_signing_certificate;
1004 	gboolean have_encryption_certificate;
1005 
1006 	/* Return silently if we're not signing or encrypting with S/MIME. */
1007 	if (!context->smime_sign && !context->smime_encrypt)
1008 		return TRUE;
1009 
1010 	extension_name = E_SOURCE_EXTENSION_SMIME;
1011 	extension = e_source_get_extension (context->source, extension_name);
1012 
1013 	encrypt_to_self = context->is_draft ||
1014 		e_source_smime_get_encrypt_to_self (extension);
1015 
1016 	signing_algorithm =
1017 		e_source_smime_get_signing_algorithm (extension);
1018 
1019 	signing_certificate =
1020 		e_source_smime_get_signing_certificate (extension);
1021 
1022 	encryption_certificate =
1023 		e_source_smime_get_encryption_certificate (extension);
1024 
1025 	have_signing_certificate =
1026 		(signing_certificate != NULL) &&
1027 		(*signing_certificate != '\0');
1028 
1029 	have_encryption_certificate =
1030 		(encryption_certificate != NULL) &&
1031 		(*encryption_certificate != '\0');
1032 
1033 	if (context->smime_sign && !have_signing_certificate) {
1034 		g_set_error (
1035 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1036 			_("Cannot sign outgoing message: "
1037 			"No signing certificate set for "
1038 			"this account"));
1039 		return FALSE;
1040 	}
1041 
1042 	if (context->smime_encrypt && !have_encryption_certificate) {
1043 		g_set_error (
1044 			error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1045 			_("Cannot encrypt outgoing message: "
1046 			"No encryption certificate set for "
1047 			"this account"));
1048 		return FALSE;
1049 	}
1050 
1051 	mime_part = camel_mime_part_new ();
1052 
1053 	camel_medium_set_content (
1054 		CAMEL_MEDIUM (mime_part),
1055 		context->top_level_part);
1056 
1057 	if (context->top_level_part == context->text_plain_part)
1058 		camel_mime_part_set_encoding (
1059 			mime_part, context->plain_encoding);
1060 
1061 	g_object_unref (context->top_level_part);
1062 	context->top_level_part = NULL;
1063 
1064 	if (context->smime_sign) {
1065 		CamelMimePart *npart;
1066 		gboolean success;
1067 
1068 		npart = camel_mime_part_new ();
1069 
1070 		cipher = camel_smime_context_new (context->session);
1071 
1072 		/* if we're also encrypting, envelope-sign rather than clear-sign */
1073 		if (context->smime_encrypt) {
1074 			camel_smime_context_set_sign_mode (
1075 				(CamelSMIMEContext *) cipher,
1076 				CAMEL_SMIME_SIGN_ENVELOPED);
1077 			camel_smime_context_set_encrypt_key (
1078 				(CamelSMIMEContext *) cipher,
1079 				TRUE, encryption_certificate);
1080 		} else if (have_encryption_certificate) {
1081 			camel_smime_context_set_encrypt_key (
1082 				(CamelSMIMEContext *) cipher,
1083 				TRUE, encryption_certificate);
1084 		}
1085 
1086 		success = camel_cipher_context_sign_sync (
1087 			cipher, signing_certificate,
1088 			account_hash_algo_to_camel_hash (signing_algorithm),
1089 			mime_part, npart, cancellable, error);
1090 
1091 		g_object_unref (cipher);
1092 
1093 		g_object_unref (mime_part);
1094 
1095 		if (!success) {
1096 			g_object_unref (npart);
1097 			return FALSE;
1098 		}
1099 
1100 		mime_part = npart;
1101 	}
1102 
1103 	if (context->smime_encrypt) {
1104 		gulong handler_id;
1105 		gboolean success;
1106 
1107 		/* Check to see if we should encrypt to self.
1108 		 * NB: Gets removed immediately after use. */
1109 		if (encrypt_to_self)
1110 			g_ptr_array_add (
1111 				context->recipients, g_strdup (
1112 				encryption_certificate));
1113 
1114 		cipher = camel_smime_context_new (context->session);
1115 		camel_smime_context_set_encrypt_key (
1116 			(CamelSMIMEContext *) cipher, TRUE,
1117 			encryption_certificate);
1118 
1119 		handler_id = g_signal_connect (context->session, "get-recipient-certificate",
1120 			G_CALLBACK (composer_get_recipient_certificate_cb), context);
1121 
1122 		success = camel_cipher_context_encrypt_sync (
1123 			cipher, NULL,
1124 			context->recipients, mime_part,
1125 			CAMEL_MIME_PART (context->message),
1126 			cancellable, error);
1127 
1128 		if (handler_id)
1129 			g_signal_handler_disconnect (context->session, handler_id);
1130 
1131 		g_object_unref (cipher);
1132 
1133 		if (!success)
1134 			return FALSE;
1135 
1136 		if (encrypt_to_self)
1137 			g_ptr_array_set_size (
1138 				context->recipients,
1139 				context->recipients->len - 1);
1140 	}
1141 
1142 	/* we replaced the message directly, we don't want to do reparenting foo */
1143 	if (context->smime_encrypt) {
1144 		context->skip_content = TRUE;
1145 	} else {
1146 		CamelDataWrapper *content;
1147 
1148 		content = camel_medium_get_content (
1149 			CAMEL_MEDIUM (mime_part));
1150 		context->top_level_part = g_object_ref (content);
1151 	}
1152 
1153 	g_object_unref (mime_part);
1154 
1155 	return TRUE;
1156 }
1157 #endif
1158 
1159 static void
composer_build_message_thread(GSimpleAsyncResult * simple,EMsgComposer * composer,GCancellable * cancellable)1160 composer_build_message_thread (GSimpleAsyncResult *simple,
1161                                EMsgComposer *composer,
1162                                GCancellable *cancellable)
1163 {
1164 	AsyncContext *context;
1165 	GError *error = NULL;
1166 
1167 	context = g_simple_async_result_get_op_res_gpointer (simple);
1168 
1169 	/* Setup working recipient list if we're encrypting. */
1170 	if (context->pgp_encrypt || context->smime_encrypt) {
1171 		gint ii, jj;
1172 
1173 		const gchar *types[] = {
1174 			CAMEL_RECIPIENT_TYPE_TO,
1175 			CAMEL_RECIPIENT_TYPE_CC,
1176 			CAMEL_RECIPIENT_TYPE_BCC
1177 		};
1178 
1179 		context->recipients = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
1180 		for (ii = 0; ii < G_N_ELEMENTS (types) && !context->is_draft; ii++) {
1181 			CamelInternetAddress *addr;
1182 			const gchar *address;
1183 
1184 			addr = camel_mime_message_get_recipients (
1185 				context->message, types[ii]);
1186 			for (jj = 0; camel_internet_address_get (addr, jj, NULL, &address); jj++)
1187 				g_ptr_array_add (
1188 					context->recipients,
1189 					g_strdup (address));
1190 		}
1191 	}
1192 
1193 	if (!composer_build_message_pgp (context, cancellable, &error)) {
1194 		g_simple_async_result_take_error (simple, error);
1195 		return;
1196 	}
1197 
1198 #if defined (ENABLE_SMIME)
1199 	if (!composer_build_message_smime (context, cancellable, &error)) {
1200 		g_simple_async_result_take_error (simple, error);
1201 		return;
1202 	}
1203 #endif /* ENABLE_SMIME */
1204 }
1205 
1206 static void
composer_add_evolution_composer_mode_header(CamelMedium * medium,EMsgComposer * composer)1207 composer_add_evolution_composer_mode_header (CamelMedium *medium,
1208                                              EMsgComposer *composer)
1209 {
1210 	gboolean html_mode;
1211 	EHTMLEditor *editor;
1212 	EContentEditor *cnt_editor;
1213 
1214 	editor = e_msg_composer_get_editor (composer);
1215 	cnt_editor = e_html_editor_get_content_editor (editor);
1216 	html_mode = e_content_editor_get_html_mode (cnt_editor);
1217 
1218 	camel_medium_add_header (
1219 		medium,
1220 		"X-Evolution-Composer-Mode",
1221 		html_mode ? "text/html" : "text/plain");
1222 }
1223 
1224 static void
composer_add_evolution_format_header(CamelMedium * medium,ComposerFlags flags)1225 composer_add_evolution_format_header (CamelMedium *medium,
1226                                       ComposerFlags flags)
1227 {
1228 	GString *string;
1229 
1230 	string = g_string_sized_new (128);
1231 
1232 	if ((flags & COMPOSER_FLAG_HTML_CONTENT) || (flags & COMPOSER_FLAG_SAVE_DRAFT))
1233 		g_string_append (string, "text/html");
1234 	else
1235 		g_string_append (string, "text/plain");
1236 
1237 	if (flags & COMPOSER_FLAG_PGP_SIGN)
1238 		g_string_append (string, ", pgp-sign");
1239 
1240 	if (flags & COMPOSER_FLAG_PGP_ENCRYPT)
1241 		g_string_append (string, ", pgp-encrypt");
1242 
1243 	if (flags & COMPOSER_FLAG_SMIME_SIGN)
1244 		g_string_append (string, ", smime-sign");
1245 
1246 	if (flags & COMPOSER_FLAG_SMIME_ENCRYPT)
1247 		g_string_append (string, ", smime-encrypt");
1248 
1249 	camel_medium_add_header (
1250 		medium, "X-Evolution-Format", string->str);
1251 
1252 	g_string_free (string, TRUE);
1253 }
1254 
1255 static void
composer_build_message(EMsgComposer * composer,ComposerFlags flags,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1256 composer_build_message (EMsgComposer *composer,
1257                         ComposerFlags flags,
1258                         gint io_priority,
1259                         GCancellable *cancellable,
1260                         GAsyncReadyCallback callback,
1261                         gpointer user_data)
1262 {
1263 	EMsgComposerPrivate *priv;
1264 	GSimpleAsyncResult *simple;
1265 	AsyncContext *context;
1266 	EAttachmentView *view;
1267 	EAttachmentStore *store;
1268 	EComposerHeaderTable *table;
1269 	CamelDataWrapper *html;
1270 	ESourceMailIdentity *mi;
1271 	const gchar *extension_name;
1272 	const gchar *iconv_charset = NULL;
1273 	const gchar *organization;
1274 	gchar *identity_uid;
1275 	CamelMultipart *body = NULL;
1276 	CamelContentType *type;
1277 	CamelStream *stream;
1278 	CamelStream *mem_stream;
1279 	CamelMimePart *part;
1280 	GByteArray *data;
1281 	ESource *source;
1282 	gchar *charset, *message_uid;
1283 	const gchar *from_domain;
1284 	gint i;
1285 	GError *last_error = NULL;
1286 
1287 	e_msg_composer_inc_soft_busy (composer);
1288 
1289 	priv = composer->priv;
1290 	table = e_msg_composer_get_header_table (composer);
1291 
1292 	identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
1293 	if (identity_uid) {
1294 		source = e_composer_header_table_ref_source (table, identity_uid);
1295 		g_free (identity_uid);
1296 
1297 		g_warn_if_fail (source != NULL);
1298 	} else {
1299 		source = NULL;
1300 	}
1301 
1302 	/* Do all the non-blocking work here, and defer
1303 	 * any blocking operations to a separate thread. */
1304 
1305 	context = g_slice_new0 (AsyncContext);
1306 	context->source = source;  /* takes the reference */
1307 	context->session = e_msg_composer_ref_session (composer);
1308 	context->from = e_msg_composer_get_from (composer);
1309 	context->is_draft = (flags & COMPOSER_FLAG_SAVE_DRAFT) != 0;
1310 	context->pgp_sign = !context->is_draft && (flags & COMPOSER_FLAG_PGP_SIGN) != 0;
1311 	context->pgp_encrypt = (flags & COMPOSER_FLAG_PGP_ENCRYPT) != 0;
1312 	context->smime_sign = !context->is_draft && (flags & COMPOSER_FLAG_SMIME_SIGN) != 0;
1313 	context->smime_encrypt = (flags & COMPOSER_FLAG_SMIME_ENCRYPT) != 0;
1314 	context->need_thread =
1315 		context->pgp_sign || context->pgp_encrypt ||
1316 		context->smime_sign || context->smime_encrypt;
1317 
1318 	simple = g_simple_async_result_new (
1319 		G_OBJECT (composer), callback,
1320 		user_data, composer_build_message);
1321 
1322 	g_simple_async_result_set_check_cancellable (simple, cancellable);
1323 
1324 	g_simple_async_result_set_op_res_gpointer (
1325 		simple, context, (GDestroyNotify) async_context_free);
1326 
1327 	/* If this is a redirected message, just tweak the headers. */
1328 	if (priv->redirect) {
1329 		e_msg_composer_dec_soft_busy (composer);
1330 
1331 		context->skip_content = TRUE;
1332 		context->is_redirect = TRUE;
1333 		context->message = g_object_ref (priv->redirect);
1334 		build_message_headers (composer, context->message, TRUE);
1335 		g_simple_async_result_complete (simple);
1336 		g_object_unref (simple);
1337 		return;
1338 	}
1339 
1340 	context->message = camel_mime_message_new ();
1341 
1342 	if (context->from && camel_internet_address_get (context->from, 0, NULL, &from_domain)) {
1343 		const gchar *at = strchr (from_domain, '@');
1344 		if (at)
1345 			from_domain = at + 1;
1346 		else
1347 			from_domain = NULL;
1348 	} else {
1349 		from_domain = NULL;
1350 	}
1351 
1352 	if (!from_domain || !*from_domain)
1353 		from_domain = "localhost";
1354 
1355 	message_uid = camel_header_msgid_generate (from_domain);
1356 
1357 	/* Explicitly generate a Message-ID header here so it's
1358 	 * consistent for all outbound streams (SMTP, Fcc, etc). */
1359 	camel_mime_message_set_message_id (context->message, message_uid);
1360 	g_free (message_uid);
1361 
1362 	build_message_headers (composer, context->message, FALSE);
1363 	for (i = 0; i < priv->extra_hdr_names->len; i++) {
1364 		camel_medium_add_header (
1365 			CAMEL_MEDIUM (context->message),
1366 			priv->extra_hdr_names->pdata[i],
1367 			priv->extra_hdr_values->pdata[i]);
1368 	}
1369 
1370 	if (source) {
1371 		extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
1372 		mi = e_source_get_extension (source, extension_name);
1373 		organization = e_source_mail_identity_get_organization (mi);
1374 
1375 		/* Disposition-Notification-To */
1376 		if (flags & COMPOSER_FLAG_REQUEST_READ_RECEIPT) {
1377 			EComposerHeader *header;
1378 			const gchar *mdn_address;
1379 
1380 			header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_REPLY_TO);
1381 			mdn_address = e_composer_text_header_get_text (E_COMPOSER_TEXT_HEADER (header));
1382 
1383 			if (!mdn_address || !*mdn_address) {
1384 				header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
1385 				mdn_address = e_composer_from_header_get_address (E_COMPOSER_FROM_HEADER (header));
1386 			}
1387 
1388 			if (!mdn_address || !*mdn_address)
1389 				mdn_address = e_source_mail_identity_get_reply_to (mi);
1390 			if (mdn_address == NULL)
1391 				mdn_address = e_source_mail_identity_get_address (mi);
1392 			if (mdn_address != NULL)
1393 				camel_medium_add_header (
1394 					CAMEL_MEDIUM (context->message),
1395 					"Disposition-Notification-To", mdn_address);
1396 		}
1397 
1398 		/* Organization */
1399 		if (organization != NULL && *organization != '\0') {
1400 			gchar *encoded_organization;
1401 
1402 			encoded_organization = camel_header_encode_string (
1403 				(const guchar *) organization);
1404 			camel_medium_set_header (
1405 				CAMEL_MEDIUM (context->message),
1406 				"Organization", encoded_organization);
1407 			g_free (encoded_organization);
1408 		}
1409 	}
1410 
1411 	/* X-Priority */
1412 	if (flags & COMPOSER_FLAG_PRIORITIZE_MESSAGE)
1413 		camel_medium_add_header (
1414 			CAMEL_MEDIUM (context->message),
1415 			"X-Priority", "1");
1416 
1417 	/* Build the text/plain part. */
1418 
1419 	if (priv->mime_body) {
1420 		if (text_requires_quoted_printable (priv->mime_body, -1)) {
1421 			context->plain_encoding =
1422 				CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1423 		} else {
1424 			context->plain_encoding = CAMEL_TRANSFER_ENCODING_7BIT;
1425 			for (i = 0; priv->mime_body[i]; i++) {
1426 				if ((guchar) priv->mime_body[i] > 127) {
1427 					context->plain_encoding =
1428 					CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE;
1429 					break;
1430 				}
1431 			}
1432 		}
1433 
1434 		data = g_byte_array_new ();
1435 		g_byte_array_append (
1436 			data, (const guint8 *) priv->mime_body,
1437 			strlen (priv->mime_body));
1438 		type = camel_content_type_decode (priv->mime_type);
1439 
1440 	} else {
1441 		const gchar *text;
1442 		EHTMLEditor *editor;
1443 		EContentEditor *cnt_editor;
1444 
1445 		editor = e_msg_composer_get_editor (composer);
1446 		cnt_editor = e_html_editor_get_content_editor (editor);
1447 		data = g_byte_array_new ();
1448 
1449 		text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash (composer),
1450 			E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
1451 
1452 		if (!text) {
1453 			g_warning ("%s: Failed to retrieve text/plain processed content", G_STRFUNC);
1454 			text = "";
1455 
1456 			last_error = e_content_editor_dup_last_error (cnt_editor);
1457 		}
1458 
1459 		g_byte_array_append (data, (guint8 *) text, strlen (text));
1460 		if (!g_str_has_suffix (text, "\r\n") && !g_str_has_suffix (text, "\n"))
1461 			g_byte_array_append (data, (const guint8 *) "\r\n", 2);
1462 
1463 		type = camel_content_type_new ("text", "plain");
1464 		charset = best_charset (
1465 			data, priv->charset, &context->plain_encoding);
1466 		if (charset != NULL) {
1467 			camel_content_type_set_param (type, "charset", charset);
1468 			iconv_charset = camel_iconv_charset_name (charset);
1469 			g_free (charset);
1470 		}
1471 	}
1472 
1473 	mem_stream = camel_stream_mem_new_with_byte_array (data);
1474 	stream = camel_stream_filter_new (mem_stream);
1475 	g_object_unref (mem_stream);
1476 
1477 	/* Convert the stream to the appropriate charset. */
1478 	if (iconv_charset && g_ascii_strcasecmp (iconv_charset, "UTF-8") != 0)
1479 		composer_add_charset_filter (stream, iconv_charset);
1480 
1481 	/* Encode the stream to quoted-printable if necessary. */
1482 	if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1483 		composer_add_quoted_printable_filter (stream);
1484 
1485 	/* Construct the content object.  This does not block since
1486 	 * we're constructing the data wrapper from a memory stream. */
1487 	context->top_level_part = camel_data_wrapper_new ();
1488 	camel_data_wrapper_construct_from_stream_sync (
1489 		context->top_level_part, stream, NULL, NULL);
1490 	g_object_unref (stream);
1491 
1492 	context->text_plain_part = g_object_ref (context->top_level_part);
1493 
1494 	/* Avoid re-encoding the data when adding it to a MIME part. */
1495 	if (context->plain_encoding == CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE)
1496 		camel_data_wrapper_set_encoding (context->top_level_part, context->plain_encoding);
1497 
1498 	camel_data_wrapper_set_mime_type_field (
1499 		context->top_level_part, type);
1500 
1501 	camel_content_type_unref (type);
1502 
1503 	/* Build the text/html part, and wrap it and the text/plain part
1504 	 * in a multipart/alternative part.  Additionally, if there are
1505 	 * inline images then wrap the multipart/alternative part along
1506 	 * with the images in a multipart/related part.
1507 	 *
1508 	 * So the structure of all this will be:
1509 	 *
1510 	 *    multipart/related
1511 	 *        multipart/alternative
1512 	 *            text/plain
1513 	 *            text/html
1514 	 *        image/<<whatever>>
1515 	 *        image/<<whatever>>
1516 	 *        ...
1517 	 */
1518 
1519 	if ((flags & COMPOSER_FLAG_HTML_CONTENT) != 0 ||
1520 	    (flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
1521 		const gchar *text;
1522 		gsize length;
1523 		gboolean pre_encode;
1524 		GSList *inline_images_parts = NULL, *link;
1525 
1526 		data = g_byte_array_new ();
1527 		if ((flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
1528 			/* X-Evolution-Format */
1529 			composer_add_evolution_format_header (
1530 				CAMEL_MEDIUM (context->message), flags);
1531 
1532 			/* X-Evolution-Composer-Mode */
1533 			composer_add_evolution_composer_mode_header (
1534 				CAMEL_MEDIUM (context->message), composer);
1535 
1536 			text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash (composer),
1537 				E_CONTENT_EDITOR_GET_RAW_DRAFT);
1538 
1539 			if (!text) {
1540 				g_warning ("%s: Failed to retrieve draft content", G_STRFUNC);
1541 				text = "";
1542 			}
1543 		} else {
1544 			text = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash (composer),
1545 				E_CONTENT_EDITOR_GET_TO_SEND_HTML);
1546 
1547 			if (!text) {
1548 				g_warning ("%s: Failed to retrieve HTML processed content", G_STRFUNC);
1549 				text = "";
1550 			}
1551 		}
1552 
1553 		inline_images_parts = e_content_editor_util_get_content_data (e_msg_composer_get_content_hash (composer),
1554 			E_CONTENT_EDITOR_GET_INLINE_IMAGES);
1555 
1556 		length = strlen (text);
1557 		g_byte_array_append (data, (guint8 *) text, (guint) length);
1558 		if (!g_str_has_suffix (text, "\r\n") && !g_str_has_suffix (text, "\n"))
1559 			g_byte_array_append (data, (const guint8 *) "\r\n", 2);
1560 		pre_encode = text_requires_quoted_printable (text, length);
1561 
1562 		mem_stream = camel_stream_mem_new_with_byte_array (data);
1563 		stream = camel_stream_filter_new (mem_stream);
1564 		g_object_unref (mem_stream);
1565 
1566 		if (pre_encode)
1567 			composer_add_quoted_printable_filter (stream);
1568 
1569 		/* Construct the content object.  This does not block since
1570 		 * we're constructing the data wrapper from a memory stream. */
1571 		html = camel_data_wrapper_new ();
1572 		camel_data_wrapper_construct_from_stream_sync (
1573 			html, stream, NULL, NULL);
1574 		g_object_unref (stream);
1575 
1576 		camel_data_wrapper_set_mime_type (
1577 			html, "text/html; charset=utf-8");
1578 
1579 		/* Avoid re-encoding the data when adding it to a MIME part. */
1580 		if (pre_encode)
1581 			camel_data_wrapper_set_encoding (html, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1582 
1583 		/* Build the multipart/alternative */
1584 		body = camel_multipart_new ();
1585 		camel_data_wrapper_set_mime_type (
1586 			CAMEL_DATA_WRAPPER (body), "multipart/alternative");
1587 		camel_multipart_set_boundary (body, NULL);
1588 
1589 		/* Add the text/plain part. */
1590 		part = camel_mime_part_new ();
1591 		camel_medium_set_content (
1592 			CAMEL_MEDIUM (part), context->top_level_part);
1593 		camel_mime_part_set_encoding (part, context->plain_encoding);
1594 		camel_multipart_add_part (body, part);
1595 		g_object_unref (part);
1596 
1597 		/* Add the text/html part. */
1598 		part = camel_mime_part_new ();
1599 		camel_medium_set_content (CAMEL_MEDIUM (part), html);
1600 		camel_mime_part_set_encoding (
1601 			part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
1602 		camel_multipart_add_part (body, part);
1603 		g_object_unref (part);
1604 
1605 		g_object_unref (context->top_level_part);
1606 		g_object_unref (html);
1607 
1608 		/* If there are inlined images, construct a multipart/related
1609 		 * containing the multipart/alternative and the images. */
1610 		if (inline_images_parts) {
1611 			CamelMultipart *html_with_images;
1612 
1613 			html_with_images = camel_multipart_new ();
1614 			camel_data_wrapper_set_mime_type (
1615 				CAMEL_DATA_WRAPPER (html_with_images),
1616 				"multipart/related; "
1617 				"type=\"multipart/alternative\"");
1618 			camel_multipart_set_boundary (html_with_images, NULL);
1619 
1620 			part = camel_mime_part_new ();
1621 			camel_medium_set_content (
1622 				CAMEL_MEDIUM (part),
1623 				CAMEL_DATA_WRAPPER (body));
1624 			camel_multipart_add_part (html_with_images, part);
1625 			g_object_unref (part);
1626 
1627 			g_object_unref (body);
1628 
1629 			for (link = inline_images_parts; link; link = g_slist_next (link)) {
1630 				CamelMimePart *part = link->data;
1631 
1632 				camel_multipart_add_part (html_with_images, g_object_ref (part));
1633 			}
1634 
1635 			context->top_level_part =
1636 				CAMEL_DATA_WRAPPER (html_with_images);
1637 		} else {
1638 			context->top_level_part =
1639 				CAMEL_DATA_WRAPPER (body);
1640 		}
1641 	}
1642 
1643 	view = e_msg_composer_get_attachment_view (composer);
1644 	store = e_attachment_view_get_store (view);
1645 
1646 	/* If there are attachments, wrap what we've built so far
1647 	 * along with the attachments in a multipart/mixed part. */
1648 	if (e_attachment_store_get_num_attachments (store) > 0) {
1649 		CamelMultipart *multipart = camel_multipart_new ();
1650 
1651 		/* Generate a random boundary. */
1652 		camel_multipart_set_boundary (multipart, NULL);
1653 
1654 		part = camel_mime_part_new ();
1655 		camel_medium_set_content (
1656 			CAMEL_MEDIUM (part),
1657 			context->top_level_part);
1658 		if (context->top_level_part == context->text_plain_part)
1659 			camel_mime_part_set_encoding (
1660 				part, context->plain_encoding);
1661 		camel_multipart_add_part (multipart, part);
1662 		g_object_unref (part);
1663 
1664 		e_attachment_store_add_to_multipart (
1665 			store, multipart, priv->charset);
1666 
1667 		g_object_unref (context->top_level_part);
1668 		context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
1669 	}
1670 
1671 	if (last_error) {
1672 		g_simple_async_result_take_error (simple, last_error);
1673 		g_simple_async_result_complete (simple);
1674 
1675 	/* Run any blocking operations in a separate thread. */
1676 	} else if (context->need_thread) {
1677 		if (!context->is_draft)
1678 			context->recipients_with_certificate = composer_get_completed_recipients_with_certificate (composer);
1679 
1680 		g_simple_async_result_run_in_thread (
1681 			simple, (GSimpleAsyncThreadFunc)
1682 			composer_build_message_thread,
1683 			io_priority, cancellable);
1684 	} else {
1685 		g_simple_async_result_complete (simple);
1686 	}
1687 
1688 	e_msg_composer_dec_soft_busy (composer);
1689 
1690 	g_object_unref (simple);
1691 }
1692 
1693 static CamelMimeMessage *
composer_build_message_finish(EMsgComposer * composer,GAsyncResult * result,GError ** error)1694 composer_build_message_finish (EMsgComposer *composer,
1695                                GAsyncResult *result,
1696                                GError **error)
1697 {
1698 	GSimpleAsyncResult *simple;
1699 	AsyncContext *context;
1700 
1701 	g_return_val_if_fail (
1702 		g_simple_async_result_is_valid (
1703 		result, G_OBJECT (composer), composer_build_message), NULL);
1704 
1705 	simple = G_SIMPLE_ASYNC_RESULT (result);
1706 	context = g_simple_async_result_get_op_res_gpointer (simple);
1707 
1708 	if (g_simple_async_result_propagate_error (simple, error))
1709 		return NULL;
1710 
1711 	/* Finalize some details before returning. */
1712 
1713 	if (!context->skip_content) {
1714 		if (context->top_level_part != context->text_plain_part &&
1715 		    CAMEL_IS_MIME_PART (context->top_level_part)) {
1716 			CamelDataWrapper *content;
1717 			CamelMedium *imedium, *omedium;
1718 			const CamelNameValueArray *headers;
1719 
1720 			imedium = CAMEL_MEDIUM (context->top_level_part);
1721 			omedium = CAMEL_MEDIUM (context->message);
1722 
1723 			content = camel_medium_get_content (imedium);
1724 			camel_medium_set_content (omedium, content);
1725 			camel_data_wrapper_set_encoding (CAMEL_DATA_WRAPPER (omedium), camel_data_wrapper_get_encoding (CAMEL_DATA_WRAPPER (imedium)));
1726 
1727 			headers = camel_medium_get_headers (imedium);
1728 			if (headers) {
1729 				gint ii, length;
1730 				length = camel_name_value_array_get_length (headers);
1731 
1732 				for (ii = 0; ii < length; ii++) {
1733 					const gchar *header_name = NULL;
1734 					const gchar *header_value = NULL;
1735 
1736 					if (camel_name_value_array_get (headers, ii, &header_name, &header_value))
1737 						camel_medium_set_header (omedium, header_name, header_value);
1738 				}
1739 			}
1740 		} else {
1741 			camel_medium_set_content (
1742 				CAMEL_MEDIUM (context->message),
1743 				context->top_level_part);
1744 		}
1745 	}
1746 
1747 	if (!context->is_redirect && context->top_level_part == context->text_plain_part) {
1748 		camel_mime_part_set_encoding (
1749 			CAMEL_MIME_PART (context->message),
1750 			context->plain_encoding);
1751 	}
1752 
1753 	return g_object_ref (context->message);
1754 }
1755 
1756 /* Signatures */
1757 
1758 static void
set_editor_text(EMsgComposer * composer,const gchar * text,gboolean is_html,gboolean set_signature)1759 set_editor_text (EMsgComposer *composer,
1760                  const gchar *text,
1761                  gboolean is_html,
1762                  gboolean set_signature)
1763 {
1764 	EHTMLEditor *editor;
1765 	EContentEditor *cnt_editor;
1766 
1767 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
1768 	g_return_if_fail (text != NULL);
1769 
1770 	editor = e_msg_composer_get_editor (composer);
1771 	cnt_editor = e_html_editor_get_content_editor (editor);
1772 
1773 	if (is_html)
1774 		e_content_editor_insert_content (
1775 			cnt_editor,
1776 			text,
1777 			E_CONTENT_EDITOR_INSERT_TEXT_HTML |
1778 			E_CONTENT_EDITOR_INSERT_REPLACE_ALL |
1779 			(e_msg_composer_get_is_reply_or_forward (composer) ? E_CONTENT_EDITOR_INSERT_CLEANUP_SIGNATURE_ID : 0));
1780 	else
1781 		e_content_editor_insert_content (
1782 			cnt_editor,
1783 			text,
1784 			E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
1785 			E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
1786 
1787 	if (set_signature)
1788 		e_composer_update_signature (composer);
1789 }
1790 
1791 /* Miscellaneous callbacks.  */
1792 
1793 static void
attachment_store_changed_cb(EMsgComposer * composer)1794 attachment_store_changed_cb (EMsgComposer *composer)
1795 {
1796 	EHTMLEditor *editor;
1797 
1798 	/* Mark the editor as changed so it prompts about unsaved
1799 	 * changes on close. */
1800 	editor = e_msg_composer_get_editor (composer);
1801 	if (editor) {
1802 		EContentEditor *cnt_editor;
1803 
1804 		cnt_editor = e_html_editor_get_content_editor (editor);
1805 		e_content_editor_set_changed (cnt_editor, TRUE);
1806 	}
1807 }
1808 
1809 static void
msg_composer_subject_changed_cb(EMsgComposer * composer)1810 msg_composer_subject_changed_cb (EMsgComposer *composer)
1811 {
1812 	EComposerHeaderTable *table;
1813 	const gchar *subject;
1814 
1815 	table = e_msg_composer_get_header_table (composer);
1816 	subject = e_composer_header_table_get_subject (table);
1817 
1818 	if (subject == NULL || *subject == '\0')
1819 		subject = _("Compose Message");
1820 
1821 	gtk_window_set_title (GTK_WINDOW (composer), subject);
1822 }
1823 
1824 static void
msg_composer_mail_identity_changed_cb(EMsgComposer * composer)1825 msg_composer_mail_identity_changed_cb (EMsgComposer *composer)
1826 {
1827 	EMailSignatureComboBox *combo_box;
1828 	ESourceMailComposition *mc;
1829 	ESourceOpenPGP *pgp;
1830 	ESourceSMIME *smime;
1831 	EComposerHeaderTable *table;
1832 	EContentEditor *cnt_editor;
1833 	GtkToggleAction *action;
1834 	ESource *source;
1835 	gboolean active;
1836 	gboolean can_sign;
1837 	gboolean pgp_sign;
1838 	gboolean pgp_encrypt;
1839 	gboolean smime_sign;
1840 	gboolean smime_encrypt;
1841 	gboolean composer_realized;
1842 	gboolean was_disable_signature, unset_signature = FALSE;
1843 	const gchar *extension_name;
1844 	const gchar *active_signature_id;
1845 	gchar *uid, *alias_name = NULL, *alias_address = NULL, *pgp_keyid, *smime_cert;
1846 
1847 	cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
1848 	table = e_msg_composer_get_header_table (composer);
1849 	uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
1850 
1851 	/* Silently return if no identity is selected. */
1852 	if (!uid) {
1853 		e_content_editor_set_start_bottom (cnt_editor, E_THREE_STATE_INCONSISTENT);
1854 		e_content_editor_set_top_signature (cnt_editor,
1855 			e_msg_composer_get_is_reply_or_forward (composer) ? E_THREE_STATE_INCONSISTENT :
1856 			E_THREE_STATE_OFF);
1857 
1858 		g_free (alias_name);
1859 		g_free (alias_address);
1860 		return;
1861 	}
1862 
1863 	source = e_composer_header_table_ref_source (table, uid);
1864 	g_return_if_fail (source != NULL);
1865 
1866 	extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
1867 	mc = e_source_get_extension (source, extension_name);
1868 
1869 	e_content_editor_set_start_bottom (cnt_editor,
1870 		e_source_mail_composition_get_start_bottom (mc));
1871 	e_content_editor_set_top_signature (cnt_editor,
1872 		e_msg_composer_get_is_reply_or_forward (composer) ? e_source_mail_composition_get_top_signature (mc) :
1873 		E_THREE_STATE_OFF);
1874 
1875 	extension_name = E_SOURCE_EXTENSION_OPENPGP;
1876 	pgp = e_source_get_extension (source, extension_name);
1877 	pgp_keyid = e_source_openpgp_dup_key_id (pgp);
1878 	pgp_sign = pgp_keyid && *pgp_keyid && e_source_openpgp_get_sign_by_default (pgp);
1879 	pgp_encrypt = pgp_keyid && *pgp_keyid && e_source_openpgp_get_encrypt_by_default (pgp);
1880 	g_free (pgp_keyid);
1881 
1882 	extension_name = E_SOURCE_EXTENSION_SMIME;
1883 	smime = e_source_get_extension (source, extension_name);
1884 	smime_cert = e_source_smime_dup_signing_certificate (smime);
1885 	smime_sign = smime_cert && *smime_cert && e_source_smime_get_sign_by_default (smime);
1886 	g_free (smime_cert);
1887 	smime_cert = e_source_smime_dup_encryption_certificate (smime);
1888 	smime_encrypt = smime_cert && *smime_cert && e_source_smime_get_encrypt_by_default (smime);
1889 	g_free (smime_cert);
1890 
1891 	can_sign =
1892 		(composer->priv->mime_type == NULL) ||
1893 		e_source_mail_composition_get_sign_imip (mc) ||
1894 		(g_ascii_strncasecmp (
1895 			composer->priv->mime_type,
1896 			"text/calendar", 13) != 0);
1897 
1898 	/* Preserve options only if the composer was realized, otherwise an account
1899 	   change according to current folder or similar reasons can cause the options
1900 	   to be set, when the default account has it set, but the other not. */
1901 	composer_realized = gtk_widget_get_realized (GTK_WIDGET (composer));
1902 
1903 	action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
1904 	active = composer_realized && gtk_toggle_action_get_active (action);
1905 	active |= (can_sign && pgp_sign);
1906 	gtk_toggle_action_set_active (action, active);
1907 
1908 	action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
1909 	active = composer_realized && gtk_toggle_action_get_active (action);
1910 	active |= pgp_encrypt;
1911 	gtk_toggle_action_set_active (action, active);
1912 
1913 	action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
1914 	active = composer_realized && gtk_toggle_action_get_active (action);
1915 	active |= (can_sign && smime_sign);
1916 	gtk_toggle_action_set_active (action, active);
1917 
1918 	action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
1919 	active = composer_realized && gtk_toggle_action_get_active (action);
1920 	active |= smime_encrypt;
1921 	gtk_toggle_action_set_active (action, active);
1922 
1923 	was_disable_signature = composer->priv->disable_signature;
1924 
1925 	if (e_msg_composer_get_is_reply_or_forward (composer)) {
1926 		GSettings *settings;
1927 
1928 		settings = e_util_ref_settings ("org.gnome.evolution.mail");
1929 		unset_signature = g_settings_get_boolean (settings, "composer-signature-in-new-only");
1930 		g_object_unref (settings);
1931 	}
1932 
1933 	combo_box = e_composer_header_table_get_signature_combo_box (table);
1934 
1935 	if (unset_signature)
1936 		composer->priv->disable_signature = TRUE;
1937 
1938 	e_mail_signature_combo_box_set_identity (combo_box, uid, alias_name, alias_address);
1939 
1940 	if (unset_signature)
1941 		gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), "none");
1942 
1943 	composer->priv->disable_signature = was_disable_signature;
1944 
1945 	g_object_unref (source);
1946 	g_free (uid);
1947 
1948 	active_signature_id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box));
1949 	if (unset_signature || g_strcmp0 (active_signature_id, E_MAIL_SIGNATURE_AUTOGENERATED_UID) == 0)
1950 		e_composer_update_signature (composer);
1951 
1952 	g_free (alias_name);
1953 	g_free (alias_address);
1954 }
1955 
1956 static void
msg_composer_paste_clipboard_targets_cb(GtkClipboard * clipboard,GdkAtom * targets,gint n_targets,EMsgComposer * composer)1957 msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
1958                                          GdkAtom *targets,
1959                                          gint n_targets,
1960                                          EMsgComposer *composer)
1961 {
1962 	EHTMLEditor *editor;
1963 	EContentEditor *cnt_editor;
1964 
1965 	if (targets == NULL || n_targets < 0)
1966 		return;
1967 
1968 	editor = e_msg_composer_get_editor (composer);
1969 	cnt_editor = e_html_editor_get_content_editor (editor);
1970 
1971 	if (!e_content_editor_get_html_mode (cnt_editor) &&
1972 	    gtk_targets_include_image (targets, n_targets, TRUE)) {
1973 		e_composer_paste_image (composer, clipboard);
1974 		return;
1975 	}
1976 
1977 	if (gtk_targets_include_uri (targets, n_targets)) {
1978 		e_composer_paste_uris (composer, clipboard);
1979 		return;
1980 	}
1981 
1982 	/* Order is important here to ensure common use cases are
1983 	 * handled correctly.  See GNOME bug #603715 for details. */
1984 	if (gtk_targets_include_text (targets, n_targets) ||
1985 	    e_targets_include_html (targets, n_targets)) {
1986 		if (composer->priv->last_signal_was_paste_primary) {
1987 			e_content_editor_paste_primary (cnt_editor);
1988 		} else
1989 			e_content_editor_paste (cnt_editor);
1990 		return;
1991 	}
1992 
1993 	if (composer->priv->last_signal_was_paste_primary) {
1994 		e_content_editor_paste_primary (cnt_editor);
1995 	} else
1996 		e_content_editor_paste (cnt_editor);
1997 }
1998 
1999 static gboolean
msg_composer_paste_primary_clipboard_cb(EContentEditor * cnt_editor,EMsgComposer * composer)2000 msg_composer_paste_primary_clipboard_cb (EContentEditor *cnt_editor,
2001                                          EMsgComposer *composer)
2002 {
2003 	GtkClipboard *clipboard;
2004 	GdkAtom *targets = NULL;
2005 	gint n_targets;
2006 
2007 	clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
2008 
2009 	composer->priv->last_signal_was_paste_primary = TRUE;
2010 
2011 	if (gtk_clipboard_wait_for_targets (clipboard, &targets, &n_targets)) {
2012 		msg_composer_paste_clipboard_targets_cb (clipboard, targets, n_targets, composer);
2013 		g_free (targets);
2014 	}
2015 
2016 	return TRUE;
2017 }
2018 
2019 static gboolean
msg_composer_paste_clipboard_cb(EContentEditor * cnt_editor,EMsgComposer * composer)2020 msg_composer_paste_clipboard_cb (EContentEditor *cnt_editor,
2021                                  EMsgComposer *composer)
2022 {
2023 	GtkClipboard *clipboard;
2024 	GdkAtom *targets = NULL;
2025 	gint n_targets;
2026 
2027 	clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
2028 
2029 	composer->priv->last_signal_was_paste_primary = FALSE;
2030 
2031 	if (gtk_clipboard_wait_for_targets (clipboard, &targets, &n_targets)) {
2032 		msg_composer_paste_clipboard_targets_cb (clipboard, targets, n_targets, composer);
2033 		g_free (targets);
2034 	}
2035 
2036 	return TRUE;
2037 }
2038 
2039 static void
msg_composer_drag_data_received_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection,guint info,guint time,EMsgComposer * composer)2040 msg_composer_drag_data_received_cb (GtkWidget *widget,
2041                                     GdkDragContext *context,
2042                                     gint x,
2043                                     gint y,
2044                                     GtkSelectionData *selection,
2045                                     guint info,
2046                                     guint time,
2047                                     EMsgComposer *composer)
2048 {
2049 	EHTMLEditor *editor;
2050 	EContentEditor *cnt_editor;
2051 	gboolean html_mode, is_move;
2052 
2053 	editor = e_msg_composer_get_editor (composer);
2054 	cnt_editor = e_html_editor_get_content_editor (editor);
2055 	html_mode = e_content_editor_get_html_mode (cnt_editor);
2056 
2057 	g_signal_handler_disconnect (cnt_editor, composer->priv->drag_data_received_handler_id);
2058 	composer->priv->drag_data_received_handler_id = 0;
2059 
2060 	is_move = gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE;
2061 
2062 	/* HTML mode has a few special cases for drops... */
2063 	/* If we're receiving URIs and -all- the URIs point to
2064 	 * image files, we want the image(s) to be inserted in
2065 	 * the message body. */
2066 	if (html_mode &&
2067 	    (e_composer_selection_is_image_uris (composer, selection) ||
2068 	     e_composer_selection_is_base64_uris (composer, selection))) {
2069 		const guchar *data;
2070 		gint length;
2071 		gint list_len, len;
2072 		gchar *uri;
2073 
2074 		data = gtk_selection_data_get_data (selection);
2075 		length = gtk_selection_data_get_length (selection);
2076 
2077 		if (!data || length < 0) {
2078 			gtk_drag_finish (context, FALSE, FALSE, time);
2079 			return;
2080 		}
2081 
2082 		e_content_editor_move_caret_on_coordinates (cnt_editor, x, y, FALSE);
2083 
2084 		list_len = length;
2085 		do {
2086 			uri = e_util_next_uri_from_uri_list ((guchar **) &data, &len, &list_len);
2087 			e_content_editor_insert_image (cnt_editor, uri);
2088 			g_free (uri);
2089 		} while (list_len);
2090 
2091 		gtk_drag_finish (context, TRUE, is_move, time);
2092 	} else {
2093 		EAttachmentView *attachment_view =
2094 			e_msg_composer_get_attachment_view (composer);
2095 		/* Forward the data to the attachment view.  Note that calling
2096 		 * e_attachment_view_drag_data_received() will not work because
2097 		 * that function only handles the case where all the other drag
2098 		 * handlers have failed. */
2099 		e_attachment_paned_drag_data_received (
2100 			E_ATTACHMENT_PANED (attachment_view),
2101 			context, x, y, selection, info, time);
2102 	}
2103 }
2104 
2105 static gboolean
msg_composer_drag_drop_cb(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,EMsgComposer * composer)2106 msg_composer_drag_drop_cb (GtkWidget *widget,
2107                            GdkDragContext *context,
2108                            gint x,
2109                            gint y,
2110                            guint time,
2111                            EMsgComposer *composer)
2112 {
2113 	GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL);
2114 
2115 	if (target == GDK_NONE) {
2116 		gdk_drag_status (context, 0, time);
2117 	} else {
2118 		composer->priv->drag_data_received_handler_id = g_signal_connect (
2119 			E_CONTENT_EDITOR (widget), "drag-data-received",
2120 			G_CALLBACK (msg_composer_drag_data_received_cb), composer);
2121 
2122 		gtk_drag_get_data (widget, context, target, time);
2123 
2124 		return TRUE;
2125 	}
2126 
2127 	return FALSE;
2128 }
2129 
2130 static void
msg_composer_drop_handled_cb(EContentEditor * cnt_editor,EMsgComposer * composer)2131 msg_composer_drop_handled_cb (EContentEditor *cnt_editor,
2132                               EMsgComposer *composer)
2133 {
2134 	if (composer->priv->drag_data_received_handler_id != 0) {
2135 		g_signal_handler_disconnect (cnt_editor, composer->priv->drag_data_received_handler_id);
2136 		composer->priv->drag_data_received_handler_id = 0;
2137 	}
2138 }
2139 
2140 static void
msg_composer_drag_begin_cb(GtkWidget * widget,GdkDragContext * context,EMsgComposer * composer)2141 msg_composer_drag_begin_cb (GtkWidget *widget,
2142                             GdkDragContext *context,
2143                             EMsgComposer *composer)
2144 {
2145 	if (composer->priv->drag_data_received_handler_id != 0) {
2146 		g_signal_handler_disconnect (E_CONTENT_EDITOR( widget), composer->priv->drag_data_received_handler_id);
2147 		composer->priv->drag_data_received_handler_id = 0;
2148 	}
2149 }
2150 
2151 static void
msg_composer_notify_header_cb(EMsgComposer * composer)2152 msg_composer_notify_header_cb (EMsgComposer *composer)
2153 {
2154 	EContentEditor *cnt_editor;
2155 	EHTMLEditor *editor;
2156 
2157 	editor = e_msg_composer_get_editor (composer);
2158 	cnt_editor = e_html_editor_get_content_editor (editor);
2159 	e_content_editor_set_changed (cnt_editor, TRUE);
2160 }
2161 
2162 static gboolean
msg_composer_delete_event_cb(EMsgComposer * composer)2163 msg_composer_delete_event_cb (EMsgComposer *composer)
2164 {
2165 	/* If the "async" action group is insensitive, it means an
2166 	 * asynchronous operation is in progress.  Block the event. */
2167 	if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
2168 		return TRUE;
2169 
2170 	gtk_action_activate (ACTION (CLOSE));
2171 
2172 	return TRUE;
2173 }
2174 
2175 static void
msg_composer_realize_cb(EMsgComposer * composer)2176 msg_composer_realize_cb (EMsgComposer *composer)
2177 {
2178 	GSettings *settings;
2179 	GtkAction *action;
2180 
2181 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2182 
2183 	action = ACTION (TOOLBAR_PGP_SIGN);
2184 	if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2185 		gtk_action_set_visible (action, FALSE);
2186 
2187 	action = ACTION (TOOLBAR_PGP_ENCRYPT);
2188 	if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2189 		gtk_action_set_visible (action, FALSE);
2190 
2191 	action = ACTION (TOOLBAR_SMIME_SIGN);
2192 	if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2193 		gtk_action_set_visible (action, FALSE);
2194 
2195 	action = ACTION (TOOLBAR_SMIME_ENCRYPT);
2196 	if (gtk_action_get_visible (action) && !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
2197 		gtk_action_set_visible (action, FALSE);
2198 
2199 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
2200 
2201 	if (g_settings_get_boolean (settings, "composer-toolbar-show-sign-encrypt")) {
2202 		EComposerHeaderTable *table;
2203 		ESource *source;
2204 		gchar *identity_uid;
2205 
2206 		table = e_msg_composer_get_header_table (composer);
2207 		identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
2208 		source = e_composer_header_table_ref_source (table, identity_uid);
2209 
2210 		if (source) {
2211 			if (e_source_has_extension (source, E_SOURCE_EXTENSION_OPENPGP)) {
2212 				gchar *key_id;
2213 
2214 				key_id = e_source_openpgp_dup_key_id (e_source_get_extension (source, E_SOURCE_EXTENSION_OPENPGP));
2215 
2216 				if (key_id && *key_id) {
2217 					action = ACTION (TOOLBAR_PGP_SIGN);
2218 					gtk_action_set_visible (action, TRUE);
2219 
2220 					action = ACTION (TOOLBAR_PGP_ENCRYPT);
2221 					gtk_action_set_visible (action, TRUE);
2222 				}
2223 
2224 				g_free (key_id);
2225 			}
2226 
2227 			if (e_source_has_extension (source, E_SOURCE_EXTENSION_SMIME)) {
2228 				ESourceSMIME *smime_extension;
2229 				gchar *certificate;
2230 
2231 				smime_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_SMIME);
2232 
2233 				certificate = e_source_smime_dup_signing_certificate (smime_extension);
2234 				if (certificate && *certificate)
2235 					gtk_action_set_visible (ACTION (TOOLBAR_SMIME_SIGN), TRUE);
2236 				g_free (certificate);
2237 
2238 				certificate = e_source_smime_dup_encryption_certificate (smime_extension);
2239 				if (certificate && *certificate)
2240 					gtk_action_set_visible (ACTION (TOOLBAR_SMIME_ENCRYPT), TRUE);
2241 				g_free (certificate);
2242 			}
2243 
2244 			g_clear_object (&source);
2245 		}
2246 
2247 		g_free (identity_uid);
2248 	}
2249 
2250 	g_clear_object (&settings);
2251 }
2252 
2253 static void
msg_composer_prepare_for_quit_cb(EShell * shell,EActivity * activity,EMsgComposer * composer)2254 msg_composer_prepare_for_quit_cb (EShell *shell,
2255                                   EActivity *activity,
2256                                   EMsgComposer *composer)
2257 {
2258 	if (e_msg_composer_is_exiting (composer)) {
2259 		/* needs save draft first */
2260 		g_object_ref (activity);
2261 		g_object_weak_ref (
2262 			G_OBJECT (composer), (GWeakNotify)
2263 			g_object_unref, activity);
2264 		gtk_action_activate (ACTION (SAVE_DRAFT));
2265 	}
2266 }
2267 
2268 static void
msg_composer_quit_requested_cb(EShell * shell,EShellQuitReason reason,EMsgComposer * composer)2269 msg_composer_quit_requested_cb (EShell *shell,
2270                                 EShellQuitReason reason,
2271                                 EMsgComposer *composer)
2272 {
2273 	if (e_msg_composer_is_exiting (composer)) {
2274 		g_signal_handlers_disconnect_by_func (
2275 			shell, msg_composer_quit_requested_cb, composer);
2276 		g_signal_handlers_disconnect_by_func (
2277 			shell, msg_composer_prepare_for_quit_cb, composer);
2278 	} else if (!e_msg_composer_can_close (composer, FALSE) &&
2279 			!e_msg_composer_is_exiting (composer)) {
2280 		e_shell_cancel_quit (shell);
2281 	}
2282 }
2283 
2284 static void
msg_composer_set_editor(EMsgComposer * composer,EHTMLEditor * editor)2285 msg_composer_set_editor (EMsgComposer *composer,
2286 			 EHTMLEditor *editor)
2287 {
2288 	g_return_if_fail (E_IS_HTML_EDITOR (editor));
2289 	g_return_if_fail (composer->priv->editor == NULL);
2290 
2291 	composer->priv->editor = g_object_ref_sink (editor);
2292 }
2293 
2294 static void
msg_composer_set_shell(EMsgComposer * composer,EShell * shell)2295 msg_composer_set_shell (EMsgComposer *composer,
2296                         EShell *shell)
2297 {
2298 	g_return_if_fail (E_IS_SHELL (shell));
2299 	g_return_if_fail (composer->priv->shell == NULL);
2300 
2301 	composer->priv->shell = shell;
2302 
2303 	g_object_add_weak_pointer (
2304 		G_OBJECT (shell), &composer->priv->shell);
2305 }
2306 
2307 static void
msg_composer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)2308 msg_composer_set_property (GObject *object,
2309                            guint property_id,
2310                            const GValue *value,
2311                            GParamSpec *pspec)
2312 {
2313 	switch (property_id) {
2314 		case PROP_EDITOR:
2315 			msg_composer_set_editor (
2316 				E_MSG_COMPOSER (object),
2317 				g_value_get_object (value));
2318 			return;
2319 
2320 		case PROP_IS_REPLY_OR_FORWARD:
2321 			e_msg_composer_set_is_reply_or_forward (
2322 				E_MSG_COMPOSER (object),
2323 				g_value_get_boolean (value));
2324 			return;
2325 
2326 		case PROP_SHELL:
2327 			msg_composer_set_shell (
2328 				E_MSG_COMPOSER (object),
2329 				g_value_get_object (value));
2330 			return;
2331 	}
2332 
2333 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2334 }
2335 
2336 static void
msg_composer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)2337 msg_composer_get_property (GObject *object,
2338                            guint property_id,
2339                            GValue *value,
2340                            GParamSpec *pspec)
2341 {
2342 	switch (property_id) {
2343 		case PROP_BUSY:
2344 			g_value_set_boolean (
2345 				value, e_msg_composer_is_busy (
2346 				E_MSG_COMPOSER (object)));
2347 			return;
2348 
2349 		case PROP_SOFT_BUSY:
2350 			g_value_set_boolean (
2351 				value, e_msg_composer_is_soft_busy (
2352 				E_MSG_COMPOSER (object)));
2353 			return;
2354 
2355 		case PROP_EDITOR:
2356 			g_value_set_object (
2357 				value, e_msg_composer_get_editor (
2358 				E_MSG_COMPOSER (object)));
2359 			return;
2360 
2361 		case PROP_FOCUS_TRACKER:
2362 			g_value_set_object (
2363 				value, e_msg_composer_get_focus_tracker (
2364 				E_MSG_COMPOSER (object)));
2365 			return;
2366 
2367 		case PROP_IS_REPLY_OR_FORWARD:
2368 			g_value_set_boolean (
2369 				value, e_msg_composer_get_is_reply_or_forward (
2370 				E_MSG_COMPOSER (object)));
2371 			return;
2372 
2373 		case PROP_SHELL:
2374 			g_value_set_object (
2375 				value, e_msg_composer_get_shell (
2376 				E_MSG_COMPOSER (object)));
2377 			return;
2378 	}
2379 
2380 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2381 }
2382 
2383 static void
msg_composer_finalize(GObject * object)2384 msg_composer_finalize (GObject *object)
2385 {
2386 	EMsgComposer *composer = E_MSG_COMPOSER (object);
2387 
2388 	e_composer_private_finalize (composer);
2389 
2390 	/* Chain up to parent's finalize() method. */
2391 	G_OBJECT_CLASS (e_msg_composer_parent_class)->finalize (object);
2392 }
2393 
2394 static void
msg_composer_gallery_drag_data_get(GtkIconView * icon_view,GdkDragContext * context,GtkSelectionData * selection_data,guint target_type,guint time)2395 msg_composer_gallery_drag_data_get (GtkIconView *icon_view,
2396                                     GdkDragContext *context,
2397                                     GtkSelectionData *selection_data,
2398                                     guint target_type,
2399                                     guint time)
2400 {
2401 	GtkTreePath *path;
2402 	GtkCellRenderer *cell;
2403 	GtkTreeModel *model;
2404 	GtkTreeIter iter;
2405 	GdkAtom target;
2406 	gchar *str_data;
2407 
2408 	if (!gtk_icon_view_get_cursor (icon_view, &path, &cell))
2409 		return;
2410 
2411 	target = gtk_selection_data_get_target (selection_data);
2412 
2413 	model = gtk_icon_view_get_model (icon_view);
2414 	gtk_tree_model_get_iter (model, &iter, path);
2415 	gtk_tree_model_get (model, &iter, 1, &str_data, -1);
2416 	gtk_tree_path_free (path);
2417 
2418 	/* only supports "text/uri-list" */
2419 	gtk_selection_data_set (
2420 		selection_data, target, 8,
2421 		(guchar *) str_data, strlen (str_data));
2422 	g_free (str_data);
2423 }
2424 
2425 static void
composer_notify_activity_cb(EActivityBar * activity_bar,GParamSpec * pspec,EMsgComposer * composer)2426 composer_notify_activity_cb (EActivityBar *activity_bar,
2427                              GParamSpec *pspec,
2428                              EMsgComposer *composer)
2429 {
2430 	EHTMLEditor *editor;
2431 	EContentEditor *cnt_editor;
2432 	gboolean has_activities;
2433 
2434 	has_activities = (e_activity_bar_get_activity (activity_bar) != NULL);
2435 
2436 	if (has_activities == composer->priv->had_activities)
2437 		return;
2438 
2439 	composer->priv->had_activities = has_activities;
2440 
2441 	editor = e_msg_composer_get_editor (composer);
2442 	cnt_editor = e_html_editor_get_content_editor (editor);
2443 
2444 	if (has_activities) {
2445 		e_msg_composer_save_focused_widget (composer);
2446 
2447 		composer->priv->saved_editable = e_content_editor_is_editable (cnt_editor);
2448 		e_content_editor_set_editable (cnt_editor, FALSE);
2449 	} else {
2450 		e_content_editor_set_editable (cnt_editor, composer->priv->saved_editable);
2451 
2452 		e_msg_composer_restore_focus_on_composer (composer);
2453 	}
2454 
2455 	g_object_notify (G_OBJECT (composer), "busy");
2456 	g_object_notify (G_OBJECT (composer), "soft-busy");
2457 }
2458 
2459 static void
msg_composer_constructed(GObject * object)2460 msg_composer_constructed (GObject *object)
2461 {
2462 	EShell *shell;
2463 	EMsgComposer *composer;
2464 	EActivityBar *activity_bar;
2465 	EAttachmentView *attachment_view;
2466 	EAttachmentStore *store;
2467 	EComposerHeaderTable *table;
2468 	EHTMLEditor *editor;
2469 	EContentEditor *cnt_editor;
2470 	GtkUIManager *ui_manager;
2471 	GtkToggleAction *action;
2472 	GtkTargetList *target_list;
2473 	GtkTargetEntry *targets;
2474 	gint n_targets;
2475 	GSettings *settings;
2476 	const gchar *id;
2477 	gboolean active;
2478 
2479 	/* Chain up to parent's constructed() method. */
2480 	G_OBJECT_CLASS (e_msg_composer_parent_class)->constructed (object);
2481 
2482 	composer = E_MSG_COMPOSER (object);
2483 
2484 	g_return_if_fail (E_IS_HTML_EDITOR (composer->priv->editor));
2485 
2486 	shell = e_msg_composer_get_shell (composer);
2487 
2488 	e_composer_private_constructed (composer);
2489 
2490 	editor = e_msg_composer_get_editor (composer);
2491 	cnt_editor = e_html_editor_get_content_editor (editor);
2492 	ui_manager = e_html_editor_get_ui_manager (editor);
2493 	attachment_view = e_msg_composer_get_attachment_view (composer);
2494 	table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2495 
2496 	gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message"));
2497 	gtk_window_set_icon_name (GTK_WINDOW (composer), "mail-message-new");
2498 	gtk_window_set_default_size (GTK_WINDOW (composer), 600, 500);
2499 	gtk_window_set_position (GTK_WINDOW (composer), GTK_WIN_POS_CENTER);
2500 
2501 	g_signal_connect (
2502 		object, "delete-event",
2503 		G_CALLBACK (msg_composer_delete_event_cb), NULL);
2504 
2505 	g_signal_connect (
2506 		object, "realize",
2507 		G_CALLBACK (msg_composer_realize_cb), NULL);
2508 
2509 	gtk_application_add_window (
2510 		GTK_APPLICATION (shell), GTK_WINDOW (object));
2511 
2512 	g_signal_connect (
2513 		shell, "quit-requested",
2514 		G_CALLBACK (msg_composer_quit_requested_cb), composer);
2515 
2516 	g_signal_connect (
2517 		shell, "prepare-for-quit",
2518 		G_CALLBACK (msg_composer_prepare_for_quit_cb), composer);
2519 
2520 	/* Restore Persistent State */
2521 
2522 	e_restore_window (
2523 		GTK_WINDOW (composer),
2524 		"/org/gnome/evolution/mail/composer-window/",
2525 		E_RESTORE_WINDOW_SIZE);
2526 
2527 	activity_bar = e_html_editor_get_activity_bar (editor);
2528 	g_signal_connect (
2529 		activity_bar, "notify::activity",
2530 		G_CALLBACK (composer_notify_activity_cb), composer);
2531 
2532 
2533 	/* Honor User Preferences */
2534 
2535 	/* FIXME This should be an EMsgComposer property. */
2536 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
2537 	action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
2538 	active = g_settings_get_boolean (settings, "composer-request-receipt");
2539 	gtk_toggle_action_set_active (action, active);
2540 
2541 	g_object_unref (settings);
2542 
2543 	/* Clipboard Support */
2544 
2545 	g_signal_connect (
2546 		cnt_editor, "paste-clipboard",
2547 		G_CALLBACK (msg_composer_paste_clipboard_cb), composer);
2548 
2549 	g_signal_connect (
2550 		cnt_editor, "paste-primary-clipboard",
2551 		G_CALLBACK (msg_composer_paste_primary_clipboard_cb), composer);
2552 
2553 	/* Drag-and-Drop Support */
2554 	g_signal_connect (
2555 		cnt_editor, "drag-drop",
2556 		G_CALLBACK (msg_composer_drag_drop_cb), composer);
2557 
2558 	g_signal_connect (
2559 		cnt_editor, "drag-begin",
2560 		G_CALLBACK (msg_composer_drag_begin_cb), composer);
2561 
2562 	g_signal_connect (
2563 		cnt_editor, "drop-handled",
2564 		G_CALLBACK (msg_composer_drop_handled_cb), composer);
2565 
2566 	g_signal_connect (
2567 		composer->priv->gallery_icon_view, "drag-data-get",
2568 		G_CALLBACK (msg_composer_gallery_drag_data_get), NULL);
2569 
2570 	/* Configure Headers */
2571 
2572 	composer->priv->notify_destinations_bcc_handler = e_signal_connect_notify_swapped (
2573 		table, "notify::destinations-bcc",
2574 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2575 	composer->priv->notify_destinations_cc_handler = e_signal_connect_notify_swapped (
2576 		table, "notify::destinations-cc",
2577 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2578 	composer->priv->notify_destinations_to_handler = e_signal_connect_notify_swapped (
2579 		table, "notify::destinations-to",
2580 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2581 	/* Do not use e_signal_connect_notify_swapped() here, it it avoids notification
2582 	   when the property didn't change, but it's about the consolidated property,
2583 	   identity uid, name and address, where only one of the three can change. */
2584 	composer->priv->notify_identity_uid_handler = g_signal_connect_swapped (
2585 		table, "notify::identity-uid",
2586 		G_CALLBACK (msg_composer_mail_identity_changed_cb), composer);
2587 	composer->priv->notify_reply_to_handler = e_signal_connect_notify_swapped (
2588 		table, "notify::reply-to",
2589 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2590 	composer->priv->notify_signature_uid_handler = e_signal_connect_notify_swapped (
2591 		table, "notify::signature-uid",
2592 		G_CALLBACK (e_composer_update_signature), composer);
2593 	composer->priv->notify_subject_changed_handler = e_signal_connect_notify_swapped (
2594 		table, "notify::subject",
2595 		G_CALLBACK (msg_composer_subject_changed_cb), composer);
2596 	composer->priv->notify_subject_handler = e_signal_connect_notify_swapped (
2597 		table, "notify::subject",
2598 		G_CALLBACK (msg_composer_notify_header_cb), composer);
2599 
2600 	msg_composer_mail_identity_changed_cb (composer);
2601 
2602 	/* Attachments */
2603 
2604 	store = e_attachment_view_get_store (attachment_view);
2605 
2606 	g_signal_connect_swapped (
2607 		store, "row-deleted",
2608 		G_CALLBACK (attachment_store_changed_cb), composer);
2609 
2610 	g_signal_connect_swapped (
2611 		store, "row-inserted",
2612 		G_CALLBACK (attachment_store_changed_cb), composer);
2613 
2614 	/* Initialization may have tripped the "changed" state. */
2615 	e_content_editor_set_changed (cnt_editor, FALSE);
2616 
2617 	target_list = e_attachment_view_get_target_list (attachment_view);
2618 	targets = gtk_target_table_new_from_list (target_list, &n_targets);
2619 
2620 	target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (cnt_editor));
2621 
2622 	gtk_target_list_add_table (target_list, drag_dest_targets, G_N_ELEMENTS (drag_dest_targets));
2623 	gtk_target_list_add_table (target_list, targets, n_targets);
2624 
2625 	gtk_target_table_free (targets, n_targets);
2626 
2627 	id = "org.gnome.evolution.composer";
2628 	e_plugin_ui_register_manager (ui_manager, id, composer);
2629 	e_plugin_ui_enable_manager (ui_manager, id);
2630 
2631 	e_extensible_load_extensions (E_EXTENSIBLE (composer));
2632 
2633 	e_msg_composer_set_body_text (composer, "", TRUE);
2634 }
2635 
2636 static void
msg_composer_dispose(GObject * object)2637 msg_composer_dispose (GObject *object)
2638 {
2639 	EMsgComposer *composer = E_MSG_COMPOSER (object);
2640 	EMsgComposerPrivate *priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2641 	EShell *shell;
2642 
2643 	g_clear_pointer (&priv->address_dialog, gtk_widget_destroy);
2644 
2645 	/* FIXME Our EShell is already unreferenced. */
2646 	shell = e_shell_get_default ();
2647 
2648 	g_signal_handlers_disconnect_by_func (
2649 		shell, msg_composer_quit_requested_cb, composer);
2650 	g_signal_handlers_disconnect_by_func (
2651 		shell, msg_composer_prepare_for_quit_cb, composer);
2652 
2653 	if (priv->header_table != NULL) {
2654 		EComposerHeaderTable *table;
2655 
2656 		table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
2657 
2658 		e_signal_disconnect_notify_handler (
2659 			table, &priv->notify_destinations_bcc_handler);
2660 		e_signal_disconnect_notify_handler (
2661 			table, &priv->notify_destinations_cc_handler);
2662 		e_signal_disconnect_notify_handler (
2663 			table, &priv->notify_destinations_to_handler);
2664 		e_signal_disconnect_notify_handler (
2665 			table, &priv->notify_identity_uid_handler);
2666 		e_signal_disconnect_notify_handler (
2667 			table, &priv->notify_reply_to_handler);
2668 		e_signal_disconnect_notify_handler (
2669 			table, &priv->notify_destinations_to_handler);
2670 		e_signal_disconnect_notify_handler (
2671 			table, &priv->notify_subject_changed_handler);
2672 	}
2673 
2674 	e_composer_private_dispose (composer);
2675 
2676 	/* Chain up to parent's dispose() method. */
2677 	G_OBJECT_CLASS (e_msg_composer_parent_class)->dispose (object);
2678 }
2679 
2680 static void
msg_composer_map(GtkWidget * widget)2681 msg_composer_map (GtkWidget *widget)
2682 {
2683 	EMsgComposer *composer;
2684 	EComposerHeaderTable *table;
2685 	GtkWidget *input_widget;
2686 	EHTMLEditor *editor;
2687 	EContentEditor *cnt_editor;
2688 	const gchar *text;
2689 
2690 	/* Chain up to parent's map() method. */
2691 	GTK_WIDGET_CLASS (e_msg_composer_parent_class)->map (widget);
2692 
2693 	composer = E_MSG_COMPOSER (widget);
2694 	editor = e_msg_composer_get_editor (composer);
2695 	table = e_msg_composer_get_header_table (composer);
2696 
2697 	/* If the 'To' field is empty, focus it. */
2698 	input_widget =
2699 		e_composer_header_table_get_header (
2700 		table, E_COMPOSER_HEADER_TO)->input_widget;
2701 	text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2702 	if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2703 		gtk_widget_grab_focus (input_widget);
2704 		return;
2705 	}
2706 
2707 	/* If not, check the 'Subject' field. */
2708 	input_widget =
2709 		e_composer_header_table_get_header (
2710 		table, E_COMPOSER_HEADER_SUBJECT)->input_widget;
2711 	text = gtk_entry_get_text (GTK_ENTRY (input_widget));
2712 	if (gtk_widget_get_visible (input_widget) && (text == NULL || *text == '\0')) {
2713 		gtk_widget_grab_focus (input_widget);
2714 		return;
2715 	}
2716 
2717 	/* Jump to the editor as a last resort. */
2718 	cnt_editor = e_html_editor_get_content_editor (editor);
2719 	gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
2720 }
2721 
2722 static gboolean
msg_composer_key_press_event(GtkWidget * widget,GdkEventKey * event)2723 msg_composer_key_press_event (GtkWidget *widget,
2724                               GdkEventKey *event)
2725 {
2726 	EMsgComposer *composer;
2727 	GtkWidget *input_widget;
2728 	EHTMLEditor *editor;
2729 	EContentEditor *cnt_editor;
2730 
2731 	composer = E_MSG_COMPOSER (widget);
2732 	editor = e_msg_composer_get_editor (composer);
2733 	cnt_editor = e_html_editor_get_content_editor (editor);
2734 
2735 	input_widget =
2736 		e_composer_header_table_get_header (
2737 		e_msg_composer_get_header_table (composer),
2738 		E_COMPOSER_HEADER_SUBJECT)->input_widget;
2739 
2740 #ifdef HAVE_XFREE
2741 	if (event->keyval == XF86XK_Send) {
2742 		e_msg_composer_send (composer);
2743 		return TRUE;
2744 	}
2745 #endif /* HAVE_XFREE */
2746 
2747 	if (event->keyval == GDK_KEY_Escape) {
2748 		gtk_action_activate (ACTION (CLOSE));
2749 		return TRUE;
2750 	}
2751 
2752 	if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
2753 		gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
2754 		return TRUE;
2755 	}
2756 
2757 	if (gtk_widget_is_focus (GTK_WIDGET (cnt_editor))) {
2758 		if (event->keyval == GDK_KEY_ISO_Left_Tab) {
2759 			gboolean view_processed = FALSE;
2760 
2761 			g_signal_emit_by_name (cnt_editor, "key-press-event", event, &view_processed);
2762 
2763 			if (!view_processed)
2764 				gtk_widget_grab_focus (input_widget);
2765 
2766 			return TRUE;
2767 		}
2768 	}
2769 
2770 	if (e_util_check_gtk_bindings_in_key_press_event_cb (widget, (GdkEvent *) event))
2771 		return TRUE;
2772 
2773 	/* Chain up to parent's key_press_event() method. */
2774 	return GTK_WIDGET_CLASS (e_msg_composer_parent_class)->
2775 		key_press_event (widget, event);
2776 }
2777 
2778 static gboolean
msg_composer_presend(EMsgComposer * composer)2779 msg_composer_presend (EMsgComposer *composer)
2780 {
2781 	/* This keeps the signal accumulator at TRUE. */
2782 	return TRUE;
2783 }
2784 
2785 static gboolean
msg_composer_accumulator_false_abort(GSignalInvocationHint * ihint,GValue * return_accu,const GValue * handler_return,gpointer dummy)2786 msg_composer_accumulator_false_abort (GSignalInvocationHint *ihint,
2787                                       GValue *return_accu,
2788                                       const GValue *handler_return,
2789                                       gpointer dummy)
2790 {
2791 	gboolean v_boolean;
2792 
2793 	v_boolean = g_value_get_boolean (handler_return);
2794 	g_value_set_boolean (return_accu, v_boolean);
2795 
2796 	/* FALSE means abort the signal emission. */
2797 	return v_boolean;
2798 }
2799 
2800 /**
2801  * e_msg_composer_is_busy:
2802  * @composer: an #EMsgComposer
2803  *
2804  * Returns %TRUE only while an #EActivity is in progress.
2805  *
2806  * Returns: whether @composer is busy
2807  **/
2808 gboolean
e_msg_composer_is_busy(EMsgComposer * composer)2809 e_msg_composer_is_busy (EMsgComposer *composer)
2810 {
2811 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
2812 
2813 	return composer->priv->had_activities;
2814 }
2815 
2816 /**
2817  * e_msg_composer_is_soft_busy:
2818  * @composer: an #EMsgComposer
2819  *
2820  * Returns: %TRUE when e_msg_composer_is_busy() returns %TRUE or
2821  *    when the asynchronous operations are disabled.
2822  *
2823  * Since: 3.30
2824  **/
2825 gboolean
e_msg_composer_is_soft_busy(EMsgComposer * composer)2826 e_msg_composer_is_soft_busy (EMsgComposer *composer)
2827 {
2828 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
2829 
2830 	return composer->priv->soft_busy_count > 0 || e_msg_composer_is_busy (composer);
2831 }
2832 
2833 static void
e_msg_composer_class_init(EMsgComposerClass * class)2834 e_msg_composer_class_init (EMsgComposerClass *class)
2835 {
2836 	GObjectClass *object_class;
2837 	GtkWidgetClass *widget_class;
2838 
2839 	g_type_class_add_private (class, sizeof (EMsgComposerPrivate));
2840 
2841 	object_class = G_OBJECT_CLASS (class);
2842 	object_class->set_property = msg_composer_set_property;
2843 	object_class->get_property = msg_composer_get_property;
2844 	object_class->dispose = msg_composer_dispose;
2845 	object_class->finalize = msg_composer_finalize;
2846 	object_class->constructed = msg_composer_constructed;
2847 
2848 	widget_class = GTK_WIDGET_CLASS (class);
2849 	widget_class->map = msg_composer_map;
2850 	widget_class->key_press_event = msg_composer_key_press_event;
2851 
2852 	class->presend = msg_composer_presend;
2853 
2854 	g_object_class_install_property (
2855 		object_class,
2856 		PROP_BUSY,
2857 		g_param_spec_boolean (
2858 			"busy",
2859 			"Busy",
2860 			"Whether an activity is in progress",
2861 			FALSE,
2862 			G_PARAM_READABLE |
2863 			G_PARAM_STATIC_STRINGS));
2864 
2865 	g_object_class_install_property (
2866 		object_class,
2867 		PROP_SOFT_BUSY,
2868 		g_param_spec_boolean (
2869 			"soft-busy",
2870 			"Soft Busy",
2871 			"Whether asynchronous actions are disabled",
2872 			FALSE,
2873 			G_PARAM_READABLE |
2874 			G_PARAM_STATIC_STRINGS));
2875 
2876 	g_object_class_install_property (
2877 		object_class,
2878 		PROP_EDITOR,
2879 		g_param_spec_object (
2880 			"editor",
2881 			NULL,
2882 			NULL,
2883 			E_TYPE_HTML_EDITOR,
2884 			G_PARAM_READWRITE |
2885 			G_PARAM_CONSTRUCT_ONLY));
2886 
2887 	g_object_class_install_property (
2888 		object_class,
2889 		PROP_FOCUS_TRACKER,
2890 		g_param_spec_object (
2891 			"focus-tracker",
2892 			NULL,
2893 			NULL,
2894 			E_TYPE_FOCUS_TRACKER,
2895 			G_PARAM_READABLE));
2896 
2897 	g_object_class_install_property (
2898 		object_class,
2899 		PROP_IS_REPLY_OR_FORWARD,
2900 		g_param_spec_boolean (
2901 			"is-reply-or-forward",
2902 			"Is Reply Or Forward",
2903 			"Whether the composed message is a reply or a forward message",
2904 			FALSE,
2905 			G_PARAM_READWRITE |
2906 			G_PARAM_STATIC_STRINGS));
2907 
2908 	g_object_class_install_property (
2909 		object_class,
2910 		PROP_SHELL,
2911 		g_param_spec_object (
2912 			"shell",
2913 			"Shell",
2914 			"The EShell singleton",
2915 			E_TYPE_SHELL,
2916 			G_PARAM_READWRITE |
2917 			G_PARAM_CONSTRUCT_ONLY));
2918 
2919 	signals[PRESEND] = g_signal_new (
2920 		"presend",
2921 		G_OBJECT_CLASS_TYPE (class),
2922 		G_SIGNAL_RUN_LAST,
2923 		G_STRUCT_OFFSET (EMsgComposerClass, presend),
2924 		msg_composer_accumulator_false_abort,
2925 		NULL,
2926 		e_marshal_BOOLEAN__VOID,
2927 		G_TYPE_BOOLEAN, 0);
2928 
2929 	signals[SEND] = g_signal_new (
2930 		"send",
2931 		G_OBJECT_CLASS_TYPE (class),
2932 		G_SIGNAL_RUN_LAST,
2933 		G_STRUCT_OFFSET (EMsgComposerClass, send),
2934 		NULL, NULL,
2935 		e_marshal_VOID__OBJECT_OBJECT,
2936 		G_TYPE_NONE, 2,
2937 		CAMEL_TYPE_MIME_MESSAGE,
2938 		E_TYPE_ACTIVITY);
2939 
2940 	signals[SAVE_TO_DRAFTS] = g_signal_new (
2941 		"save-to-drafts",
2942 		G_OBJECT_CLASS_TYPE (class),
2943 		G_SIGNAL_RUN_LAST,
2944 		G_STRUCT_OFFSET (EMsgComposerClass, save_to_drafts),
2945 		NULL, NULL,
2946 		e_marshal_VOID__OBJECT_OBJECT,
2947 		G_TYPE_NONE, 2,
2948 		CAMEL_TYPE_MIME_MESSAGE,
2949 		E_TYPE_ACTIVITY);
2950 
2951 	signals[SAVE_TO_OUTBOX] = g_signal_new (
2952 		"save-to-outbox",
2953 		G_OBJECT_CLASS_TYPE (class),
2954 		G_SIGNAL_RUN_LAST,
2955 		G_STRUCT_OFFSET (EMsgComposerClass, save_to_outbox),
2956 		NULL, NULL,
2957 		e_marshal_VOID__OBJECT_OBJECT,
2958 		G_TYPE_NONE, 2,
2959 		CAMEL_TYPE_MIME_MESSAGE,
2960 		E_TYPE_ACTIVITY);
2961 
2962 	signals[PRINT] = g_signal_new (
2963 		"print",
2964 		G_OBJECT_CLASS_TYPE (class),
2965 		G_SIGNAL_RUN_LAST,
2966 		0, NULL, NULL,
2967 		e_marshal_VOID__ENUM_OBJECT_OBJECT,
2968 		G_TYPE_NONE, 3,
2969 		GTK_TYPE_PRINT_OPERATION_ACTION,
2970 		CAMEL_TYPE_MIME_MESSAGE,
2971 		E_TYPE_ACTIVITY);
2972 
2973 	signals[BEFORE_DESTROY] = g_signal_new (
2974 		"before-destroy",
2975 		G_OBJECT_CLASS_TYPE (class),
2976 		G_SIGNAL_RUN_LAST,
2977 		0, NULL, NULL,
2978 		g_cclosure_marshal_VOID__VOID,
2979 		G_TYPE_NONE, 0,
2980 		G_TYPE_NONE);
2981 }
2982 
2983 void
e_composer_emit_before_destroy(EMsgComposer * composer)2984 e_composer_emit_before_destroy (EMsgComposer *composer)
2985 {
2986 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
2987 
2988 	g_signal_emit (composer, signals[BEFORE_DESTROY], 0);
2989 }
2990 
2991 static void
e_msg_composer_init(EMsgComposer * composer)2992 e_msg_composer_init (EMsgComposer *composer)
2993 {
2994 	composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
2995 }
2996 
2997 static void
e_msg_composer_editor_created_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)2998 e_msg_composer_editor_created_cb (GObject *source_object,
2999 				  GAsyncResult *result,
3000 				  gpointer user_data)
3001 {
3002 	GtkWidget *editor;
3003 	ESimpleAsyncResult *eresult = user_data;
3004 	GError *error = NULL;
3005 
3006 	g_return_if_fail (E_IS_SIMPLE_ASYNC_RESULT (eresult));
3007 
3008 	editor = e_html_editor_new_finish (result, &error);
3009 	if (error) {
3010 		g_warning ("%s: Failed to create HTML editor: %s", G_STRFUNC, error->message);
3011 		g_clear_error (&error);
3012 	} else {
3013 		e_simple_async_result_set_op_pointer (eresult, editor, NULL);
3014 		e_simple_async_result_complete (eresult);
3015 	}
3016 
3017 	g_object_unref (eresult);
3018 }
3019 
3020 /**
3021  * e_msg_composer_new:
3022  * @shell: an #EShell
3023  * @callback: called when the composer is ready
3024  * @user_data: user data passed to @callback
3025  *
3026  * Asynchronously creates an #EMsgComposer. The operation is finished
3027  * with e_msg_composer_new_finish() called from within the @callback.
3028  *
3029  * Since: 3.22
3030  **/
3031 void
e_msg_composer_new(EShell * shell,GAsyncReadyCallback callback,gpointer user_data)3032 e_msg_composer_new (EShell *shell,
3033 		    GAsyncReadyCallback callback,
3034 		    gpointer user_data)
3035 {
3036 	ESimpleAsyncResult *eresult;
3037 
3038 	g_return_if_fail (E_IS_SHELL (shell));
3039 	g_return_if_fail (callback != NULL);
3040 
3041 	eresult = e_simple_async_result_new (NULL, callback, user_data, e_msg_composer_new);
3042 	e_simple_async_result_set_user_data (eresult, g_object_ref (shell), g_object_unref);
3043 
3044 	e_html_editor_new (e_msg_composer_editor_created_cb, eresult);
3045 }
3046 
3047 /**
3048  * e_msg_composer_new_finish:
3049  * @result: a #GAsyncResult provided by the callback from e_msg_composer_new()
3050  * @error: optional #GError for errors
3051  *
3052  * Finishes call of e_msg_composer_new().
3053  *
3054  * Since: 3.22
3055  **/
3056 EMsgComposer *
e_msg_composer_new_finish(GAsyncResult * result,GError ** error)3057 e_msg_composer_new_finish (GAsyncResult *result,
3058 			   GError **error)
3059 {
3060 	ESimpleAsyncResult *eresult;
3061 	EHTMLEditor *html_editor;
3062 
3063 	g_return_val_if_fail (E_IS_SIMPLE_ASYNC_RESULT (result), NULL);
3064 	g_return_val_if_fail (g_async_result_is_tagged (result, e_msg_composer_new), NULL);
3065 
3066 	eresult = E_SIMPLE_ASYNC_RESULT (result);
3067 
3068 	html_editor = e_simple_async_result_get_op_pointer (eresult);
3069 	g_return_val_if_fail (E_IS_HTML_EDITOR (html_editor), NULL);
3070 
3071 	return g_object_new (E_TYPE_MSG_COMPOSER,
3072 		"shell", e_simple_async_result_get_user_data (eresult),
3073 		"editor", html_editor,
3074 		NULL);
3075 }
3076 
3077 /**
3078  * e_msg_composer_get_editor:
3079  * @composer: an #EMsgComposer
3080  *
3081  * Returns @composer's internal #EHTMLEditor instance.
3082  *
3083  * Returns: an #EHTMLEditor
3084  **/
3085 EHTMLEditor *
e_msg_composer_get_editor(EMsgComposer * composer)3086 e_msg_composer_get_editor (EMsgComposer *composer)
3087 {
3088 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3089 
3090 	return composer->priv->editor;
3091 }
3092 
3093 EFocusTracker *
e_msg_composer_get_focus_tracker(EMsgComposer * composer)3094 e_msg_composer_get_focus_tracker (EMsgComposer *composer)
3095 {
3096 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
3097 
3098 	return composer->priv->focus_tracker;
3099 }
3100 
3101 static void
e_msg_composer_set_pending_body(EMsgComposer * composer,gchar * text,gssize length,gboolean is_html)3102 e_msg_composer_set_pending_body (EMsgComposer *composer,
3103                                  gchar *text,
3104                                  gssize length,
3105                                  gboolean is_html)
3106 {
3107 	g_object_set_data_full (
3108 		G_OBJECT (composer), "body:text_mime_type",
3109 		GINT_TO_POINTER (is_html), NULL);
3110 	g_object_set_data_full (
3111 		G_OBJECT (composer), "body:text",
3112 		text, (GDestroyNotify) g_free);
3113 }
3114 
3115 static void
e_msg_composer_flush_pending_body(EMsgComposer * composer)3116 e_msg_composer_flush_pending_body (EMsgComposer *composer)
3117 {
3118 	const gchar *body;
3119 	gboolean is_html;
3120 
3121 	body = g_object_get_data (G_OBJECT (composer), "body:text");
3122 	is_html = GPOINTER_TO_INT (
3123 		g_object_get_data (G_OBJECT (composer), "body:text_mime_type"));
3124 
3125 	if (body != NULL) {
3126 		const gchar *signature_uid;
3127 
3128 		signature_uid = e_composer_header_table_get_signature_uid (e_msg_composer_get_header_table (composer));
3129 
3130 		set_editor_text (composer, body, is_html, g_strcmp0 (signature_uid, "none") != 0);
3131 	}
3132 
3133 	g_object_set_data (G_OBJECT (composer), "body:text", NULL);
3134 }
3135 
3136 static gboolean
emc_is_attachment_part(CamelMimePart * mime_part,CamelMimePart * parent_part)3137 emc_is_attachment_part (CamelMimePart *mime_part,
3138 			CamelMimePart *parent_part)
3139 {
3140 	const CamelContentDisposition *cd;
3141 	CamelContentType *ct, *parent_ct = NULL;
3142 
3143 	g_return_val_if_fail (CAMEL_IS_MIME_PART (mime_part), FALSE);
3144 
3145 	ct = camel_mime_part_get_content_type (mime_part);
3146 	cd = camel_mime_part_get_content_disposition (mime_part);
3147 
3148 	if (parent_part)
3149 		parent_ct = camel_mime_part_get_content_type (parent_part);
3150 
3151 	if (!camel_content_disposition_is_attachment_ex (cd, ct, parent_ct))
3152 		return FALSE;
3153 
3154 	/* It looks like an attachment now. Make it an attachment for all but images
3155 	   under multipart/related, to avoid this group of false positives. */
3156 	return !(parent_ct && ct &&
3157 		camel_content_type_is (parent_ct, "multipart", "related") &&
3158 		camel_content_type_is (ct, "image", "*"));
3159 }
3160 
3161 static void
add_attachments_handle_mime_part(EMsgComposer * composer,CamelMimePart * mime_part,gboolean just_inlines,gboolean related,gint depth)3162 add_attachments_handle_mime_part (EMsgComposer *composer,
3163                                   CamelMimePart *mime_part,
3164                                   gboolean just_inlines,
3165                                   gboolean related,
3166                                   gint depth)
3167 {
3168 	CamelContentType *content_type;
3169 	CamelDataWrapper *wrapper;
3170 	EHTMLEditor *editor;
3171 
3172 	if (!mime_part)
3173 		return;
3174 
3175 	content_type = camel_mime_part_get_content_type (mime_part);
3176 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3177 	editor = e_msg_composer_get_editor (composer);
3178 
3179 	if (CAMEL_IS_MULTIPART (wrapper)) {
3180 		/* another layer of multipartness... */
3181 		add_attachments_from_multipart (
3182 			composer, (CamelMultipart *) wrapper,
3183 			just_inlines, depth + 1);
3184 	} else if (just_inlines) {
3185 		if (camel_content_type_is (content_type, "image", "*") && (
3186 		    camel_mime_part_get_content_id (mime_part) ||
3187 		    camel_mime_part_get_content_location (mime_part)))
3188 			e_html_editor_add_cid_part (editor, mime_part);
3189 	} else if (related && camel_content_type_is (content_type, "image", "*")) {
3190 		e_html_editor_add_cid_part (editor, mime_part);
3191 	} else if (camel_content_type_is (content_type, "text", "*") &&
3192 		camel_mime_part_get_filename (mime_part) == NULL) {
3193 		/* Do nothing if this is a text/anything without a
3194 		 * filename, otherwise attach it too. */
3195 	} else {
3196 		e_msg_composer_attach (composer, mime_part);
3197 	}
3198 }
3199 
3200 static void
add_attachments_from_multipart(EMsgComposer * composer,CamelMultipart * multipart,gboolean just_inlines,gint depth)3201 add_attachments_from_multipart (EMsgComposer *composer,
3202                                 CamelMultipart *multipart,
3203                                 gboolean just_inlines,
3204                                 gint depth)
3205 {
3206 	/* find appropriate message attachments to add to the composer */
3207 	CamelMimePart *mime_part;
3208 	gboolean related;
3209 	gint i, nparts;
3210 
3211 	related = camel_content_type_is (
3212 		camel_data_wrapper_get_mime_type_field (CAMEL_DATA_WRAPPER (multipart)),
3213 		"multipart", "related");
3214 
3215 	if (CAMEL_IS_MULTIPART_SIGNED (multipart)) {
3216 		mime_part = camel_multipart_get_part (
3217 			multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
3218 		add_attachments_handle_mime_part (
3219 			composer, mime_part, just_inlines, related, depth);
3220 	} else if (CAMEL_IS_MULTIPART_ENCRYPTED (multipart)) {
3221 		/* XXX What should we do in this case? */
3222 	} else {
3223 		nparts = camel_multipart_get_number (multipart);
3224 
3225 		for (i = 0; i < nparts; i++) {
3226 			mime_part = camel_multipart_get_part (multipart, i);
3227 			add_attachments_handle_mime_part (
3228 				composer, mime_part, just_inlines,
3229 				related, depth);
3230 		}
3231 	}
3232 }
3233 
3234 /**
3235  * e_msg_composer_add_message_attachments:
3236  * @composer: the composer to add the attachments to.
3237  * @message: the source message to copy the attachments from.
3238  * @just_inlines: whether to attach all attachments or just add
3239  * inline images.
3240  *
3241  * Walk through all the mime parts in @message and add them to the composer
3242  * specified in @composer.
3243  */
3244 void
e_msg_composer_add_message_attachments(EMsgComposer * composer,CamelMimeMessage * message,gboolean just_inlines)3245 e_msg_composer_add_message_attachments (EMsgComposer *composer,
3246                                         CamelMimeMessage *message,
3247                                         gboolean just_inlines)
3248 {
3249 	CamelDataWrapper *wrapper;
3250 
3251 	wrapper = camel_medium_get_content (CAMEL_MEDIUM (message));
3252 	if (!CAMEL_IS_MULTIPART (wrapper))
3253 		return;
3254 
3255 	add_attachments_from_multipart (
3256 		composer, (CamelMultipart *) wrapper, just_inlines, 0);
3257 }
3258 
3259 /**
3260  * e_msg_composer_add_attachments_from_part_list:
3261  * @composer: the composer to add the attachments to
3262  * @part_list: an #EMailPartList with parts used to format the message
3263  * @just_inlines: whether to attach all attachments or just add inline images
3264  *
3265  * Walk through all the parts in @part_list and add them to the @composer.
3266  *
3267  * Since: 3.42
3268  */
3269 void
e_msg_composer_add_attachments_from_part_list(EMsgComposer * composer,EMailPartList * part_list,gboolean just_inlines)3270 e_msg_composer_add_attachments_from_part_list (EMsgComposer *composer,
3271 					       EMailPartList *part_list,
3272 					       gboolean just_inlines)
3273 {
3274 	EHTMLEditor *editor;
3275 	GHashTable *added_mime_parts;
3276 	GQueue queue = G_QUEUE_INIT;
3277 	GList *link;
3278 
3279 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3280 
3281 	if (!part_list)
3282 		return;
3283 
3284 	/* One mime part can be in the part list multiple times */
3285 	added_mime_parts = g_hash_table_new (g_direct_hash, g_direct_equal);
3286 	editor = e_msg_composer_get_editor (composer);
3287 
3288 	e_mail_part_list_queue_parts (part_list, NULL, &queue);
3289 
3290 	for (link = g_queue_peek_head_link (&queue); link; link = g_list_next (link)) {
3291 		EMailPart *part = link->data;
3292 		CamelMimePart *mime_part;
3293 		CamelContentType *content_type;
3294 
3295 		if (!e_mail_part_get_is_attachment (part))
3296 			continue;
3297 
3298 		mime_part = e_mail_part_ref_mime_part (part);
3299 		if (!mime_part)
3300 			continue;
3301 
3302 		if (g_hash_table_contains (added_mime_parts, mime_part)) {
3303 			g_object_unref (mime_part);
3304 			continue;
3305 		}
3306 
3307 		content_type = camel_mime_part_get_content_type (mime_part);
3308 		if (!content_type) {
3309 			g_object_unref (mime_part);
3310 			continue;
3311 		}
3312 
3313 		if (!just_inlines &&
3314 		    camel_content_type_is (content_type, "text", "*") &&
3315 		    camel_mime_part_get_filename (mime_part) == NULL) {
3316 			/* Do nothing if this is a text/anything without a
3317 			 * filename, otherwise attach it too. */
3318 		} else if (camel_content_type_is (content_type, "image", "*") && (
3319 			   camel_mime_part_get_content_id (mime_part) ||
3320 			   camel_mime_part_get_content_location (mime_part))) {
3321 				e_html_editor_add_cid_part (editor, mime_part);
3322 				g_hash_table_add (added_mime_parts, mime_part);
3323 		} else if (!just_inlines) {
3324 			e_msg_composer_attach (composer, mime_part);
3325 			g_hash_table_add (added_mime_parts, mime_part);
3326 		}
3327 
3328 		g_object_unref (mime_part);
3329 	}
3330 
3331 	while (!g_queue_is_empty (&queue))
3332 		g_object_unref (g_queue_pop_head (&queue));
3333 
3334 	g_hash_table_destroy (added_mime_parts);
3335 }
3336 
3337 static void
handle_multipart_signed(EMsgComposer * composer,CamelMultipart * multipart,CamelMimePart * parent_part,gboolean keep_signature,GCancellable * cancellable,gint depth)3338 handle_multipart_signed (EMsgComposer *composer,
3339                          CamelMultipart *multipart,
3340 			 CamelMimePart *parent_part,
3341                          gboolean keep_signature,
3342                          GCancellable *cancellable,
3343                          gint depth)
3344 {
3345 	CamelContentType *content_type;
3346 	CamelDataWrapper *content;
3347 	CamelMimePart *mime_part;
3348 	GtkToggleAction *action = NULL;
3349 	const gchar *protocol;
3350 
3351 	content = CAMEL_DATA_WRAPPER (multipart);
3352 	content_type = camel_data_wrapper_get_mime_type_field (content);
3353 	protocol = camel_content_type_param (content_type, "protocol");
3354 
3355 	if (protocol == NULL) {
3356 		action = NULL;
3357 	} else if (g_ascii_strcasecmp (protocol, "application/pgp-signature") == 0) {
3358 		if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3359 		    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3360 			action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
3361 	} else if (g_ascii_strcasecmp (protocol, "application/pkcs7-signature") == 0 ||
3362 		   g_ascii_strcasecmp (protocol, "application/xpkcs7signature") == 0 ||
3363 		   g_ascii_strcasecmp (protocol, "application/xpkcs7-signature") == 0 ||
3364 		   g_ascii_strcasecmp (protocol, "application/x-pkcs7-signature") == 0) {
3365 		if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3366 		    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3367 			action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
3368 	}
3369 
3370 	if (action)
3371 		gtk_toggle_action_set_active (action, TRUE);
3372 
3373 	mime_part = camel_multipart_get_part (
3374 		multipart, CAMEL_MULTIPART_SIGNED_CONTENT);
3375 
3376 	if (mime_part == NULL)
3377 		return;
3378 
3379 	content_type = camel_mime_part_get_content_type (mime_part);
3380 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3381 
3382 	if (CAMEL_IS_MULTIPART (content)) {
3383 		multipart = CAMEL_MULTIPART (content);
3384 
3385 		/* Note: depth is preserved here because we're not
3386 		 * counting multipart/signed as a multipart, instead
3387 		 * we want to treat the content part as our mime part
3388 		 * here. */
3389 
3390 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3391 			/* Handle the signed content and configure
3392 			 * the composer to sign outgoing messages. */
3393 			handle_multipart_signed (
3394 				composer, multipart, parent_part, keep_signature, cancellable, depth);
3395 
3396 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3397 			/* Decrypt the encrypted content and configure
3398 			 * the composer to encrypt outgoing messages. */
3399 			handle_multipart_encrypted (
3400 				composer, mime_part, parent_part, keep_signature, cancellable, depth);
3401 
3402 		} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3403 			/* This contains the text/plain and text/html
3404 			 * versions of the message body. */
3405 			handle_multipart_alternative (
3406 				composer, multipart, parent_part, keep_signature, cancellable, depth);
3407 
3408 		} else {
3409 			/* There must be attachments... */
3410 			handle_multipart (
3411 				composer, multipart, parent_part, keep_signature, cancellable, depth);
3412 		}
3413 
3414 	} else if (camel_content_type_is (content_type, "text", "*")) {
3415 		gchar *html;
3416 		gssize length;
3417 
3418 		html = emcu_part_to_html (
3419 			composer, mime_part, &length, keep_signature, cancellable);
3420 		if (html)
3421 			e_msg_composer_set_pending_body (composer, html, length, TRUE);
3422 
3423 	} else {
3424 		e_msg_composer_attach (composer, mime_part);
3425 	}
3426 }
3427 
3428 static void
handle_multipart_encrypted(EMsgComposer * composer,CamelMimePart * multipart,CamelMimePart * parent_part,gboolean keep_signature,GCancellable * cancellable,gint depth)3429 handle_multipart_encrypted (EMsgComposer *composer,
3430                             CamelMimePart *multipart,
3431 			    CamelMimePart *parent_part,
3432                             gboolean keep_signature,
3433                             GCancellable *cancellable,
3434                             gint depth)
3435 {
3436 	CamelContentType *content_type;
3437 	CamelCipherContext *cipher;
3438 	CamelDataWrapper *content;
3439 	CamelMimePart *mime_part;
3440 	CamelSession *session;
3441 	CamelCipherValidity *valid;
3442 	GtkToggleAction *action = NULL;
3443 	const gchar *protocol;
3444 
3445 	content_type = camel_mime_part_get_content_type (multipart);
3446 	protocol = camel_content_type_param (content_type, "protocol");
3447 
3448 	if (protocol && g_ascii_strcasecmp (protocol, "application/pgp-encrypted") == 0) {
3449 		if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN))) &&
3450 		    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT))))
3451 			action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
3452 	} else if (content_type && (
3453 		   camel_content_type_is (content_type, "application", "pkcs7-mime") ||
3454 		   camel_content_type_is (content_type, "application", "xpkcs7mime") ||
3455 		   camel_content_type_is (content_type, "application", "xpkcs7-mime") ||
3456 		   camel_content_type_is (content_type, "application", "x-pkcs7-mime"))) {
3457 		if (!gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_SIGN))) &&
3458 		    !gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT))))
3459 			action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
3460 	}
3461 
3462 	if (action)
3463 		gtk_toggle_action_set_active (action, TRUE);
3464 
3465 	session = e_msg_composer_ref_session (composer);
3466 	cipher = camel_gpg_context_new (session);
3467 	mime_part = camel_mime_part_new ();
3468 	valid = camel_cipher_context_decrypt_sync (
3469 		cipher, multipart, mime_part, cancellable, NULL);
3470 	g_object_unref (cipher);
3471 	g_object_unref (session);
3472 
3473 	if (valid == NULL) {
3474 		g_object_unref (mime_part);
3475 		return;
3476 	}
3477 
3478 	camel_cipher_validity_free (valid);
3479 
3480 	content_type = camel_mime_part_get_content_type (mime_part);
3481 
3482 	content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3483 
3484 	if (CAMEL_IS_MULTIPART (content)) {
3485 		CamelMultipart *content_multipart = CAMEL_MULTIPART (content);
3486 
3487 		/* Note: depth is preserved here because we're not
3488 		 * counting multipart/encrypted as a multipart, instead
3489 		 * we want to treat the content part as our mime part
3490 		 * here. */
3491 
3492 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3493 			/* Handle the signed content and configure the
3494 			 * composer to sign outgoing messages. */
3495 			handle_multipart_signed (
3496 				composer, content_multipart, multipart, keep_signature, cancellable, depth);
3497 
3498 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3499 			/* Decrypt the encrypted content and configure the
3500 			 * composer to encrypt outgoing messages. */
3501 			handle_multipart_encrypted (
3502 				composer, mime_part, multipart, keep_signature, cancellable, depth);
3503 
3504 		} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
3505 			/* This contains the text/plain and text/html
3506 			 * versions of the message body. */
3507 			handle_multipart_alternative (
3508 				composer, content_multipart, multipart, keep_signature, cancellable, depth);
3509 
3510 		} else {
3511 			/* There must be attachments... */
3512 			handle_multipart (
3513 				composer, content_multipart, multipart, keep_signature, cancellable, depth);
3514 		}
3515 
3516 	} else if (camel_content_type_is (content_type, "text", "*")) {
3517 		gchar *html;
3518 		gssize length;
3519 
3520 		html = emcu_part_to_html (
3521 			composer, mime_part, &length, keep_signature, cancellable);
3522 		if (html)
3523 			e_msg_composer_set_pending_body (composer, html, length, TRUE);
3524 
3525 	} else {
3526 		e_msg_composer_attach (composer, mime_part);
3527 	}
3528 
3529 	g_object_unref (mime_part);
3530 }
3531 
3532 static void
handle_multipart_alternative(EMsgComposer * composer,CamelMultipart * multipart,CamelMimePart * parent_part,gboolean keep_signature,GCancellable * cancellable,gint depth)3533 handle_multipart_alternative (EMsgComposer *composer,
3534                               CamelMultipart *multipart,
3535 			      CamelMimePart *parent_part,
3536                               gboolean keep_signature,
3537                               GCancellable *cancellable,
3538                               gint depth)
3539 {
3540 	/* Find the text/html part and set the composer body to its content */
3541 	CamelMimePart *text_part = NULL, *fallback_text_part = NULL;
3542 	gint i, nparts;
3543 
3544 	nparts = camel_multipart_get_number (multipart);
3545 
3546 	for (i = 0; i < nparts; i++) {
3547 		CamelContentType *content_type;
3548 		CamelDataWrapper *content;
3549 		CamelMimePart *mime_part;
3550 
3551 		mime_part = camel_multipart_get_part (multipart, i);
3552 
3553 		if (!mime_part)
3554 			continue;
3555 
3556 		content_type = camel_mime_part_get_content_type (mime_part);
3557 		content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3558 
3559 		if (CAMEL_IS_MULTIPART (content)) {
3560 			CamelMultipart *mp;
3561 
3562 			mp = CAMEL_MULTIPART (content);
3563 
3564 			if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3565 				/* Handle the signed content and configure
3566 				 * the composer to sign outgoing messages. */
3567 				handle_multipart_signed (
3568 					composer, mp, parent_part, keep_signature, cancellable, depth + 1);
3569 
3570 			} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3571 				/* Decrypt the encrypted content and configure
3572 				 * the composer to encrypt outgoing messages. */
3573 				handle_multipart_encrypted (
3574 					composer, mime_part, parent_part, keep_signature,
3575 					cancellable, depth + 1);
3576 
3577 			} else {
3578 				/* Depth doesn't matter so long as we
3579 				 * don't pass 0. */
3580 				handle_multipart (
3581 					composer, mp, parent_part, keep_signature, cancellable, depth + 1);
3582 			}
3583 
3584 		} else if (camel_content_type_is (content_type, "text", "html")) {
3585 			/* text/html is preferable, so once we find it we're done... */
3586 			text_part = mime_part;
3587 			break;
3588 		} else if (camel_content_type_is (content_type, "text", "*")) {
3589 			/* anyt text part not text/html is second rate so the first
3590 			 * text part we find isn't necessarily the one we'll use. */
3591 			if (!text_part)
3592 				text_part = mime_part;
3593 
3594 			/* this is when prefer-plain filters out text/html part, then
3595 			 * the text/plain should be used */
3596 			if (camel_content_type_is (content_type, "text", "plain"))
3597 				fallback_text_part = mime_part;
3598 		} else {
3599 			e_msg_composer_attach (composer, mime_part);
3600 		}
3601 	}
3602 
3603 	if (text_part) {
3604 		gchar *html;
3605 		gssize length;
3606 
3607 		html = emcu_part_to_html (
3608 			composer, text_part, &length, keep_signature, cancellable);
3609 		if (!html && fallback_text_part)
3610 			html = emcu_part_to_html (
3611 				composer, fallback_text_part, &length, keep_signature, cancellable);
3612 		if (html)
3613 			e_msg_composer_set_pending_body (composer, html, length, TRUE);
3614 	}
3615 }
3616 
3617 static void
handle_multipart(EMsgComposer * composer,CamelMultipart * multipart,CamelMimePart * parent_part,gboolean keep_signature,GCancellable * cancellable,gint depth)3618 handle_multipart (EMsgComposer *composer,
3619                   CamelMultipart *multipart,
3620 		  CamelMimePart *parent_part,
3621                   gboolean keep_signature,
3622                   GCancellable *cancellable,
3623                   gint depth)
3624 {
3625 	gint i, nparts;
3626 
3627 	nparts = camel_multipart_get_number (multipart);
3628 
3629 	for (i = 0; i < nparts; i++) {
3630 		CamelContentType *content_type;
3631 		CamelDataWrapper *content;
3632 		CamelMimePart *mime_part;
3633 
3634 		mime_part = camel_multipart_get_part (multipart, i);
3635 
3636 		if (!mime_part)
3637 			continue;
3638 
3639 		content_type = camel_mime_part_get_content_type (mime_part);
3640 		content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
3641 
3642 		if (CAMEL_IS_MULTIPART (content)) {
3643 			CamelMultipart *mp;
3644 
3645 			mp = CAMEL_MULTIPART (content);
3646 
3647 			if (CAMEL_IS_MULTIPART_SIGNED (content)) {
3648 				/* Handle the signed content and configure
3649 				 * the composer to sign outgoing messages. */
3650 				handle_multipart_signed (
3651 					composer, mp, parent_part, keep_signature, cancellable, depth + 1);
3652 
3653 			} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
3654 				/* Decrypt the encrypted content and configure
3655 				 * the composer to encrypt outgoing messages. */
3656 				handle_multipart_encrypted (
3657 					composer, mime_part, parent_part, keep_signature,
3658 					cancellable, depth + 1);
3659 
3660 			} else if (camel_content_type_is (
3661 				content_type, "multipart", "alternative")) {
3662 				handle_multipart_alternative (
3663 					composer, mp, parent_part, keep_signature, cancellable, depth + 1);
3664 
3665 			} else {
3666 				/* Depth doesn't matter so long as we
3667 				 * don't pass 0. */
3668 				handle_multipart (
3669 					composer, mp, parent_part, keep_signature, cancellable, depth + 1);
3670 			}
3671 
3672 		} else if (depth == 0 && i == 0) {
3673 			gchar *html = NULL;
3674 			gssize length = 0;
3675 
3676 			/* Since the first part is not multipart/alternative,
3677 			 * this must be the body. */
3678 			html = emcu_part_to_html (
3679 				composer, mime_part, &length, keep_signature, cancellable);
3680 
3681 			e_msg_composer_set_pending_body (composer, html, length, TRUE);
3682 
3683 		} else if (camel_content_type_is (content_type, "image", "*") && (
3684 			   camel_mime_part_get_content_id (mime_part) ||
3685 			   camel_mime_part_get_content_location (mime_part))) {
3686 			/* special in-line attachment */
3687 			EHTMLEditor *editor;
3688 
3689 			editor = e_msg_composer_get_editor (composer);
3690 
3691 			e_html_editor_add_cid_part (editor, mime_part);
3692 
3693 			/* Add it to both, to not lose attachments not referenced in HTML body.
3694 			   The inserted images are not included in the message when not referenced. */
3695 			if (emc_is_attachment_part (mime_part, parent_part))
3696 				e_msg_composer_attach (composer, mime_part);
3697 		} else {
3698 			/* normal attachment */
3699 			e_msg_composer_attach (composer, mime_part);
3700 		}
3701 	}
3702 }
3703 
3704 static void
set_signature_gui(EMsgComposer * composer)3705 set_signature_gui (EMsgComposer *composer)
3706 {
3707 	EHTMLEditor *editor;
3708 	EContentEditor *cnt_editor;
3709 	EComposerHeaderTable *table;
3710 	EMailSignatureComboBox *combo_box;
3711 	gchar *uid;
3712 
3713 	table = e_msg_composer_get_header_table (composer);
3714 	combo_box = e_composer_header_table_get_signature_combo_box (table);
3715 
3716 	editor = e_msg_composer_get_editor (composer);
3717 	cnt_editor = e_html_editor_get_content_editor (editor);
3718 
3719 	uid = e_content_editor_get_current_signature_uid (cnt_editor);
3720 	if (uid) {
3721 		/* The combo box active ID is the signature's ESource UID. */
3722 		gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo_box), uid);
3723 		g_free (uid);
3724 	}
3725 }
3726 
3727 static void
composer_add_auto_recipients(ESource * source,const gchar * property_name,GHashTable * hash_table,GList ** inout_destinations)3728 composer_add_auto_recipients (ESource *source,
3729                               const gchar *property_name,
3730                               GHashTable *hash_table,
3731 			      GList **inout_destinations)
3732 {
3733 	ESourceMailComposition *extension;
3734 	CamelInternetAddress *inet_addr;
3735 	const gchar *extension_name;
3736 	gchar *comma_separated_addrs;
3737 	gchar **addr_array = NULL;
3738 	gint length, ii;
3739 	gint retval;
3740 
3741 	extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
3742 	extension = e_source_get_extension (source, extension_name);
3743 
3744 	g_object_get (extension, property_name, &addr_array, NULL);
3745 
3746 	if (addr_array == NULL)
3747 		return;
3748 
3749 	inet_addr = camel_internet_address_new ();
3750 	comma_separated_addrs = g_strjoinv (", ", addr_array);
3751 
3752 	retval = camel_address_decode (
3753 		CAMEL_ADDRESS (inet_addr), comma_separated_addrs);
3754 
3755 	g_free (comma_separated_addrs);
3756 	g_strfreev (addr_array);
3757 
3758 	if (retval == -1)
3759 		return;
3760 
3761 	length = camel_address_length (CAMEL_ADDRESS (inet_addr));
3762 
3763 	for (ii = 0; ii < length; ii++) {
3764 		const gchar *name;
3765 		const gchar *addr;
3766 
3767 		if (camel_internet_address_get (inet_addr, ii, &name, &addr)) {
3768 			EDestination *dest;
3769 
3770 			g_hash_table_add (hash_table, g_strdup (addr));
3771 
3772 			dest = e_destination_new ();
3773 			e_destination_set_name (dest, name);
3774 			e_destination_set_email (dest, addr);
3775 			e_destination_set_auto_recipient (dest, TRUE);
3776 
3777 			*inout_destinations = g_list_append (*inout_destinations, dest);
3778 		}
3779 	}
3780 
3781 	g_object_unref (inet_addr);
3782 }
3783 
3784 /**
3785  * e_msg_composer_setup_with_message:
3786  * @composer: an #EMsgComposer
3787  * @message: The message to use as the source
3788  * @keep_signature: Keep message signature, if any
3789  * @override_identity_uid: (allow none): Optional identity UID to use, or %NULL
3790  * @override_alias_name: (nullable): an alias name to use together with the override_identity_uid, or %NULL
3791  * @override_alias_address: (nullable): an alias address to use together with the override_identity_uid, or %NULL
3792  * @cancellable: optional #GCancellable object, or %NULL
3793  *
3794  * Sets up the message @composer with a specific @message.
3795  *
3796  * Note: Designed to work only for messages constructed using Evolution.
3797  *
3798  * Since: 3.22
3799  **/
3800 void
e_msg_composer_setup_with_message(EMsgComposer * composer,CamelMimeMessage * message,gboolean keep_signature,const gchar * override_identity_uid,const gchar * override_alias_name,const gchar * override_alias_address,GCancellable * cancellable)3801 e_msg_composer_setup_with_message (EMsgComposer *composer,
3802 				   CamelMimeMessage *message,
3803 				   gboolean keep_signature,
3804 				   const gchar *override_identity_uid,
3805 				   const gchar *override_alias_name,
3806 				   const gchar *override_alias_address,
3807 				   GCancellable *cancellable)
3808 {
3809 	CamelInternetAddress *from, *to, *cc, *bcc;
3810 	GList *To = NULL, *Cc = NULL, *Bcc = NULL, *postto = NULL;
3811 	const gchar *format, *subject, *composer_mode;
3812 	EDestination **Tov, **Ccv, **Bccv;
3813 	GHashTable *auto_cc, *auto_bcc;
3814 	CamelMimePart *mime_part;
3815 	CamelContentType *content_type;
3816 	const CamelNameValueArray *headers;
3817 	CamelDataWrapper *content;
3818 	EMsgComposerPrivate *priv;
3819 	EComposerHeaderTable *table;
3820 	ESource *source = NULL;
3821 	EHTMLEditor *editor;
3822 	EContentEditor *cnt_editor;
3823 	GtkToggleAction *action;
3824 	gchar *identity_uid;
3825 	gint len, i;
3826 	guint jj, jjlen;
3827 	gboolean is_message_from_draft = FALSE;
3828 	gboolean is_editor_ready;
3829 	#ifdef ENABLE_SMIME
3830 	CamelMimePart *decrypted_part = NULL;
3831 	#endif
3832 
3833 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
3834 
3835 	headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
3836 	jjlen = camel_name_value_array_get_length (headers);
3837 	for (jj = 0; jj < jjlen; jj++) {
3838 		const gchar *header_name = NULL, *header_value = NULL;
3839 		gchar *value;
3840 
3841 		if (!camel_name_value_array_get (headers, jj, &header_name, &header_value) ||
3842 		    !header_name)
3843 			continue;
3844 
3845 		if (g_ascii_strcasecmp (header_name, "X-Evolution-PostTo") == 0) {
3846 			value = g_strstrip (g_strdup (header_value));
3847 			postto = g_list_append (postto, value);
3848 		}
3849 	}
3850 
3851 	priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
3852 	table = e_msg_composer_get_header_table (composer);
3853 	editor = e_msg_composer_get_editor (composer);
3854 	cnt_editor = e_html_editor_get_content_editor (editor);
3855 
3856 	/* Editing message as new, then keep the signature always below the original message */
3857 	if (keep_signature && !e_msg_composer_get_is_reply_or_forward (composer))
3858 		e_content_editor_set_top_signature (cnt_editor, E_THREE_STATE_OFF);
3859 
3860 	if (postto) {
3861 		e_composer_header_table_set_post_to_list (table, postto);
3862 		g_list_foreach (postto, (GFunc) g_free, NULL);
3863 		g_list_free (postto);
3864 		postto = NULL;
3865 	}
3866 
3867 	if (override_identity_uid && *override_identity_uid) {
3868 		identity_uid = (gchar *) override_identity_uid;
3869 	} else {
3870 		/* Restore the mail identity preference. */
3871 		identity_uid = (gchar *) camel_medium_get_header (
3872 			CAMEL_MEDIUM (message), "X-Evolution-Identity");
3873 		if (!identity_uid) {
3874 			/* for backward compatibility */
3875 			identity_uid = (gchar *) camel_medium_get_header (
3876 				CAMEL_MEDIUM (message), "X-Evolution-Account");
3877 		}
3878 		if (!identity_uid) {
3879 			source = em_utils_guess_mail_identity_with_recipients (
3880 				e_shell_get_registry (e_msg_composer_get_shell (composer)), message, NULL, NULL, NULL, NULL);
3881 			if (source)
3882 				identity_uid = e_source_dup_uid (source);
3883 		}
3884 	}
3885 
3886 	if (identity_uid != NULL && !source) {
3887 		identity_uid = g_strstrip (g_strdup (identity_uid));
3888 		source = e_composer_header_table_ref_source (
3889 			table, identity_uid);
3890 	}
3891 
3892 	auto_cc = g_hash_table_new_full (
3893 		(GHashFunc) camel_strcase_hash,
3894 		(GEqualFunc) camel_strcase_equal,
3895 		(GDestroyNotify) g_free,
3896 		(GDestroyNotify) NULL);
3897 
3898 	auto_bcc = g_hash_table_new_full (
3899 		(GHashFunc) camel_strcase_hash,
3900 		(GEqualFunc) camel_strcase_equal,
3901 		(GDestroyNotify) g_free,
3902 		(GDestroyNotify) NULL);
3903 
3904 	if (source != NULL) {
3905 		composer_add_auto_recipients (source, "cc", auto_cc, &Cc);
3906 		composer_add_auto_recipients (source, "bcc", auto_bcc, &Bcc);
3907 	}
3908 
3909 	to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO);
3910 	cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC);
3911 	bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC);
3912 
3913 	len = camel_address_length (CAMEL_ADDRESS (to));
3914 	for (i = 0; i < len; i++) {
3915 		const gchar *name, *addr;
3916 
3917 		if (camel_internet_address_get (to, i, &name, &addr)) {
3918 			EDestination *dest = e_destination_new ();
3919 			e_destination_set_name (dest, name);
3920 			e_destination_set_email (dest, addr);
3921 			To = g_list_append (To, dest);
3922 		}
3923 	}
3924 
3925 	Tov = destination_list_to_vector (To);
3926 	g_list_free (To);
3927 
3928 	len = camel_address_length (CAMEL_ADDRESS (cc));
3929 	for (i = 0; i < len; i++) {
3930 		const gchar *name, *addr;
3931 
3932 		if (camel_internet_address_get (cc, i, &name, &addr)) {
3933 			EDestination *dest;
3934 
3935 			if (g_hash_table_contains (auto_cc, addr))
3936 				continue;
3937 
3938 			dest = e_destination_new ();
3939 			e_destination_set_name (dest, name);
3940 			e_destination_set_email (dest, addr);
3941 
3942 			Cc = g_list_append (Cc, dest);
3943 		}
3944 	}
3945 
3946 	Ccv = destination_list_to_vector (Cc);
3947 	g_hash_table_destroy (auto_cc);
3948 	g_list_free (Cc);
3949 
3950 	len = camel_address_length (CAMEL_ADDRESS (bcc));
3951 	for (i = 0; i < len; i++) {
3952 		const gchar *name, *addr;
3953 
3954 		if (camel_internet_address_get (bcc, i, &name, &addr)) {
3955 			EDestination *dest;
3956 
3957 			if (g_hash_table_contains (auto_bcc, addr))
3958 				continue;
3959 
3960 			dest = e_destination_new ();
3961 			e_destination_set_name (dest, name);
3962 			e_destination_set_email (dest, addr);
3963 
3964 			Bcc = g_list_append (Bcc, dest);
3965 		}
3966 	}
3967 
3968 	Bccv = destination_list_to_vector (Bcc);
3969 	g_hash_table_destroy (auto_bcc);
3970 	g_list_free (Bcc);
3971 
3972 	if (source != NULL)
3973 		g_object_unref (source);
3974 
3975 	subject = camel_mime_message_get_subject (message);
3976 
3977 	e_composer_header_table_set_destinations_to (table, Tov);
3978 	e_composer_header_table_set_destinations_cc (table, Ccv);
3979 	e_composer_header_table_set_destinations_bcc (table, Bccv);
3980 	e_composer_header_table_set_subject (table, subject);
3981 
3982 	e_destination_freev (Tov);
3983 	e_destination_freev (Ccv);
3984 	e_destination_freev (Bccv);
3985 
3986 	from = camel_mime_message_get_from (message);
3987 	if ((!override_identity_uid || !*override_identity_uid) && from) {
3988 		const gchar *name = NULL, *address = NULL;
3989 
3990 		if (camel_address_length (CAMEL_ADDRESS (from)) == 1 &&
3991 		    camel_internet_address_get (from, 0, &name, &address)) {
3992 			EComposerFromHeader *header_from;
3993 			const gchar *filled_name, *filled_address;
3994 
3995 			/* First try whether such alias exists... */
3996 			e_composer_header_table_set_identity_uid (table, identity_uid, name, address);
3997 
3998 			header_from = E_COMPOSER_FROM_HEADER (e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM));
3999 
4000 			filled_name = e_composer_from_header_get_name (header_from);
4001 			filled_address = e_composer_from_header_get_address (header_from);
4002 
4003 			if (name && !*name)
4004 				name = NULL;
4005 
4006 			if (address && !*address)
4007 				address = NULL;
4008 
4009 			if (g_strcmp0 (filled_name, name) != 0 ||
4010 			    g_strcmp0 (filled_address, address) != 0) {
4011 				/* ... and if not, then reset to the main identity address */
4012 				e_composer_header_table_set_identity_uid (table, identity_uid, NULL, NULL);
4013 				e_composer_from_header_set_name (header_from, name);
4014 				e_composer_from_header_set_address (header_from, address);
4015 				e_composer_from_header_set_override_visible (header_from, TRUE);
4016 			}
4017 		} else {
4018 			e_composer_header_table_set_identity_uid (table, identity_uid, NULL, NULL);
4019 		}
4020 	} else {
4021 		e_composer_header_table_set_identity_uid (table, identity_uid, override_alias_name, override_alias_address);
4022 	}
4023 
4024 	g_free (identity_uid);
4025 
4026 	/* Restore the format editing preference */
4027 	format = camel_medium_get_header (
4028 		CAMEL_MEDIUM (message), "X-Evolution-Format");
4029 
4030 	composer_mode = camel_medium_get_header (
4031 		CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode");
4032 
4033 	if (composer_mode && *composer_mode)
4034 		is_message_from_draft = TRUE;
4035 
4036 	if (format != NULL) {
4037 		gchar **flags;
4038 
4039 		while (*format && camel_mime_is_lwsp (*format))
4040 			format++;
4041 
4042 		flags = g_strsplit (format, ", ", 0);
4043 		for (i = 0; flags[i]; i++) {
4044 			if (g_ascii_strcasecmp (flags[i], "text/html") == 0 ||
4045 			    g_ascii_strcasecmp (flags[i], "text/plain") == 0) {
4046 				gboolean html_mode;
4047 
4048 				html_mode = composer_mode && !g_ascii_strcasecmp (composer_mode, "text/html");
4049 				e_content_editor_set_html_mode (cnt_editor, html_mode);
4050 			} else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) {
4051 				action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
4052 				gtk_toggle_action_set_active (action, TRUE);
4053 			} else if (g_ascii_strcasecmp (flags[i], "pgp-encrypt") == 0) {
4054 				action = GTK_TOGGLE_ACTION (ACTION (PGP_ENCRYPT));
4055 				gtk_toggle_action_set_active (action, TRUE);
4056 			} else if (g_ascii_strcasecmp (flags[i], "smime-sign") == 0) {
4057 				action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
4058 				gtk_toggle_action_set_active (action, TRUE);
4059 			} else if (g_ascii_strcasecmp (flags[i], "smime-encrypt") == 0) {
4060 				action = GTK_TOGGLE_ACTION (ACTION (SMIME_ENCRYPT));
4061 				gtk_toggle_action_set_active (action, TRUE);
4062 			}
4063 		}
4064 		g_strfreev (flags);
4065 	}
4066 
4067 	if (is_message_from_draft || (
4068 	    camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Identity") &&
4069 	    camel_medium_get_header (CAMEL_MEDIUM (message), "X-Evolution-Transport"))) {
4070 		const gchar *reply_to;
4071 
4072 		reply_to = camel_medium_get_header (CAMEL_MEDIUM (message), "Reply-To");
4073 
4074 		if (reply_to)
4075 			e_composer_header_table_set_reply_to (table, reply_to);
4076 	}
4077 
4078 	/* Remove any other X-Evolution-* headers that may have been set */
4079 	camel_name_value_array_free (mail_tool_remove_xevolution_headers (message));
4080 
4081 	/* Check for receipt request */
4082 	if (camel_medium_get_header (
4083 		CAMEL_MEDIUM (message), "Disposition-Notification-To")) {
4084 		action = GTK_TOGGLE_ACTION (ACTION (REQUEST_READ_RECEIPT));
4085 		gtk_toggle_action_set_active (action, TRUE);
4086 	}
4087 
4088 	/* Check for mail priority */
4089 	if (camel_medium_get_header (CAMEL_MEDIUM (message), "X-Priority")) {
4090 		action = GTK_TOGGLE_ACTION (ACTION (PRIORITIZE_MESSAGE));
4091 		gtk_toggle_action_set_active (action, TRUE);
4092 	}
4093 
4094 	/* set extra headers */
4095 	headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
4096 	jjlen = camel_name_value_array_get_length (headers);
4097 	for (jj = 0; jj < jjlen; jj++) {
4098 		const gchar *header_name = NULL, *header_value = NULL;
4099 
4100 		if (!camel_name_value_array_get (headers, jj, &header_name, &header_value) || !header_name)
4101 			continue;
4102 
4103 		if (g_ascii_strcasecmp (header_name, "References") == 0 ||
4104 		    g_ascii_strcasecmp (header_name, "In-Reply-To") == 0) {
4105 			g_ptr_array_add (
4106 				composer->priv->extra_hdr_names,
4107 				g_strdup (header_name));
4108 			g_ptr_array_add (
4109 				composer->priv->extra_hdr_values,
4110 				camel_header_unfold (header_value));
4111 		}
4112 	}
4113 
4114 	/* Restore the attachments and body text */
4115 	content = camel_medium_get_content (CAMEL_MEDIUM (message));
4116 	if (CAMEL_IS_MULTIPART (content)) {
4117 		CamelMultipart *multipart;
4118 
4119 		mime_part = CAMEL_MIME_PART (message);
4120  multipart_content:
4121 		multipart = CAMEL_MULTIPART (content);
4122 		content_type = camel_mime_part_get_content_type (mime_part);
4123 
4124 		if (CAMEL_IS_MULTIPART_SIGNED (content)) {
4125 			/* Handle the signed content and configure the
4126 			 * composer to sign outgoing messages. */
4127 			handle_multipart_signed (
4128 				composer, multipart, mime_part, keep_signature, cancellable, 0);
4129 
4130 		} else if (CAMEL_IS_MULTIPART_ENCRYPTED (content)) {
4131 			/* Decrypt the encrypted content and configure the
4132 			 * composer to encrypt outgoing messages. */
4133 			handle_multipart_encrypted (
4134 				composer, mime_part, mime_part, keep_signature, cancellable, 0);
4135 
4136 		} else if (camel_content_type_is (content_type, "multipart", "alternative")) {
4137 			/* This contains the text/plain and text/html
4138 			 * versions of the message body. */
4139 			handle_multipart_alternative (
4140 				composer, multipart, mime_part, keep_signature, cancellable, 0);
4141 
4142 		} else {
4143 			/* There must be attachments... */
4144 			handle_multipart (
4145 				composer, multipart, mime_part, keep_signature, cancellable, 0);
4146 		}
4147 	} else {
4148 		gboolean is_html = FALSE;
4149 		#ifdef ENABLE_SMIME
4150 		gboolean is_smime_encrypted = FALSE;
4151 		#endif
4152 		gchar *html = NULL;
4153 		gssize length = 0;
4154 
4155 		mime_part = CAMEL_MIME_PART (message);
4156 		content_type = camel_mime_part_get_content_type (mime_part);
4157 		is_html = camel_content_type_is (content_type, "text", "html");
4158 
4159 		if (content_type != NULL && (
4160 		    camel_content_type_is (content_type, "application", "pkcs7-mime") ||
4161 		    camel_content_type_is (content_type, "application", "xpkcs7mime") ||
4162 		    camel_content_type_is (content_type, "application", "xpkcs7-mime") ||
4163 		    camel_content_type_is (content_type, "application", "x-pkcs7-mime"))) {
4164 			#ifdef ENABLE_SMIME
4165 			gtk_toggle_action_set_active (
4166 				GTK_TOGGLE_ACTION (
4167 				ACTION (SMIME_ENCRYPT)), TRUE);
4168 			is_smime_encrypted = TRUE;
4169 			#endif
4170 		}
4171 
4172 		/* If we are opening message from Drafts */
4173 		if (is_message_from_draft) {
4174 			/* Extract the body */
4175 			CamelDataWrapper *dw;
4176 
4177 			#ifdef ENABLE_SMIME
4178 			if (is_smime_encrypted) {
4179 				CamelSession *session;
4180 				CamelCipherContext *cipher;
4181 				CamelCipherValidity *validity;
4182 
4183 				session = e_msg_composer_ref_session (composer);
4184 				cipher = camel_smime_context_new (session);
4185 				decrypted_part = camel_mime_part_new ();
4186 				validity = camel_cipher_context_decrypt_sync (cipher, mime_part, decrypted_part, cancellable, NULL);
4187 				g_object_unref (cipher);
4188 				g_object_unref (session);
4189 
4190 				if (validity) {
4191 					camel_cipher_validity_free (validity);
4192 
4193 					mime_part = decrypted_part;
4194 					content = camel_medium_get_content (CAMEL_MEDIUM (decrypted_part));
4195 
4196 					if (CAMEL_IS_MULTIPART (content))
4197 						goto multipart_content;
4198 				} else {
4199 					g_clear_object (&decrypted_part);
4200 				}
4201 			}
4202 			#endif
4203 
4204 			dw = camel_medium_get_content ((CamelMedium *) mime_part);
4205 			if (dw) {
4206 				CamelStream *mem = camel_stream_mem_new ();
4207 				GByteArray *bytes;
4208 
4209 				camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
4210 				camel_stream_close (mem, cancellable, NULL);
4211 
4212 				bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
4213 				if (bytes && bytes->len) {
4214 					html = g_strndup ((const gchar *) bytes->data, bytes->len);
4215 					length = bytes->len;
4216 				} else {
4217 					html = g_strdup ("");
4218 					length = 0;
4219 				}
4220 
4221 				g_object_unref (mem);
4222 			} else {
4223 				html = g_strdup ("");
4224 				length = 0;
4225 			}
4226 		} else {
4227 			is_html = TRUE;
4228 			html = emcu_part_to_html (
4229 				composer, CAMEL_MIME_PART (message),
4230 				&length, keep_signature, cancellable);
4231 		}
4232 		e_msg_composer_set_pending_body (composer, html, length, is_html);
4233 	}
4234 
4235 	priv->set_signature_from_message = TRUE;
4236 
4237 	is_editor_ready = e_content_editor_is_ready (cnt_editor);
4238 
4239 	/* We wait until now to set the body text because we need to
4240 	 * ensure that the attachment bar has all the attachments before
4241 	 * we request them. */
4242 	e_msg_composer_flush_pending_body (composer);
4243 
4244 	set_signature_gui (composer);
4245 
4246 	/* This makes sure the signature is used from the real message body,
4247 	   not from the empty body when the composer is in the HTML mode */
4248 	if (!is_editor_ready)
4249 		priv->set_signature_from_message = TRUE;
4250 
4251 	#ifdef ENABLE_SMIME
4252 	g_clear_object (&decrypted_part);
4253 	#endif
4254 }
4255 
4256 /**
4257  * e_msg_composer_setup_redirect:
4258  * @composer: an #EMsgComposer
4259  * @message: The message to use as the source
4260  * @identity_uid: (nullable): an identity UID to use, if any
4261  * @alias_name: (nullable): an alias name to use together with the identity_uid, or %NULL
4262  * @alias_address: (nullable): an alias address to use together with the identity_uid, or %NULL
4263  * @cancellable: an optional #GCancellable
4264  *
4265  * Sets up the message @composer as a redirect of the @message.
4266  *
4267  * Since: 3.22
4268  **/
4269 void
e_msg_composer_setup_redirect(EMsgComposer * composer,CamelMimeMessage * message,const gchar * identity_uid,const gchar * alias_name,const gchar * alias_address,GCancellable * cancellable)4270 e_msg_composer_setup_redirect (EMsgComposer *composer,
4271 			       CamelMimeMessage *message,
4272 			       const gchar *identity_uid,
4273 			       const gchar *alias_name,
4274 			       const gchar *alias_address,
4275 			       GCancellable *cancellable)
4276 {
4277 	EComposerHeaderTable *table;
4278 	EHTMLEditor *editor;
4279 	EContentEditor *cnt_editor;
4280 	const gchar *subject;
4281 
4282 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4283 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4284 
4285 	composer->priv->redirect = g_object_ref (message);
4286 
4287 	e_msg_composer_setup_with_message (composer, message, TRUE, identity_uid, alias_name, alias_address, cancellable);
4288 
4289 	table = e_msg_composer_get_header_table (composer);
4290 	subject = camel_mime_message_get_subject (message);
4291 
4292 	e_composer_header_table_set_subject (table, subject);
4293 
4294 	gtk_widget_hide (GTK_WIDGET (e_composer_header_table_get_signature_combo_box (table)));
4295 	gtk_table_set_col_spacings (GTK_TABLE (table), 0);
4296 
4297 	editor = e_msg_composer_get_editor (composer);
4298 	cnt_editor = e_html_editor_get_content_editor (editor);
4299 	e_content_editor_set_editable (cnt_editor, FALSE);
4300 }
4301 
4302 /**
4303  * e_msg_composer_ref_session:
4304  * @composer: an #EMsgComposer
4305  *
4306  * Returns the mail module's global #CamelSession instance.  Calling
4307  * this function will load the mail module if it isn't already loaded.
4308  *
4309  * The returned #CamelSession is referenced for thread-safety and must
4310  * be unreferenced with g_object_unref() when finished with it.
4311  *
4312  * Returns: the mail module's #CamelSession
4313  **/
4314 CamelSession *
e_msg_composer_ref_session(EMsgComposer * composer)4315 e_msg_composer_ref_session (EMsgComposer *composer)
4316 {
4317 	EShell *shell;
4318 	EShellBackend *shell_backend;
4319 	CamelSession *session = NULL;
4320 
4321 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
4322 
4323 	shell = e_msg_composer_get_shell (composer);
4324 	shell_backend = e_shell_get_backend_by_name (shell, "mail");
4325 
4326 	g_object_get (shell_backend, "session", &session, NULL);
4327 	g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
4328 
4329 	return session;
4330 }
4331 
4332 /**
4333  * e_msg_composer_get_shell:
4334  * @composer: an #EMsgComposer
4335  *
4336  * Returns the #EShell that was passed to e_msg_composer_new().
4337  *
4338  * Returns: the #EShell
4339  **/
4340 EShell *
e_msg_composer_get_shell(EMsgComposer * composer)4341 e_msg_composer_get_shell (EMsgComposer *composer)
4342 {
4343 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
4344 
4345 	return E_SHELL (composer->priv->shell);
4346 }
4347 
4348 /**
4349  * e_msg_composer_get_content_hash:
4350  * @composer: an #EMsgComposer
4351  *
4352  * Returns current #EContentEditorContentHash with content
4353  * of the composer. It's valid, and available, only during
4354  * operations/signals, which construct message from the @composer
4355  * content. The @composer precaches the content, thus it can
4356  * be accessed in a synchronous way (in constrast to EContentEditor,
4357  * which allows getting the content only asynchronously).
4358  * The content hash is owned by the @composer and it is freed
4359  * as soon as the respective operation is finished.
4360  *
4361  * Returns: (transfer none) (nullable): an #EContentEditorContentHash
4362  *    with current content data, or %NULL, when it is not loaded.
4363  *
4364  * Since: 3.38
4365  **/
4366 EContentEditorContentHash *
e_msg_composer_get_content_hash(EMsgComposer * composer)4367 e_msg_composer_get_content_hash (EMsgComposer *composer)
4368 {
4369 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
4370 
4371 	/* Calling the function out of expected place should warn that something goes wrong */
4372 	g_warn_if_fail (composer->priv->content_hash != NULL);
4373 
4374 	return composer->priv->content_hash;
4375 }
4376 
4377 typedef void (* PrepareContentHashCallback) (EMsgComposer *composer,
4378 					     gpointer user_data,
4379 					     const GError *error);
4380 
4381 typedef struct _PrepareContentHashData {
4382 	EMsgComposer *composer;
4383 	PrepareContentHashCallback callback;
4384 	gpointer user_data;
4385 } PrepareContentHashData;
4386 
4387 static PrepareContentHashData *
prepare_content_hash_data_new(EMsgComposer * composer,PrepareContentHashCallback callback,gpointer user_data)4388 prepare_content_hash_data_new (EMsgComposer *composer,
4389 			       PrepareContentHashCallback callback,
4390 			       gpointer user_data)
4391 {
4392 	PrepareContentHashData *pchd;
4393 
4394 	pchd = g_slice_new (PrepareContentHashData);
4395 	pchd->composer = g_object_ref (composer);
4396 	pchd->callback = callback;
4397 	pchd->user_data = user_data;
4398 
4399 	return pchd;
4400 }
4401 
4402 static void
prepare_content_hash_data_free(gpointer ptr)4403 prepare_content_hash_data_free (gpointer ptr)
4404 {
4405 	PrepareContentHashData *pchd = ptr;
4406 
4407 	if (pchd) {
4408 		g_clear_object (&pchd->composer);
4409 		g_slice_free (PrepareContentHashData, pchd);
4410 	}
4411 }
4412 
4413 static void
e_msg_composer_prepare_content_hash_ready_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)4414 e_msg_composer_prepare_content_hash_ready_cb (GObject *source_object,
4415 					      GAsyncResult *result,
4416 					      gpointer user_data)
4417 {
4418 	PrepareContentHashData *pchd = user_data;
4419 	EContentEditorContentHash *content_hash;
4420 	GError *error = NULL;
4421 
4422 	g_return_if_fail (pchd != NULL);
4423 	g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
4424 
4425 	content_hash = e_content_editor_get_content_finish (E_CONTENT_EDITOR (source_object), result, &error);
4426 
4427 	if (content_hash) {
4428 		g_warn_if_fail (pchd->composer->priv->content_hash == NULL);
4429 		g_warn_if_fail (pchd->composer->priv->content_hash_ref_count == 0);
4430 
4431 		pchd->composer->priv->content_hash = content_hash;
4432 		pchd->composer->priv->content_hash_ref_count = 1;
4433 	}
4434 
4435 	pchd->callback (pchd->composer, pchd->user_data, error);
4436 
4437 	prepare_content_hash_data_free (pchd);
4438 	g_clear_error (&error);
4439 }
4440 
4441 static void
e_msg_composer_prepare_content_hash(EMsgComposer * composer,GCancellable * cancellable,EActivity * activity,PrepareContentHashCallback callback,gpointer user_data)4442 e_msg_composer_prepare_content_hash (EMsgComposer *composer,
4443 				     GCancellable *cancellable,
4444 				     EActivity *activity,
4445 				     PrepareContentHashCallback callback,
4446 				     gpointer user_data)
4447 {
4448 	EHTMLEditor *editor;
4449 	EContentEditor *cnt_editor;
4450 	CamelInternetAddress *from;
4451 	PrepareContentHashData *pchd;
4452 	const gchar *from_domain = NULL;
4453 
4454 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4455 	g_return_if_fail (callback != NULL);
4456 
4457 	/* Cannot use e_msg_composer_get_content_hash() here, because it prints
4458 	   a runtime warning when the content_hash is NULL. */
4459 	if (composer->priv->content_hash) {
4460 		composer->priv->content_hash_ref_count++;
4461 
4462 		callback (composer, user_data, NULL);
4463 		return;
4464 	}
4465 
4466 	if (activity)
4467 		e_activity_set_text (activity, _("Reading text content…"));
4468 
4469 	pchd = prepare_content_hash_data_new (composer, callback, user_data);
4470 	editor = e_msg_composer_get_editor (composer);
4471 	cnt_editor = e_html_editor_get_content_editor (editor);
4472 	from = e_msg_composer_get_from (composer);
4473 
4474 	if (from && camel_internet_address_get (from, 0, NULL, &from_domain)) {
4475 		const gchar *at = strchr (from_domain, '@');
4476 
4477 		if (at)
4478 			from_domain = at + 1;
4479 		else
4480 			from_domain = NULL;
4481 	}
4482 
4483 	if (!from_domain || !*from_domain)
4484 		from_domain = "localhost";
4485 
4486 	e_content_editor_get_content (cnt_editor, E_CONTENT_EDITOR_GET_ALL, from_domain, cancellable,
4487 		e_msg_composer_prepare_content_hash_ready_cb, pchd);
4488 
4489 	g_clear_object (&from);
4490 }
4491 
4492 static gboolean
e_msg_composer_claim_no_build_message_error(EMsgComposer * composer,EActivity * activity,const GError * error,gboolean unref_content_hash_on_error)4493 e_msg_composer_claim_no_build_message_error (EMsgComposer *composer,
4494 					     EActivity *activity,
4495 					     const GError *error,
4496 					     gboolean unref_content_hash_on_error)
4497 {
4498 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
4499 
4500 	if (error) {
4501 		if (!e_activity_handle_cancellation (activity, error)) {
4502 			EAlertSink *alert_sink;
4503 
4504 			alert_sink = e_activity_get_alert_sink (activity);
4505 
4506 			e_alert_submit (
4507 				alert_sink,
4508 				"mail-composer:no-build-message",
4509 				error->message, NULL);
4510 		}
4511 
4512 		if (e_msg_composer_is_exiting (composer)) {
4513 			gtk_window_present (GTK_WINDOW (composer));
4514 			composer->priv->application_exiting = FALSE;
4515 		}
4516 
4517 		gtk_window_present (GTK_WINDOW (composer));
4518 
4519 		if (unref_content_hash_on_error)
4520 			e_msg_composer_unref_content_hash (composer);
4521 	}
4522 
4523 	return error != NULL;
4524 }
4525 
4526 static void
msg_composer_send_cb(EMsgComposer * composer,GAsyncResult * result,AsyncContext * context)4527 msg_composer_send_cb (EMsgComposer *composer,
4528                       GAsyncResult *result,
4529                       AsyncContext *context)
4530 {
4531 	CamelMimeMessage *message;
4532 	EHTMLEditor *editor;
4533 	EContentEditor *cnt_editor;
4534 	GError *error = NULL;
4535 
4536 	message = e_msg_composer_get_message_finish (composer, result, &error);
4537 
4538 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
4539 		g_warn_if_fail (message == NULL);
4540 		async_context_free (context);
4541 		g_clear_error (&error);
4542 		return;
4543 	}
4544 
4545 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4546 
4547 	/* The callback can set editor 'changed' if anything failed. */
4548 	editor = e_msg_composer_get_editor (composer);
4549 	cnt_editor = e_html_editor_get_content_editor (editor);
4550 	e_content_editor_set_changed (cnt_editor, TRUE);
4551 
4552 	composer->priv->is_sending_message = TRUE;
4553 
4554 	g_signal_emit (
4555 		composer, signals[SEND], 0,
4556 		message, context->activity);
4557 
4558 	composer->priv->is_sending_message = FALSE;
4559 
4560 	g_object_unref (message);
4561 
4562 	e_msg_composer_unref_content_hash (composer);
4563 	async_context_free (context);
4564 }
4565 
4566 static void
e_msg_composer_send_content_hash_ready_cb(EMsgComposer * composer,gpointer user_data,const GError * error)4567 e_msg_composer_send_content_hash_ready_cb (EMsgComposer *composer,
4568 					   gpointer user_data,
4569 					   const GError *error)
4570 {
4571 	AsyncContext *context = user_data;
4572 	gboolean proceed_with_send = TRUE;
4573 
4574 	g_return_if_fail (context != NULL);
4575 
4576 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
4577 		async_context_free (context);
4578 		return;
4579 	}
4580 
4581 	/* This gives the user a chance to abort the send. */
4582 	g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_send);
4583 
4584 	if (!proceed_with_send) {
4585 		gtk_window_present (GTK_WINDOW (composer));
4586 		e_msg_composer_unref_content_hash (composer);
4587 
4588 		if (e_msg_composer_is_exiting (composer)) {
4589 			gtk_window_present (GTK_WINDOW (composer));
4590 			composer->priv->application_exiting = FALSE;
4591 		}
4592 
4593 		async_context_free (context);
4594 		return;
4595 	}
4596 
4597 	e_msg_composer_get_message (
4598 		composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
4599 		(GAsyncReadyCallback) msg_composer_send_cb,
4600 		context);
4601 }
4602 
4603 /**
4604  * e_msg_composer_send:
4605  * @composer: an #EMsgComposer
4606  *
4607  * Send the message in @composer.
4608  **/
4609 void
e_msg_composer_send(EMsgComposer * composer)4610 e_msg_composer_send (EMsgComposer *composer)
4611 {
4612 	EHTMLEditor *editor;
4613 	AsyncContext *context;
4614 	GCancellable *cancellable;
4615 
4616 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4617 
4618 	editor = e_msg_composer_get_editor (composer);
4619 
4620 	context = g_slice_new0 (AsyncContext);
4621 	context->activity = e_html_editor_new_activity (editor);
4622 
4623 	cancellable = e_activity_get_cancellable (context->activity);
4624 
4625 	e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
4626 		e_msg_composer_send_content_hash_ready_cb, context);
4627 }
4628 
4629 static void
msg_composer_save_to_drafts_done_cb(gpointer user_data,GObject * gone_object)4630 msg_composer_save_to_drafts_done_cb (gpointer user_data,
4631 				     GObject *gone_object)
4632 {
4633 	EMsgComposer *composer = user_data;
4634 	EHTMLEditor *editor;
4635 	EContentEditor *cnt_editor;
4636 
4637 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4638 
4639 	editor = e_msg_composer_get_editor (composer);
4640 	cnt_editor = e_html_editor_get_content_editor (editor);
4641 
4642 	if (e_msg_composer_is_exiting (composer) &&
4643 	    !e_content_editor_get_changed (cnt_editor)) {
4644 		e_composer_emit_before_destroy (composer);
4645 		gtk_widget_destroy (GTK_WIDGET (composer));
4646 	} else if (e_msg_composer_is_exiting (composer)) {
4647 		gtk_widget_set_sensitive (GTK_WIDGET (composer), TRUE);
4648 		gtk_window_present (GTK_WINDOW (composer));
4649 		composer->priv->application_exiting = FALSE;
4650 	}
4651 }
4652 
4653 static void
msg_composer_save_to_drafts_cb(EMsgComposer * composer,GAsyncResult * result,AsyncContext * context)4654 msg_composer_save_to_drafts_cb (EMsgComposer *composer,
4655                                 GAsyncResult *result,
4656                                 AsyncContext *context)
4657 {
4658 	CamelMimeMessage *message;
4659 	EHTMLEditor *editor;
4660 	EContentEditor *cnt_editor;
4661 	GError *error = NULL;
4662 
4663 	message = e_msg_composer_get_message_draft_finish (composer, result, &error);
4664 
4665 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
4666 		g_warn_if_fail (message == NULL);
4667 		async_context_free (context);
4668 		g_clear_error (&error);
4669 		return;
4670 	}
4671 
4672 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4673 
4674 	/* The callback can set editor 'changed' if anything failed. */
4675 	editor = e_msg_composer_get_editor (composer);
4676 	cnt_editor = e_html_editor_get_content_editor (editor);
4677 	e_content_editor_set_changed (cnt_editor, TRUE);
4678 
4679 	g_signal_emit (
4680 		composer, signals[SAVE_TO_DRAFTS],
4681 		0, message, context->activity);
4682 
4683 	g_object_unref (message);
4684 
4685 	if (e_msg_composer_is_exiting (composer))
4686 		g_object_weak_ref (
4687 			G_OBJECT (context->activity),
4688 			msg_composer_save_to_drafts_done_cb, composer);
4689 
4690 	e_msg_composer_unref_content_hash (composer);
4691 	async_context_free (context);
4692 }
4693 
4694 static void
e_msg_composer_save_to_drafts_content_hash_ready_cb(EMsgComposer * composer,gpointer user_data,const GError * error)4695 e_msg_composer_save_to_drafts_content_hash_ready_cb (EMsgComposer *composer,
4696 						     gpointer user_data,
4697 						     const GError *error)
4698 {
4699 	AsyncContext *context = user_data;
4700 
4701 	g_return_if_fail (context != NULL);
4702 
4703 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
4704 		if (e_msg_composer_is_exiting (composer)) {
4705 			gtk_window_present (GTK_WINDOW (composer));
4706 			composer->priv->application_exiting = FALSE;
4707 		}
4708 		async_context_free (context);
4709 		return;
4710 	}
4711 
4712 	e_msg_composer_get_message_draft (
4713 		composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
4714 		(GAsyncReadyCallback) msg_composer_save_to_drafts_cb,
4715 		context);
4716 }
4717 
4718 /**
4719  * e_msg_composer_save_to_drafts:
4720  * @composer: an #EMsgComposer
4721  *
4722  * Save the message in @composer to the selected account's Drafts folder.
4723  **/
4724 void
e_msg_composer_save_to_drafts(EMsgComposer * composer)4725 e_msg_composer_save_to_drafts (EMsgComposer *composer)
4726 {
4727 	EHTMLEditor *editor;
4728 	AsyncContext *context;
4729 	GCancellable *cancellable;
4730 
4731 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4732 
4733 	editor = e_msg_composer_get_editor (composer);
4734 
4735 	context = g_slice_new0 (AsyncContext);
4736 	context->activity = e_html_editor_new_activity (editor);
4737 	context->is_draft = TRUE;
4738 
4739 	cancellable = e_activity_get_cancellable (context->activity);
4740 
4741 	e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
4742 		e_msg_composer_save_to_drafts_content_hash_ready_cb, context);
4743 }
4744 
4745 static void
msg_composer_save_to_outbox_cb(EMsgComposer * composer,GAsyncResult * result,AsyncContext * context)4746 msg_composer_save_to_outbox_cb (EMsgComposer *composer,
4747                                 GAsyncResult *result,
4748                                 AsyncContext *context)
4749 {
4750 	CamelMimeMessage *message;
4751 	EHTMLEditor *editor;
4752 	EContentEditor *cnt_editor;
4753 	GError *error = NULL;
4754 
4755 	message = e_msg_composer_get_message_finish (composer, result, &error);
4756 
4757 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
4758 		g_warn_if_fail (message == NULL);
4759 		async_context_free (context);
4760 		g_clear_error (&error);
4761 		return;
4762 	}
4763 
4764 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4765 
4766 	g_signal_emit (
4767 		composer, signals[SAVE_TO_OUTBOX],
4768 		0, message, context->activity);
4769 
4770 	g_object_unref (message);
4771 
4772 	editor = e_msg_composer_get_editor (composer);
4773 	cnt_editor = e_html_editor_get_content_editor (editor);
4774 	e_content_editor_set_changed (cnt_editor, TRUE);
4775 
4776 	async_context_free (context);
4777 }
4778 
4779 static void
e_msg_composer_save_to_outbox_content_hash_ready_cb(EMsgComposer * composer,gpointer user_data,const GError * error)4780 e_msg_composer_save_to_outbox_content_hash_ready_cb (EMsgComposer *composer,
4781 						     gpointer user_data,
4782 						     const GError *error)
4783 {
4784 	AsyncContext *context = user_data;
4785 
4786 	g_return_if_fail (context != NULL);
4787 
4788 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
4789 		async_context_free (context);
4790 		return;
4791 	}
4792 
4793 	if (!composer->priv->is_sending_message) {
4794 		gboolean proceed_with_save = TRUE;
4795 
4796 		/* This gives the user a chance to abort the save. */
4797 		g_signal_emit (composer, signals[PRESEND], 0, &proceed_with_save);
4798 
4799 		if (!proceed_with_save) {
4800 			if (e_msg_composer_is_exiting (composer)) {
4801 				gtk_window_present (GTK_WINDOW (composer));
4802 				composer->priv->application_exiting = FALSE;
4803 			}
4804 
4805 			e_msg_composer_unref_content_hash (composer);
4806 			async_context_free (context);
4807 			return;
4808 		}
4809 	}
4810 
4811 	e_msg_composer_get_message (
4812 		composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
4813 		(GAsyncReadyCallback) msg_composer_save_to_outbox_cb,
4814 		context);
4815 }
4816 
4817 /**
4818  * e_msg_composer_save_to_outbox:
4819  * @composer: an #EMsgComposer
4820  *
4821  * Save the message in @composer to the local Outbox folder.
4822  **/
4823 void
e_msg_composer_save_to_outbox(EMsgComposer * composer)4824 e_msg_composer_save_to_outbox (EMsgComposer *composer)
4825 {
4826 	EHTMLEditor *editor;
4827 	AsyncContext *context;
4828 	GCancellable *cancellable;
4829 
4830 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4831 
4832 	editor = e_msg_composer_get_editor (composer);
4833 
4834 	context = g_slice_new0 (AsyncContext);
4835 	context->activity = e_html_editor_new_activity (editor);
4836 
4837 	cancellable = e_activity_get_cancellable (context->activity);
4838 
4839 	e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
4840 		e_msg_composer_save_to_outbox_content_hash_ready_cb, context);
4841 }
4842 
4843 static void
msg_composer_print_cb(EMsgComposer * composer,GAsyncResult * result,AsyncContext * context)4844 msg_composer_print_cb (EMsgComposer *composer,
4845                        GAsyncResult *result,
4846                        AsyncContext *context)
4847 {
4848 	CamelMimeMessage *message;
4849 	GError *error = NULL;
4850 
4851 	message = e_msg_composer_get_message_print_finish (composer, result, &error);
4852 
4853 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, TRUE)) {
4854 		g_warn_if_fail (message == NULL);
4855 		async_context_free (context);
4856 		g_clear_error (&error);
4857 		return;
4858 	}
4859 
4860 	g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
4861 
4862 	g_signal_emit (
4863 		composer, signals[PRINT], 0,
4864 		context->print_action, message, context->activity);
4865 
4866 	g_object_unref (message);
4867 
4868 	async_context_free (context);
4869 }
4870 
4871 static void
e_msg_composer_print_content_hash_ready_cb(EMsgComposer * composer,gpointer user_data,const GError * error)4872 e_msg_composer_print_content_hash_ready_cb (EMsgComposer *composer,
4873 					    gpointer user_data,
4874 					    const GError *error)
4875 {
4876 	AsyncContext *context = user_data;
4877 
4878 	g_return_if_fail (context != NULL);
4879 
4880 	if (e_msg_composer_claim_no_build_message_error (composer, context->activity, error, FALSE)) {
4881 		async_context_free (context);
4882 		return;
4883 	}
4884 
4885 	e_msg_composer_get_message_print (
4886 		composer, G_PRIORITY_DEFAULT, e_activity_get_cancellable (context->activity),
4887 		(GAsyncReadyCallback) msg_composer_print_cb,
4888 		context);
4889 }
4890 
4891 /**
4892  * e_msg_composer_print:
4893  * @composer: an #EMsgComposer
4894  * @print_action: the print action to start
4895  *
4896  * Print the message in @composer.
4897  **/
4898 void
e_msg_composer_print(EMsgComposer * composer,GtkPrintOperationAction print_action)4899 e_msg_composer_print (EMsgComposer *composer,
4900                       GtkPrintOperationAction print_action)
4901 {
4902 	EHTMLEditor *editor;
4903 	AsyncContext *context;
4904 	GCancellable *cancellable;
4905 
4906 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
4907 
4908 	editor = e_msg_composer_get_editor (composer);
4909 
4910 	context = g_slice_new0 (AsyncContext);
4911 	context->activity = e_html_editor_new_activity (editor);
4912 	context->print_action = print_action;
4913 
4914 	cancellable = e_activity_get_cancellable (context->activity);
4915 
4916 	e_msg_composer_prepare_content_hash (composer, cancellable, context->activity,
4917 		e_msg_composer_print_content_hash_ready_cb, context);
4918 }
4919 
4920 static GList *
add_recipients(GList * list,const gchar * recips)4921 add_recipients (GList *list,
4922                 const gchar *recips)
4923 {
4924 	CamelInternetAddress *cia;
4925 	const gchar *name, *addr;
4926 	gint num, i;
4927 
4928 	cia = camel_internet_address_new ();
4929 	num = camel_address_decode (CAMEL_ADDRESS (cia), recips);
4930 
4931 	for (i = 0; i < num; i++) {
4932 		if (camel_internet_address_get (cia, i, &name, &addr)) {
4933 			EDestination *dest = e_destination_new ();
4934 			e_destination_set_name (dest, name);
4935 			e_destination_set_email (dest, addr);
4936 
4937 			list = g_list_append (list, dest);
4938 		}
4939 	}
4940 
4941 	g_object_unref (cia);
4942 
4943 	return list;
4944 }
4945 
4946 static gboolean
list_contains_addr(const GList * list,EDestination * dest)4947 list_contains_addr (const GList *list,
4948                     EDestination *dest)
4949 {
4950 	g_return_val_if_fail (dest != NULL, FALSE);
4951 
4952 	while (list != NULL) {
4953 		if (e_destination_equal (dest, list->data))
4954 			return TRUE;
4955 
4956 		list = list->next;
4957 	}
4958 
4959 	return FALSE;
4960 }
4961 
4962 static void
merge_cc_bcc(EDestination ** addrv,GList ** merge_into,const GList * to,const GList * cc,const GList * bcc)4963 merge_cc_bcc (EDestination **addrv,
4964               GList **merge_into,
4965               const GList *to,
4966               const GList *cc,
4967               const GList *bcc)
4968 {
4969 	gint ii;
4970 
4971 	for (ii = 0; addrv && addrv[ii]; ii++) {
4972 		if (!list_contains_addr (to, addrv[ii]) &&
4973 		    !list_contains_addr (cc, addrv[ii]) &&
4974 		    !list_contains_addr (bcc, addrv[ii])) {
4975 			*merge_into = g_list_append (
4976 				*merge_into, g_object_ref (addrv[ii]));
4977 		}
4978 	}
4979 }
4980 
4981 static void
merge_always_cc_and_bcc(EComposerHeaderTable * table,const GList * to,GList ** cc,GList ** bcc)4982 merge_always_cc_and_bcc (EComposerHeaderTable *table,
4983                          const GList *to,
4984                          GList **cc,
4985                          GList **bcc)
4986 {
4987 	EDestination **addrv;
4988 
4989 	g_return_if_fail (table != NULL);
4990 	g_return_if_fail (cc != NULL);
4991 	g_return_if_fail (bcc != NULL);
4992 
4993 	addrv = e_composer_header_table_get_destinations_cc (table);
4994 	merge_cc_bcc (addrv, cc, to, *cc, *bcc);
4995 	e_destination_freev (addrv);
4996 
4997 	addrv = e_composer_header_table_get_destinations_bcc (table);
4998 	merge_cc_bcc (addrv, bcc, to, *cc, *bcc);
4999 	e_destination_freev (addrv);
5000 }
5001 
5002 static const gchar *blacklist[] = { ".", "etc", ".." };
5003 
5004 static gboolean
file_is_blacklisted(const gchar * argument)5005 file_is_blacklisted (const gchar *argument)
5006 {
5007 	GFile *file;
5008 	gboolean blacklisted = FALSE;
5009 	guint ii, jj, n_parts;
5010 	gchar *filename;
5011 	gchar **parts;
5012 
5013 	/* The "attach" argument may be a URI or local path.  Normalize
5014 	 * it to a local path if we can.  We only blacklist local files. */
5015 	file = g_file_new_for_commandline_arg (argument);
5016 	filename = g_file_get_path (file);
5017 	g_object_unref (file);
5018 
5019 	if (filename == NULL)
5020 		return FALSE;
5021 
5022 	parts = g_strsplit (filename, G_DIR_SEPARATOR_S, -1);
5023 	n_parts = g_strv_length (parts);
5024 
5025 	for (ii = 0; ii < G_N_ELEMENTS (blacklist); ii++) {
5026 		for (jj = 0; jj < n_parts; jj++) {
5027 			if (g_str_has_prefix (parts[jj], blacklist[ii])) {
5028 				blacklisted = TRUE;
5029 				break;
5030 			}
5031 		}
5032 	}
5033 
5034 	if (blacklisted) {
5035 		gchar *base_dir;
5036 
5037 		/* Don't blacklist files in trusted base directories. */
5038 		if (g_str_has_prefix (filename, g_get_user_data_dir ()))
5039 			blacklisted = FALSE;
5040 		if (g_str_has_prefix (filename, g_get_user_cache_dir ()))
5041 			blacklisted = FALSE;
5042 		if (g_str_has_prefix (filename, g_get_user_config_dir ()))
5043 			blacklisted = FALSE;
5044 
5045 		/* Apparently KDE still uses ~/.kde heavily, and some
5046 		 * distributions use ~/.kde4 to distinguish KDE4 data
5047 		 * from KDE3 data.  Trust these directories as well. */
5048 
5049 		base_dir = g_build_filename (g_get_home_dir (), ".kde", NULL);
5050 		if (g_str_has_prefix (filename, base_dir))
5051 			blacklisted = FALSE;
5052 		g_free (base_dir);
5053 
5054 		base_dir = g_build_filename (g_get_home_dir (), ".kde4", NULL);
5055 		if (g_str_has_prefix (filename, base_dir))
5056 			blacklisted = FALSE;
5057 		g_free (base_dir);
5058 	}
5059 
5060 	g_strfreev (parts);
5061 	g_free (filename);
5062 
5063 	return blacklisted;
5064 }
5065 
5066 static void
handle_mailto(EMsgComposer * composer,const gchar * mailto)5067 handle_mailto (EMsgComposer *composer,
5068                const gchar *mailto)
5069 {
5070 	EAttachmentView *view;
5071 	EAttachmentStore *store;
5072 	EComposerHeaderTable *table;
5073 	GList *to = NULL, *cc = NULL, *bcc = NULL;
5074 	EDestination **tov, **ccv, **bccv;
5075 	gchar *subject = NULL, *body = NULL;
5076 	gchar *header, *content, *buf;
5077 	gsize nread, nwritten;
5078 	const gchar *p;
5079 	gint len, clen, has_attachments = 0;
5080 	gboolean has_blacklisted_attachment = FALSE;
5081 
5082 	table = e_msg_composer_get_header_table (composer);
5083 	view = e_msg_composer_get_attachment_view (composer);
5084 	store = e_attachment_view_get_store (view);
5085 
5086 	buf = g_strdup (mailto);
5087 
5088 	/* Parse recipients (everything after ':' and up to three leading forward slashes until '?' or eos). */
5089 	p = buf + 7;
5090 
5091 	while (*p == '/' && p - buf < 10)
5092 		p++;
5093 
5094 	len = strcspn (p, "?");
5095 	if (len) {
5096 		content = g_strndup (p, len);
5097 		camel_url_decode (content);
5098 		to = add_recipients (to, content);
5099 		g_free (content);
5100 	}
5101 
5102 	p += len;
5103 	if (*p == '?') {
5104 		p++;
5105 
5106 		while (*p) {
5107 			len = strcspn (p, "=&");
5108 
5109 			/* If it's malformed, give up. */
5110 			if (p[len] != '=')
5111 				break;
5112 
5113 			header = (gchar *) p;
5114 			header[len] = '\0';
5115 			p += len + 1;
5116 
5117 			clen = strcspn (p, "&");
5118 
5119 			content = g_strndup (p, clen);
5120 
5121 			if (!g_ascii_strcasecmp (header, "to")) {
5122 				camel_url_decode (content);
5123 				to = add_recipients (to, content);
5124 			} else if (!g_ascii_strcasecmp (header, "cc")) {
5125 				camel_url_decode (content);
5126 				cc = add_recipients (cc, content);
5127 			} else if (!g_ascii_strcasecmp (header, "bcc")) {
5128 				camel_url_decode (content);
5129 				bcc = add_recipients (bcc, content);
5130 			} else if (!g_ascii_strcasecmp (header, "subject")) {
5131 				g_free (subject);
5132 				camel_url_decode (content);
5133 				if (g_utf8_validate (content, -1, NULL)) {
5134 					subject = content;
5135 					content = NULL;
5136 				} else {
5137 					subject = g_locale_to_utf8 (
5138 						content, clen, &nread,
5139 						&nwritten, NULL);
5140 					if (subject) {
5141 						subject = g_realloc (subject, nwritten + 1);
5142 						subject[nwritten] = '\0';
5143 					}
5144 				}
5145 			} else if (!g_ascii_strcasecmp (header, "body")) {
5146 				g_free (body);
5147 				camel_url_decode (content);
5148 				if (g_utf8_validate (content, -1, NULL)) {
5149 					body = content;
5150 					content = NULL;
5151 				} else {
5152 					body = g_locale_to_utf8 (
5153 						content, clen, &nread,
5154 						&nwritten, NULL);
5155 					if (body) {
5156 						body = g_realloc (body, nwritten + 1);
5157 						body[nwritten] = '\0';
5158 					}
5159 				}
5160 			} else if (!g_ascii_strcasecmp (header, "attach") ||
5161 				   !g_ascii_strcasecmp (header, "attachment")) {
5162 				EAttachment *attachment;
5163 				GFile *file;
5164 
5165 				camel_url_decode (content);
5166 				if (g_ascii_strncasecmp (content, "file:", 5) == 0)
5167 					attachment = e_attachment_new_for_uri (content);
5168 				else
5169 					attachment = e_attachment_new_for_path (content);
5170 				file = e_attachment_ref_file (attachment);
5171 				if (!file || !g_file_peek_path (file) ||
5172 				    !g_file_test (g_file_peek_path (file), G_FILE_TEST_EXISTS) ||
5173 				    g_file_test (g_file_peek_path (file), G_FILE_TEST_IS_DIR)) {
5174 					/* Do nothing, simply ignore the attachment request */
5175 				} else {
5176 					has_attachments++;
5177 
5178 					if (file_is_blacklisted (content)) {
5179 						has_blacklisted_attachment = TRUE;
5180 						e_alert_submit (
5181 							E_ALERT_SINK (e_msg_composer_get_editor (composer)),
5182 							"mail:blacklisted-file",
5183 							content, NULL);
5184 					}
5185 
5186 					e_attachment_store_add_attachment (store, attachment);
5187 					e_attachment_load_async (
5188 						attachment, (GAsyncReadyCallback)
5189 						e_attachment_load_handle_error, composer);
5190 				}
5191 				g_object_unref (attachment);
5192 				g_clear_object (&file);
5193 			} else if (!g_ascii_strcasecmp (header, "from")) {
5194 				EComposerHeader *composer_header;
5195 
5196 				camel_url_decode (content);
5197 
5198 				composer_header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_FROM);
5199 
5200 				if (content && *content && composer_header && composer_header->input_widget) {
5201 					GtkTreeModel *model;
5202 					GtkTreeIter iter;
5203 
5204 					model = gtk_combo_box_get_model (GTK_COMBO_BOX (composer_header->input_widget));
5205 
5206 					if (model && gtk_tree_model_get_iter_first (model, &iter)) {
5207 						ESourceRegistry *registry;
5208 						gchar *combo_id = NULL, *address = NULL, *uid = NULL;
5209 						gboolean done;
5210 
5211 						registry = e_mail_identity_combo_box_get_registry (E_MAIL_IDENTITY_COMBO_BOX (composer_header->input_widget));
5212 
5213 						do {
5214 							gtk_tree_model_get (model, &iter,
5215 								E_MAIL_IDENTITY_COMBO_BOX_COLUMN_COMBO_ID, &combo_id,
5216 								E_MAIL_IDENTITY_COMBO_BOX_COLUMN_UID, &uid,
5217 								E_MAIL_IDENTITY_COMBO_BOX_COLUMN_ADDRESS, &address,
5218 								-1);
5219 
5220 							done = combo_id && address && g_ascii_strcasecmp (address, content) == 0;
5221 
5222 							if (!done && uid) {
5223 								ESource *source;
5224 
5225 								source = e_source_registry_ref_source (registry, uid);
5226 
5227 								if (source && e_source_has_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY)) {
5228 									ESourceMailIdentity *extension;
5229 
5230 									g_clear_pointer (&address, g_free);
5231 
5232 									extension = e_source_get_extension (source, E_SOURCE_EXTENSION_MAIL_IDENTITY);
5233 									address = e_source_mail_identity_dup_address (extension);
5234 
5235 									done = combo_id && address && g_ascii_strcasecmp (address, content) == 0;
5236 								}
5237 
5238 								g_clear_object (&source);
5239 							}
5240 
5241 							if (done)
5242 								gtk_combo_box_set_active_id (GTK_COMBO_BOX (composer_header->input_widget), combo_id);
5243 
5244 							g_clear_pointer (&combo_id, g_free);
5245 							g_clear_pointer (&address, g_free);
5246 							g_clear_pointer (&uid, g_free);
5247 						} while (!done && gtk_tree_model_iter_next (model, &iter));
5248 					}
5249 				}
5250 			} else if (!g_ascii_strcasecmp (header, "reply-to")) {
5251 				camel_url_decode (content);
5252 				e_composer_header_table_set_reply_to (table, content);
5253 			} else {
5254 				/* add an arbitrary header? */
5255 				camel_url_decode (content);
5256 				e_msg_composer_add_header (composer, header, content);
5257 			}
5258 
5259 			g_free (content);
5260 
5261 			p += clen;
5262 			if (*p == '&') {
5263 				p++;
5264 				if (!g_ascii_strncasecmp (p, "amp;", 4))
5265 					p += 4;
5266 			}
5267 		}
5268 	}
5269 
5270 	g_free (buf);
5271 
5272 	if (has_attachments && !has_blacklisted_attachment) {
5273 		const gchar *primary;
5274 		gchar *secondary;
5275 
5276 		primary = g_dngettext (GETTEXT_PACKAGE,
5277 			"Review attachment before sending.",
5278 			"Review attachments before sending.",
5279 			has_attachments);
5280 
5281 		secondary = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE,
5282 			"There had been added %d attachment. Make sure it does not contain any sensitive information before sending the message.",
5283 			"There had been added %d attachments. Make sure they do not contain any sensitive information before sending the message.",
5284 			has_attachments),
5285 			has_attachments);
5286 
5287 		e_alert_submit (
5288 			E_ALERT_SINK (e_msg_composer_get_editor (composer)),
5289 			"system:generic-warning",
5290 			primary, secondary, NULL);
5291 
5292 		g_free (secondary);
5293 	}
5294 
5295 	merge_always_cc_and_bcc (table, to, &cc, &bcc);
5296 
5297 	tov = destination_list_to_vector (to);
5298 	ccv = destination_list_to_vector (cc);
5299 	bccv = destination_list_to_vector (bcc);
5300 
5301 	g_list_free (to);
5302 	g_list_free (cc);
5303 	g_list_free (bcc);
5304 
5305 	e_composer_header_table_set_destinations_to (table, tov);
5306 	e_composer_header_table_set_destinations_cc (table, ccv);
5307 	e_composer_header_table_set_destinations_bcc (table, bccv);
5308 
5309 	e_destination_freev (tov);
5310 	e_destination_freev (ccv);
5311 	e_destination_freev (bccv);
5312 
5313 	e_composer_header_table_set_subject (table, subject);
5314 	g_free (subject);
5315 
5316 	if (body) {
5317 		GSettings *settings;
5318 		gchar *html_body;
5319 		guint32 flags = 0;
5320 
5321 		settings = e_util_ref_settings ("org.gnome.evolution.mail");
5322 
5323 		if (g_settings_get_boolean (settings, "composer-magic-links")) {
5324 			flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS | CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES;
5325 		}
5326 
5327 		if (g_settings_get_boolean (settings, "composer-mailto-body-in-pre"))
5328 			flags |= CAMEL_MIME_FILTER_TOHTML_PRE;
5329 		else
5330 			flags |= CAMEL_MIME_FILTER_TOHTML_CONVERT_NL | CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES | CAMEL_MIME_FILTER_TOHTML_DIV;
5331 
5332 		g_clear_object (&settings);
5333 
5334 		html_body = camel_text_to_html (body, flags, 0);
5335 		set_editor_text (composer, html_body, TRUE, TRUE);
5336 		g_free (html_body);
5337 
5338 		g_free (body);
5339 	}
5340 }
5341 
5342 /**
5343  * e_msg_composer_setup_from_url:
5344  * @composer: an #EMsgComposer
5345  * @url: a mailto URL
5346  *
5347  * Sets up the message @composer content as defined by the provided URL.
5348  *
5349  * Since: 3.22
5350  **/
5351 void
e_msg_composer_setup_from_url(EMsgComposer * composer,const gchar * url)5352 e_msg_composer_setup_from_url (EMsgComposer *composer,
5353 			       const gchar *url)
5354 {
5355 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5356 	g_return_if_fail (g_ascii_strncasecmp (url, "mailto:", 7) == 0);
5357 
5358 	handle_mailto (composer, url);
5359 }
5360 
5361 /**
5362  * e_msg_composer_set_body_text:
5363  * @composer: a composer object
5364  * @text: the HTML text to initialize the editor with
5365  * @update_signature: whether update signature in the text after setting it;
5366  *    Might be usually called with TRUE.
5367  *
5368  * Loads the given HTML text into the editor.
5369  **/
5370 void
e_msg_composer_set_body_text(EMsgComposer * composer,const gchar * text,gboolean update_signature)5371 e_msg_composer_set_body_text (EMsgComposer *composer,
5372                               const gchar *text,
5373                               gboolean update_signature)
5374 {
5375 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5376 	g_return_if_fail (text != NULL);
5377 
5378 	/* Every usage of e_msg_composer_set_body_text is called with HTML text */
5379 	set_editor_text (composer, text, TRUE, update_signature);
5380 }
5381 
5382 /**
5383  * e_msg_composer_set_body:
5384  * @composer: a composer object
5385  * @body: the data to initialize the composer with
5386  * @mime_type: the MIME type of data
5387  *
5388  * Loads the given data into the composer as the message body.
5389  **/
5390 void
e_msg_composer_set_body(EMsgComposer * composer,const gchar * body,const gchar * mime_type)5391 e_msg_composer_set_body (EMsgComposer *composer,
5392                          const gchar *body,
5393                          const gchar *mime_type)
5394 {
5395 	EMsgComposerPrivate *priv = composer->priv;
5396 	EComposerHeaderTable *table;
5397 	EHTMLEditor *editor;
5398 	EContentEditor *cnt_editor;
5399 	ESource *source;
5400 	gchar *identity_uid;
5401 	const gchar *content;
5402 
5403 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5404 
5405 	editor = e_msg_composer_get_editor (composer);
5406 	cnt_editor = e_html_editor_get_content_editor (editor);
5407 	table = e_msg_composer_get_header_table (composer);
5408 
5409 	/* Disable signature */
5410 	priv->disable_signature = TRUE;
5411 
5412 	identity_uid = e_composer_header_table_dup_identity_uid (table, NULL, NULL);
5413 	source = e_composer_header_table_ref_source (table, identity_uid);
5414 
5415 	content = _("The composer contains a non-text message body, which cannot be edited.");
5416 	set_editor_text (composer, content, TRUE, FALSE);
5417 
5418 	e_content_editor_set_html_mode (cnt_editor, FALSE);
5419 	e_content_editor_set_editable (cnt_editor, FALSE);
5420 
5421 	g_free (priv->mime_body);
5422 	priv->mime_body = g_strdup (body);
5423 	g_free (priv->mime_type);
5424 	priv->mime_type = g_strdup (mime_type);
5425 
5426 	if (g_ascii_strncasecmp (priv->mime_type, "text/calendar", 13) == 0) {
5427 		ESourceMailComposition *extension;
5428 		const gchar *extension_name;
5429 
5430 		extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
5431 		extension = e_source_get_extension (source, extension_name);
5432 
5433 		if (!e_source_mail_composition_get_sign_imip (extension)) {
5434 			GtkToggleAction *action;
5435 
5436 			action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
5437 			gtk_toggle_action_set_active (action, FALSE);
5438 
5439 			action = GTK_TOGGLE_ACTION (ACTION (SMIME_SIGN));
5440 			gtk_toggle_action_set_active (action, FALSE);
5441 		}
5442 	}
5443 
5444 	g_object_unref (source);
5445 	g_free (identity_uid);
5446 }
5447 
5448 /**
5449  * e_msg_composer_add_header:
5450  * @composer: an #EMsgComposer
5451  * @name: the header's name
5452  * @value: the header's value
5453  *
5454  * Adds a new custom header created from @name and @value.  The header
5455  * is not shown in the user interface but will be added to the resulting
5456  * MIME message when sending or saving.
5457  **/
5458 void
e_msg_composer_add_header(EMsgComposer * composer,const gchar * name,const gchar * value)5459 e_msg_composer_add_header (EMsgComposer *composer,
5460                            const gchar *name,
5461                            const gchar *value)
5462 {
5463 	EMsgComposerPrivate *priv;
5464 
5465 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5466 	g_return_if_fail (name != NULL);
5467 	g_return_if_fail (value != NULL);
5468 
5469 	priv = composer->priv;
5470 
5471 	g_ptr_array_add (priv->extra_hdr_names, g_strdup (name));
5472 	g_ptr_array_add (priv->extra_hdr_values, g_strdup (value));
5473 }
5474 
5475 /**
5476  * e_msg_composer_set_header:
5477  * @composer: an #EMsgComposer
5478  * @name: the header's name
5479  * @value: the header's value
5480  *
5481  * Replaces all custom headers matching @name that were added with
5482  * e_msg_composer_add_header() or e_msg_composer_set_header(), with
5483  * a new custom header created from @name and @value.  The header is
5484  * not shown in the user interface but will be added to the resulting
5485  * MIME message when sending or saving.
5486  **/
5487 void
e_msg_composer_set_header(EMsgComposer * composer,const gchar * name,const gchar * value)5488 e_msg_composer_set_header (EMsgComposer *composer,
5489                            const gchar *name,
5490                            const gchar *value)
5491 {
5492 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5493 	g_return_if_fail (name != NULL);
5494 	g_return_if_fail (value != NULL);
5495 
5496 	e_msg_composer_remove_header (composer, name);
5497 	e_msg_composer_add_header (composer, name, value);
5498 }
5499 
5500 /**
5501  * e_msg_composer_remove_header:
5502  * @composer: an #EMsgComposer
5503  * @name: the header's name
5504  *
5505  * Removes all custom headers matching @name that were added with
5506  * e_msg_composer_add_header() or e_msg_composer_set_header().
5507  **/
5508 void
e_msg_composer_remove_header(EMsgComposer * composer,const gchar * name)5509 e_msg_composer_remove_header (EMsgComposer *composer,
5510                               const gchar *name)
5511 {
5512 	EMsgComposerPrivate *priv;
5513 	guint ii;
5514 
5515 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5516 	g_return_if_fail (name != NULL);
5517 
5518 	priv = composer->priv;
5519 
5520 	for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
5521 		if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
5522 			g_free (priv->extra_hdr_names->pdata[ii]);
5523 			g_free (priv->extra_hdr_values->pdata[ii]);
5524 			g_ptr_array_remove_index (priv->extra_hdr_names, ii);
5525 			g_ptr_array_remove_index (priv->extra_hdr_values, ii);
5526 		}
5527 	}
5528 }
5529 
5530 /**
5531  * e_msg_composer_get_header:
5532  * @composer: an #EMsgComposer
5533  * @name: the header's name
5534  * @index: index of the header, 0-based
5535  *
5536  * Returns header value of the header named @name previously added
5537  * by e_msg_composer_add_header() or set by e_msg_composer_set_header().
5538  * The @index is which header index to return. Returns %NULL on error
5539  * or when the given index of the header couldn't be found.
5540  *
5541  * Returns: stored header value or NULL, if couldn't be found.
5542  *
5543  * Since: 3.20
5544  **/
5545 const gchar *
e_msg_composer_get_header(EMsgComposer * composer,const gchar * name,gint index)5546 e_msg_composer_get_header (EMsgComposer *composer,
5547 			   const gchar *name,
5548 			   gint index)
5549 {
5550 	EMsgComposerPrivate *priv;
5551 	guint ii;
5552 
5553 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
5554 	g_return_val_if_fail (name != NULL, NULL);
5555 
5556 	priv = composer->priv;
5557 
5558 	for (ii = 0; ii < priv->extra_hdr_names->len; ii++) {
5559 		if (g_strcmp0 (priv->extra_hdr_names->pdata[ii], name) == 0) {
5560 			if (index <= 0)
5561 				return priv->extra_hdr_values->pdata[ii];
5562 
5563 			index--;
5564 		}
5565 	}
5566 
5567 	return NULL;
5568 }
5569 
5570 /**
5571  * e_msg_composer_set_draft_headers:
5572  * @composer: an #EMsgComposer
5573  * @folder_uri: folder URI of the last saved draft
5574  * @message_uid: message UID of the last saved draft
5575  *
5576  * Add special X-Evolution-Draft headers to remember the most recently
5577  * saved draft message, even across Evolution sessions.  These headers
5578  * can be used to mark the draft message for deletion after saving a
5579  * newer draft or sending the composed message.
5580  **/
5581 void
e_msg_composer_set_draft_headers(EMsgComposer * composer,const gchar * folder_uri,const gchar * message_uid)5582 e_msg_composer_set_draft_headers (EMsgComposer *composer,
5583                                   const gchar *folder_uri,
5584                                   const gchar *message_uid)
5585 {
5586 	const gchar *header_name;
5587 
5588 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5589 	g_return_if_fail (folder_uri != NULL);
5590 	g_return_if_fail (message_uid != NULL);
5591 
5592 	header_name = "X-Evolution-Draft-Folder";
5593 	e_msg_composer_set_header (composer, header_name, folder_uri);
5594 
5595 	header_name = "X-Evolution-Draft-Message";
5596 	e_msg_composer_set_header (composer, header_name, message_uid);
5597 }
5598 
5599 /**
5600  * e_msg_composer_set_source_headers:
5601  * @composer: an #EMsgComposer
5602  * @folder_uri: folder URI of the source message
5603  * @message_uid: message UID of the source message
5604  * @flags: flags to set on the source message after sending
5605  *
5606  * Add special X-Evolution-Source headers to remember the message being
5607  * forwarded or replied to, even across Evolution sessions.  These headers
5608  * can be used to set appropriate flags on the source message after sending
5609  * the composed message.
5610  **/
5611 void
e_msg_composer_set_source_headers(EMsgComposer * composer,const gchar * folder_uri,const gchar * message_uid,CamelMessageFlags flags)5612 e_msg_composer_set_source_headers (EMsgComposer *composer,
5613                                    const gchar *folder_uri,
5614                                    const gchar *message_uid,
5615                                    CamelMessageFlags flags)
5616 {
5617 	GString *buffer;
5618 	const gchar *header_name;
5619 
5620 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5621 	g_return_if_fail (folder_uri != NULL);
5622 	g_return_if_fail (message_uid != NULL);
5623 
5624 	buffer = g_string_sized_new (32);
5625 
5626 	if (flags & CAMEL_MESSAGE_ANSWERED)
5627 		g_string_append (buffer, "ANSWERED ");
5628 	if (flags & CAMEL_MESSAGE_ANSWERED_ALL)
5629 		g_string_append (buffer, "ANSWERED_ALL ");
5630 	if (flags & CAMEL_MESSAGE_FORWARDED)
5631 		g_string_append (buffer, "FORWARDED ");
5632 	if (flags & CAMEL_MESSAGE_SEEN)
5633 		g_string_append (buffer, "SEEN ");
5634 
5635 	header_name = "X-Evolution-Source-Folder";
5636 	e_msg_composer_set_header (composer, header_name, folder_uri);
5637 
5638 	header_name = "X-Evolution-Source-Message";
5639 	e_msg_composer_set_header (composer, header_name, message_uid);
5640 
5641 	header_name = "X-Evolution-Source-Flags";
5642 	e_msg_composer_set_header (composer, header_name, buffer->str);
5643 
5644 	g_string_free (buffer, TRUE);
5645 }
5646 
5647 /**
5648  * e_msg_composer_attach:
5649  * @composer: a composer object
5650  * @mime_part: the #CamelMimePart to attach
5651  *
5652  * Attaches @attachment to the message being composed in the composer.
5653  **/
5654 void
e_msg_composer_attach(EMsgComposer * composer,CamelMimePart * mime_part)5655 e_msg_composer_attach (EMsgComposer *composer,
5656                        CamelMimePart *mime_part)
5657 {
5658 	EAttachmentView *view;
5659 	EAttachmentStore *store;
5660 	EAttachment *attachment;
5661 
5662 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5663 	g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
5664 
5665 	view = e_msg_composer_get_attachment_view (composer);
5666 	store = e_attachment_view_get_store (view);
5667 
5668 	attachment = e_attachment_new ();
5669 	e_attachment_set_mime_part (attachment, mime_part);
5670 	e_attachment_store_add_attachment (store, attachment);
5671 	e_attachment_load_async (
5672 		attachment, (GAsyncReadyCallback)
5673 		e_attachment_load_handle_error, composer);
5674 	g_object_unref (attachment);
5675 }
5676 
5677 static void
composer_get_message_ready(EMsgComposer * composer,GAsyncResult * result,GSimpleAsyncResult * simple)5678 composer_get_message_ready (EMsgComposer *composer,
5679                             GAsyncResult *result,
5680                             GSimpleAsyncResult *simple)
5681 {
5682 	CamelMimeMessage *message;
5683 	GError *error = NULL;
5684 
5685 	message = composer_build_message_finish (composer, result, &error);
5686 
5687 	if (message != NULL)
5688 		g_simple_async_result_set_op_res_gpointer (
5689 			simple, message, (GDestroyNotify) g_object_unref);
5690 
5691 	if (error != NULL) {
5692 		g_warn_if_fail (message == NULL);
5693 		g_simple_async_result_take_error (simple, error);
5694 	}
5695 
5696 	g_simple_async_result_complete (simple);
5697 
5698 	e_msg_composer_unref_content_hash (composer);
5699 
5700 	g_object_unref (simple);
5701 }
5702 
5703 typedef struct _BuildMessageWrapperData {
5704 	EMsgComposer *composer;
5705 	ComposerFlags flags;
5706 	gint io_priority;
5707 	GCancellable *cancellable;
5708 	GSimpleAsyncResult *simple;
5709 } BuildMessageWrapperData;
5710 
5711 static BuildMessageWrapperData *
build_message_wrapper_data_new(EMsgComposer * composer,ComposerFlags flags,gint io_priority,GCancellable * cancellable,GSimpleAsyncResult * simple)5712 build_message_wrapper_data_new (EMsgComposer *composer,
5713 				ComposerFlags flags,
5714 				gint io_priority,
5715 				GCancellable *cancellable,
5716 				GSimpleAsyncResult *simple)
5717 {
5718 	BuildMessageWrapperData *bmwd;
5719 
5720 	bmwd = g_slice_new (BuildMessageWrapperData);
5721 	bmwd->composer = g_object_ref (composer);
5722 	bmwd->flags = flags;
5723 	bmwd->io_priority = io_priority;
5724 	bmwd->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
5725 	bmwd->simple = g_object_ref (simple);
5726 
5727 	return bmwd;
5728 }
5729 
5730 static void
build_message_wrapper_data_free(gpointer ptr)5731 build_message_wrapper_data_free (gpointer ptr)
5732 {
5733 	BuildMessageWrapperData *bmwd = ptr;
5734 
5735 	if (bmwd) {
5736 		g_clear_object (&bmwd->composer);
5737 		g_clear_object (&bmwd->cancellable);
5738 		g_clear_object (&bmwd->simple);
5739 		g_slice_free (BuildMessageWrapperData, bmwd);
5740 	}
5741 }
5742 
5743 static void
composer_build_message_wrapper_content_hash_ready_cb(EMsgComposer * composer,gpointer user_data,const GError * error)5744 composer_build_message_wrapper_content_hash_ready_cb (EMsgComposer *composer,
5745 						      gpointer user_data,
5746 						      const GError *error)
5747 {
5748 	BuildMessageWrapperData *bmwd = user_data;
5749 
5750 	g_return_if_fail (bmwd != NULL);
5751 
5752 	if (error) {
5753 		g_simple_async_result_set_from_error (bmwd->simple, error);
5754 		g_simple_async_result_complete (bmwd->simple);
5755 	} else {
5756 		composer_build_message (composer, bmwd->flags, bmwd->io_priority,
5757 			bmwd->cancellable, (GAsyncReadyCallback)
5758 			composer_get_message_ready, bmwd->simple);
5759 	}
5760 
5761 	build_message_wrapper_data_free (bmwd);
5762 }
5763 
5764 static void
composer_build_message_wrapper(EMsgComposer * composer,ComposerFlags flags,gint io_priority,GCancellable * cancellable,GSimpleAsyncResult * simple)5765 composer_build_message_wrapper (EMsgComposer *composer,
5766 				ComposerFlags flags,
5767 				gint io_priority,
5768 				GCancellable *cancellable,
5769 				GSimpleAsyncResult *simple)
5770 {
5771 	BuildMessageWrapperData *bmwd;
5772 
5773 	bmwd = build_message_wrapper_data_new (composer, flags, io_priority, cancellable, simple);
5774 
5775 	e_msg_composer_prepare_content_hash (composer, cancellable, NULL, composer_build_message_wrapper_content_hash_ready_cb, bmwd);
5776 }
5777 
5778 /**
5779  * e_msg_composer_get_message:
5780  * @composer: an #EMsgComposer
5781  *
5782  * Retrieve the message edited by the user as a #CamelMimeMessage.  The
5783  * #CamelMimeMessage object is created on the fly; subsequent calls to this
5784  * function will always create new objects from scratch.
5785  **/
5786 void
e_msg_composer_get_message(EMsgComposer * composer,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)5787 e_msg_composer_get_message (EMsgComposer *composer,
5788                             gint io_priority,
5789                             GCancellable *cancellable,
5790                             GAsyncReadyCallback callback,
5791                             gpointer user_data)
5792 {
5793 	GSimpleAsyncResult *simple;
5794 	GtkAction *action;
5795 	ComposerFlags flags = 0;
5796 	EHTMLEditor *editor;
5797 	EContentEditor *cnt_editor;
5798 
5799 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5800 
5801 	editor = e_msg_composer_get_editor (composer);
5802 	cnt_editor = e_html_editor_get_content_editor (editor);
5803 
5804 	simple = g_simple_async_result_new (
5805 		G_OBJECT (composer), callback,
5806 		user_data, e_msg_composer_get_message);
5807 
5808 	g_simple_async_result_set_check_cancellable (simple, cancellable);
5809 
5810 	if (e_content_editor_get_html_mode (cnt_editor))
5811 		flags |= COMPOSER_FLAG_HTML_CONTENT;
5812 
5813 	action = ACTION (PRIORITIZE_MESSAGE);
5814 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5815 		flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
5816 
5817 	action = ACTION (REQUEST_READ_RECEIPT);
5818 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5819 		flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
5820 
5821 	action = ACTION (PGP_SIGN);
5822 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5823 		flags |= COMPOSER_FLAG_PGP_SIGN;
5824 
5825 	action = ACTION (PGP_ENCRYPT);
5826 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5827 		flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5828 
5829 #ifdef ENABLE_SMIME
5830 	action = ACTION (SMIME_SIGN);
5831 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5832 		flags |= COMPOSER_FLAG_SMIME_SIGN;
5833 
5834 	action = ACTION (SMIME_ENCRYPT);
5835 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5836 		flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5837 #endif
5838 
5839 	composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
5840 }
5841 
5842 CamelMimeMessage *
e_msg_composer_get_message_finish(EMsgComposer * composer,GAsyncResult * result,GError ** error)5843 e_msg_composer_get_message_finish (EMsgComposer *composer,
5844                                    GAsyncResult *result,
5845                                    GError **error)
5846 {
5847 	GSimpleAsyncResult *simple;
5848 	CamelMimeMessage *message;
5849 
5850 	g_return_val_if_fail (
5851 		g_simple_async_result_is_valid (
5852 		result, G_OBJECT (composer),
5853 		e_msg_composer_get_message), NULL);
5854 
5855 	simple = G_SIMPLE_ASYNC_RESULT (result);
5856 	message = g_simple_async_result_get_op_res_gpointer (simple);
5857 
5858 	if (g_simple_async_result_propagate_error (simple, error))
5859 		return NULL;
5860 
5861 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5862 
5863 	return g_object_ref (message);
5864 }
5865 
5866 void
e_msg_composer_get_message_print(EMsgComposer * composer,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)5867 e_msg_composer_get_message_print (EMsgComposer *composer,
5868                                   gint io_priority,
5869                                   GCancellable *cancellable,
5870                                   GAsyncReadyCallback callback,
5871                                   gpointer user_data)
5872 {
5873 	GSimpleAsyncResult *simple;
5874 	ComposerFlags flags = 0;
5875 
5876 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5877 
5878 	simple = g_simple_async_result_new (
5879 		G_OBJECT (composer), callback,
5880 		user_data, e_msg_composer_get_message_print);
5881 
5882 	g_simple_async_result_set_check_cancellable (simple, cancellable);
5883 
5884 	flags |= COMPOSER_FLAG_HTML_CONTENT;
5885 	flags |= COMPOSER_FLAG_SAVE_OBJECT_DATA;
5886 
5887 	composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
5888 }
5889 
5890 CamelMimeMessage *
e_msg_composer_get_message_print_finish(EMsgComposer * composer,GAsyncResult * result,GError ** error)5891 e_msg_composer_get_message_print_finish (EMsgComposer *composer,
5892                                          GAsyncResult *result,
5893                                          GError **error)
5894 {
5895 	GSimpleAsyncResult *simple;
5896 	CamelMimeMessage *message;
5897 
5898 	g_return_val_if_fail (
5899 		g_simple_async_result_is_valid (
5900 		result, G_OBJECT (composer),
5901 		e_msg_composer_get_message_print), NULL);
5902 
5903 	simple = G_SIMPLE_ASYNC_RESULT (result);
5904 	message = g_simple_async_result_get_op_res_gpointer (simple);
5905 
5906 	if (g_simple_async_result_propagate_error (simple, error))
5907 		return NULL;
5908 
5909 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5910 
5911 	return g_object_ref (message);
5912 }
5913 
5914 void
e_msg_composer_get_message_draft(EMsgComposer * composer,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)5915 e_msg_composer_get_message_draft (EMsgComposer *composer,
5916                                   gint io_priority,
5917                                   GCancellable *cancellable,
5918                                   GAsyncReadyCallback callback,
5919                                   gpointer user_data)
5920 {
5921 	EHTMLEditor *editor;
5922 	EContentEditor *cnt_editor;
5923 	GSimpleAsyncResult *simple;
5924 	ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT;
5925 	GtkAction *action;
5926 
5927 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
5928 
5929 	simple = g_simple_async_result_new (
5930 		G_OBJECT (composer), callback,
5931 		user_data, e_msg_composer_get_message_draft);
5932 
5933 	g_simple_async_result_set_check_cancellable (simple, cancellable);
5934 
5935 	editor = e_msg_composer_get_editor (composer);
5936 	cnt_editor = e_html_editor_get_content_editor (editor);
5937 	/* We need to remember composer mode */
5938 	if (e_content_editor_get_html_mode (cnt_editor))
5939 		flags |= COMPOSER_FLAG_HTML_MODE;
5940 	/* We want to save HTML content everytime when we save as draft */
5941 	flags |= COMPOSER_FLAG_SAVE_DRAFT;
5942 
5943 	action = ACTION (PRIORITIZE_MESSAGE);
5944 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5945 		flags |= COMPOSER_FLAG_PRIORITIZE_MESSAGE;
5946 
5947 	action = ACTION (REQUEST_READ_RECEIPT);
5948 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5949 		flags |= COMPOSER_FLAG_REQUEST_READ_RECEIPT;
5950 
5951 	action = ACTION (PGP_SIGN);
5952 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5953 		flags |= COMPOSER_FLAG_PGP_SIGN;
5954 
5955 	action = ACTION (PGP_ENCRYPT);
5956 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5957 		flags |= COMPOSER_FLAG_PGP_ENCRYPT;
5958 
5959 #ifdef ENABLE_SMIME
5960 	action = ACTION (SMIME_SIGN);
5961 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5962 		flags |= COMPOSER_FLAG_SMIME_SIGN;
5963 
5964 	action = ACTION (SMIME_ENCRYPT);
5965 	if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)))
5966 		flags |= COMPOSER_FLAG_SMIME_ENCRYPT;
5967 #endif
5968 
5969 	composer_build_message_wrapper (composer, flags, io_priority, cancellable, simple);
5970 }
5971 
5972 CamelMimeMessage *
e_msg_composer_get_message_draft_finish(EMsgComposer * composer,GAsyncResult * result,GError ** error)5973 e_msg_composer_get_message_draft_finish (EMsgComposer *composer,
5974                                          GAsyncResult *result,
5975                                          GError **error)
5976 {
5977 	GSimpleAsyncResult *simple;
5978 	CamelMimeMessage *message;
5979 
5980 	g_return_val_if_fail (
5981 		g_simple_async_result_is_valid (
5982 		result, G_OBJECT (composer),
5983 		e_msg_composer_get_message_draft), NULL);
5984 
5985 	simple = G_SIMPLE_ASYNC_RESULT (result);
5986 	message = g_simple_async_result_get_op_res_gpointer (simple);
5987 
5988 	if (g_simple_async_result_propagate_error (simple, error))
5989 		return NULL;
5990 
5991 	g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
5992 
5993 	return g_object_ref (message);
5994 }
5995 
5996 CamelInternetAddress *
e_msg_composer_get_from(EMsgComposer * composer)5997 e_msg_composer_get_from (EMsgComposer *composer)
5998 {
5999 	CamelInternetAddress *inet_address = NULL;
6000 	ESourceMailIdentity *mail_identity;
6001 	EComposerHeaderTable *table;
6002 	ESource *source;
6003 	const gchar *extension_name;
6004 	gchar *uid, *alias_name = NULL, *alias_address = NULL;
6005 	gchar *name;
6006 	gchar *address;
6007 
6008 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6009 
6010 	table = e_msg_composer_get_header_table (composer);
6011 
6012 	uid = e_composer_header_table_dup_identity_uid (table, &alias_name, &alias_address);
6013 	if (!uid)
6014 		return NULL;
6015 
6016 	source = e_composer_header_table_ref_source (table, uid);
6017 	g_return_val_if_fail (source != NULL, NULL);
6018 
6019 	extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
6020 	mail_identity = e_source_get_extension (source, extension_name);
6021 
6022 	if (alias_name) {
6023 		name = alias_name;
6024 		alias_name = NULL;
6025 	} else {
6026 		name = e_source_mail_identity_dup_name (mail_identity);
6027 	}
6028 
6029 	if (!name)
6030 		name = e_source_mail_identity_dup_name (mail_identity);
6031 
6032 	if (alias_address) {
6033 		address = alias_address;
6034 		alias_address = NULL;
6035 	} else {
6036 		address = e_source_mail_identity_dup_address (mail_identity);
6037 	}
6038 
6039 	g_object_unref (source);
6040 
6041 	if (address != NULL) {
6042 		inet_address = camel_internet_address_new ();
6043 		camel_internet_address_add (inet_address, name, address);
6044 	}
6045 
6046 	g_free (uid);
6047 	g_free (name);
6048 	g_free (address);
6049 	g_free (alias_name);
6050 	g_free (alias_address);
6051 
6052 	return inet_address;
6053 }
6054 
6055 CamelInternetAddress *
e_msg_composer_get_reply_to(EMsgComposer * composer)6056 e_msg_composer_get_reply_to (EMsgComposer *composer)
6057 {
6058 	CamelInternetAddress *address;
6059 	EComposerHeaderTable *table;
6060 	const gchar *reply_to;
6061 
6062 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6063 
6064 	table = e_msg_composer_get_header_table (composer);
6065 
6066 	reply_to = e_composer_header_table_get_reply_to (table);
6067 	if (reply_to == NULL || *reply_to == '\0')
6068 		return NULL;
6069 
6070 	address = camel_internet_address_new ();
6071 	if (camel_address_unformat (CAMEL_ADDRESS (address), reply_to) == -1) {
6072 		g_object_unref (address);
6073 		address = NULL;
6074 	}
6075 
6076 	return address;
6077 }
6078 
6079 /**
6080  * e_msg_composer_get_raw_message_text_without_signature:
6081  *
6082  * Returns the text/plain of the message from composer without signature
6083  **/
6084 GByteArray *
e_msg_composer_get_raw_message_text_without_signature(EMsgComposer * composer)6085 e_msg_composer_get_raw_message_text_without_signature (EMsgComposer *composer)
6086 {
6087 	EContentEditorContentHash *content_hash;
6088 	const gchar *content;
6089 	gsize content_length;
6090 	GByteArray *bytes;
6091 	gboolean needs_crlf;
6092 
6093 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6094 
6095 	content_hash = e_msg_composer_get_content_hash (composer);
6096 	g_return_val_if_fail (content_hash != NULL, NULL);
6097 
6098 	content = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED);
6099 
6100 	if (!content) {
6101 		g_warning ("%s: Failed to retrieve content", G_STRFUNC);
6102 
6103 		content = "";
6104 	}
6105 
6106 	needs_crlf = !g_str_has_suffix (content, "\r\n") && !g_str_has_suffix (content, "\n");
6107 
6108 	content_length = strlen (content);
6109 
6110 	bytes = g_byte_array_sized_new (content_length + 3);
6111 
6112 	g_byte_array_append (bytes, (const guint8 *) content, content_length);
6113 
6114 	if (needs_crlf)
6115 		g_byte_array_append (bytes, (const guint8 *) "\r\n", 2);
6116 
6117 	return bytes;
6118 }
6119 
6120 /**
6121  * e_msg_composer_get_raw_message_text:
6122  *
6123  * Returns the text/plain of the message from composer
6124  **/
6125 GByteArray *
e_msg_composer_get_raw_message_text(EMsgComposer * composer)6126 e_msg_composer_get_raw_message_text (EMsgComposer *composer)
6127 {
6128 	EContentEditorContentHash *content_hash;
6129 	const gchar *content;
6130 	gsize content_length;
6131 	GByteArray *bytes;
6132 	gboolean needs_crlf;
6133 
6134 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6135 
6136 	content_hash = e_msg_composer_get_content_hash (composer);
6137 	g_return_val_if_fail (content_hash != NULL, NULL);
6138 
6139 	content = e_content_editor_util_get_content_data (content_hash, E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN);
6140 
6141 	if (!content) {
6142 		g_warning ("%s: Failed to retrieve content", G_STRFUNC);
6143 
6144 		content = "";
6145 	}
6146 
6147 	needs_crlf = !g_str_has_suffix (content, "\r\n") && !g_str_has_suffix (content, "\n");
6148 
6149 	content_length = strlen (content);
6150 
6151 	bytes = g_byte_array_sized_new (content_length + 3);
6152 
6153 	g_byte_array_append (bytes, (const guint8 *) content, content_length);
6154 
6155 	if (needs_crlf)
6156 		g_byte_array_append (bytes, (const guint8 *) "\r\n", 2);
6157 
6158 	return bytes;
6159 }
6160 
6161 gboolean
e_msg_composer_is_exiting(EMsgComposer * composer)6162 e_msg_composer_is_exiting (EMsgComposer *composer)
6163 {
6164 	g_return_val_if_fail (composer != NULL, FALSE);
6165 
6166 	return composer->priv->application_exiting;
6167 }
6168 
6169 void
e_msg_composer_request_close(EMsgComposer * composer)6170 e_msg_composer_request_close (EMsgComposer *composer)
6171 {
6172 	g_return_if_fail (composer != NULL);
6173 
6174 	composer->priv->application_exiting = TRUE;
6175 }
6176 
6177 /* Returns whether can close the composer immediately. It will return FALSE
6178  * also when saving to drafts, but the e_msg_composer_is_exiting will return
6179  * TRUE for this case.  can_save_draft means whether can save draft
6180  * immediately, or rather keep it on the caller (when FALSE). If kept on the
6181  * folder, then returns FALSE and sets interval variable to return TRUE in
6182  * e_msg_composer_is_exiting. */
6183 gboolean
e_msg_composer_can_close(EMsgComposer * composer,gboolean can_save_draft)6184 e_msg_composer_can_close (EMsgComposer *composer,
6185                           gboolean can_save_draft)
6186 {
6187 	gboolean res = FALSE;
6188 	EHTMLEditor *editor;
6189 	EContentEditor *cnt_editor;
6190 	EComposerHeaderTable *table;
6191 	GdkWindow *window;
6192 	GtkWidget *widget;
6193 	const gchar *subject, *message_name;
6194 	gint response;
6195 
6196 	widget = GTK_WIDGET (composer);
6197 	editor = e_msg_composer_get_editor (composer);
6198 	cnt_editor = e_html_editor_get_content_editor (editor);
6199 
6200 	/* this means that there is an async operation running,
6201 	 * in which case the composer cannot be closed */
6202 	if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
6203 		return FALSE;
6204 
6205 	if (!e_content_editor_get_changed (cnt_editor) ||
6206 	    e_content_editor_is_malfunction (cnt_editor))
6207 		return TRUE;
6208 
6209 	window = gtk_widget_get_window (widget);
6210 	gdk_window_raise (window);
6211 
6212 	table = e_msg_composer_get_header_table (composer);
6213 	subject = e_composer_header_table_get_subject (table);
6214 
6215 	if (subject == NULL || *subject == '\0')
6216 		message_name = "mail-composer:exit-unsaved-no-subject";
6217 	else
6218 		message_name = "mail-composer:exit-unsaved";
6219 
6220 	response = e_alert_run_dialog_for_args (
6221 		GTK_WINDOW (composer),
6222 		message_name,
6223 		subject, NULL);
6224 
6225 	switch (response) {
6226 		case GTK_RESPONSE_YES:
6227 			e_msg_composer_request_close (composer);
6228 			if (can_save_draft)
6229 				gtk_action_activate (ACTION (SAVE_DRAFT));
6230 			break;
6231 
6232 		case GTK_RESPONSE_NO:
6233 			res = TRUE;
6234 			break;
6235 
6236 		case GTK_RESPONSE_CANCEL:
6237 			break;
6238 	}
6239 
6240 	return res;
6241 }
6242 
6243 EComposerHeaderTable *
e_msg_composer_get_header_table(EMsgComposer * composer)6244 e_msg_composer_get_header_table (EMsgComposer *composer)
6245 {
6246 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6247 
6248 	return E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
6249 }
6250 
6251 EAttachmentView *
e_msg_composer_get_attachment_view(EMsgComposer * composer)6252 e_msg_composer_get_attachment_view (EMsgComposer *composer)
6253 {
6254 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
6255 
6256 	return E_ATTACHMENT_VIEW (composer->priv->attachment_paned);
6257 }
6258 
6259 void
e_save_spell_languages(const GList * spell_dicts)6260 e_save_spell_languages (const GList *spell_dicts)
6261 {
6262 	GSettings *settings;
6263 	GPtrArray *lang_array;
6264 
6265 	/* Build a list of spell check language codes. */
6266 	lang_array = g_ptr_array_new ();
6267 
6268 	while (spell_dicts != NULL) {
6269 		ESpellDictionary *dict = spell_dicts->data;
6270 		const gchar *language_code;
6271 
6272 		language_code = e_spell_dictionary_get_code (dict);
6273 		g_ptr_array_add (lang_array, (gpointer) language_code);
6274 
6275 		spell_dicts = g_list_next (spell_dicts);
6276 	}
6277 
6278 	g_ptr_array_add (lang_array, NULL);
6279 
6280 	/* Save the language codes to GSettings. */
6281 	settings = e_util_ref_settings ("org.gnome.evolution.mail");
6282 	g_settings_set_strv (
6283 		settings, "composer-spell-languages",
6284 		(const gchar * const *) lang_array->pdata);
6285 	g_object_unref (settings);
6286 
6287 	g_ptr_array_free (lang_array, TRUE);
6288 }
6289 
6290 void
e_msg_composer_save_focused_widget(EMsgComposer * composer)6291 e_msg_composer_save_focused_widget (EMsgComposer *composer)
6292 {
6293 	GtkWidget *widget;
6294 
6295 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
6296 
6297 	widget = gtk_window_get_focus (GTK_WINDOW (composer));
6298 	composer->priv->focused_entry = widget;
6299 
6300 	if (E_IS_CONTENT_EDITOR (widget))
6301 		e_content_editor_selection_save (E_CONTENT_EDITOR (widget));
6302 
6303 	if (GTK_IS_EDITABLE (widget)) {
6304 		gtk_editable_get_selection_bounds (
6305 			GTK_EDITABLE (widget),
6306 			&composer->priv->focused_entry_selection_start,
6307 			&composer->priv->focused_entry_selection_end);
6308 	}
6309 }
6310 
6311 void
e_msg_composer_restore_focus_on_composer(EMsgComposer * composer)6312 e_msg_composer_restore_focus_on_composer (EMsgComposer *composer)
6313 {
6314 	GtkWidget *widget = composer->priv->focused_entry;
6315 
6316 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
6317 
6318 	if (!widget)
6319 		return;
6320 
6321 	gtk_window_set_focus (GTK_WINDOW (composer), widget);
6322 
6323 	if (GTK_IS_EDITABLE (widget)) {
6324 		gtk_editable_select_region (
6325 			GTK_EDITABLE (widget),
6326 			composer->priv->focused_entry_selection_start,
6327 			composer->priv->focused_entry_selection_end);
6328 	}
6329 
6330 	if (E_IS_CONTENT_EDITOR (widget)) {
6331 		EContentEditor *cnt_editor = E_CONTENT_EDITOR (widget);
6332 		e_content_editor_selection_restore (cnt_editor);
6333 	}
6334 
6335 	composer->priv->focused_entry = NULL;
6336 }
6337 
6338 gboolean
e_msg_composer_get_is_reply_or_forward(EMsgComposer * composer)6339 e_msg_composer_get_is_reply_or_forward (EMsgComposer *composer)
6340 {
6341 	g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
6342 
6343 	return composer->priv->is_reply_or_forward;
6344 }
6345 
6346 void
e_msg_composer_set_is_reply_or_forward(EMsgComposer * composer,gboolean is_reply_or_forward)6347 e_msg_composer_set_is_reply_or_forward (EMsgComposer *composer,
6348 					gboolean is_reply_or_forward)
6349 {
6350 	g_return_if_fail (E_IS_MSG_COMPOSER (composer));
6351 
6352 	if ((composer->priv->is_reply_or_forward ? 1 : 0) == (is_reply_or_forward ? 1 : 0))
6353 		return;
6354 
6355 	composer->priv->is_reply_or_forward = is_reply_or_forward;
6356 
6357 	g_object_notify (G_OBJECT (composer), "is-reply-or-forward");
6358 
6359 	msg_composer_mail_identity_changed_cb (composer);
6360 }
6361