/* * 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 * */ #ifdef HAVE_CONFIG_H #include #endif #include "e-mail-session-utils.h" #include #include #include #include #include /* 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; }