Files
evolution/libemail-engine/e-mail-session-utils.c
Matthew Barnes d3e0f96c73 Fix error handling glitch in mail_session_send_to_thread().
If sending a message fails but the user was already connected to the
MTA, the error is missed and not handled until further into the logic,
where it's treated as though sending succeeded and only post-processing
failed.  This results in the user seeing a copy of the message in Sent,
but the message was never actually sent.

Fallout from the investigation of bug 710807.
2013-10-29 09:28:17 -04:00

1606 lines
46 KiB
C

/*
* 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_unref (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) {
CamelMessageFlags message_flags;
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);
}
message_flags = camel_message_info_flags (context->info);
if (message_flags & CAMEL_MESSAGE_DELETED)
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;
}