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