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