/*
* evolution-mdn.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/>
*
*/
#include <config.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include <libebackend/libebackend.h>
#include <libemail-engine/e-mail-session-utils.h>
#include <mail/em-utils.h>
#include <mail/e-mail-reader.h>
#include <mail/mail-send-recv.h>
#include <mail/message-list.h>
#include <mail/em-composer-utils.h>
#define MDN_USER_FLAG "receipt-handled"
#define E_TYPE_MDN (e_mdn_get_type ())
#define E_MDN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_MDN, EMdn))
#define E_IS_MDN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_MDN))
typedef struct _EMdn EMdn;
typedef struct _EMdnClass EMdnClass;
struct _EMdn {
EExtension parent;
gpointer alert; /* weak pointer */
};
struct _EMdnClass {
EExtensionClass parent_class;
};
typedef struct _MdnContext MdnContext;
struct _MdnContext {
ESource *source;
EMailReader *reader;
CamelFolder *folder;
CamelMessageInfo *info;
CamelMimeMessage *message;
gchar *notify_to;
};
typedef enum {
MDN_ACTION_MODE_MANUAL,
MDN_ACTION_MODE_AUTOMATIC
} MdnActionMode;
typedef enum {
MDN_SENDING_MODE_MANUAL,
MDN_SENDING_MODE_AUTOMATIC
} MdnSendingMode;
/* Module Entry Points */
void e_module_load (GTypeModule *type_module);
void e_module_unload (GTypeModule *type_module);
/* Forward Declarations */
GType e_mdn_get_type (void);
G_DEFINE_DYNAMIC_TYPE (EMdn, e_mdn, E_TYPE_EXTENSION)
static void
mdn_context_free (MdnContext *context)
{
if (context->info != NULL)
camel_folder_free_message_info (
context->folder, context->info);
g_object_unref (context->source);
g_object_unref (context->reader);
g_object_unref (context->folder);
g_object_unref (context->message);
g_free (context->notify_to);
g_slice_free (MdnContext, context);
}
static void
mdn_remove_alert (EMdn *mdn)
{
g_return_if_fail (E_IS_MDN (mdn));
if (mdn->alert != NULL)
e_alert_response (mdn->alert, GTK_RESPONSE_OK);
}
static void
mdn_submit_alert (EMdn *mdn,
EMailReader *reader,
EAlert *alert)
{
EPreviewPane *preview_pane;
g_return_if_fail (E_IS_MDN (mdn));
mdn_remove_alert (mdn);
g_return_if_fail (mdn->alert == NULL);
/* Make sure alerts are shown in the preview pane and not
* wherever e_mail_reader_get_alert_sink() might show it. */
preview_pane = e_mail_reader_get_preview_pane (reader);
e_alert_sink_submit_alert (E_ALERT_SINK (preview_pane), alert);
mdn->alert = alert;
g_object_add_weak_pointer (G_OBJECT (mdn->alert), &mdn->alert);
}
static gchar *
mdn_get_notify_to (CamelMimeMessage *message)
{
CamelMedium *medium;
const gchar *address;
const gchar *header_name;
medium = CAMEL_MEDIUM (message);
header_name = "Disposition-Notification-To";
address = camel_medium_get_header (medium, header_name);
/* TODO Should probably decode/format the address,
* since it could be in RFC 2047 format. */
if (address != NULL)
while (camel_mime_is_lwsp (*address))
address++;
return g_strdup (address);
}
static gchar *
mdn_get_disposition (MdnActionMode action_mode,
MdnSendingMode sending_mode)
{
GString *string;
string = g_string_sized_new (64);
switch (action_mode) {
case MDN_ACTION_MODE_MANUAL:
g_string_append (string, "manual-action");
break;
case MDN_ACTION_MODE_AUTOMATIC:
g_string_append (string, "automatic-action");
break;
default:
g_warn_if_reached ();
}
g_string_append_c (string, '/');
switch (sending_mode) {
case MDN_SENDING_MODE_MANUAL:
g_string_append (string, "MDN-sent-manually");
break;
case MDN_SENDING_MODE_AUTOMATIC:
g_string_append (string, "MDN-sent-automatically");
break;
default:
g_warn_if_reached ();
}
g_string_append (string, ";displayed");
return g_string_free (string, FALSE);
}
static void
mdn_receipt_done (EMailSession *session,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
e_mail_session_append_to_local_folder_finish (
session, result, NULL, &error);
if (error == NULL) {
mail_send (session);
} else {
/* FIXME Poor error handling. */
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
}
}
static void
mdn_notify_sender (ESource *identity_source,
EMailReader *reader,
CamelFolder *folder,
CamelMimeMessage *message,
CamelMessageInfo *info,
const gchar *notify_to,
MdnActionMode action_mode,
MdnSendingMode sending_mode)
{
/* See RFC 3798 for a description of message receipts. */
CamelMimeMessage *receipt;
CamelMultipart *body;
CamelMimePart *part;
CamelMedium *medium;
CamelDataWrapper *receipt_text, *receipt_data;
CamelContentType *type;
CamelInternetAddress *address;
CamelStream *stream;
CamelMessageInfo *receipt_info;
EMailBackend *backend;
EMailSession *session;
ESourceExtension *extension;
const gchar *message_id;
const gchar *message_date;
const gchar *message_subject;
const gchar *extension_name;
const gchar *transport_uid;
const gchar *self_address;
const gchar *sent_folder_uri;
gchar *fake_msgid;
gchar *hostname;
gchar *receipt_subject;
gchar *disposition;
gchar *recipient;
gchar *content;
gchar *ua;
backend = e_mail_reader_get_backend (reader);
session = e_mail_backend_get_session (backend);
/* Tag the message immediately even though we haven't actually sent
* the read receipt yet. Not a big deal if we fail to send it, and
* we don't want to keep badgering the user about it. */
camel_message_info_set_user_flag (info, MDN_USER_FLAG, TRUE);
medium = CAMEL_MEDIUM (message);
message_id = camel_medium_get_header (medium, "Message-ID");
message_date = camel_medium_get_header (medium, "Date");
message_subject = camel_mime_message_get_subject (message);
if (message_id == NULL)
message_id = "";
if (message_date == NULL)
message_date = "";
/* Collect information for the receipt. */
g_return_if_fail (identity_source != NULL);
extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
extension = e_source_get_extension (identity_source, extension_name);
self_address = e_source_mail_identity_get_address (
E_SOURCE_MAIL_IDENTITY (extension));
extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
extension = e_source_get_extension (identity_source, extension_name);
sent_folder_uri = e_source_mail_submission_get_sent_folder (
E_SOURCE_MAIL_SUBMISSION (extension));
transport_uid = e_source_mail_submission_get_transport_uid (
E_SOURCE_MAIL_SUBMISSION (extension));
/* We use camel_header_msgid_generate() to get a canonical
* hostname, then skip the part leading to '@' */
fake_msgid = camel_header_msgid_generate ();
hostname = strchr (fake_msgid, '@');
g_return_if_fail (hostname != NULL);
hostname++;
/* Create toplevel container. */
body = camel_multipart_new ();
camel_data_wrapper_set_mime_type (
CAMEL_DATA_WRAPPER (body),
"multipart/report;"
"report-type=\"disposition-notification\"");
camel_multipart_set_boundary (body, NULL);
/* Create textual receipt. */
receipt_text = camel_data_wrapper_new ();
type = camel_content_type_new ("text", "plain");
camel_content_type_set_param (type, "format", "flowed");
camel_content_type_set_param (type, "charset", "UTF-8");
camel_data_wrapper_set_mime_type_field (receipt_text, type);
camel_content_type_unref (type);
content = g_strdup_printf (
/* Translators: First %s is an email address, second %s
* is the subject of the email, third %s is the date. */
_("Your message to %s about \"%s\" on %s has been read."),
self_address, message_subject, message_date);
stream = camel_stream_mem_new ();
camel_stream_write_string (stream, content, NULL, NULL);
camel_data_wrapper_construct_from_stream_sync (
receipt_text, stream, NULL, NULL);
g_object_unref (stream);
g_free (content);
part = camel_mime_part_new ();
camel_medium_set_content (CAMEL_MEDIUM (part), receipt_text);
camel_mime_part_set_encoding (
part, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
camel_multipart_add_part (body, part);
g_object_unref (part);
g_object_unref (receipt_text);
/* Create the machine-readable receipt. */
receipt_data = camel_data_wrapper_new ();
ua = g_strdup_printf (
"%s; %s", hostname, "Evolution "
VERSION SUB_VERSION " " VERSION_COMMENT);
recipient = g_strdup_printf ("rfc822; %s", self_address);
disposition = mdn_get_disposition (action_mode, sending_mode);
type = camel_content_type_new ("message", "disposition-notification");
camel_data_wrapper_set_mime_type_field (receipt_data, type);
camel_content_type_unref (type);
content = g_strdup_printf (
"Reporting-UA: %s\n"
"Final-Recipient: %s\n"
"Original-Message-ID: %s\n"
"Disposition: %s\n",
ua, recipient, message_id, disposition);
stream = camel_stream_mem_new ();
camel_stream_write_string (stream, content, NULL, NULL);
camel_data_wrapper_construct_from_stream_sync (
receipt_data, stream, NULL, NULL);
g_object_unref (stream);
g_free (content);
g_free (ua);
g_free (recipient);
g_free (fake_msgid);
g_free (disposition);
part = camel_mime_part_new ();
camel_medium_set_content (CAMEL_MEDIUM (part), receipt_data);
camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_7BIT);
camel_multipart_add_part (body, part);
g_object_unref (part);
g_object_unref (receipt_data);
/* Finish creating the message. */
receipt = camel_mime_message_new ();
camel_medium_set_content (
CAMEL_MEDIUM (receipt), CAMEL_DATA_WRAPPER (body));
g_object_unref (body);
receipt_subject = g_strdup_printf (
/* Translators: %s is the subject of the email message. */
_("Delivery Notification for \"%s\""), message_subject);
camel_mime_message_set_subject (receipt, receipt_subject);
g_free (receipt_subject);
address = camel_internet_address_new ();
camel_address_decode (CAMEL_ADDRESS (address), self_address);
camel_mime_message_set_from (receipt, address);
g_object_unref (address);
address = camel_internet_address_new ();
camel_address_decode (CAMEL_ADDRESS (address), notify_to);
camel_mime_message_set_recipients (
receipt, CAMEL_RECIPIENT_TYPE_TO, address);
g_object_unref (address);
camel_medium_set_header (
CAMEL_MEDIUM (receipt),
"Return-Path", "<>");
camel_medium_set_header (
CAMEL_MEDIUM (receipt),
"X-Evolution-Identity",
e_source_get_uid (identity_source));
camel_medium_set_header (
CAMEL_MEDIUM (receipt),
"X-Evolution-Transport",
transport_uid);
camel_medium_set_header (
CAMEL_MEDIUM (receipt),
"X-Evolution-Fcc",
sent_folder_uri);
/* RFC 3834, Section 5 describes this header. */
camel_medium_set_header (
CAMEL_MEDIUM (receipt),
"Auto-Submitted", "auto-replied");
/* Send the receipt. */
receipt_info = camel_message_info_new (NULL);
camel_message_info_set_flags (
receipt_info, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN);
/* FIXME Pass a GCancellable. */
e_mail_session_append_to_local_folder (
session, E_MAIL_LOCAL_FOLDER_OUTBOX,
receipt, receipt_info, G_PRIORITY_DEFAULT,
NULL, (GAsyncReadyCallback) mdn_receipt_done,
g_object_ref (session));
camel_message_info_free (receipt_info);
}
static void
mdn_notify_action_cb (GtkAction *action,
MdnContext *context)
{
mdn_notify_sender (
context->source,
context->reader,
context->folder,
context->message,
context->info,
context->notify_to,
MDN_ACTION_MODE_MANUAL,
MDN_SENDING_MODE_MANUAL);
/* Make sure the newly-added user flag gets saved. */
camel_folder_free_message_info (context->folder, context->info);
context->info = NULL;
}
static void
mdn_mail_reader_changed_cb (EMailReader *reader,
EMdn *mdn)
{
MessageList *message_list;
g_return_if_fail (E_IS_MAIL_READER (reader));
message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
if (!message_list || message_list_selected_count (message_list) != 1)
mdn_remove_alert (mdn);
}
static void
mdn_message_loaded_cb (EMailReader *reader,
const gchar *message_uid,
CamelMimeMessage *message,
EMdn *mdn)
{
EAlert *alert;
ESource *source;
ESourceMDN *extension;
ESourceRegistry *registry;
EMailBackend *backend;
EMailSession *session;
CamelFolder *folder;
CamelMessageInfo *info;
EMdnResponsePolicy response_policy;
const gchar *extension_name;
gchar *notify_to = NULL;
backend = e_mail_reader_get_backend (reader);
session = e_mail_backend_get_session (backend);
registry = e_mail_session_get_registry (session);
folder = e_mail_reader_ref_folder (reader);
mdn_remove_alert (mdn);
info = camel_folder_get_message_info (folder, message_uid);
if (info == NULL)
goto exit;
if (camel_message_info_user_flag (info, MDN_USER_FLAG)) {
alert = e_alert_new ("mdn:sender-notified", NULL);
mdn_submit_alert (mdn, reader, alert);
g_object_unref (alert);
goto exit;
}
notify_to = mdn_get_notify_to (message);
if (notify_to == NULL)
goto exit;
/* Do not show the notice in special folders. */
if (em_utils_folder_is_drafts (registry, folder))
goto exit;
if (em_utils_folder_is_templates (registry, folder))
goto exit;
if (em_utils_folder_is_sent (registry, folder))
goto exit;
if (em_utils_folder_is_outbox (registry, folder))
goto exit;
/* This returns a new ESource reference. */
source = em_utils_guess_mail_identity_with_recipients (
registry, message, folder, message_uid);
if (source == NULL)
goto exit;
extension_name = E_SOURCE_EXTENSION_MDN;
extension = e_source_get_extension (source, extension_name);
response_policy = e_source_mdn_get_response_policy (extension);
if (response_policy == E_MDN_RESPONSE_POLICY_ASK) {
MdnContext *context;
GtkAction *action;
gchar *tooltip;
context = g_slice_new0 (MdnContext);
context->source = g_object_ref (source);
context->reader = g_object_ref (reader);
context->folder = g_object_ref (folder);
context->message = g_object_ref (message);
context->info = camel_message_info_ref (info);
context->notify_to = notify_to;
notify_to = NULL;
tooltip = g_strdup_printf (
_("Send a read receipt to '%s'"),
context->notify_to);
action = gtk_action_new (
"notify-sender", /* name doesn't matter */
_("_Notify Sender"),
tooltip, NULL);
g_signal_connect_data (
action, "activate",
G_CALLBACK (mdn_notify_action_cb),
context,
(GClosureNotify) mdn_context_free,
(GConnectFlags) 0);
alert = e_alert_new ("mdn:notify-sender", NULL);
e_alert_add_action (alert, action, GTK_RESPONSE_APPLY);
mdn_submit_alert (mdn, reader, alert);
g_object_unref (alert);
g_object_unref (action);
g_free (tooltip);
}
g_object_unref (source);
exit:
if (info != NULL)
camel_folder_free_message_info (folder, info);
g_clear_object (&folder);
g_free (notify_to);
}
static void
mdn_message_seen_cb (EMailReader *reader,
const gchar *message_uid,
CamelMimeMessage *message)
{
ESource *source;
ESourceMDN *extension;
ESourceRegistry *registry;
EMailBackend *backend;
EMailSession *session;
CamelFolder *folder;
CamelMessageInfo *info;
EMdnResponsePolicy response_policy;
const gchar *extension_name;
gchar *notify_to = NULL;
backend = e_mail_reader_get_backend (reader);
session = e_mail_backend_get_session (backend);
registry = e_mail_session_get_registry (session);
folder = e_mail_reader_ref_folder (reader);
info = camel_folder_get_message_info (folder, message_uid);
if (info == NULL)
goto exit;
if (camel_message_info_user_flag (info, MDN_USER_FLAG))
goto exit;
notify_to = mdn_get_notify_to (message);
if (notify_to == NULL)
goto exit;
/* This returns a new ESource reference. */
source = em_utils_guess_mail_identity_with_recipients (
registry, message, folder, message_uid);
if (source == NULL)
goto exit;
extension_name = E_SOURCE_EXTENSION_MDN;
extension = e_source_get_extension (source, extension_name);
response_policy = e_source_mdn_get_response_policy (extension);
if (response_policy == E_MDN_RESPONSE_POLICY_ALWAYS)
mdn_notify_sender (
source, reader, folder,
message, info, notify_to,
MDN_ACTION_MODE_AUTOMATIC,
MDN_SENDING_MODE_AUTOMATIC);
g_object_unref (source);
exit:
if (info != NULL)
camel_folder_free_message_info (folder, info);
g_clear_object (&folder);
g_free (notify_to);
}
static void
mdn_constructed (GObject *object)
{
EExtension *extension;
EExtensible *extensible;
extension = E_EXTENSION (object);
extensible = e_extension_get_extensible (extension);
g_return_if_fail (E_IS_MAIL_READER (extensible));
g_signal_connect (
extensible, "changed",
G_CALLBACK (mdn_mail_reader_changed_cb), extension);
g_signal_connect (
extensible, "message-loaded",
G_CALLBACK (mdn_message_loaded_cb), extension);
g_signal_connect (
extensible, "message-seen",
G_CALLBACK (mdn_message_seen_cb), NULL);
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_mdn_parent_class)->constructed (object);
}
static void
mdn_dispose (GObject *object)
{
mdn_remove_alert (E_MDN (object));
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_mdn_parent_class)->dispose (object);
}
static void
e_mdn_class_init (EMdnClass *class)
{
GObjectClass *object_class;
EExtensionClass *extension_class;
object_class = G_OBJECT_CLASS (class);
object_class->constructed = mdn_constructed;
object_class->dispose = mdn_dispose;
extension_class = E_EXTENSION_CLASS (class);
extension_class->extensible_type = E_TYPE_MAIL_READER;
}
static void
e_mdn_class_finalize (EMdnClass *class)
{
}
static void
e_mdn_init (EMdn *extension)
{
}
G_MODULE_EXPORT void
e_module_load (GTypeModule *type_module)
{
e_mdn_register_type (type_module);
}
G_MODULE_EXPORT void
e_module_unload (GTypeModule *type_module)
{
}