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