diff options
Diffstat (limited to 'libemail-engine/e-mail-session-utils.c')
-rw-r--r-- | libemail-engine/e-mail-session-utils.c | 1601 |
1 files changed, 1601 insertions, 0 deletions
diff --git a/libemail-engine/e-mail-session-utils.c b/libemail-engine/e-mail-session-utils.c new file mode 100644 index 0000000000..1c9dcbb544 --- /dev/null +++ b/libemail-engine/e-mail-session-utils.c @@ -0,0 +1,1601 @@ +/* + * e-mail-session-utils.c + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-mail-session-utils.h" + +#include <glib/gi18n-lib.h> +#include <libedataserver/libedataserver.h> + +#include <libemail-engine/e-mail-folder-utils.h> +#include <libemail-engine/e-mail-utils.h> +#include <libemail-engine/mail-tools.h> + +/* X-Mailer header value */ +#define X_MAILER ("Evolution " VERSION SUB_VERSION " " VERSION_COMMENT) + +/* FIXME: Temporary - remove this after we move filter/ to eds */ +#define E_FILTER_SOURCE_OUTGOING "outgoing" + +typedef struct _AsyncContext AsyncContext; + +struct _AsyncContext { + CamelFolder *folder; + + CamelMimeMessage *message; + CamelMessageInfo *info; + + CamelAddress *from; + CamelAddress *recipients; + + CamelFilterDriver *driver; + + CamelService *transport; + + GCancellable *cancellable; + gint io_priority; + + /* X-Evolution headers */ + struct _camel_header_raw *xev; + + GPtrArray *post_to_uris; + + EMailLocalFolder local_id; + + gchar *folder_uri; + gchar *message_uid; +}; + +static void +async_context_free (AsyncContext *context) +{ + if (context->folder != NULL) + g_object_unref (context->folder); + + if (context->message != NULL) + g_object_unref (context->message); + + if (context->info != NULL) + camel_message_info_free (context->info); + + if (context->from != NULL) + g_object_unref (context->from); + + if (context->recipients != NULL) + g_object_unref (context->recipients); + + if (context->driver != NULL) + g_object_unref (context->driver); + + if (context->transport != NULL) + g_object_unref (context->transport); + + if (context->cancellable != NULL) { + camel_operation_pop_message (context->cancellable); + g_object_unref (context->cancellable); + } + + if (context->xev != NULL) + camel_header_raw_clear (&context->xev); + + if (context->post_to_uris != NULL) { + g_ptr_array_foreach ( + context->post_to_uris, (GFunc) g_free, NULL); + g_ptr_array_free (context->post_to_uris, TRUE); + } + + g_free (context->folder_uri); + g_free (context->message_uid); + + g_slice_free (AsyncContext, context); +} + +GQuark +e_mail_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) { + const gchar *string = "e-mail-error-quark"; + quark = g_quark_from_static_string (string); + } + + return quark; +} + +static void +mail_session_append_to_local_folder_thread (GSimpleAsyncResult *simple, + GObject *object, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + e_mail_session_append_to_local_folder_sync ( + E_MAIL_SESSION (object), + context->local_id, context->message, + context->info, &context->message_uid, + cancellable, &error); + + if (error != NULL) + g_simple_async_result_take_error (simple, error); +} + +gboolean +e_mail_session_append_to_local_folder_sync (EMailSession *session, + EMailLocalFolder local_id, + CamelMimeMessage *message, + CamelMessageInfo *info, + gchar **appended_uid, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + const gchar *folder_uri; + gboolean success = FALSE; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE); + + folder_uri = e_mail_session_get_local_folder_uri (session, local_id); + g_return_val_if_fail (folder_uri != NULL, FALSE); + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, CAMEL_STORE_FOLDER_CREATE, + cancellable, error); + + if (folder != NULL) { + success = e_mail_folder_append_message_sync ( + folder, message, info, appended_uid, + cancellable, error); + g_object_unref (folder); + } + + return success; +} + +void +e_mail_session_append_to_local_folder (EMailSession *session, + EMailLocalFolder local_id, + CamelMimeMessage *message, + CamelMessageInfo *info, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + context = g_slice_new0 (AsyncContext); + context->local_id = local_id; + context->message = g_object_ref (message); + + if (info != NULL) + context->info = camel_message_info_ref (info); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_append_to_local_folder); + + g_simple_async_result_set_check_cancellable (simple, cancellable); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, mail_session_append_to_local_folder_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_append_to_local_folder_finish (EMailSession *session, + GAsyncResult *result, + gchar **appended_uid, + GError **error) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_append_to_local_folder), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + context = g_simple_async_result_get_op_res_gpointer (simple); + + if (appended_uid != NULL) { + *appended_uid = context->message_uid; + context->message_uid = NULL; + } + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +static void +mail_session_handle_draft_headers_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + e_mail_session_handle_draft_headers_sync ( + session, context->message, cancellable, &error); + + if (error != NULL) + g_simple_async_result_take_error (simple, error); +} + +gboolean +e_mail_session_handle_draft_headers_sync (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + CamelMedium *medium; + const gchar *folder_uri; + const gchar *message_uid; + const gchar *header_name; + gboolean success; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE); + + medium = CAMEL_MEDIUM (message); + + header_name = "X-Evolution-Draft-Folder"; + folder_uri = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Draft-Message"; + message_uid = camel_medium_get_header (medium, header_name); + + /* Don't report errors about missing X-Evolution-Draft + * headers. These headers are optional, so their absence + * is handled by doing nothing. */ + if (folder_uri == NULL || message_uid == NULL) + return TRUE; + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, error); + + if (folder == NULL) + return FALSE; + + camel_folder_set_message_flags ( + folder, message_uid, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN); + + success = camel_folder_synchronize_message_sync ( + folder, message_uid, cancellable, error); + + g_object_unref (folder); + + return success; +} + +void +e_mail_session_handle_draft_headers (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_handle_draft_headers); + + g_simple_async_result_set_check_cancellable (simple, cancellable); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_handle_draft_headers_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_handle_draft_headers_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_handle_draft_headers), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +static void +mail_session_handle_source_headers_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + e_mail_session_handle_source_headers_sync ( + session, context->message, cancellable, &error); + + if (error != NULL) + g_simple_async_result_take_error (simple, error); +} + +gboolean +e_mail_session_handle_source_headers_sync (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + CamelMedium *medium; + CamelMessageFlags flags = 0; + const gchar *folder_uri; + const gchar *message_uid; + const gchar *flag_string; + const gchar *header_name; + gboolean success; + guint length, ii; + gchar **tokens; + gchar *string; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), FALSE); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), FALSE); + + medium = CAMEL_MEDIUM (message); + + header_name = "X-Evolution-Source-Folder"; + folder_uri = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Source-Message"; + message_uid = camel_medium_get_header (medium, header_name); + + header_name = "X-Evolution-Source-Flags"; + flag_string = camel_medium_get_header (medium, header_name); + + /* Don't report errors about missing X-Evolution-Source + * headers. These headers are optional, so their absence + * is handled by doing nothing. */ + if (folder_uri == NULL || message_uid == NULL || flag_string == NULL) + return TRUE; + + /* Convert the flag string to CamelMessageFlags. */ + + string = g_strstrip (g_strdup (flag_string)); + tokens = g_strsplit (string, " ", 0); + g_free (string); + + /* If tokens is NULL, a length of 0 will skip the loop. */ + length = (tokens != NULL) ? g_strv_length (tokens) : 0; + + for (ii = 0; ii < length; ii++) { + /* Note: We're only checking for flags known to + * be used in X-Evolution-Source-Flags headers. + * Add more as needed. */ + if (g_strcmp0 (tokens[ii], "ANSWERED") == 0) + flags |= CAMEL_MESSAGE_ANSWERED; + else if (g_strcmp0 (tokens[ii], "ANSWERED_ALL") == 0) + flags |= CAMEL_MESSAGE_ANSWERED_ALL; + else if (g_strcmp0 (tokens[ii], "FORWARDED") == 0) + flags |= CAMEL_MESSAGE_FORWARDED; + else if (g_strcmp0 (tokens[ii], "SEEN") == 0) + flags |= CAMEL_MESSAGE_SEEN; + else + g_warning ( + "Unknown flag '%s' in %s", + tokens[ii], header_name); + } + + g_strfreev (tokens); + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, error); + + if (folder == NULL) + return FALSE; + + camel_folder_set_message_flags ( + folder, message_uid, flags, flags); + + success = camel_folder_synchronize_message_sync ( + folder, message_uid, cancellable, error); + + g_object_unref (folder); + + return success; +} + +void +e_mail_session_handle_source_headers (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_handle_source_headers); + + g_simple_async_result_set_check_cancellable (simple, cancellable); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_handle_source_headers_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_handle_source_headers_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_handle_draft_headers), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +static void +mail_session_send_to_thread (GSimpleAsyncResult *simple, + EMailSession *session, + GCancellable *cancellable) +{ + AsyncContext *context; + CamelProvider *provider; + CamelFolder *folder = NULL; + CamelFolder *local_sent_folder; + CamelServiceConnectionStatus status; + GString *error_messages; + gboolean copy_to_sent = TRUE; + gboolean did_connect = FALSE; + guint ii; + GError *error = NULL; + + context = g_simple_async_result_get_op_res_gpointer (simple); + + if (camel_address_length (context->recipients) == 0) + goto skip_send; + + /* Send the message to all recipients. */ + + if (context->transport == NULL) { + g_simple_async_result_set_error ( + simple, CAMEL_SERVICE_ERROR, + CAMEL_SERVICE_ERROR_UNAVAILABLE, + _("No mail transport service available")); + return; + } + + status = camel_service_get_connection_status (context->transport); + if (status != CAMEL_SERVICE_CONNECTED) { + did_connect = TRUE; + + camel_service_connect_sync ( + context->transport, cancellable, &error); + + if (error != NULL) { + g_simple_async_result_take_error (simple, error); + return; + } + } + + provider = camel_service_get_provider (context->transport); + + if (provider->flags & CAMEL_PROVIDER_DISABLE_SENT_FOLDER) + copy_to_sent = FALSE; + + camel_transport_send_to_sync ( + CAMEL_TRANSPORT (context->transport), + context->message, context->from, + context->recipients, cancellable, &error); + + if (did_connect) { + /* Disconnect regardless of error or cancellation, + * but be mindful of these conditions when calling + * camel_service_disconnect_sync(). */ + if (g_cancellable_is_cancelled (cancellable)) { + camel_service_disconnect_sync ( + context->transport, FALSE, NULL, NULL); + } else if (error != NULL) { + camel_service_disconnect_sync ( + context->transport, FALSE, cancellable, NULL); + } else { + camel_service_disconnect_sync ( + context->transport, TRUE, cancellable, &error); + } + + if (error != NULL) { + g_simple_async_result_take_error (simple, error); + return; + } + } + +skip_send: + /* Post the message to requested folders. */ + for (ii = 0; ii < context->post_to_uris->len; ii++) { + CamelFolder *folder; + const gchar *folder_uri; + + folder_uri = g_ptr_array_index (context->post_to_uris, ii); + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, &error); + + if (error != NULL) { + g_warn_if_fail (folder == NULL); + g_simple_async_result_take_error (simple, error); + return; + } + + g_return_if_fail (CAMEL_IS_FOLDER (folder)); + + camel_folder_append_message_sync ( + folder, context->message, context->info, + NULL, cancellable, &error); + + g_object_unref (folder); + + if (error != NULL) { + g_simple_async_result_take_error (simple, error); + return; + } + } + + /*** Post Processing ***/ + + /* This accumulates error messages during post-processing. */ + error_messages = g_string_sized_new (256); + + mail_tool_restore_xevolution_headers (context->message, context->xev); + + /* Run filters on the outgoing message. */ + if (context->driver != NULL) { + camel_filter_driver_filter_message ( + context->driver, context->message, context->info, + NULL, NULL, NULL, "", cancellable, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + if (error != NULL) { + g_string_append_printf ( + error_messages, + _("Failed to apply outgoing filters: %s"), + error->message); + g_clear_error (&error); + } + + if ((camel_message_info_flags (context->info) & CAMEL_MESSAGE_DELETED) != 0) + copy_to_sent = FALSE; + } + + if (!copy_to_sent) + goto cleanup; + + /* Append the sent message to a Sent folder. */ + + local_sent_folder = + e_mail_session_get_local_folder ( + session, E_MAIL_LOCAL_FOLDER_SENT); + + folder = e_mail_session_get_fcc_for_message_sync ( + session, context->message, cancellable, &error); + + /* Sanity check. */ + g_return_if_fail ( + ((folder != NULL) && (error == NULL)) || + ((folder == NULL) && (error != NULL))); + + /* Append the message. */ + if (folder != NULL) + camel_folder_append_message_sync ( + folder, context->message, + context->info, NULL, cancellable, &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + if (error == NULL) + goto cleanup; + + if (folder != NULL && folder != local_sent_folder) { + const gchar *description; + + description = camel_folder_get_description (folder); + + if (error_messages->len > 0) + g_string_append (error_messages, "\n\n"); + g_string_append_printf ( + error_messages, + _("Failed to append to %s: %s\n" + "Appending to local 'Sent' folder instead."), + description, error->message); + } + + /* If appending to a remote Sent folder failed, + * try appending to the local Sent folder. */ + if (folder != local_sent_folder) { + + g_clear_error (&error); + + camel_folder_append_message_sync ( + local_sent_folder, context->message, + context->info, NULL, cancellable, &error); + } + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto exit; + + /* We can't even append to the local Sent folder? + * In that case just leave the message in Outbox. */ + if (error != NULL) { + if (error_messages->len > 0) + g_string_append (error_messages, "\n\n"); + g_string_append_printf ( + error_messages, + _("Failed to append to local 'Sent' folder: %s"), + error->message); + g_clear_error (&error); + goto exit; + } + +cleanup: + + /* The send operation was successful; ignore cleanup errors. */ + + /* Mark the draft message for deletion, if present. */ + e_mail_session_handle_draft_headers_sync ( + session, context->message, cancellable, &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + + /* Set flags on the original source message, if present. + * Source message refers to the message being forwarded + * or replied to. */ + e_mail_session_handle_source_headers_sync ( + session, context->message, cancellable, &error); + if (error != NULL) { + g_warning ("%s", error->message); + g_clear_error (&error); + } + +exit: + + /* If we were cancelled, disregard any other errors. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_simple_async_result_take_error (simple, error); + + /* Stuff the accumulated error messages in a GError. */ + } else if (error_messages->len > 0) { + g_simple_async_result_set_error ( + simple, E_MAIL_ERROR, + E_MAIL_ERROR_POST_PROCESSING, + "%s", error_messages->str); + } + + /* Synchronize the Sent folder. */ + if (folder != NULL) { + camel_folder_synchronize_sync ( + folder, FALSE, cancellable, NULL); + g_object_unref (folder); + } + + g_string_free (error_messages, TRUE); +} + +static guint32 +get_message_size (CamelMimeMessage *message, + GCancellable *cancellable) +{ + CamelStream *null; + guint32 size; + + null = camel_stream_null_new (); + camel_data_wrapper_write_to_stream_sync ( + CAMEL_DATA_WRAPPER (message), null, cancellable, NULL); + size = CAMEL_STREAM_NULL (null)->written; + g_object_unref (null); + + return size; +} + +void +e_mail_session_send_to (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + CamelFilterGetFolderFunc get_folder_func, + gpointer get_folder_data, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *context; + CamelAddress *from; + CamelAddress *recipients; + CamelMedium *medium; + CamelMessageInfo *info; + CamelService *transport; + GPtrArray *post_to_uris; + struct _camel_header_raw *xev; + struct _camel_header_raw *header; + const gchar *resent_from; + GError *error = NULL; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + medium = CAMEL_MEDIUM (message); + + camel_medium_set_header (medium, "X-Mailer", X_MAILER); + + /* Do this before removing "X-Evolution" headers. */ + transport = e_mail_session_ref_transport_for_message ( + session, message); + + xev = mail_tool_remove_xevolution_headers (message); + + /* Extract directives from X-Evolution headers. */ + + post_to_uris = g_ptr_array_new (); + for (header = xev; header != NULL; header = header->next) { + gchar *folder_uri; + + if (g_strcmp0 (header->name, "X-Evolution-PostTo") != 0) + continue; + + folder_uri = g_strstrip (g_strdup (header->value)); + g_ptr_array_add (post_to_uris, folder_uri); + } + + /* Collect sender and recipients from headers. */ + + from = (CamelAddress *) camel_internet_address_new (); + recipients = (CamelAddress *) camel_internet_address_new (); + resent_from = camel_medium_get_header (medium, "Resent-From"); + + if (resent_from != NULL) { + const CamelInternetAddress *addr; + const gchar *type; + + camel_address_decode (from, resent_from); + + type = CAMEL_RECIPIENT_TYPE_RESENT_TO; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_RESENT_CC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_RESENT_BCC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + } else { + const CamelInternetAddress *addr; + const gchar *type; + + addr = camel_mime_message_get_from (message); + camel_address_copy (from, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_TO; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_CC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + + type = CAMEL_RECIPIENT_TYPE_BCC; + addr = camel_mime_message_get_recipients (message, type); + camel_address_cat (recipients, CAMEL_ADDRESS (addr)); + } + + /* Miscellaneous preparations. */ + + info = camel_message_info_new_from_header ( + NULL, CAMEL_MIME_PART (message)->headers); + ((CamelMessageInfoBase *) info)->size = + get_message_size (message, cancellable); + camel_message_info_set_flags (info, CAMEL_MESSAGE_SEEN, ~0); + + /* expand, or remove empty, group addresses */ + em_utils_expand_groups (CAMEL_INTERNET_ADDRESS (recipients)); + + /* The rest of the processing happens in a thread. */ + + context = g_slice_new0 (AsyncContext); + context->message = g_object_ref (message); + context->io_priority = io_priority; + context->from = from; + context->recipients = recipients; + context->info = info; + context->xev = xev; + context->post_to_uris = post_to_uris; + context->transport = transport; + + if (G_IS_CANCELLABLE (cancellable)) + context->cancellable = g_object_ref (cancellable); + + /* Failure here emits a runtime warning but is non-fatal. */ + context->driver = camel_session_get_filter_driver ( + CAMEL_SESSION (session), E_FILTER_SOURCE_OUTGOING, &error); + if (context->driver != NULL && get_folder_func) + camel_filter_driver_set_folder_func ( + context->driver, get_folder_func, get_folder_data); + if (error != NULL) { + g_warn_if_fail (context->driver == NULL); + g_warning ("%s", error->message); + g_error_free (error); + } + + /* This gets popped in async_context_free(). */ + camel_operation_push_message ( + context->cancellable, _("Sending message")); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, + user_data, e_mail_session_send_to); + + g_simple_async_result_set_check_cancellable (simple, cancellable); + + g_simple_async_result_set_op_res_gpointer ( + simple, context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, (GSimpleAsyncThreadFunc) + mail_session_send_to_thread, + context->io_priority, + context->cancellable); + + g_object_unref (simple); +} + +gboolean +e_mail_session_send_to_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_send_to), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + + /* Assume success unless a GError is set. */ + return !g_simple_async_result_propagate_error (simple, error); +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_try_uri_to_folder (EMailSession *session, + const gchar *folder_uri, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder; + GError *local_error = NULL; + + folder = e_mail_session_uri_to_folder_sync ( + session, folder_uri, 0, cancellable, &local_error); + + /* Sanity check. */ + g_return_val_if_fail ( + ((folder != NULL) && (local_error == NULL)) || + ((folder == NULL) && (local_error != NULL)), NULL); + + /* Disregard specific errors. */ + + /* Invalid URI. */ + if (g_error_matches ( + local_error, CAMEL_FOLDER_ERROR, + CAMEL_FOLDER_ERROR_INVALID)) + g_clear_error (&local_error); + + /* Folder not found. */ + if (g_error_matches ( + local_error, CAMEL_STORE_ERROR, + CAMEL_STORE_ERROR_NO_FOLDER)) + g_clear_error (&local_error); + + if (local_error != NULL) + g_propagate_error (error, local_error); + + return folder; +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_ref_origin_folder (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelMedium *medium; + const gchar *header_name; + const gchar *header_value; + + medium = CAMEL_MEDIUM (message); + + /* Check that a "X-Evolution-Source-Flags" header is present + * and its value does not contain the substring "FORWARDED". */ + + header_name = "X-Evolution-Source-Flags"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + if (strstr (header_value, "FORWARDED") != NULL) + return NULL; + + /* Check that a "X-Evolution-Source-Message" header is present. */ + + header_name = "X-Evolution-Source-Message"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + /* Check that a "X-Evolution-Source-Folder" header is present. + * Its value specifies the origin folder as a folder URI. */ + + header_name = "X-Evolution-Source-Folder"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + /* This may return NULL without setting a GError. */ + return mail_session_try_uri_to_folder ( + session, header_value, cancellable, error); +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_ref_fcc_from_identity (EMailSession *session, + ESource *source, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + ESourceRegistry *registry; + ESourceMailSubmission *extension; + CamelFolder *folder = NULL; + const gchar *extension_name; + gchar *folder_uri; + + registry = e_mail_session_get_registry (session); + extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION; + + if (source == NULL) + return NULL; + + if (!e_source_registry_check_enabled (registry, source)) + return NULL; + + if (!e_source_has_extension (source, extension_name)) + return NULL; + + extension = e_source_get_extension (source, extension_name); + + if (e_source_mail_submission_get_replies_to_origin_folder (extension)) { + GError *local_error = NULL; + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_origin_folder ( + session, message, cancellable, &local_error); + + if (local_error != NULL) { + g_warn_if_fail (folder == NULL); + g_propagate_error (error, local_error); + return NULL; + } + } + + folder_uri = e_source_mail_submission_dup_sent_folder (extension); + + if (folder_uri != NULL && folder == NULL) { + /* This may return NULL without setting a GError. */ + folder = mail_session_try_uri_to_folder ( + session, folder_uri, cancellable, error); + } + + g_free (folder_uri); + + return folder; +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_ref_fcc_from_x_identity (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + ESource *source; + ESourceRegistry *registry; + CamelFolder *folder; + CamelMedium *medium; + const gchar *header_name; + const gchar *header_value; + gchar *uid; + + medium = CAMEL_MEDIUM (message); + header_name = "X-Evolution-Identity"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + uid = g_strstrip (g_strdup (header_value)); + + registry = e_mail_session_get_registry (session); + source = e_source_registry_ref_source (registry, uid); + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_fcc_from_identity ( + session, source, message, cancellable, error); + + g_clear_object (&source); + + g_free (uid); + + return folder; +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_ref_fcc_from_x_fcc (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelMedium *medium; + const gchar *header_name; + const gchar *header_value; + + medium = CAMEL_MEDIUM (message); + header_name = "X-Evolution-Fcc"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + /* This may return NULL without setting a GError. */ + return mail_session_try_uri_to_folder ( + session, header_value, cancellable, error); +} + +/* Helper for e_mail_session_get_fcc_for_message_sync() */ +static CamelFolder * +mail_session_ref_fcc_from_default_identity (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + ESource *source; + ESourceRegistry *registry; + CamelFolder *folder; + + registry = e_mail_session_get_registry (session); + source = e_source_registry_ref_default_mail_identity (registry); + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_fcc_from_identity ( + session, source, message, cancellable, error); + + g_clear_object (&source); + + return folder; +} + +/** + * e_mail_session_get_fcc_for_message_sync: + * @session: an #EMailSession + * @message: a #CamelMimeMessage + * @cancellable: optional #GCancellable object, or %NULL + * @error: return location for a #GError, or %NULL + * + * Obtains the preferred "carbon-copy" folder (a.k.a Fcc) for @message + * by first checking @message for an "X-Evolution-Identity" header, and + * then an "X-Evolution-Fcc" header. Failing that, the function checks + * the default mail identity (if available), and failing even that, the + * function falls back to the Sent folder from the built-in mail store. + * + * Where applicable, the function attempts to honor the + * #ESourceMailSubmission:replies-to-origin-folder preference. + * + * The returned #CamelFolder is referenced for thread-safety and must be + * unreferenced with g_object_unref() when finished with it. + * + * If a non-recoverable error occurs, the function sets @error and returns + * %NULL. + * + * Returns: a #CamelFolder, or %NULL + **/ +CamelFolder * +e_mail_session_get_fcc_for_message_sync (EMailSession *session, + CamelMimeMessage *message, + GCancellable *cancellable, + GError **error) +{ + CamelFolder *folder = NULL; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); + + /* Check for "X-Evolution-Identity" header. */ + if (folder == NULL) { + GError *local_error = NULL; + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_fcc_from_x_identity ( + session, message, cancellable, &local_error); + + if (local_error != NULL) { + g_warn_if_fail (folder == NULL); + g_propagate_error (error, local_error); + return NULL; + } + } + + /* Check for "X-Evolution-Fcc" header. */ + if (folder == NULL) { + GError *local_error = NULL; + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_fcc_from_x_fcc ( + session, message, cancellable, &local_error); + + if (local_error != NULL) { + g_warn_if_fail (folder == NULL); + g_propagate_error (error, local_error); + return NULL; + } + } + + /* Check the default mail identity. */ + if (folder == NULL) { + GError *local_error = NULL; + + /* This may return NULL without setting a GError. */ + folder = mail_session_ref_fcc_from_default_identity ( + session, message, cancellable, &local_error); + + if (local_error != NULL) { + g_warn_if_fail (folder == NULL); + g_propagate_error (error, local_error); + return NULL; + } + } + + /* Last resort - local Sent folder. */ + if (folder == NULL) { + folder = e_mail_session_get_local_folder ( + session, E_MAIL_LOCAL_FOLDER_SENT); + g_object_ref (folder); + } + + return folder; +} + +/* Helper for e_mail_session_get_fcc_for_message() */ +static void +mail_session_get_fcc_for_message_thread (GSimpleAsyncResult *simple, + GObject *source_object, + GCancellable *cancellable) +{ + AsyncContext *async_context; + GError *local_error = NULL; + + async_context = g_simple_async_result_get_op_res_gpointer (simple); + + async_context->folder = + e_mail_session_get_fcc_for_message_sync ( + E_MAIL_SESSION (source_object), + async_context->message, + cancellable, &local_error); + + if (local_error != NULL) + g_simple_async_result_take_error (simple, local_error); +} + +/** + * e_mail_session_get_fcc_for_message: + * @session: an #EMailSession + * @message: a #CamelMimeMessage + * @io_priority: the I/O priority of the request + * @cancellable: optional #GCancellable object, or %NULL + * @callback: a #GAsyncReadyCallback to call when the request is satisfied + * @user_data: data to pass to the callback function + * + * Asynchronously obtains the preferred "carbon-copy" folder (a.k.a Fcc) for + * @message by first checking @message for an "X-Evolution-Identity" header, + * and then an "X-Evolution-Fcc" header. Failing that, the function checks + * the default mail identity (if available), and failing even that, the + * function falls back to the Sent folder from the built-in mail store. + * + * Where applicable, the function attempts to honor the + * #ESourceMailSubmission:replies-to-origin-folder preference. + * + * When the operation is finished, @callback will be called. You can then + * call e_mail_session_get_fcc_for_message_finish() to get the result of the + * operation. + **/ +void +e_mail_session_get_fcc_for_message (EMailSession *session, + CamelMimeMessage *message, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *simple; + AsyncContext *async_context; + + g_return_if_fail (E_IS_MAIL_SESSION (session)); + g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message)); + + async_context = g_slice_new0 (AsyncContext); + async_context->message = g_object_ref (message); + + simple = g_simple_async_result_new ( + G_OBJECT (session), callback, user_data, + e_mail_session_get_fcc_for_message); + + g_simple_async_result_set_check_cancellable (simple, cancellable); + + g_simple_async_result_set_op_res_gpointer ( + simple, async_context, (GDestroyNotify) async_context_free); + + g_simple_async_result_run_in_thread ( + simple, mail_session_get_fcc_for_message_thread, + io_priority, cancellable); + + g_object_unref (simple); +} + +/** + * e_mail_session_get_fcc_for_message_finish: + * @session: an #EMailSession + * @result: a #GAsyncResult + * @error: return location for a #GError, or %NULL + * + * Finishes the operation started with e_mail_session_get_fcc_for_message(). + * + * The returned #CamelFolder is referenced for thread-safety and must be + * unreferenced with g_object_unref() when finished with it. + * + * If a non-recoverable error occurred, the function sets @error and + * returns %NULL. + * + * Returns: a #CamelFolder, or %NULL + **/ +CamelFolder * +e_mail_session_get_fcc_for_message_finish (EMailSession *session, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + AsyncContext *async_context; + + g_return_val_if_fail ( + g_simple_async_result_is_valid ( + result, G_OBJECT (session), + e_mail_session_get_fcc_for_message), NULL); + + simple = G_SIMPLE_ASYNC_RESULT (result); + async_context = g_simple_async_result_get_op_res_gpointer (simple); + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + g_return_val_if_fail (async_context->folder != NULL, NULL); + + return g_object_ref (async_context->folder); +} + +/** + * e_mail_session_ref_transport: + * @session: an #EMailSession + * @transport_uid: the UID of a mail transport + * + * Returns the transport #CamelService instance for @transport_uid, + * verifying first that the @transport_uid is indeed a mail transport and + * that the corresponding #ESource is enabled. If these checks fail, the + * function returns %NULL. + * + * The returned #CamelService is referenced for thread-safety and must be + * unreferenced with g_object_unref() when finished with it. + * + * Returns: a #CamelService, or %NULL + **/ +CamelService * +e_mail_session_ref_transport (EMailSession *session, + const gchar *transport_uid) +{ + ESourceRegistry *registry; + ESource *source = NULL; + CamelService *transport = NULL; + const gchar *extension_name; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL); + g_return_val_if_fail (transport_uid != NULL, NULL); + + registry = e_mail_session_get_registry (session); + extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT; + + source = e_source_registry_ref_source (registry, transport_uid); + + if (source == NULL) + goto exit; + + if (!e_source_registry_check_enabled (registry, source)) + goto exit; + + if (!e_source_has_extension (source, extension_name)) + goto exit; + + transport = camel_session_ref_service ( + CAMEL_SESSION (session), transport_uid); + + /* Sanity check. */ + if (transport != NULL) + g_warn_if_fail (CAMEL_IS_TRANSPORT (transport)); + +exit: + g_clear_object (&source); + + return transport; +} + +/* Helper for e_mail_session_ref_default_transport() + * and mail_session_ref_transport_from_x_identity(). */ +static CamelService * +mail_session_ref_transport_for_identity (EMailSession *session, + ESource *source) +{ + ESourceRegistry *registry; + ESourceMailSubmission *extension; + CamelService *transport = NULL; + const gchar *extension_name; + gchar *uid; + + registry = e_mail_session_get_registry (session); + extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION; + + if (source == NULL) + return NULL; + + if (!e_source_registry_check_enabled (registry, source)) + return NULL; + + if (!e_source_has_extension (source, extension_name)) + return NULL; + + extension = e_source_get_extension (source, extension_name); + uid = e_source_mail_submission_dup_transport_uid (extension); + + if (uid != NULL) { + transport = e_mail_session_ref_transport (session, uid); + g_free (uid); + } + + return transport; +} + +/** + * e_mail_session_ref_default_transport: + * @session: an #EMailSession + * + * Returns the default transport #CamelService instance according to + * #ESourceRegistry's #ESourceRegistry:default-mail-identity setting, + * verifying first that the #ESourceMailSubmission:transport-uid named by + * the #ESourceRegistry:default-mail-identity is indeed a mail transport, + * and that the corresponding #ESource is enabled. If these checks fail, + * the function returns %NULL. + * + * The returned #CamelService is referenced for thread-safety and must be + * unreferenced with g_object_unref() when finished with it. + * + * Returns: a #CamelService, or %NULL + **/ +CamelService * +e_mail_session_ref_default_transport (EMailSession *session) +{ + ESource *source; + ESourceRegistry *registry; + CamelService *transport; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL); + + registry = e_mail_session_get_registry (session); + source = e_source_registry_ref_default_mail_identity (registry); + transport = mail_session_ref_transport_for_identity (session, source); + g_clear_object (&source); + + return transport; +} + +/* Helper for e_mail_session_ref_transport_for_message() */ +static CamelService * +mail_session_ref_transport_from_x_identity (EMailSession *session, + CamelMimeMessage *message) +{ + ESource *source; + ESourceRegistry *registry; + CamelMedium *medium; + CamelService *transport; + const gchar *header_name; + const gchar *header_value; + gchar *uid; + + medium = CAMEL_MEDIUM (message); + header_name = "X-Evolution-Identity"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + uid = g_strstrip (g_strdup (header_value)); + + registry = e_mail_session_get_registry (session); + source = e_source_registry_ref_source (registry, uid); + transport = mail_session_ref_transport_for_identity (session, source); + g_clear_object (&source); + + g_free (uid); + + return transport; +} + +/* Helper for e_mail_session_ref_transport_for_message() */ +static CamelService * +mail_session_ref_transport_from_x_transport (EMailSession *session, + CamelMimeMessage *message) +{ + CamelMedium *medium; + CamelService *transport; + const gchar *header_name; + const gchar *header_value; + gchar *uid; + + medium = CAMEL_MEDIUM (message); + header_name = "X-Evolution-Transport"; + header_value = camel_medium_get_header (medium, header_name); + + if (header_value == NULL) + return NULL; + + uid = g_strstrip (g_strdup (header_value)); + + transport = e_mail_session_ref_transport (session, uid); + + g_free (uid); + + return transport; +} + +/** + * e_mail_session_ref_transport_for_message: + * @session: an #EMailSession + * @message: a #CamelMimeMessage + * + * Returns the preferred transport #CamelService instance for @message by + * first checking @message for an "X-Evolution-Identity" header, and then + * an "X-Evolution-Transport" header. Failing that, the function returns + * the default transport #CamelService instance (if available). + * + * The returned #CamelService is referenced for thread-safety and must be + * unreferenced with g_object_unref() when finished with it. + * + * Returns: a #CamelService, or %NULL + **/ +CamelService * +e_mail_session_ref_transport_for_message (EMailSession *session, + CamelMimeMessage *message) +{ + CamelService *transport = NULL; + + g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL); + g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL); + + /* Check for "X-Evolution-Identity" header. */ + if (transport == NULL) + transport = mail_session_ref_transport_from_x_identity ( + session, message); + + /* Check for "X-Evolution-Transport" header. */ + if (transport == NULL) + transport = mail_session_ref_transport_from_x_transport ( + session, message); + + /* Fall back to the default mail transport. */ + if (transport == NULL) + transport = e_mail_session_ref_default_transport (session); + + return transport; +} + |