/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Jeffrey Stedfast * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "e-mail-printer.h" #include "e-mail-ui-session.h" #include "em-utils.h" #include "em-composer-utils.h" #include "em-folder-selector.h" #include "em-folder-tree.h" #include "em-event.h" #include "mail-send-recv.h" #ifdef G_OS_WIN32 #ifdef gmtime_r #undef gmtime_r #endif /* The gmtime() in Microsoft's C library is MT-safe */ #define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0) #endif typedef struct _AsyncContext AsyncContext; typedef struct _ForwardData ForwardData; struct _AsyncContext { CamelMimeMessage *message; EMailSession *session; EMsgComposer *composer; EActivity *activity; gchar *folder_uri; gchar *message_uid; }; struct _ForwardData { EShell *shell; CamelFolder *folder; GPtrArray *uids; EMailForwardStyle style; }; static void async_context_free (AsyncContext *async_context) { g_clear_object (&async_context->message); g_clear_object (&async_context->session); g_clear_object (&async_context->composer); g_clear_object (&async_context->activity); g_free (async_context->folder_uri); g_free (async_context->message_uid); g_slice_free (AsyncContext, async_context); } static void forward_data_free (ForwardData *data) { if (data->shell != NULL) g_object_unref (data->shell); if (data->folder != NULL) g_object_unref (data->folder); if (data->uids != NULL) g_ptr_array_unref (data->uids); g_slice_free (ForwardData, data); } static gboolean ask_confirm_for_unwanted_html_mail (EMsgComposer *composer, EDestination **recipients) { gboolean res; GString *str; gint i; str = g_string_new (""); for (i = 0; recipients[i] != NULL; ++i) { if (!e_destination_get_html_mail_pref (recipients[i])) { const gchar *name; name = e_destination_get_textrep (recipients[i], FALSE); g_string_append_printf (str, " %s\n", name); } } if (str->len) res = em_utils_prompt_user ( GTK_WINDOW (composer), "prompt-on-unwanted-html", "mail:ask-send-html", str->str, NULL); else res = TRUE; g_string_free (str, TRUE); return res; } static gboolean ask_confirm_for_empty_subject (EMsgComposer *composer) { return em_utils_prompt_user ( GTK_WINDOW (composer), "prompt-on-empty-subject", "mail:ask-send-no-subject", NULL); } static gboolean ask_confirm_for_only_bcc (EMsgComposer *composer, gboolean hidden_list_case) { /* If the user is mailing a hidden contact list, it is possible for * them to create a message with only Bcc recipients without really * realizing it. To try to avoid being totally confusing, I've changed * this dialog to provide slightly different text in that case, to * better explain what the hell is going on. */ return em_utils_prompt_user ( GTK_WINDOW (composer), "prompt-on-only-bcc", hidden_list_case ? "mail:ask-send-only-bcc-contact" : "mail:ask-send-only-bcc", NULL); } static gboolean is_group_definition (const gchar *str) { const gchar *colon; if (!str || !*str) return FALSE; colon = strchr (str, ':'); return colon > str && strchr (str, ';') > colon; } static gboolean composer_presend_check_recipients (EMsgComposer *composer, EMailSession *session) { EDestination **recipients; EDestination **recipients_bcc; CamelInternetAddress *cia; EComposerHeaderTable *table; EComposerHeader *post_to_header; GString *invalid_addrs = NULL; gboolean check_passed = FALSE; gint hidden = 0; gint shown = 0; gint num = 0; gint num_bcc = 0; gint num_post = 0; gint ii; /* We should do all of the validity checks based on the composer, * and not on the created message, as extra interaction may occur * when we get the message (e.g. passphrase to sign a message). */ table = e_msg_composer_get_header_table (composer); recipients = e_composer_header_table_get_destinations (table); cia = camel_internet_address_new (); /* See which ones are visible, present, etc. */ for (ii = 0; recipients != NULL && recipients[ii] != NULL; ii++) { const gchar *addr; gint len, j; addr = e_destination_get_address (recipients[ii]); if (addr == NULL || *addr == '\0') continue; camel_address_decode (CAMEL_ADDRESS (cia), addr); len = camel_address_length (CAMEL_ADDRESS (cia)); if (len > 0) { if (!e_destination_is_evolution_list (recipients[ii])) { for (j = 0; j < len; j++) { const gchar *name = NULL, *eml = NULL; if (!camel_internet_address_get (cia, j, &name, &eml) || !eml || strchr (eml, '@') <= eml) { if (!invalid_addrs) invalid_addrs = g_string_new (""); else g_string_append (invalid_addrs, ", "); if (name) g_string_append (invalid_addrs, name); if (eml) { g_string_append (invalid_addrs, name ? " <" : ""); g_string_append (invalid_addrs, eml); g_string_append (invalid_addrs, name ? ">" : ""); } } } } camel_address_remove (CAMEL_ADDRESS (cia), -1); num++; if (e_destination_is_evolution_list (recipients[ii]) && !e_destination_list_show_addresses (recipients[ii])) { hidden++; } else { shown++; } } else if (is_group_definition (addr)) { /* like an address, it will not claim on only-bcc */ shown++; num++; } else if (!invalid_addrs) { invalid_addrs = g_string_new (addr); } else { g_string_append (invalid_addrs, ", "); g_string_append (invalid_addrs, addr); } } recipients_bcc = e_composer_header_table_get_destinations_bcc (table); if (recipients_bcc) { for (ii = 0; recipients_bcc[ii] != NULL; ii++) { const gchar *addr; addr = e_destination_get_address (recipients_bcc[ii]); if (addr == NULL || *addr == '\0') continue; camel_address_decode (CAMEL_ADDRESS (cia), addr); if (camel_address_length (CAMEL_ADDRESS (cia)) > 0) { camel_address_remove (CAMEL_ADDRESS (cia), -1); num_bcc++; } } e_destination_freev (recipients_bcc); } g_object_unref (cia); post_to_header = e_composer_header_table_get_header ( table, E_COMPOSER_HEADER_POST_TO); if (e_composer_header_get_visible (post_to_header)) { GList *postlist; postlist = e_composer_header_table_get_post_to (table); num_post = g_list_length (postlist); g_list_foreach (postlist, (GFunc) g_free, NULL); g_list_free (postlist); } /* I'm sensing a lack of love, er, I mean recipients. */ if (num == 0 && num_post == 0) { e_alert_submit ( E_ALERT_SINK (composer), "mail:send-no-recipients", NULL); goto finished; } if (invalid_addrs) { if (!em_utils_prompt_user ( GTK_WINDOW (composer), "prompt-on-invalid-recip", strstr (invalid_addrs->str, ", ") ? "mail:ask-send-invalid-recip-multi" : "mail:ask-send-invalid-recip-one", invalid_addrs->str, NULL)) { g_string_free (invalid_addrs, TRUE); goto finished; } g_string_free (invalid_addrs, TRUE); } if (num > 0 && (num == num_bcc || shown == 0)) { /* this means that the only recipients are Bcc's */ if (!ask_confirm_for_only_bcc (composer, shown == 0)) goto finished; } check_passed = TRUE; finished: if (recipients != NULL) e_destination_freev (recipients); return check_passed; } static gboolean composer_presend_check_identity (EMsgComposer *composer, EMailSession *session) { EComposerHeaderTable *table; EClientCache *client_cache; ESourceRegistry *registry; ESource *source; const gchar *uid; gboolean success = TRUE; table = e_msg_composer_get_header_table (composer); uid = e_composer_header_table_get_identity_uid (table); source = e_composer_header_table_ref_source (table, uid); g_return_val_if_fail (source != NULL, FALSE); client_cache = e_composer_header_table_ref_client_cache (table); registry = e_client_cache_ref_registry (client_cache); if (!e_source_registry_check_enabled (registry, source)) { e_alert_submit ( E_ALERT_SINK (composer), "mail:send-no-account-enabled", NULL); success = FALSE; } g_object_unref (client_cache); g_object_unref (registry); g_object_unref (source); return success; } static gboolean composer_presend_check_downloads (EMsgComposer *composer, EMailSession *session) { EAttachmentView *view; EAttachmentStore *store; gboolean check_passed = TRUE; view = e_msg_composer_get_attachment_view (composer); store = e_attachment_view_get_store (view); if (e_attachment_store_get_num_loading (store) > 0) { if (!em_utils_prompt_user (GTK_WINDOW (composer), NULL, "mail-composer:ask-send-message-pending-download", NULL)) check_passed = FALSE; } return check_passed; } static gboolean composer_presend_check_plugins (EMsgComposer *composer, EMailSession *session) { EMEvent *eme; EMEventTargetComposer *target; gpointer data; /** @Event: composer.presendchecks * @Title: Composer PreSend Checks * @Target: EMEventTargetMessage * * composer.presendchecks is emitted during pre-checks for the * message just before sending. Since the e-plugin framework * doesn't provide a way to return a value from the plugin, * use 'presend_check_status' to set whether the check passed. */ eme = em_event_peek (); target = em_event_target_new_composer (eme, composer, 0); e_event_emit ( (EEvent *) eme, "composer.presendchecks", (EEventTarget *) target); /* A non-NULL value for this key means the check failed. */ data = g_object_get_data (G_OBJECT (composer), "presend_check_status"); /* Clear the value in case we have to run these checks again. */ g_object_set_data (G_OBJECT (composer), "presend_check_status", NULL); return (data == NULL); } static gboolean composer_presend_check_subject (EMsgComposer *composer, EMailSession *session) { EComposerHeaderTable *table; const gchar *subject; gboolean check_passed = TRUE; table = e_msg_composer_get_header_table (composer); subject = e_composer_header_table_get_subject (table); if (subject == NULL || subject[0] == '\0') { if (!ask_confirm_for_empty_subject (composer)) check_passed = FALSE; } return check_passed; } static gboolean composer_presend_check_unwanted_html (EMsgComposer *composer, EMailSession *session) { EDestination **recipients; EComposerHeaderTable *table; GSettings *settings; gboolean check_passed = TRUE; gboolean html_mode; gboolean send_html; gboolean confirm_html; gint ii; settings = g_settings_new ("org.gnome.evolution.mail"); table = e_msg_composer_get_header_table (composer); recipients = e_composer_header_table_get_destinations (table); html_mode = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (composer)); send_html = g_settings_get_boolean (settings, "composer-send-html"); confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html"); /* Only show this warning if our default is to send html. If it * isn't, we've manually switched into html mode in the composer * and (presumably) had a good reason for doing this. */ if (html_mode && send_html && confirm_html && recipients != NULL) { gboolean html_problem = FALSE; for (ii = 0; recipients[ii] != NULL; ii++) { if (!e_destination_get_html_mail_pref (recipients[ii])) html_problem = TRUE; break; } if (html_problem) { if (!ask_confirm_for_unwanted_html_mail ( composer, recipients)) check_passed = FALSE; } } if (recipients != NULL) e_destination_freev (recipients); g_object_unref (settings); return check_passed; } static void composer_send_completed (GObject *source_object, GAsyncResult *result, gpointer user_data) { EActivity *activity; gboolean service_unavailable; gboolean set_changed = FALSE; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) user_data; activity = async_context->activity; e_mail_session_send_to_finish ( E_MAIL_SESSION (source_object), result, &local_error); if (e_activity_handle_cancellation (activity, local_error)) { set_changed = TRUE; goto exit; } /* Check for error codes which may indicate we're offline * or name resolution failed or connection attempt failed. */ service_unavailable = g_error_matches ( local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE) || /* name resolution failed */ g_error_matches ( local_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND) || g_error_matches ( local_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE) || /* something internal to Camel failed */ g_error_matches ( local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_URL_INVALID); if (service_unavailable) { /* Inform the user. */ e_alert_run_dialog_for_args ( GTK_WINDOW (async_context->composer), "mail-composer:saving-to-outbox", NULL); e_msg_composer_save_to_outbox (async_context->composer); goto exit; } /* Post-processing errors are shown in the shell window. */ if (g_error_matches ( local_error, E_MAIL_ERROR, E_MAIL_ERROR_POST_PROCESSING)) { EAlert *alert; EShell *shell; shell = e_msg_composer_get_shell (async_context->composer); alert = e_alert_new ( "mail-composer:send-post-processing-error", local_error->message, NULL); e_shell_submit_alert (shell, alert); g_object_unref (alert); /* All other errors are shown in the composer window. */ } else if (local_error != NULL) { gint response; /* Clear the activity bar before * presenting the error dialog. */ g_clear_object (&async_context->activity); activity = async_context->activity; response = e_alert_run_dialog_for_args ( GTK_WINDOW (async_context->composer), "mail-composer:send-error", local_error->message, NULL); if (response == GTK_RESPONSE_OK) /* Try Again */ e_msg_composer_send (async_context->composer); if (response == GTK_RESPONSE_ACCEPT) /* Save to Outbox */ e_msg_composer_save_to_outbox (async_context->composer); set_changed = TRUE; goto exit; } e_activity_set_state (activity, E_ACTIVITY_COMPLETED); /* Wait for the EActivity's completion message to * time out and then destroy the composer window. */ g_object_weak_ref ( G_OBJECT (activity), (GWeakNotify) gtk_widget_destroy, async_context->composer); exit: g_clear_error (&local_error); if (set_changed) { gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); gtk_window_present (GTK_WINDOW (async_context->composer)); } async_context_free (async_context); } static void em_utils_composer_send_cb (EMsgComposer *composer, CamelMimeMessage *message, EActivity *activity, EMailSession *session) { AsyncContext *async_context; GCancellable *cancellable; async_context = g_slice_new0 (AsyncContext); async_context->message = g_object_ref (message); async_context->composer = g_object_ref (composer); async_context->activity = g_object_ref (activity); cancellable = e_activity_get_cancellable (activity); e_mail_session_send_to ( session, message, G_PRIORITY_DEFAULT, cancellable, NULL, NULL, composer_send_completed, async_context); } static void composer_set_no_change (EMsgComposer *composer) { GtkhtmlEditor *editor; g_return_if_fail (composer != NULL); editor = GTKHTML_EDITOR (composer); gtkhtml_editor_drop_undo (editor); gtkhtml_editor_set_changed (editor, FALSE); } /* delete original messages from Outbox folder */ static void manage_x_evolution_replace_outbox (EMsgComposer *composer, EMailSession *session, CamelMimeMessage *message, GCancellable *cancellable) { const gchar *message_uid; const gchar *header; CamelFolder *outbox; g_return_if_fail (composer != NULL); g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); header = "X-Evolution-Replace-Outbox-UID"; message_uid = camel_medium_get_header (CAMEL_MEDIUM (message), header); e_msg_composer_remove_header (composer, header); if (!message_uid) return; outbox = e_mail_session_get_local_folder ( session, E_MAIL_LOCAL_FOLDER_OUTBOX); g_return_if_fail (outbox != NULL); camel_folder_set_message_flags ( outbox, message_uid, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); /* ignore errors here */ camel_folder_synchronize_message_sync ( outbox, message_uid, cancellable, NULL); } static void composer_save_to_drafts_complete (GObject *source_object, GAsyncResult *result, gpointer user_data) { EActivity *activity; AsyncContext *async_context; GError *local_error = NULL; /* We don't really care if this failed. If something other than * cancellation happened, emit a runtime warning so the error is * not completely lost. */ async_context = (AsyncContext *) user_data; activity = async_context->activity; e_mail_session_handle_draft_headers_finish ( E_MAIL_SESSION (source_object), result, &local_error); if (e_activity_handle_cancellation (activity, local_error)) { gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); g_error_free (local_error); } else if (local_error != NULL) { gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); g_warning ("%s", local_error->message); g_error_free (local_error); } else e_activity_set_state (activity, E_ACTIVITY_COMPLETED); /* Encode the draft message we just saved into the EMsgComposer * as X-Evolution-Draft headers. The message will be marked for * deletion if the user saves a newer draft message or sends the * composed message. */ e_msg_composer_set_draft_headers ( async_context->composer, async_context->folder_uri, async_context->message_uid); async_context_free (async_context); } static void composer_save_to_drafts_cleanup (GObject *source_object, GAsyncResult *result, gpointer user_data) { CamelSession *session; EActivity *activity; EAlertSink *alert_sink; GCancellable *cancellable; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) user_data; activity = async_context->activity; alert_sink = e_activity_get_alert_sink (activity); cancellable = e_activity_get_cancellable (activity); e_mail_folder_append_message_finish ( CAMEL_FOLDER (source_object), result, &async_context->message_uid, &local_error); if (e_activity_handle_cancellation (activity, local_error)) { g_warn_if_fail (async_context->message_uid == NULL); gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); async_context_free (async_context); g_error_free (local_error); return; } else if (local_error != NULL) { g_warn_if_fail (async_context->message_uid == NULL); e_alert_submit ( alert_sink, "mail-composer:save-to-drafts-error", local_error->message, NULL); gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); async_context_free (async_context); g_error_free (local_error); return; } session = e_msg_composer_ref_session (async_context->composer); /* Mark the previously saved draft message for deletion. * Note: This is just a nice-to-have; ignore failures. */ e_mail_session_handle_draft_headers ( E_MAIL_SESSION (session), async_context->message, G_PRIORITY_DEFAULT, cancellable, composer_save_to_drafts_complete, async_context); g_object_unref (session); } static void composer_save_to_drafts_append_mail (AsyncContext *async_context, CamelFolder *drafts_folder) { CamelFolder *local_drafts_folder; GCancellable *cancellable; CamelMessageInfo *info; local_drafts_folder = e_mail_session_get_local_folder ( async_context->session, E_MAIL_LOCAL_FOLDER_DRAFTS); if (drafts_folder == NULL) drafts_folder = g_object_ref (local_drafts_folder); cancellable = e_activity_get_cancellable (async_context->activity); info = camel_message_info_new (NULL); camel_message_info_set_flags ( info, CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_SEEN, ~0); camel_medium_remove_header ( CAMEL_MEDIUM (async_context->message), "X-Evolution-Replace-Outbox-UID"); e_mail_folder_append_message ( drafts_folder, async_context->message, info, G_PRIORITY_DEFAULT, cancellable, composer_save_to_drafts_cleanup, async_context); camel_message_info_unref (info); g_object_unref (drafts_folder); } static void composer_save_to_drafts_got_folder (GObject *source_object, GAsyncResult *result, gpointer user_data) { EActivity *activity; CamelFolder *drafts_folder; AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) user_data; activity = async_context->activity; drafts_folder = e_mail_session_uri_to_folder_finish ( E_MAIL_SESSION (source_object), result, &local_error); /* Sanity check. */ g_return_if_fail ( ((drafts_folder != NULL) && (local_error == NULL)) || ((drafts_folder == NULL) && (local_error != NULL))); if (e_activity_handle_cancellation (activity, local_error)) { gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); async_context_free (async_context); g_error_free (local_error); return; } else if (local_error != NULL) { gint response; /* XXX Not showing the error message in the dialog? */ g_error_free (local_error); /* If we can't retrieve the Drafts folder for the * selected account, ask the user if he wants to * save to the local Drafts folder instead. */ response = e_alert_run_dialog_for_args ( GTK_WINDOW (async_context->composer), "mail:ask-default-drafts", NULL); if (response != GTK_RESPONSE_YES) { gtkhtml_editor_set_changed ( GTKHTML_EDITOR (async_context->composer), TRUE); async_context_free (async_context); return; } } composer_save_to_drafts_append_mail (async_context, drafts_folder); } static void em_utils_composer_save_to_drafts_cb (EMsgComposer *composer, CamelMimeMessage *message, EActivity *activity, EMailSession *session) { AsyncContext *async_context; EComposerHeaderTable *table; ESource *source; const gchar *local_drafts_folder_uri; const gchar *identity_uid; gchar *drafts_folder_uri = NULL; async_context = g_slice_new0 (AsyncContext); async_context->message = g_object_ref (message); async_context->session = g_object_ref (session); async_context->composer = g_object_ref (composer); async_context->activity = g_object_ref (activity); table = e_msg_composer_get_header_table (composer); identity_uid = e_composer_header_table_get_identity_uid (table); source = e_composer_header_table_ref_source (table, identity_uid); /* Get the selected identity's preferred Drafts folder. */ if (source != NULL) { ESourceMailComposition *extension; const gchar *extension_name; gchar *uri; extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION; extension = e_source_get_extension (source, extension_name); uri = e_source_mail_composition_dup_drafts_folder (extension); drafts_folder_uri = uri; g_object_unref (source); } local_drafts_folder_uri = e_mail_session_get_local_folder_uri ( session, E_MAIL_LOCAL_FOLDER_DRAFTS); if (drafts_folder_uri == NULL) { composer_save_to_drafts_append_mail (async_context, NULL); async_context->folder_uri = g_strdup (local_drafts_folder_uri); } else { GCancellable *cancellable; cancellable = e_activity_get_cancellable (activity); async_context->folder_uri = g_strdup (drafts_folder_uri); e_mail_session_uri_to_folder ( session, drafts_folder_uri, 0, G_PRIORITY_DEFAULT, cancellable, composer_save_to_drafts_got_folder, async_context); g_free (drafts_folder_uri); } } static void composer_save_to_outbox_completed (GObject *source_object, GAsyncResult *result, gpointer user_data) { EMailSession *session; EActivity *activity; EAlertSink *alert_sink; GCancellable *cancellable; AsyncContext *async_context; GError *local_error = NULL; session = E_MAIL_SESSION (source_object); async_context = (AsyncContext *) user_data; activity = async_context->activity; alert_sink = e_activity_get_alert_sink (activity); cancellable = e_activity_get_cancellable (activity); e_mail_session_append_to_local_folder_finish ( session, result, NULL, &local_error); if (e_activity_handle_cancellation (activity, local_error)) { g_error_free (local_error); goto exit; } else if (local_error != NULL) { e_alert_submit ( alert_sink, "mail-composer:append-to-outbox-error", local_error->message, NULL); g_error_free (local_error); goto exit; } /* special processing for Outbox folder */ manage_x_evolution_replace_outbox ( async_context->composer, session, async_context->message, cancellable); e_activity_set_state (activity, E_ACTIVITY_COMPLETED); /* Wait for the EActivity's completion message to * time out and then destroy the composer window. */ g_object_weak_ref ( G_OBJECT (activity), (GWeakNotify) gtk_widget_destroy, async_context->composer); exit: async_context_free (async_context); } static void em_utils_composer_save_to_outbox_cb (EMsgComposer *composer, CamelMimeMessage *message, EActivity *activity, EMailSession *session) { AsyncContext *async_context; CamelMessageInfo *info; GCancellable *cancellable; async_context = g_slice_new0 (AsyncContext); async_context->message = g_object_ref (message); async_context->composer = g_object_ref (composer); async_context->activity = g_object_ref (activity); cancellable = e_activity_get_cancellable (activity); info = camel_message_info_new (NULL); camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0); e_mail_session_append_to_local_folder ( session, E_MAIL_LOCAL_FOLDER_OUTBOX, message, info, G_PRIORITY_DEFAULT, cancellable, composer_save_to_outbox_completed, async_context); camel_message_info_unref (info); } static void em_utils_composer_print_cb (EMsgComposer *composer, GtkPrintOperationAction action, CamelMimeMessage *message, EActivity *activity, EMailSession *session) { /* as long as EMsgComposer uses GtkHTML, use its routine for printing; * this conditional compile is here rather to not forget to fix this * once the WebKit-based composer will land */ #if defined(GTK_TYPE_HTML) EWebViewGtkHTML *gtkhtml_web_view; GtkPrintOperation *operation; GError *error = NULL; gtkhtml_web_view = e_msg_composer_get_web_view (composer); g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (gtkhtml_web_view)); operation = gtk_print_operation_new (); gtk_html_print_operation_run ( GTK_HTML (gtkhtml_web_view), operation, action, GTK_WINDOW (composer), NULL, NULL, NULL, NULL, NULL, &error); g_object_unref (operation); if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_error_free (error); } else if (error != NULL) { g_warning ( "%s: Failed to run print operation: %s", G_STRFUNC, error->message); g_error_free (error); } #else EMailParser *parser; EMailPartList *parts; EMailPrinter *printer; const gchar *message_id; parser = e_mail_parser_new (CAMEL_SESSION (session)); message_id = camel_mime_message_get_message_id (message); parts = e_mail_parser_parse_sync ( parser, NULL, g_strdup (message_id), message, NULL); /* FIXME Show an alert on error. */ printer = e_mail_printer_new (parts); e_mail_printer_print (printer, action, NULL, NULL, NULL, NULL); g_object_unref (printer); g_object_unref (parts); #endif } /* Composing messages... */ static EMsgComposer * create_new_composer (EShell *shell, const gchar *subject, CamelFolder *folder) { EMsgComposer *composer; EClientCache *client_cache; ESourceRegistry *registry; EComposerHeaderTable *table; ESource *source = NULL; gchar *identity = NULL; composer = e_msg_composer_new (shell); table = e_msg_composer_get_header_table (composer); client_cache = e_composer_header_table_ref_client_cache (table); registry = e_client_cache_ref_registry (client_cache); if (folder != NULL) { CamelStore *store; gchar *folder_uri; GList *list; store = camel_folder_get_parent_store (folder); source = em_utils_ref_mail_identity_for_store (registry, store); folder_uri = e_mail_folder_uri_from_folder (folder); list = g_list_prepend (NULL, folder_uri); e_composer_header_table_set_post_to_list (table, list); g_list_free (list); g_free (folder_uri); } if (source != NULL) { identity = e_source_dup_uid (source); g_object_unref (source); } e_composer_header_table_set_subject (table, subject); e_composer_header_table_set_identity_uid (table, identity); em_utils_apply_send_account_override_to_composer (composer, shell, folder); g_free (identity); g_object_unref (client_cache); g_object_unref (registry); return composer; } /** * em_utils_compose_new_message: * @shell: an #EShell * @folder: a #CamelFolder, or %NULL * * Opens a new composer window as a child window of @parent's toplevel * window. * * Returns: the resulting #EMsgComposer **/ EMsgComposer * em_utils_compose_new_message (EShell *shell, CamelFolder *folder) { EMsgComposer *composer; g_return_val_if_fail (E_IS_SHELL (shell), NULL); if (folder != NULL) g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL); composer = create_new_composer (shell, "", folder); composer_set_no_change (composer); gtk_widget_show (GTK_WIDGET (composer)); return composer; } static CamelMimeMessage * em_utils_get_composer_recipients_as_message (EMsgComposer *composer) { CamelMimeMessage *message; EComposerHeaderTable *table; EComposerHeader *header; EDestination **destv; CamelInternetAddress *to_addr, *cc_addr, *bcc_addr, *dest_addr; const gchar *text_addr; gint ii; g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL); table = e_msg_composer_get_header_table (composer); header = e_composer_header_table_get_header (table, E_COMPOSER_HEADER_TO); if (!e_composer_header_get_visible (header)) return NULL; message = camel_mime_message_new (); to_addr = camel_internet_address_new (); cc_addr = camel_internet_address_new (); bcc_addr = camel_internet_address_new (); /* To */ dest_addr = to_addr; destv = e_composer_header_table_get_destinations_to (table); for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) { text_addr = e_destination_get_address (destv[ii]); if (text_addr && *text_addr) { if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0) camel_internet_address_add (dest_addr, "", text_addr); } } e_destination_freev (destv); /* CC */ dest_addr = cc_addr; destv = e_composer_header_table_get_destinations_cc (table); for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) { text_addr = e_destination_get_address (destv[ii]); if (text_addr && *text_addr) { if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0) camel_internet_address_add (dest_addr, "", text_addr); } } e_destination_freev (destv); /* Bcc */ dest_addr = bcc_addr; destv = e_composer_header_table_get_destinations_bcc (table); for (ii = 0; destv != NULL && destv[ii] != NULL; ii++) { text_addr = e_destination_get_address (destv[ii]); if (text_addr && *text_addr) { if (camel_address_decode (CAMEL_ADDRESS (dest_addr), text_addr) <= 0) camel_internet_address_add (dest_addr, "", text_addr); } } e_destination_freev (destv); if (camel_address_length (CAMEL_ADDRESS (to_addr)) > 0) camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_TO, to_addr); if (camel_address_length (CAMEL_ADDRESS (cc_addr)) > 0) camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_CC, cc_addr); if (camel_address_length (CAMEL_ADDRESS (bcc_addr)) > 0) camel_mime_message_set_recipients (message, CAMEL_RECIPIENT_TYPE_BCC, bcc_addr); g_object_unref (to_addr); g_object_unref (cc_addr); g_object_unref (bcc_addr); return message; } /** * em_utils_compose_new_message_with_mailto: * @shell: an #EShell * @mailto: a mailto URL * @folder: a #CamelFolder, or %NULL * * Opens a new composer window as a child window of @parent's toplevel * window. If @mailto is non-NULL, the composer fields will be filled in * according to the values in the mailto URL. **/ EMsgComposer * em_utils_compose_new_message_with_mailto (EShell *shell, const gchar *mailto, CamelFolder *folder) { EMsgComposer *composer; EComposerHeaderTable *table; EClientCache *client_cache; ESourceRegistry *registry; g_return_val_if_fail (E_IS_SHELL (shell), NULL); if (folder != NULL) g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL); if (mailto != NULL) composer = e_msg_composer_new_from_url (shell, mailto); else composer = e_msg_composer_new (shell); em_utils_apply_send_account_override_to_composer (composer, shell, folder); table = e_msg_composer_get_header_table (composer); client_cache = e_composer_header_table_ref_client_cache (table); registry = e_client_cache_ref_registry (client_cache); composer_set_no_change (composer); gtk_window_present (GTK_WINDOW (composer)); /* If a CamelFolder was given, we need to backtrack and find * the corresponding ESource with a Mail Identity extension. */ if (folder != NULL) { ESource *source; CamelStore *store; store = camel_folder_get_parent_store (folder); source = em_utils_ref_mail_identity_for_store (registry, store); if (source != NULL) { const gchar *uid = e_source_get_uid (source); e_composer_header_table_set_identity_uid (table, uid); g_object_unref (source); } } g_object_unref (client_cache); g_object_unref (registry); return composer; } static gboolean replace_variables (GSList *clues, CamelMimeMessage *message, gchar **pstr) { gint i; gboolean string_changed = FALSE, count1 = FALSE; gchar *str; g_return_val_if_fail (pstr != NULL, FALSE); g_return_val_if_fail (*pstr != NULL, FALSE); g_return_val_if_fail (message != NULL, FALSE); str = *pstr; for (i = 0; i < strlen (str); i++) { const gchar *cur = str + i; if (!g_ascii_strncasecmp (cur, "$", 1)) { const gchar *end = cur + 1; gchar *out; gchar **temp_str; GSList *list; while (*end && (g_unichar_isalnum (*end) || *end == '_')) end++; out = g_strndup ((const gchar *) cur, end - cur); temp_str = g_strsplit (str, out, 2); for (list = clues; list; list = g_slist_next (list)) { gchar **temp = g_strsplit (list->data, "=", 2); if (!g_ascii_strcasecmp (temp[0], out + 1)) { g_free (str); str = g_strconcat (temp_str[0], temp[1], temp_str[1], NULL); count1 = TRUE; string_changed = TRUE; } else count1 = FALSE; g_strfreev (temp); } if (!count1) { if (getenv (out + 1)) { g_free (str); str = g_strconcat ( temp_str[0], getenv (out + 1), temp_str[1], NULL); count1 = TRUE; string_changed = TRUE; } else count1 = FALSE; } if (!count1) { CamelInternetAddress *to; const gchar *name, *addr; to = camel_mime_message_get_recipients ( message, CAMEL_RECIPIENT_TYPE_TO); if (!camel_internet_address_get (to, 0, &name, &addr)) continue; if (name && g_ascii_strcasecmp ("sender_name", out + 1) == 0) { g_free (str); str = g_strconcat (temp_str[0], name, temp_str[1], NULL); count1 = TRUE; string_changed = TRUE; } else if (addr && g_ascii_strcasecmp ("sender_email", out + 1) == 0) { g_free (str); str = g_strconcat (temp_str[0], addr, temp_str[1], NULL); count1 = TRUE; string_changed = TRUE; } } g_strfreev (temp_str); g_free (out); } } *pstr = str; return string_changed; } static void traverse_parts (GSList *clues, CamelMimeMessage *message, CamelDataWrapper *content) { g_return_if_fail (message != NULL); if (!content) return; if (CAMEL_IS_MULTIPART (content)) { guint i, n; CamelMultipart *multipart = CAMEL_MULTIPART (content); CamelMimePart *part; n = camel_multipart_get_number (multipart); for (i = 0; i < n; i++) { part = camel_multipart_get_part (multipart, i); if (!part) continue; traverse_parts (clues, message, CAMEL_DATA_WRAPPER (part)); } } else if (CAMEL_IS_MIME_PART (content)) { CamelMimePart *part = CAMEL_MIME_PART (content); CamelContentType *type; CamelStream *stream; GByteArray *byte_array; gchar *str; content = camel_medium_get_content (CAMEL_MEDIUM (part)); if (!content) return; if (CAMEL_IS_MULTIPART (content)) { traverse_parts (clues, message, CAMEL_DATA_WRAPPER (content)); return; } type = camel_mime_part_get_content_type (part); if (!camel_content_type_is (type, "text", "*")) return; byte_array = g_byte_array_new (); stream = camel_stream_mem_new_with_byte_array (byte_array); camel_data_wrapper_decode_to_stream_sync ( content, stream, NULL, NULL); str = g_strndup ((gchar *) byte_array->data, byte_array->len); g_object_unref (stream); if (replace_variables (clues, message, &str)) { stream = camel_stream_mem_new_with_buffer (str, strlen (str)); camel_data_wrapper_construct_from_stream_sync ( content, stream, NULL, NULL); g_object_unref (stream); } g_free (str); } } /* Editing messages... */ typedef enum { QUOTING_ATTRIBUTION, QUOTING_FORWARD, QUOTING_ORIGINAL } QuotingTextEnum; static struct { const gchar * conf_key; const gchar * message; } conf_messages[] = { [QUOTING_ATTRIBUTION] = { "composer-message-attribution", /* Note to translators: this is the attribution string used * when quoting messages. Each ${Variable} gets replaced * with a value. To see a full list of available variables, * see mail/em-composer-utils.c:attribvars array. */ N_("On ${AbbrevWeekdayName}, ${Year}-${Month}-${Day} at " "${24Hour}:${Minute} ${TimeZone}, ${Sender} wrote:") }, [QUOTING_FORWARD] = { "composer-message-forward", N_("-------- Forwarded Message --------") }, [QUOTING_ORIGINAL] = { "composer-message-original", N_("-----Original Message-----") } }; static gchar * quoting_text (QuotingTextEnum type) { GSettings *settings; gchar *text; settings = g_settings_new ("org.gnome.evolution.mail"); text = g_settings_get_string (settings, conf_messages[type].conf_key); g_object_unref (settings); if (text && *text) return text; g_free (text); return g_strdup (_(conf_messages[type].message)); } /** * em_utils_edit_message: * @shell: an #EShell * @folder: a #CamelFolder * @message: a #CamelMimeMessage * @message_uid: UID of @message, or %NULL * * Opens a composer filled in with the headers/mime-parts/etc of * @message. **/ EMsgComposer * em_utils_edit_message (EShell *shell, CamelFolder *folder, CamelMimeMessage *message, const gchar *message_uid, gboolean keep_signature) { EMsgComposer *composer; ESourceRegistry *registry; gboolean folder_is_drafts; gboolean folder_is_outbox; gboolean folder_is_templates; g_return_val_if_fail (E_IS_SHELL (shell), NULL); g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); registry = e_shell_get_registry (shell); folder_is_drafts = em_utils_folder_is_drafts (registry, folder); folder_is_outbox = em_utils_folder_is_outbox (registry, folder); folder_is_templates = em_utils_folder_is_templates (registry, folder); /* Template specific code follows. */ if (folder_is_templates) { CamelDataWrapper *content; GSettings *settings; gchar **strv; gint i; GSList *clue_list = NULL; settings = g_settings_new ("org.gnome.evolution.plugin.templates"); /* Get the list from GSettings */ strv = g_settings_get_strv (settings, "template-placeholders"); for (i = 0; strv[i] != NULL; i++) clue_list = g_slist_append (clue_list, g_strdup (strv[i])); g_object_unref (settings); g_strfreev (strv); content = camel_medium_get_content (CAMEL_MEDIUM (message)); traverse_parts (clue_list, message, content); g_slist_foreach (clue_list, (GFunc) g_free, NULL); g_slist_free (clue_list); } composer = e_msg_composer_new_with_message (shell, message, keep_signature, NULL); em_utils_apply_send_account_override_to_composer (composer, shell, folder); if (!folder_is_templates) { EComposerHeaderTable *table; ESource *source; CamelStore *store; gchar *folder_uri; GList *list; table = e_msg_composer_get_header_table (composer); store = camel_folder_get_parent_store (folder); source = em_utils_ref_mail_identity_for_store (registry, store); if (source != NULL) { const gchar *uid = e_source_get_uid (source); e_composer_header_table_set_identity_uid (table, uid); g_object_unref (source); } folder_uri = e_mail_folder_uri_from_folder (folder); list = g_list_prepend (NULL, folder_uri); e_composer_header_table_set_post_to_list (table, list); g_list_free (list); g_free (folder_uri); } e_msg_composer_remove_header ( composer, "X-Evolution-Replace-Outbox-UID"); if (message_uid != NULL && folder_is_drafts) { gchar *folder_uri; folder_uri = e_mail_folder_uri_from_folder (folder); e_msg_composer_set_draft_headers ( composer, folder_uri, message_uid); g_free (folder_uri); } else if (message_uid != NULL && folder_is_outbox) { e_msg_composer_set_header ( composer, "X-Evolution-Replace-Outbox-UID", message_uid); } composer_set_no_change (composer); gtk_widget_show (GTK_WIDGET (composer)); return composer; } static void emu_update_composers_security (EMsgComposer *composer, guint32 validity_found) { GtkAction *action; GSettings *settings; gboolean sign_by_default; g_return_if_fail (composer != NULL); settings = g_settings_new ("org.gnome.evolution.mail"); sign_by_default = (validity_found & E_MAIL_PART_VALIDITY_SIGNED) != 0 && /* FIXME This should be an EMsgComposer property. */ g_settings_get_boolean ( settings, "composer-sign-reply-if-signed"); g_object_unref (settings); /* Pre-set only for encrypted messages, not for signed */ if (sign_by_default) { if (validity_found & E_MAIL_PART_VALIDITY_SMIME) action = E_COMPOSER_ACTION_SMIME_SIGN (composer); else action = E_COMPOSER_ACTION_PGP_SIGN (composer); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION (action), TRUE); } if (validity_found & E_MAIL_PART_VALIDITY_ENCRYPTED) { if (validity_found & E_MAIL_PART_VALIDITY_SMIME) action = E_COMPOSER_ACTION_SMIME_ENCRYPT (composer); else action = E_COMPOSER_ACTION_PGP_ENCRYPT (composer); gtk_toggle_action_set_active ( GTK_TOGGLE_ACTION (action), TRUE); } } void em_utils_get_real_folder_uri_and_message_uid (CamelFolder *folder, const gchar *uid, gchar **folder_uri, gchar **message_uid) { g_return_if_fail (folder != NULL); g_return_if_fail (uid != NULL); g_return_if_fail (folder_uri != NULL); g_return_if_fail (message_uid != NULL); if (CAMEL_IS_VEE_FOLDER (folder)) { CamelMessageInfo *mi; mi = camel_folder_get_message_info (folder, uid); if (mi) { CamelFolder *real_folder; gchar *real_uid = NULL; real_folder = camel_vee_folder_get_location ( CAMEL_VEE_FOLDER (folder), (CamelVeeMessageInfo *) mi, &real_uid); if (real_folder) { *folder_uri = e_mail_folder_uri_from_folder (real_folder); *message_uid = real_uid; camel_message_info_unref (mi); return; } camel_message_info_unref (mi); } } *folder_uri = e_mail_folder_uri_from_folder (folder); *message_uid = g_strdup (uid); } static void real_update_forwarded_flag (gpointer uid, gpointer folder) { if (uid && folder) camel_folder_set_message_flags ( folder, uid, CAMEL_MESSAGE_FORWARDED, CAMEL_MESSAGE_FORWARDED); } static void update_forwarded_flags_cb (EMsgComposer *composer, ForwardData *data) { if (data && data->uids && data->folder) g_ptr_array_foreach ( data->uids, real_update_forwarded_flag, data->folder); } static void setup_forward_attached_callbacks (EMsgComposer *composer, CamelFolder *folder, GPtrArray *uids) { ForwardData *data; if (!composer || !folder || !uids || !uids->len) return; g_object_ref (folder); data = g_slice_new0 (ForwardData); data->folder = g_object_ref (folder); data->uids = g_ptr_array_ref (uids); g_signal_connect ( composer, "send", G_CALLBACK (update_forwarded_flags_cb), data); g_signal_connect ( composer, "save-to-drafts", G_CALLBACK (update_forwarded_flags_cb), data); g_object_set_data_full ( G_OBJECT (composer), "forward-data", data, (GDestroyNotify) forward_data_free); } static EMsgComposer * forward_non_attached (EMailBackend *backend, CamelFolder *folder, const gchar *uid, CamelMimeMessage *message, EMailForwardStyle style) { EMsgComposer *composer = NULL; EMailSession *session; EShell *shell; gchar *text, *forward; guint32 validity_found = 0; guint32 flags; flags = E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS | E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG; if (style == E_MAIL_FORWARD_STYLE_QUOTED) flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE; session = e_mail_backend_get_session (backend); shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend)); forward = quoting_text (QUOTING_FORWARD); text = em_utils_message_to_html ( CAMEL_SESSION (session), message, forward, flags, NULL, NULL, &validity_found); if (text != NULL) { CamelDataWrapper *content; gchar *subject; subject = mail_tool_generate_forward_subject (message); composer = create_new_composer (shell, subject, folder); g_free (subject); content = camel_medium_get_content (CAMEL_MEDIUM (message)); if (CAMEL_IS_MULTIPART (content)) e_msg_composer_add_message_attachments ( composer, message, FALSE); e_msg_composer_set_body_text (composer, text, TRUE); if (uid != NULL) { gchar *folder_uri = NULL, *tmp_message_uid = NULL; em_utils_get_real_folder_uri_and_message_uid ( folder, uid, &folder_uri, &tmp_message_uid); e_msg_composer_set_source_headers ( composer, folder_uri, tmp_message_uid, CAMEL_MESSAGE_FORWARDED); g_free (folder_uri); g_free (tmp_message_uid); } emu_update_composers_security ( composer, validity_found); composer_set_no_change (composer); gtk_widget_show (GTK_WIDGET (composer)); g_free (text); } g_free (forward); return composer; } /** * em_utils_forward_message: * @backend: an #EMailBackend * @message: a #CamelMimeMessage to forward * @style: the forward style to use * @folder: a #CamelFolder, or %NULL * @uid: the UID of %message, or %NULL * * Forwards @message in the given @style. * * If @style is #E_MAIL_FORWARD_STYLE_ATTACHED, the new message is * created as follows. If there is more than a single message in @uids, * a multipart/digest will be constructed and attached to a new composer * window preset with the appropriate header defaults for forwarding the * first message in the list. If only one message is to be forwarded, * it is forwarded as a simple message/rfc822 attachment. * * If @style is #E_MAIL_FORWARD_STYLE_INLINE, each message is forwarded * in its own composer window in 'inline' form. * * If @style is #E_MAIL_FORWARD_STYLE_QUOTED, each message is forwarded * in its own composer window in 'quoted' form (each line starting with * a "> "). **/ EMsgComposer * em_utils_forward_message (EMailBackend *backend, CamelMimeMessage *message, EMailForwardStyle style, CamelFolder *folder, const gchar *uid) { CamelMimePart *part; gchar *subject; EMsgComposer *composer = NULL; g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); switch (style) { case E_MAIL_FORWARD_STYLE_ATTACHED: default: part = mail_tool_make_message_attachment (message); subject = mail_tool_generate_forward_subject (message); composer = em_utils_forward_attachment ( backend, part, subject, NULL, NULL); g_object_unref (part); g_free (subject); break; case E_MAIL_FORWARD_STYLE_INLINE: case E_MAIL_FORWARD_STYLE_QUOTED: composer = forward_non_attached ( backend, folder, uid, message, style); break; } return composer; } EMsgComposer * em_utils_forward_attachment (EMailBackend *backend, CamelMimePart *part, const gchar *subject, CamelFolder *folder, GPtrArray *uids) { EShell *shell; EMsgComposer *composer; g_return_val_if_fail (E_IS_MAIL_BACKEND (backend), NULL); g_return_val_if_fail (CAMEL_IS_MIME_PART (part), NULL); if (folder != NULL) g_return_val_if_fail (CAMEL_IS_FOLDER (folder), NULL); shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend)); composer = create_new_composer (shell, subject, folder); e_msg_composer_attach (composer, part); if (uids != NULL) setup_forward_attached_callbacks (composer, folder, uids); composer_set_no_change (composer); gtk_widget_show (GTK_WIDGET (composer)); return composer; } static gint compare_sources_with_uids_order_cb (gconstpointer a, gconstpointer b, gpointer user_data) { ESource *asource = (ESource *) a; ESource *bsource = (ESource *) b; GHashTable *uids_order = user_data; gint aindex, bindex; aindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (asource))); bindex = GPOINTER_TO_INT (g_hash_table_lookup (uids_order, e_source_get_uid (bsource))); if (aindex <= 0) aindex = g_hash_table_size (uids_order); if (bindex <= 0) bindex = g_hash_table_size (uids_order); return aindex - bindex; } static void sort_sources_by_ui (GList **psources, gpointer user_data) { EShell *shell = user_data; EShellBackend *shell_backend; EMailSession *mail_session; EMailAccountStore *account_store; GtkTreeModel *model; GtkTreeIter iter; GHashTable *uids_order; gint index = 0; g_return_if_fail (psources != NULL); g_return_if_fail (E_IS_SHELL (shell)); /* nothing to sort */ if (!*psources || !g_list_next (*psources)) return; shell_backend = e_shell_get_backend_by_name (shell, "mail"); g_return_if_fail (shell_backend != NULL); mail_session = e_mail_backend_get_session (E_MAIL_BACKEND (shell_backend)); g_return_if_fail (mail_session != NULL); account_store = e_mail_ui_session_get_account_store (E_MAIL_UI_SESSION (mail_session)); g_return_if_fail (account_store != NULL); model = GTK_TREE_MODEL (account_store); if (!gtk_tree_model_get_iter_first (model, &iter)) return; uids_order = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); do { CamelService *service = NULL; gtk_tree_model_get (model, &iter, E_MAIL_ACCOUNT_STORE_COLUMN_SERVICE, &service, -1); if (service) { index++; g_hash_table_insert (uids_order, g_strdup (camel_service_get_uid (service)), GINT_TO_POINTER (index)); g_object_unref (service); } } while (gtk_tree_model_iter_next (model, &iter)); *psources = g_list_sort_with_data (*psources, compare_sources_with_uids_order_cb, uids_order); g_hash_table_destroy (uids_order); } /* Redirecting messages... */ static EMsgComposer * redirect_get_composer (EShell *shell, CamelMimeMessage *message) { EMsgComposer *composer; ESourceRegistry *registry; CamelMedium *medium; ESource *source; gchar *identity_uid = NULL; medium = CAMEL_MEDIUM (message); /* QMail will refuse to send a message if it finds one of * it's Delivered-To headers in the message, so remove all * Delivered-To headers. Fixes bug #23635. */ while (camel_medium_get_header (medium, "Delivered-To")) camel_medium_remove_header (medium, "Delivered-To"); while (camel_medium_get_header (medium, "Bcc")) camel_medium_remove_header (medium, "Bcc"); while (camel_medium_get_header (medium, "Resent-Bcc")) camel_medium_remove_header (medium, "Resent-Bcc"); registry = e_shell_get_registry (shell); /* This returns a new ESource reference. */ source = em_utils_check_send_account_override (shell, message, NULL); if (!source) source = em_utils_guess_mail_identity_with_recipients_and_sort ( registry, message, NULL, NULL, sort_sources_by_ui, shell); if (source != NULL) { identity_uid = e_source_dup_uid (source); g_object_unref (source); } composer = e_msg_composer_new_redirect ( shell, message, identity_uid, NULL); g_free (identity_uid); return composer; } /** * em_utils_redirect_message: * @shell: an #EShell * @message: message to redirect * * Opens a composer to redirect @message (Note: only headers will be * editable). Adds Resent-From/Resent-To/etc headers. * * Returns: the resulting #EMsgComposer **/ EMsgComposer * em_utils_redirect_message (EShell *shell, CamelMimeMessage *message) { EMsgComposer *composer; g_return_val_if_fail (E_IS_SHELL (shell), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); composer = redirect_get_composer (shell, message); gtk_widget_show (GTK_WIDGET (composer)); composer_set_no_change (composer); return composer; } /* Replying to messages... */ EDestination ** em_utils_camel_address_to_destination (CamelInternetAddress *iaddr) { EDestination *dest, **destv; gint n, i, j; if (iaddr == NULL) return NULL; if ((n = camel_address_length ((CamelAddress *) iaddr)) == 0) return NULL; destv = g_malloc (sizeof (EDestination *) * (n + 1)); for (i = 0, j = 0; i < n; i++) { const gchar *name, *addr; if (camel_internet_address_get (iaddr, i, &name, &addr)) { dest = e_destination_new (); e_destination_set_name (dest, name); e_destination_set_email (dest, addr); destv[j++] = dest; } } if (j == 0) { g_free (destv); return NULL; } destv[j] = NULL; return destv; } static EMsgComposer * reply_get_composer (EShell *shell, CamelMimeMessage *message, const gchar *identity_uid, CamelInternetAddress *to, CamelInternetAddress *cc, CamelFolder *folder, CamelNNTPAddress *postto) { const gchar *message_id, *references; EDestination **tov, **ccv; EMsgComposer *composer; EComposerHeaderTable *table; CamelMedium *medium; gchar *subject; g_return_val_if_fail (E_IS_SHELL (shell), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); if (to != NULL) g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (to), NULL); if (cc != NULL) g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc), NULL); composer = e_msg_composer_new (shell); /* construct the tov/ccv */ tov = em_utils_camel_address_to_destination (to); ccv = em_utils_camel_address_to_destination (cc); /* Set the subject of the new message. */ if ((subject = (gchar *) camel_mime_message_get_subject (message))) { gboolean skip_len = -1; if (em_utils_is_re_in_subject (subject, &skip_len) && skip_len > 0) subject = subject + skip_len; subject = g_strdup_printf ("Re: %s", subject); } else { subject = g_strdup (""); } table = e_msg_composer_get_header_table (composer); e_composer_header_table_set_subject (table, subject); e_composer_header_table_set_destinations_to (table, tov); e_composer_header_table_set_identity_uid (table, identity_uid); /* Add destinations instead of setting, so we don't remove * automatic CC addresses that have already been added. */ e_composer_header_table_add_destinations_cc (table, ccv); e_destination_freev (tov); e_destination_freev (ccv); g_free (subject); /* add post-to, if nessecary */ if (postto && camel_address_length ((CamelAddress *) postto)) { gchar *store_url = NULL; gchar *post; if (folder) { CamelStore *parent_store; CamelService *service; CamelURL *url; parent_store = camel_folder_get_parent_store (folder); service = CAMEL_SERVICE (parent_store); url = camel_service_new_camel_url (service); store_url = camel_url_to_string ( url, CAMEL_URL_HIDE_ALL); if (store_url[strlen (store_url) - 1] == '/') store_url[strlen (store_url) - 1] = '\0'; camel_url_free (url); } post = camel_address_encode ((CamelAddress *) postto); e_composer_header_table_set_post_to_base ( table, store_url ? store_url : "", post); g_free (post); g_free (store_url); } /* Add In-Reply-To and References. */ medium = CAMEL_MEDIUM (message); message_id = camel_medium_get_header (medium, "Message-ID"); references = camel_medium_get_header (medium, "References"); if (message_id != NULL) { gchar *reply_refs; e_msg_composer_add_header ( composer, "In-Reply-To", message_id); if (references) reply_refs = g_strdup_printf ( "%s %s", references, message_id); else reply_refs = g_strdup (message_id); e_msg_composer_add_header ( composer, "References", reply_refs); g_free (reply_refs); } else if (references != NULL) { e_msg_composer_add_header ( composer, "References", references); } return composer; } static gboolean get_reply_list (CamelMimeMessage *message, CamelInternetAddress *to) { const gchar *header, *p; gchar *addr; /* Examples: * * List-Post: * List-Post: * List-Post: NO (posting not allowed on this list) */ if (!(header = camel_medium_get_header ((CamelMedium *) message, "List-Post"))) return FALSE; while (*header == ' ' || *header == '\t') header++; /* check for NO */ if (!g_ascii_strncasecmp (header, "NO", 2)) return FALSE; /* Search for the first mailto angle-bracket enclosed URL. * (See rfc2369, Section 2, paragraph 3 for details) */ if (!(header = camel_strstrcase (header, "", *p)) p++; addr = g_strndup (header, p - header); camel_internet_address_add (to, NULL, addr); g_free (addr); return TRUE; } gboolean em_utils_is_munged_list_message (CamelMimeMessage *message) { CamelInternetAddress *reply_to, *list; gboolean result = FALSE; reply_to = camel_mime_message_get_reply_to (message); if (reply_to) { list = camel_internet_address_new (); if (get_reply_list (message, list) && camel_address_length (CAMEL_ADDRESS (list)) == camel_address_length (CAMEL_ADDRESS (reply_to))) { gint i; const gchar *r_name, *r_addr; const gchar *l_name, *l_addr; for (i = 0; i < camel_address_length (CAMEL_ADDRESS (list)); i++) { if (!camel_internet_address_get (reply_to, i, &r_name, &r_addr)) break; if (!camel_internet_address_get (list, i, &l_name, &l_addr)) break; if (strcmp (l_addr, r_addr)) break; } if (i == camel_address_length (CAMEL_ADDRESS (list))) result = TRUE; } g_object_unref (list); } return result; } static CamelInternetAddress * get_reply_to (CamelMimeMessage *message) { CamelInternetAddress *reply_to; reply_to = camel_mime_message_get_reply_to (message); if (reply_to) { GSettings *settings; gboolean ignore_list_reply_to; settings = g_settings_new ("org.gnome.evolution.mail"); ignore_list_reply_to = g_settings_get_boolean ( settings, "composer-ignore-list-reply-to"); g_object_unref (settings); if (ignore_list_reply_to && em_utils_is_munged_list_message (message)) reply_to = NULL; } if (!reply_to) reply_to = camel_mime_message_get_from (message); return reply_to; } static void get_reply_sender (CamelMimeMessage *message, CamelInternetAddress *to, CamelNNTPAddress *postto) { CamelInternetAddress *reply_to; CamelMedium *medium; const gchar *posthdr = NULL; medium = CAMEL_MEDIUM (message); /* check whether there is a 'Newsgroups: ' header in there */ if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Followup-To"); if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Newsgroups"); if (postto != NULL && posthdr != NULL) { camel_address_decode (CAMEL_ADDRESS (postto), posthdr); return; } reply_to = get_reply_to (message); if (reply_to != NULL) { const gchar *name; const gchar *addr; gint ii = 0; while (camel_internet_address_get (reply_to, ii++, &name, &addr)) camel_internet_address_add (to, name, addr); } } void em_utils_get_reply_sender (CamelMimeMessage *message, CamelInternetAddress *to, CamelNNTPAddress *postto) { get_reply_sender (message, to, postto); } static void get_reply_from (CamelMimeMessage *message, CamelInternetAddress *to, CamelNNTPAddress *postto) { CamelInternetAddress *from; CamelMedium *medium; const gchar *name, *addr; const gchar *posthdr = NULL; medium = CAMEL_MEDIUM (message); /* check whether there is a 'Newsgroups: ' header in there */ if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Followup-To"); if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Newsgroups"); if (postto != NULL && posthdr != NULL) { camel_address_decode (CAMEL_ADDRESS (postto), posthdr); return; } from = camel_mime_message_get_from (message); if (from != NULL) { gint ii = 0; while (camel_internet_address_get (from, ii++, &name, &addr)) camel_internet_address_add (to, name, addr); } } static void get_reply_recipient (CamelMimeMessage *message, CamelInternetAddress *to, CamelNNTPAddress *postto, CamelInternetAddress *address) { CamelMedium *medium; const gchar *posthdr = NULL; medium = CAMEL_MEDIUM (message); /* check whether there is a 'Newsgroups: ' header in there */ if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Followup-To"); if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Newsgroups"); if (postto != NULL && posthdr != NULL) { camel_address_decode (CAMEL_ADDRESS (postto), posthdr); return; } if (address != NULL) { const gchar *name; const gchar *addr; gint ii = 0; while (camel_internet_address_get (address, ii++, &name, &addr)) camel_internet_address_add (to, name, addr); } } static void concat_unique_addrs (CamelInternetAddress *dest, CamelInternetAddress *src, GHashTable *rcpt_hash) { const gchar *name, *addr; gint i; for (i = 0; camel_internet_address_get (src, i, &name, &addr); i++) { if (!g_hash_table_contains (rcpt_hash, addr)) { camel_internet_address_add (dest, name, addr); g_hash_table_add (rcpt_hash, (gpointer) addr); } } } static GHashTable * generate_recipient_hash (ESourceRegistry *registry) { GHashTable *rcpt_hash; ESource *default_source; GList *list, *link; const gchar *extension_name; g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); rcpt_hash = g_hash_table_new ( (GHashFunc) camel_strcase_hash, (GEqualFunc) camel_strcase_equal); default_source = e_source_registry_ref_default_mail_identity (registry); extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; list = e_source_registry_list_sources (registry, extension_name); for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); ESource *cached_source; ESourceMailIdentity *extension; const gchar *address; gboolean insert_source; gboolean cached_is_default; gboolean cached_is_enabled; gboolean source_is_default; gboolean source_is_enabled; /* No default mail identity implies there are no mail * identities at all and so we should never get here. */ g_warn_if_fail (default_source != NULL); source_is_default = e_source_equal (source, default_source); source_is_enabled = e_source_registry_check_enabled (registry, source); extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY; extension = e_source_get_extension (source, extension_name); address = e_source_mail_identity_get_address (extension); if (address == NULL) continue; cached_source = g_hash_table_lookup (rcpt_hash, address); if (cached_source != NULL) { cached_is_default = e_source_equal ( cached_source, default_source); cached_is_enabled = e_source_registry_check_enabled ( registry, cached_source); } else { cached_is_default = FALSE; cached_is_enabled = FALSE; } /* Accounts with identical email addresses that are enabled * take precedence over disabled accounts. If all accounts * with matching email addresses are disabled, the first * one in the list takes precedence. The default account * always takes precedence no matter what. */ insert_source = source_is_default || cached_source == NULL || (source_is_enabled && !cached_is_enabled && !cached_is_default); if (insert_source) g_hash_table_insert ( rcpt_hash, (gchar *) address, source); } g_list_free_full (list, (GDestroyNotify) g_object_unref); if (default_source != NULL) g_object_unref (default_source); return rcpt_hash; } void em_utils_get_reply_all (ESourceRegistry *registry, CamelMimeMessage *message, CamelInternetAddress *to, CamelInternetAddress *cc, CamelNNTPAddress *postto) { CamelInternetAddress *reply_to; CamelInternetAddress *to_addrs; CamelInternetAddress *cc_addrs; CamelMedium *medium; const gchar *name, *addr; const gchar *posthdr = NULL; GHashTable *rcpt_hash; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (to)); g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (cc)); medium = CAMEL_MEDIUM (message); /* check whether there is a 'Newsgroups: ' header in there */ if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Followup-To"); if (postto != NULL && posthdr == NULL) posthdr = camel_medium_get_header (medium, "Newsgroups"); if (postto != NULL && posthdr != NULL) camel_address_decode (CAMEL_ADDRESS (postto), posthdr); rcpt_hash = generate_recipient_hash (registry); reply_to = get_reply_to (message); to_addrs = camel_mime_message_get_recipients ( message, CAMEL_RECIPIENT_TYPE_TO); cc_addrs = camel_mime_message_get_recipients ( message, CAMEL_RECIPIENT_TYPE_CC); if (reply_to != NULL) { gint ii = 0; while (camel_internet_address_get (reply_to, ii++, &name, &addr)) { /* Ignore references to the Reply-To address * in the To and Cc lists. */ if (addr && !g_hash_table_contains (rcpt_hash, addr)) { /* In the case we are doing a Reply-To-All, * we do not want to include the user's email * address because replying to oneself is * kinda silly. */ camel_internet_address_add (to, name, addr); g_hash_table_add (rcpt_hash, (gpointer) addr); } } } concat_unique_addrs (cc, to_addrs, rcpt_hash); concat_unique_addrs (cc, cc_addrs, rcpt_hash); /* Promote the first Cc: address to To: if To: is empty. */ if (camel_address_length ((CamelAddress *) to) == 0 && camel_address_length ((CamelAddress *) cc) > 0) { camel_internet_address_get (cc, 0, &name, &addr); camel_internet_address_add (to, name, addr); camel_address_remove ((CamelAddress *) cc, 0); } /* If To: is still empty, may we removed duplicates (i.e. ourself), * so add the original To if it was set. */ if (camel_address_length ((CamelAddress *) to) == 0 && (camel_internet_address_get (to_addrs, 0, &name, &addr) || camel_internet_address_get (cc_addrs, 0, &name, &addr))) { camel_internet_address_add (to, name, addr); } g_hash_table_destroy (rcpt_hash); } enum { ATTRIB_UNKNOWN, ATTRIB_CUSTOM, ATTRIB_TIMEZONE, ATTRIB_STRFTIME, ATTRIB_TM_SEC, ATTRIB_TM_MIN, ATTRIB_TM_24HOUR, ATTRIB_TM_12HOUR, ATTRIB_TM_MDAY, ATTRIB_TM_MON, ATTRIB_TM_YEAR, ATTRIB_TM_2YEAR, ATTRIB_TM_WDAY, /* not actually used */ ATTRIB_TM_YDAY }; typedef void (*AttribFormatter) (GString *str, const gchar *attr, CamelMimeMessage *message); static void format_sender (GString *str, const gchar *attr, CamelMimeMessage *message) { CamelInternetAddress *sender; const gchar *name, *addr = NULL; sender = camel_mime_message_get_from (message); if (sender != NULL && camel_address_length (CAMEL_ADDRESS (sender)) > 0) { camel_internet_address_get (sender, 0, &name, &addr); } else { name = _("an unknown sender"); } if (name && !strcmp (attr, "{SenderName}")) { g_string_append (str, name); } else if (addr && !strcmp (attr, "{SenderEMail}")) { g_string_append (str, addr); } else if (name && *name) { g_string_append (str, name); } else if (addr) { g_string_append (str, addr); } } static struct { const gchar *name; gint type; struct { const gchar *format; /* strftime or printf format */ AttribFormatter formatter; /* custom formatter */ } v; } attribvars[] = { { "{Sender}", ATTRIB_CUSTOM, { NULL, format_sender } }, { "{SenderName}", ATTRIB_CUSTOM, { NULL, format_sender } }, { "{SenderEMail}", ATTRIB_CUSTOM, { NULL, format_sender } }, { "{AbbrevWeekdayName}", ATTRIB_STRFTIME, { "%a", NULL } }, { "{WeekdayName}", ATTRIB_STRFTIME, { "%A", NULL } }, { "{AbbrevMonthName}", ATTRIB_STRFTIME, { "%b", NULL } }, { "{MonthName}", ATTRIB_STRFTIME, { "%B", NULL } }, { "{AmPmUpper}", ATTRIB_STRFTIME, { "%p", NULL } }, { "{AmPmLower}", ATTRIB_STRFTIME, { "%P", NULL } }, { "{Day}", ATTRIB_TM_MDAY, { "%02d", NULL } }, /* %d 01-31 */ { "{ Day}", ATTRIB_TM_MDAY, { "% 2d", NULL } }, /* %e 1-31 */ { "{24Hour}", ATTRIB_TM_24HOUR, { "%02d", NULL } }, /* %H 00-23 */ { "{12Hour}", ATTRIB_TM_12HOUR, { "%02d", NULL } }, /* %I 00-12 */ { "{DayOfYear}", ATTRIB_TM_YDAY, { "%d", NULL } }, /* %j 1-366 */ { "{Month}", ATTRIB_TM_MON, { "%02d", NULL } }, /* %m 01-12 */ { "{Minute}", ATTRIB_TM_MIN, { "%02d", NULL } }, /* %M 00-59 */ { "{Seconds}", ATTRIB_TM_SEC, { "%02d", NULL } }, /* %S 00-61 */ { "{2DigitYear}", ATTRIB_TM_2YEAR, { "%02d", NULL } }, /* %y */ { "{Year}", ATTRIB_TM_YEAR, { "%04d", NULL } }, /* %Y */ { "{TimeZone}", ATTRIB_TIMEZONE, { "%+05d", NULL } } }; static gchar * attribution_format (CamelMimeMessage *message) { register const gchar *inptr; const gchar *start; gint tzone, len, i; gchar buf[64], *s; GString *str; struct tm tm; time_t date; gint type; gchar *format = quoting_text (QUOTING_ATTRIBUTION); str = g_string_new (""); date = camel_mime_message_get_date (message, &tzone); if (date == CAMEL_MESSAGE_DATE_CURRENT) { /* The message has no Date: header, look at Received: */ date = camel_mime_message_get_date_received (message, &tzone); } if (date == CAMEL_MESSAGE_DATE_CURRENT) { /* That didn't work either, use current time */ time (&date); tzone = 0; } /* Convert to UTC */ date += (tzone / 100) * 60 * 60; date += (tzone % 100) * 60; gmtime_r (&date, &tm); inptr = format; while (*inptr != '\0') { start = inptr; while (*inptr && strncmp (inptr, "${", 2) != 0) inptr++; g_string_append_len (str, start, inptr - start); if (*inptr == '\0') break; start = ++inptr; while (*inptr && *inptr != '}') inptr++; if (*inptr != '}') { /* broken translation */ g_string_append_len (str, "${", 2); inptr = start + 1; continue; } inptr++; len = inptr - start; type = ATTRIB_UNKNOWN; for (i = 0; i < G_N_ELEMENTS (attribvars); i++) { if (!strncmp (attribvars[i].name, start, len)) { type = attribvars[i].type; break; } } switch (type) { case ATTRIB_CUSTOM: attribvars[i].v.formatter ( str, attribvars[i].name, message); break; case ATTRIB_TIMEZONE: g_string_append_printf ( str, attribvars[i].v.format, tzone); break; case ATTRIB_STRFTIME: e_utf8_strftime ( buf, sizeof (buf), attribvars[i].v.format, &tm); g_string_append (str, buf); break; case ATTRIB_TM_SEC: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_sec); break; case ATTRIB_TM_MIN: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_min); break; case ATTRIB_TM_24HOUR: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_hour); break; case ATTRIB_TM_12HOUR: g_string_append_printf ( str, attribvars[i].v.format, (tm.tm_hour + 1) % 13); break; case ATTRIB_TM_MDAY: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_mday); break; case ATTRIB_TM_MON: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_mon + 1); break; case ATTRIB_TM_YEAR: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_year + 1900); break; case ATTRIB_TM_2YEAR: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_year % 100); break; case ATTRIB_TM_WDAY: /* not actually used */ g_string_append_printf ( str, attribvars[i].v.format, tm.tm_wday); break; case ATTRIB_TM_YDAY: g_string_append_printf ( str, attribvars[i].v.format, tm.tm_yday + 1); break; default: /* Misspelled variable? Drop the * format argument and continue. */ break; } } s = str->str; g_string_free (str, FALSE); g_free (format); return s; } static void composer_set_body (EMsgComposer *composer, CamelMimeMessage *message, EMailReplyStyle style, EMailPartList *parts_list) { gchar *text, *credits, *original; CamelMimePart *part; CamelSession *session; GSettings *settings; gboolean start_bottom, has_body_text = FALSE; guint32 validity_found = 0; session = e_msg_composer_ref_session (composer); settings = g_settings_new ("org.gnome.evolution.mail"); start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom"); switch (style) { case E_MAIL_REPLY_STYLE_DO_NOT_QUOTE: /* do nothing */ break; case E_MAIL_REPLY_STYLE_ATTACH: /* attach the original message as an attachment */ part = mail_tool_make_message_attachment (message); e_msg_composer_attach (composer, part); g_object_unref (part); break; case E_MAIL_REPLY_STYLE_OUTLOOK: original = quoting_text (QUOTING_ORIGINAL); text = em_utils_message_to_html ( session, message, original, E_MAIL_FORMATTER_QUOTE_FLAG_HEADERS, parts_list, start_bottom ? "
" : NULL, &validity_found); e_msg_composer_set_body_text (composer, text, TRUE); has_body_text = text && *text; g_free (text); g_free (original); emu_update_composers_security (composer, validity_found); break; case E_MAIL_REPLY_STYLE_QUOTED: default: /* do what any sane user would want when replying... */ credits = attribution_format (message); text = em_utils_message_to_html ( session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE, parts_list, start_bottom ? "
" : NULL, &validity_found); g_free (credits); e_msg_composer_set_body_text (composer, text, TRUE); has_body_text = text && *text; g_free (text); emu_update_composers_security (composer, validity_found); break; } if (has_body_text && start_bottom) { GtkhtmlEditor *editor = GTKHTML_EDITOR (composer); gboolean move_cursor_to_end; gboolean top_signature; /* If we are placing signature on top, then move cursor to the end, * otherwise try to find the signature place and place cursor just * before the signature. We added there an empty line already. */ gtkhtml_editor_run_command (editor, "block-selection"); gtkhtml_editor_run_command (editor, "cursor-bod"); top_signature = g_settings_get_boolean (settings, "composer-top-signature"); move_cursor_to_end = top_signature || !gtkhtml_editor_search_by_data ( editor, 1, "ClueFlow", "signature", "1"); if (move_cursor_to_end) gtkhtml_editor_run_command (editor, "cursor-eod"); else gtkhtml_editor_run_command (editor, "selection-move-left"); gtkhtml_editor_run_command (editor, "unblock-selection"); } g_object_unref (settings); g_object_unref (session); } gchar * em_utils_construct_composer_text (CamelSession *session, CamelMimeMessage *message, EMailPartList *parts_list) { gchar *text, *credits; gboolean start_bottom = 0; g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); credits = attribution_format (message); text = em_utils_message_to_html ( session, message, credits, E_MAIL_FORMATTER_QUOTE_FLAG_CITE, parts_list, start_bottom ? "
" : NULL, NULL); g_free (credits); return text; } /** * em_utils_reply_to_message: * @shell: an #EShell * @message: a #CamelMimeMessage * @folder: a #CamelFolder, or %NULL * @message_uid: the UID of @message, or %NULL * @type: the type of reply to create * @style: the reply style to use * @source: source to inherit view settings from * @address: used for E_MAIL_REPLY_TO_RECIPIENT @type * * Creates a new composer ready to reply to @message. * * @folder and @message_uid may be supplied in order to update the message * flags once it has been replied to. **/ EMsgComposer * em_utils_reply_to_message (EShell *shell, CamelMimeMessage *message, CamelFolder *folder, const gchar *message_uid, EMailReplyType type, EMailReplyStyle style, EMailPartList *parts_list, CamelInternetAddress *address) { ESourceRegistry *registry; CamelInternetAddress *to, *cc; CamelNNTPAddress *postto = NULL; EMsgComposer *composer; ESource *source; gchar *identity_uid = NULL; guint32 flags; g_return_val_if_fail (E_IS_SHELL (shell), NULL); g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); to = camel_internet_address_new (); cc = camel_internet_address_new (); registry = e_shell_get_registry (shell); /* This returns a new ESource reference. */ source = em_utils_check_send_account_override (shell, message, folder); if (!source) source = em_utils_guess_mail_identity_with_recipients_and_sort ( registry, message, folder, message_uid, sort_sources_by_ui, shell); if (source != NULL) { identity_uid = e_source_dup_uid (source); g_object_unref (source); } flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_SEEN; if (!address && (type == E_MAIL_REPLY_TO_FROM || type == E_MAIL_REPLY_TO_SENDER) && folder && em_utils_folder_is_sent (registry, folder)) type = E_MAIL_REPLY_TO_ALL; switch (type) { case E_MAIL_REPLY_TO_FROM: if (folder) postto = camel_nntp_address_new (); get_reply_from (message, to, postto); break; case E_MAIL_REPLY_TO_RECIPIENT: if (folder) postto = camel_nntp_address_new (); get_reply_recipient (message, to, postto, address); break; case E_MAIL_REPLY_TO_SENDER: if (folder) postto = camel_nntp_address_new (); get_reply_sender (message, to, postto); break; case E_MAIL_REPLY_TO_LIST: flags |= CAMEL_MESSAGE_ANSWERED_ALL; if (get_reply_list (message, to)) break; /* falls through */ case E_MAIL_REPLY_TO_ALL: flags |= CAMEL_MESSAGE_ANSWERED_ALL; if (folder) postto = camel_nntp_address_new (); em_utils_get_reply_all (registry, message, to, cc, postto); break; } composer = reply_get_composer ( shell, message, identity_uid, to, cc, folder, postto); e_msg_composer_add_message_attachments (composer, message, TRUE); if (postto) g_object_unref (postto); g_object_unref (to); g_object_unref (cc); composer_set_body (composer, message, style, parts_list); if (folder != NULL) { gchar *folder_uri = NULL, *tmp_message_uid = NULL; em_utils_get_real_folder_uri_and_message_uid (folder, message_uid, &folder_uri, &tmp_message_uid); e_msg_composer_set_source_headers ( composer, folder_uri, tmp_message_uid, flags); g_free (folder_uri); g_free (tmp_message_uid); } /* because some reply types can change recipients after the composer is populated */ em_utils_apply_send_account_override_to_composer (composer, shell, folder); composer_set_no_change (composer); gtk_widget_show (GTK_WIDGET (composer)); g_free (identity_uid); return composer; } static void post_header_clicked_cb (EComposerPostHeader *header, EMailSession *session) { GtkTreeSelection *selection; EMFolderSelector *selector; EMFolderTreeModel *model; EMFolderTree *folder_tree; GtkWidget *dialog; GList *list; /* FIXME Limit the folder tree to the NNTP account? */ model = em_folder_tree_model_get_default (); dialog = em_folder_selector_new ( /* FIXME GTK_WINDOW (composer) */ NULL, model, EM_FOLDER_SELECTOR_CAN_CREATE, _("Posting destination"), _("Choose folders to post the message to."), NULL); selector = EM_FOLDER_SELECTOR (dialog); folder_tree = em_folder_selector_get_folder_tree (selector); em_folder_tree_set_excluded ( folder_tree, EMFT_EXCLUDE_NOSELECT | EMFT_EXCLUDE_VIRTUAL | EMFT_EXCLUDE_VTRASH); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (folder_tree)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); list = e_composer_post_header_get_folders (header); em_folder_tree_set_selected_list (folder_tree, list, FALSE); g_list_foreach (list, (GFunc) g_free, NULL); g_list_free (list); if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK) { /* Prevent the header's "custom" flag from being reset, * which is what the default method will do next. */ g_signal_stop_emission_by_name (header, "clicked"); goto exit; } list = em_folder_tree_get_selected_uris (folder_tree); e_composer_post_header_set_folders (header, list); g_list_foreach (list, (GFunc) g_free, NULL); g_list_free (list); exit: gtk_widget_destroy (dialog); } /** * em_configure_new_composer: * @composer: a newly created #EMsgComposer * * Integrates a newly created #EMsgComposer into the mail backend. The * composer can't link directly to the mail backend without introducing * circular library dependencies, so this function finishes configuring * things the #EMsgComposer instance can't do itself. **/ void em_configure_new_composer (EMsgComposer *composer, EMailSession *session) { EComposerHeaderTable *table; EComposerHeaderType header_type; EComposerHeader *header; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); g_return_if_fail (E_IS_MAIL_SESSION (session)); header_type = E_COMPOSER_HEADER_POST_TO; table = e_msg_composer_get_header_table (composer); header = e_composer_header_table_get_header (table, header_type); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_recipients), session); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_identity), session); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_downloads), session); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_plugins), session); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_subject), session); g_signal_connect ( composer, "presend", G_CALLBACK (composer_presend_check_unwanted_html), session); g_signal_connect ( composer, "send", G_CALLBACK (em_utils_composer_send_cb), session); g_signal_connect ( composer, "save-to-drafts", G_CALLBACK (em_utils_composer_save_to_drafts_cb), session); g_signal_connect ( composer, "save-to-outbox", G_CALLBACK (em_utils_composer_save_to_outbox_cb), session); g_signal_connect ( composer, "print", G_CALLBACK (em_utils_composer_print_cb), session); /* Handle "Post To:" button clicks, which displays a folder tree * widget. The composer doesn't know about folder tree widgets, * so it can't handle this itself. * * Note: This is a G_SIGNAL_RUN_LAST signal, which allows us to * stop the signal emission if the user cancels or closes * the folder selector dialog. See the handler function. */ g_signal_connect ( header, "clicked", G_CALLBACK (post_header_clicked_cb), session); } /* free returned pointer with g_object_unref(), if not NULL */ ESource * em_utils_check_send_account_override (EShell *shell, CamelMimeMessage *message, CamelFolder *folder) { EMailBackend *mail_backend; EMailSendAccountOverride *account_override; CamelInternetAddress *to = NULL, *cc = NULL, *bcc = NULL; gchar *folder_uri = NULL, *account_uid; ESource *account_source = NULL; ESourceRegistry *source_registry; g_return_val_if_fail (E_IS_SHELL (shell), NULL); if (!message && !folder) return NULL; if (message) { to = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_TO); cc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_CC); bcc = camel_mime_message_get_recipients (message, CAMEL_RECIPIENT_TYPE_BCC); } mail_backend = E_MAIL_BACKEND (e_shell_get_backend_by_name (shell, "mail")); g_return_val_if_fail (mail_backend != NULL, NULL); if (folder) folder_uri = e_mail_folder_uri_from_folder (folder); source_registry = e_shell_get_registry (shell); account_override = e_mail_backend_get_send_account_override (mail_backend); account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc); while (account_uid) { account_source = e_source_registry_ref_source (source_registry, account_uid); if (account_source) break; /* stored send account override settings contain a reference * to a dropped account, thus cleanup it now */ e_mail_send_account_override_remove_for_account_uid (account_override, account_uid); g_free (account_uid); account_uid = e_mail_send_account_override_get_account_uid (account_override, folder_uri, to, cc, bcc); } g_free (folder_uri); g_free (account_uid); return account_source; } void em_utils_apply_send_account_override_to_composer (EMsgComposer *composer, EShell *shell, CamelFolder *folder) { CamelMimeMessage *message; EComposerHeaderTable *header_table; ESource *source; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); message = em_utils_get_composer_recipients_as_message (composer); source = em_utils_check_send_account_override (shell, message, folder); g_object_unref (message); if (!source) return; header_table = e_msg_composer_get_header_table (composer); e_composer_header_table_set_identity_uid (header_table, e_source_get_uid (source)); g_object_unref (source); }