/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library 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. * * This library 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 this library. If not, see . * * Authors: Michael Zucchi * Jeffrey Stedfast */ #include "evolution-data-server-config.h" #include #include #include #include #include #include #include #include #ifndef G_OS_WIN32 #include #endif #include "camel-debug.h" #include "camel-file-utils.h" #include "camel-filter-driver.h" #include "camel-filter-search.h" #include "camel-mime-message.h" #include "camel-service.h" #include "camel-session.h" #include "camel-sexp.h" #include "camel-store.h" #include "camel-stream-fs.h" #include "camel-stream-mem.h" #include "camel-string-utils.h" #define d(x) /* an invalid pointer */ #define FOLDER_INVALID ((gpointer)~0) /* type of status for a log report */ enum filter_log_t { FILTER_LOG_NONE, FILTER_LOG_START, /* start of new log entry */ FILTER_LOG_ACTION, /* an action performed */ FILTER_LOG_INFO, /* a generic information log */ FILTER_LOG_END /* end of log */ }; /* list of rule nodes */ struct _filter_rule { gchar *match; gchar *action; gchar *name; }; struct _CamelFilterDriverPrivate { GHashTable *globals; /* global variables */ CamelSession *session; CamelFolder *defaultfolder; /* defualt folder */ CamelFilterStatusFunc statusfunc; /* status callback */ gpointer statusdata; /* status callback data */ CamelFilterShellFunc shellfunc; /* execute shell command callback */ gpointer shelldata; /* execute shell command callback data */ CamelFilterPlaySoundFunc playfunc; /* play-sound command callback */ gpointer playdata; /* play-sound command callback data */ CamelFilterSystemBeepFunc beep; /* system beep callback */ gpointer beepdata; /* system beep callback data */ /* for callback */ CamelFilterGetFolderFunc get_folder; gpointer data; /* run-time data */ GHashTable *folders; /* folders that message has been copied to */ gint closed; /* close count */ GHashTable *only_once; /* actions to run only-once */ gboolean terminated; /* message processing was terminated */ gboolean deleted; /* message was marked for deletion */ gboolean copied; /* message was copied to some folder or another */ gboolean moved; /* message was moved to some folder or another */ CamelMimeMessage *message; /* input message */ CamelMessageInfo *info; /* message summary info */ const gchar *uid; /* message uid */ CamelFolder *source; /* message source folder */ gboolean modified; /* has the input message been modified? */ FILE *logfile; /* log file */ GQueue rules; /* queue of _filter_rule structs */ GError *error; GHashTable *transfers; /* CamelFolder * ~> MessageTransferData * */ GSList *delete_after_transfer; /* CamelMessageInfo * to delete after transfers are done */ /* evaluator */ CamelSExp *eval; }; static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const gchar *desc, ...); static CamelFolder *open_folder (CamelFilterDriver *d, const gchar *folder_url); static gint close_folders (CamelFilterDriver *d, gboolean can_call_refresh, GCancellable *cancellable); static CamelSExpResult *do_delete (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_forward_to (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_copy (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_move (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_stop (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_label (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_color (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_adjust_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *set_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *unset_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_shell (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_beep (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *play_sound (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *do_only_once (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *); static gint filter_driver_filter_message_internal (CamelFilterDriver *driver, gboolean can_process_transfers, CamelMimeMessage *message, CamelMessageInfo *info, const gchar *uid, CamelFolder *source, const gchar *store_uid, const gchar *original_store_uid, GCancellable *cancellable, GError **error); /* these are our filter actions - each must have a callback */ static struct { const gchar *name; CamelSExpFunc func; gint type; /* set to 1 if a function can perform shortcut evaluation, or doesn't execute everything, 0 otherwise */ } symbols[] = { { "delete", (CamelSExpFunc) do_delete, 0 }, { "forward-to", (CamelSExpFunc) do_forward_to, 0 }, { "copy-to", (CamelSExpFunc) do_copy, 0 }, { "move-to", (CamelSExpFunc) do_move, 0 }, { "stop", (CamelSExpFunc) do_stop, 0 }, { "set-label", (CamelSExpFunc) do_label, 0 }, { "set-color", (CamelSExpFunc) do_color, 0 }, { "set-score", (CamelSExpFunc) do_score, 0 }, { "adjust-score", (CamelSExpFunc) do_adjust_score, 0 }, { "set-system-flag", (CamelSExpFunc) set_flag, 0 }, { "unset-system-flag", (CamelSExpFunc) unset_flag, 0 }, { "pipe-message", (CamelSExpFunc) pipe_message, 0 }, { "shell", (CamelSExpFunc) do_shell, 0 }, { "beep", (CamelSExpFunc) do_beep, 0 }, { "play-sound", (CamelSExpFunc) play_sound, 0 }, { "only-once", (CamelSExpFunc) do_only_once, 0 } }; G_DEFINE_TYPE_WITH_PRIVATE (CamelFilterDriver, camel_filter_driver, G_TYPE_OBJECT) typedef struct _MessageTransferData { GPtrArray *copy_uids; GPtrArray *move_uids; } MessageTransferData; static MessageTransferData * message_transfer_data_new (void) { return g_slice_new0 (MessageTransferData); } static void message_transfer_data_free (gpointer ptr) { MessageTransferData *mtd = ptr; if (mtd) { if (mtd->copy_uids) g_ptr_array_free (mtd->copy_uids, TRUE); if (mtd->move_uids) g_ptr_array_free (mtd->move_uids, TRUE); g_slice_free (MessageTransferData, mtd); } } static void filter_driver_add_to_transfers (CamelFilterDriver *driver, CamelFolder *destination, const gchar *uid, gboolean is_move) { MessageTransferData *mtd; GPtrArray **parray; g_return_if_fail (CAMEL_IS_FILTER_DRIVER (driver)); g_return_if_fail (CAMEL_IS_FOLDER (destination)); g_return_if_fail (uid); if (!driver->priv->transfers) driver->priv->transfers = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, message_transfer_data_free); mtd = g_hash_table_lookup (driver->priv->transfers, destination); if (!mtd) { mtd = message_transfer_data_new (); g_hash_table_insert (driver->priv->transfers, g_object_ref (destination), mtd); } if (is_move) parray = &(mtd->move_uids); else parray = &(mtd->copy_uids); if (!*parray) *parray = g_ptr_array_new_with_free_func ((GDestroyNotify) camel_pstring_free); g_ptr_array_add (*parray, (gpointer) camel_pstring_strdup (uid)); } static gboolean filter_driver_process_transfers (CamelFilterDriver *driver, GCancellable *cancellable, GError **error) { gboolean success = TRUE; GSList *link; g_return_val_if_fail (CAMEL_IS_FILTER_DRIVER (driver), FALSE); if (driver->priv->transfers) { CamelStore *parent_store; GHashTableIter iter; gpointer key, value; guint ii, sz; parent_store = camel_folder_get_parent_store (driver->priv->source); /* Translators: The first “%s” is replaced with an account name and the second “%s” is replaced with a full path name. The spaces around “:” are intentional, as the whole “%s : %s” is meant as an absolute identification of the folder. */ camel_operation_push_message (cancellable, _("Transferring filtered messages in “%s : %s”"), camel_service_get_display_name (CAMEL_SERVICE (parent_store)), camel_folder_get_full_display_name (driver->priv->source)); ii = 0; sz = g_hash_table_size (driver->priv->transfers); if (!sz) sz = 1; camel_operation_progress (cancellable, ii * 100 / sz); g_hash_table_iter_init (&iter, driver->priv->transfers); while (success && g_hash_table_iter_next (&iter, &key, &value)) { CamelFolder *destination = key; MessageTransferData *mtd = value; g_warn_if_fail (CAMEL_IS_FOLDER (destination)); g_warn_if_fail (mtd != NULL); if (mtd->copy_uids) { success = camel_folder_transfer_messages_to_sync (driver->priv->source, mtd->copy_uids, destination, FALSE, NULL, cancellable, error); } if (success && mtd->move_uids) { success = camel_folder_transfer_messages_to_sync (driver->priv->source, mtd->move_uids, destination, TRUE, NULL, cancellable, error); } ii++; camel_operation_progress (cancellable, ii * 100 / sz); } camel_operation_pop_message (cancellable); g_hash_table_destroy (driver->priv->transfers); driver->priv->transfers = NULL; } for (link = driver->priv->delete_after_transfer; link && success; link = g_slist_next (link)) { CamelMessageInfo *nfo = link->data; camel_message_info_set_flags ( nfo, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_FOLDER_FLAGGED, ~0); } g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref); driver->priv->delete_after_transfer = NULL; return success; } static void free_hash_strings (gpointer key, gpointer value, gpointer data) { g_free (key); g_free (value); } static gint filter_rule_compare_by_name (struct _filter_rule *rule, const gchar *name) { return g_strcmp0 (rule->name, name); } static void filter_driver_dispose (GObject *object) { CamelFilterDriverPrivate *priv; priv = CAMEL_FILTER_DRIVER (object)->priv; g_clear_pointer (&priv->transfers, g_hash_table_destroy); g_slist_free_full (priv->delete_after_transfer, g_object_unref); priv->delete_after_transfer = NULL; if (priv->defaultfolder != NULL) { camel_folder_thaw (priv->defaultfolder); g_object_unref (priv->defaultfolder); priv->defaultfolder = NULL; } g_clear_object (&priv->session); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (camel_filter_driver_parent_class)->dispose (object); } static void filter_driver_finalize (GObject *object) { CamelFilterDriverPrivate *priv; struct _filter_rule *node; priv = CAMEL_FILTER_DRIVER (object)->priv; /* close all folders that were opened for appending */ close_folders (CAMEL_FILTER_DRIVER (object), FALSE, NULL); g_hash_table_destroy (priv->folders); g_hash_table_foreach (priv->globals, free_hash_strings, object); g_hash_table_destroy (priv->globals); g_hash_table_foreach (priv->only_once, free_hash_strings, object); g_hash_table_destroy (priv->only_once); g_object_unref (priv->eval); while ((node = g_queue_pop_head (&priv->rules)) != NULL) { g_free (node->match); g_free (node->action); g_free (node->name); g_free (node); } /* Chain up to parent's finalize() method. */ G_OBJECT_CLASS (camel_filter_driver_parent_class)->finalize (object); } static void camel_filter_driver_class_init (CamelFilterDriverClass *class) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (class); object_class->dispose = filter_driver_dispose; object_class->finalize = filter_driver_finalize; } static void camel_filter_driver_init (CamelFilterDriver *filter_driver) { gint ii; filter_driver->priv = camel_filter_driver_get_instance_private (filter_driver); g_queue_init (&filter_driver->priv->rules); filter_driver->priv->eval = camel_sexp_new (); /* Load in builtin symbols */ for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) { if (symbols[ii].type == 1) { camel_sexp_add_ifunction ( filter_driver->priv->eval, 0, symbols[ii].name, (CamelSExpIFunc) symbols[ii].func, filter_driver); } else { camel_sexp_add_function ( filter_driver->priv->eval, 0, symbols[ii].name, symbols[ii].func, filter_driver); } } filter_driver->priv->globals = g_hash_table_new (g_str_hash, g_str_equal); filter_driver->priv->folders = g_hash_table_new (g_str_hash, g_str_equal); filter_driver->priv->only_once = g_hash_table_new (g_str_hash, g_str_equal); } /** * camel_filter_driver_new: * @session: (type CamelSession): * * Returns: A new CamelFilterDriver object **/ CamelFilterDriver * camel_filter_driver_new (CamelSession *session) { CamelFilterDriver *d; d = g_object_new (CAMEL_TYPE_FILTER_DRIVER, NULL); d->priv->session = g_object_ref (session); return d; } /** * camel_filter_driver_set_folder_func: * @d: a #CamelFilterDriver * @get_folder: (scope call) (closure user_data): a callback to get a folder * @user_data: user data to pass to @get_folder * * Sets a callback (of type #CamelFilterGetFolderFunc) to get a folder. **/ void camel_filter_driver_set_folder_func (CamelFilterDriver *d, CamelFilterGetFolderFunc get_folder, gpointer user_data) { d->priv->get_folder = get_folder; d->priv->data = user_data; } /** * camel_filter_driver_set_logfile: * @d: a #CamelFilterDriver * @logfile: (nullable): a FILE handle where to write logging * * Sets a log file to use for logging. **/ void camel_filter_driver_set_logfile (CamelFilterDriver *d, FILE *logfile) { d->priv->logfile = logfile; } /** * camel_filter_driver_set_status_func: * @d: a #CamelFilterDriver * @func: (scope call) (closure user_data): a callback to report progress * @user_data: user data to pass to @func * * Sets a status callback, which is used to report progress/status. **/ void camel_filter_driver_set_status_func (CamelFilterDriver *d, CamelFilterStatusFunc func, gpointer user_data) { d->priv->statusfunc = func; d->priv->statusdata = user_data; } /** * camel_filter_driver_set_shell_func: * @d: a #CamelFilterDriver * @func: (scope call) (closure user_data): a shell command callback * @user_data: user data to pass to @func * * Sets a shell command callback, which is called when a shell command * execution is requested. **/ void camel_filter_driver_set_shell_func (CamelFilterDriver *d, CamelFilterShellFunc func, gpointer user_data) { d->priv->shellfunc = func; d->priv->shelldata = user_data; } /** * camel_filter_driver_set_play_sound_func: * @d: a #CamelFilterDriver * @func: (scope call) (closure user_data): a callback to play a sound * @user_data: user data to pass to @func * * Sets a callback to call when a play of a sound is requested. **/ void camel_filter_driver_set_play_sound_func (CamelFilterDriver *d, CamelFilterPlaySoundFunc func, gpointer user_data) { d->priv->playfunc = func; d->priv->playdata = user_data; } /** * camel_filter_driver_set_system_beep_func: * @d: a #CamelFilterDriver * @func: (scope call) (closure user_data): a system beep callback * @user_data: user data to pass to @func * * Sets a callback to use for system beep. **/ void camel_filter_driver_set_system_beep_func (CamelFilterDriver *d, CamelFilterSystemBeepFunc func, gpointer user_data) { d->priv->beep = func; d->priv->beepdata = user_data; } /** * camel_filter_driver_set_default_folder: * @d: a #CamelFilterDriver * @def: (nullable): a default #CamelFolder * * Sets a default folder for the driver. The function adds * its own reference for the folder. **/ void camel_filter_driver_set_default_folder (CamelFilterDriver *d, CamelFolder *def) { if (d->priv->defaultfolder == def) return; if (d->priv->defaultfolder) { camel_folder_thaw (d->priv->defaultfolder); g_object_unref (d->priv->defaultfolder); } d->priv->defaultfolder = def; if (d->priv->defaultfolder) { camel_folder_freeze (d->priv->defaultfolder); g_object_ref (d->priv->defaultfolder); } } /** * camel_filter_driver_add_rule: * @d: a #CamelFilterDriver * @name: name of the rule * @match: a code (#CamelSExp) to execute to check whether the rule can be applied * @action: an action code (#CamelSExp) to execute, when the @match evaluates to %TRUE * * Adds a new rule to set of rules to process by the filter driver. **/ void camel_filter_driver_add_rule (CamelFilterDriver *d, const gchar *name, const gchar *match, const gchar *action) { struct _filter_rule *node; node = g_malloc (sizeof (*node)); node->match = g_strdup (match); node->action = g_strdup (action); node->name = g_strdup (name); g_queue_push_tail (&d->priv->rules, node); } /** * camel_filter_driver_remove_rule_by_name: * @d: a #CamelFilterDriver * @name: rule name * * Removes a rule by name, added by camel_filter_driver_add_rule(). * * Returns: Whether the rule had been found and removed. **/ gboolean camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d, const gchar *name) { GList *link; link = g_queue_find_custom ( &d->priv->rules, name, (GCompareFunc) filter_rule_compare_by_name); if (link != NULL) { struct _filter_rule *rule = link->data; g_queue_delete_link (&d->priv->rules, link); g_free (rule->match); g_free (rule->action); g_free (rule->name); g_free (rule); return TRUE; } return FALSE; } static void report_status (CamelFilterDriver *driver, enum camel_filter_status_t status, gint pc, const gchar *desc, ...) { /* call user-defined status report function */ va_list ap; gchar *str; if (driver->priv->statusfunc) { va_start (ap, desc); str = g_strdup_vprintf (desc, ap); va_end (ap); driver->priv->statusfunc (driver, status, pc, str, driver->priv->statusdata); g_free (str); } } #if 0 void camel_filter_driver_set_global (CamelFilterDriver *d, const gchar *name, const gchar *value) { gchar *oldkey, *oldvalue; if (g_hash_table_lookup_extended (d->priv->globals, name, (gpointer) &oldkey, (gpointer) &oldvalue)) { g_free (oldvalue); g_hash_table_insert (d->priv->globals, oldkey, g_strdup (value)); } else { g_hash_table_insert (d->priv->globals, g_strdup (name), g_strdup (value)); } } #endif static CamelSExpResult * do_delete (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "doing delete\n")); driver->priv->deleted = TRUE; camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Delete"); return NULL; } static CamelSExpResult * do_forward_to (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { const gchar *forward_with; d (fprintf (stderr, "marking message for forwarding\n")); /* requires one parameter, string with a destination address */ if (argc < 1 || argv[0]->type != CAMEL_SEXP_RES_STRING) return NULL; /* make sure we have the message... */ if (driver->priv->message == NULL) { /* FIXME Pass a GCancellable */ driver->priv->message = camel_folder_get_message_sync ( driver->priv->source, driver->priv->uid, NULL, &driver->priv->error); if (driver->priv->message == NULL) return NULL; } forward_with = (argc > 1 && argv[1]->type == CAMEL_SEXP_RES_STRING) ? argv[1]->value.string : NULL; if (forward_with && !*forward_with) forward_with = NULL; if (forward_with) { camel_medium_set_header (CAMEL_MEDIUM (driver->priv->message), "X-Evolution-Forward-With", forward_with); camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward message to '%s' with '%s'", argv[0]->value.string, forward_with); } else { camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward message to '%s'", argv[0]->value.string); } /* XXX Not cancellable. */ camel_session_forward_to_sync ( driver->priv->session, driver->priv->source, driver->priv->message, argv[0]->value.string, NULL, &driver->priv->error); if (forward_with) camel_medium_remove_header (CAMEL_MEDIUM (driver->priv->message), "X-Evolution-Forward-With"); return NULL; } static CamelSExpResult * do_copy (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { gint i; d (fprintf (stderr, "copying message...\n")); for (i = 0; i < argc; i++) { if (argv[i]->type == CAMEL_SEXP_RES_STRING) { /* open folders we intent to copy to */ gchar *folder = argv[i]->value.string; CamelFolder *outbox; outbox = open_folder (driver, folder); if (!outbox) break; if (outbox == driver->priv->source) break; if (driver->priv->message == NULL) /* FIXME Pass a GCancellable */ driver->priv->message = camel_folder_get_message_sync ( driver->priv->source, driver->priv->uid, NULL, &driver->priv->error); if (!driver->priv->message) continue; /* FIXME Pass a GCancellable */ camel_folder_append_message_sync ( outbox, driver->priv->message, driver->priv->info, NULL, NULL, &driver->priv->error); if (driver->priv->error == NULL) driver->priv->copied = TRUE; camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Copy to folder %s", folder); } } return NULL; } static CamelSExpResult * do_move (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { gint i; d (fprintf (stderr, "moving message...\n")); for (i = 0; i < argc; i++) { if (argv[i]->type == CAMEL_SEXP_RES_STRING) { /* open folders we intent to move to */ gchar *folder = argv[i]->value.string; CamelFolder *outbox; gint last; outbox = open_folder (driver, folder); if (!outbox) break; if (outbox == driver->priv->source) break; /* only delete on last folder (only 1 can ever be supplied by ui currently) */ last = (i == argc - 1); if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) { filter_driver_add_to_transfers (driver, outbox, driver->priv->uid, last); } else { if (driver->priv->message == NULL) /* FIXME Pass a GCancellable */ driver->priv->message = camel_folder_get_message_sync ( driver->priv->source, driver->priv->uid, NULL, &driver->priv->error); if (!driver->priv->message) continue; /* FIXME Pass a GCancellable */ camel_folder_append_message_sync ( outbox, driver->priv->message, driver->priv->info, NULL, NULL, &driver->priv->error); if (driver->priv->error == NULL && last) { if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_flags ( driver->priv->source, driver->priv->uid, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0); else camel_message_info_set_flags ( driver->priv->info, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_FOLDER_FLAGGED, ~0); } } if (driver->priv->error == NULL) { driver->priv->moved = TRUE; camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Move to folder %s", folder); } } } /* implicit 'stop' with 'move' */ camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Stopped processing"); driver->priv->terminated = TRUE; return NULL; } static CamelSExpResult * do_stop (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Stopped processing"); d (fprintf (stderr, "terminating message processing\n")); driver->priv->terminated = TRUE; return NULL; } static CamelSExpResult * do_label (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "setting label tag\n")); if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) { /* This is a list of new labels, we should used these in case of passing in old names. * This all is required only because backward compatibility. */ const gchar *new_labels[] = { "$Labelimportant", "$Labelwork", "$Labelpersonal", "$Labeltodo", "$Labellater", NULL}; const gchar *label; gint i; label = argv[0]->value.string; for (i = 0; new_labels[i]; i++) { if (label && strcmp (new_labels[i] + 6, label) == 0) { label = new_labels[i]; break; } } if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_user_flag (driver->priv->source, driver->priv->uid, label, TRUE); else camel_message_info_set_user_flag (driver->priv->info, label, TRUE); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Set label to %s", label); } return NULL; } static CamelSExpResult * do_color (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "setting color tag\n")); if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) { const gchar *color = argv[0]->value.string; if (color && !*color) color = NULL; if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_user_tag (driver->priv->source, driver->priv->uid, "color", color); else camel_message_info_set_user_tag (driver->priv->info, "color", color); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Set color to %s", color ? color : "None"); } return NULL; } static CamelSExpResult * do_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "setting score tag\n")); if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) { gchar *value; value = g_strdup_printf ("%d", argv[0]->value.number); camel_message_info_set_user_tag (driver->priv->info, "score", value); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Set score to %d", argv[0]->value.number); g_free (value); } return NULL; } static CamelSExpResult * do_adjust_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "adjusting score tag\n")); if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) { gchar *value; gint old; value = (gchar *) camel_message_info_get_user_tag (driver->priv->info, "score"); old = value ? atoi (value) : 0; value = g_strdup_printf ("%d", old + argv[0]->value.number); camel_message_info_set_user_tag (driver->priv->info, "score", value); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Adjust score (%d) to %s", argv[0]->value.number, value); g_free (value); } return NULL; } static CamelSExpResult * set_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { guint32 flags; d (fprintf (stderr, "setting flag\n")); if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) { flags = camel_system_flag (argv[0]->value.string); if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_flags ( driver->priv->source, driver->priv->uid, flags, ~0); else camel_message_info_set_flags ( driver->priv->info, flags | CAMEL_MESSAGE_FOLDER_FLAGGED, ~0); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Set %s flag", argv[0]->value.string); } return NULL; } static CamelSExpResult * unset_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { guint32 flags; d (fprintf (stderr, "unsetting flag\n")); if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) { flags = camel_system_flag (argv[0]->value.string); if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_flags ( driver->priv->source, driver->priv->uid, flags, 0); else camel_message_info_set_flags ( driver->priv->info, flags | CAMEL_MESSAGE_FOLDER_FLAGGED, 0); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Unset %s flag", argv[0]->value.string); } return NULL; } #ifndef G_OS_WIN32 static void child_setup_func (gpointer user_data) { setsid (); } #else #define child_setup_func NULL #endif typedef struct { gint child_status; GMainLoop *loop; } child_watch_data_t; static void child_watch (GPid pid, gint status, gpointer user_data) { child_watch_data_t *child_watch_data = user_data; g_spawn_close_pid (pid); child_watch_data->child_status = status; g_main_loop_quit (child_watch_data->loop); } static gint pipe_to_system (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { gint i, pipe_to_child, pipe_from_child; CamelMimeMessage *message = NULL; CamelMimeParser *parser; CamelStream *stream, *mem; GPid child_pid; GError *error = NULL; GByteArray *bytes; GPtrArray *args; child_watch_data_t child_watch_data; GSource *source; GMainContext *context; if (argc < 1 || argv[0]->value.string[0] == '\0') return 0; /* make sure we have the message... */ if (driver->priv->message == NULL) { /* FIXME Pass a GCancellable */ driver->priv->message = camel_folder_get_message_sync ( driver->priv->source, driver->priv->uid, NULL, &driver->priv->error); if (driver->priv->message == NULL) return -1; } args = g_ptr_array_new (); for (i = 0; i < argc; i++) g_ptr_array_add (args, argv[i]->value.string); g_ptr_array_add (args, NULL); if (!g_spawn_async_with_pipes (NULL, (gchar **) args->pdata, NULL, G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, child_setup_func, NULL, &child_pid, &pipe_to_child, &pipe_from_child, NULL, &error)) { g_ptr_array_free (args, TRUE); g_set_error ( &driver->priv->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to create child process “%s”: %s"), argv[0]->value.string, error->message); g_error_free (error); return -1; } g_ptr_array_free (args, TRUE); stream = camel_stream_fs_new_with_fd (pipe_to_child); if (camel_data_wrapper_write_to_stream_sync ( CAMEL_DATA_WRAPPER (driver->priv->message), stream, NULL, NULL) == -1) { g_object_unref (stream); close (pipe_from_child); goto wait; } if (camel_stream_flush (stream, NULL, &error) == -1) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT)) g_warning ("%s: Failed to flush output stream: %s", G_STRFUNC, error->message); /* Ignore flush errors, it can be due to calling fsync() on the pipe */ g_clear_error (&error); } g_object_unref (stream); stream = camel_stream_fs_new_with_fd (pipe_from_child); mem = camel_stream_mem_new (); if (camel_stream_write_to_stream (stream, mem, NULL, NULL) == -1) { g_object_unref (stream); g_object_unref (mem); goto wait; } g_object_unref (stream); bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem)); if (!bytes || !bytes->len) goto wait; g_seekable_seek (G_SEEKABLE (mem), 0, G_SEEK_SET, NULL, NULL); parser = camel_mime_parser_new (); camel_mime_parser_init_with_stream (parser, mem, NULL); camel_mime_parser_scan_from (parser, FALSE); g_object_unref (mem); message = camel_mime_message_new (); if (!camel_mime_part_construct_from_parser_sync ( (CamelMimePart *) message, parser, NULL, NULL)) { gint err = camel_mime_parser_errno (parser); g_set_error ( &driver->priv->error, G_IO_ERROR, g_io_error_from_errno (err), _("Invalid message stream received from %s: %s"), argv[0]->value.string, g_strerror (err)); g_object_unref (message); message = NULL; } else { g_object_unref (driver->priv->message); driver->priv->message = message; driver->priv->modified = TRUE; } g_object_unref (parser); wait: context = g_main_context_new (); child_watch_data.loop = g_main_loop_new (context, FALSE); g_main_context_unref (context); source = g_child_watch_source_new (child_pid); g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL); g_source_attach (source, g_main_loop_get_context (child_watch_data.loop)); g_source_unref (source); g_main_loop_run (child_watch_data.loop); g_main_loop_unref (child_watch_data.loop); #ifndef G_OS_WIN32 if (message && WIFEXITED (child_watch_data.child_status)) return WEXITSTATUS (child_watch_data.child_status); else return -1; #else return child_watch_data.child_status; #endif } static CamelSExpResult * pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { gint i; /* make sure all args are strings */ for (i = 0; i < argc; i++) { if (argv[i]->type != CAMEL_SEXP_RES_STRING) return NULL; } camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Piping message to %s", argv[0]->value.string); pipe_to_system (f, argc, argv, driver); return NULL; } static CamelSExpResult * do_shell (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { GString *command; GPtrArray *args; gint i; d (fprintf (stderr, "executing shell command\n")); command = g_string_new (""); args = g_ptr_array_new (); /* make sure all args are strings */ for (i = 0; i < argc; i++) { if (argv[i]->type != CAMEL_SEXP_RES_STRING) goto done; g_ptr_array_add (args, argv[i]->value.string); g_string_append (command, argv[i]->value.string); g_string_append_c (command, ' '); } g_string_truncate (command, command->len - 1); if (driver->priv->shellfunc && argc >= 1) { /* NULL-terminate the array, but do not count it into the argc */ g_ptr_array_add (args, NULL); driver->priv->shellfunc (driver, argc, (gchar **) args->pdata, driver->priv->shelldata); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Executing shell command: [%s]", command->str); } done: g_ptr_array_free (args, TRUE); g_string_free (command, TRUE); return NULL; } static CamelSExpResult * do_beep (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "beep\n")); if (driver->priv->beep) { driver->priv->beep (driver, driver->priv->beepdata); camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Beep"); } return NULL; } static CamelSExpResult * play_sound (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "play sound\n")); if (driver->priv->playfunc && argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) { driver->priv->playfunc (driver, argv[0]->value.string, driver->priv->playdata); camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Play sound"); } return NULL; } static CamelSExpResult * do_only_once (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *driver) { d (fprintf (stderr, "only once\n")); if (argc == 2 && !g_hash_table_lookup (driver->priv->only_once, argv[0]->value.string)) g_hash_table_insert ( driver->priv->only_once, g_strdup (argv[0]->value.string), g_strdup (argv[1]->value.string)); return NULL; } static CamelFolder * open_folder (CamelFilterDriver *driver, const gchar *folder_url) { CamelFolder *camelfolder; /* we have a lookup table of currently open folders */ camelfolder = g_hash_table_lookup (driver->priv->folders, folder_url); if (camelfolder) return camelfolder == FOLDER_INVALID ? NULL : camelfolder; /* if we have a default folder, ignore exceptions. This is so * a bad filter rule on pop or local delivery doesn't result * in duplicate mails, just mail going to inbox. Otherwise, * we want to know about exceptions and abort processing */ if (driver->priv->defaultfolder) { camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, NULL); } else { camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, &driver->priv->error); } if (camelfolder) { g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), camelfolder); camel_folder_freeze (camelfolder); } else { g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), FOLDER_INVALID); } return camelfolder; } typedef struct _CloseFolderData { CamelFilterDriver *driver; gboolean can_call_refresh; GCancellable *cancellable; } CloseFolderData; static void close_folder (gpointer key, gpointer value, gpointer user_data) { CamelFolder *folder = value; CamelFilterDriver *driver; CloseFolderData *cfd = user_data; g_return_if_fail (cfd != NULL); driver = cfd->driver; driver->priv->closed++; g_free (key); if (folder != FOLDER_INVALID) { if (camel_folder_synchronize_sync (folder, FALSE, cfd->cancellable, (driver->priv->error != NULL) ? NULL : &driver->priv->error)) { if (cfd->can_call_refresh && cfd->cancellable) { camel_folder_refresh_info_sync ( folder, cfd->cancellable, (driver->priv->error != NULL) ? NULL : &driver->priv->error); } } camel_folder_thaw (folder); g_object_unref (folder); } report_status ( driver, CAMEL_FILTER_STATUS_PROGRESS, g_hash_table_size (driver->priv->folders) * 100 / driver->priv->closed, _("Syncing folders")); } /* flush/close all folders */ static gint close_folders (CamelFilterDriver *driver, gboolean can_call_refresh, GCancellable *cancellable) { CloseFolderData cfd; report_status ( driver, CAMEL_FILTER_STATUS_PROGRESS, 0, _("Syncing folders")); driver->priv->closed = 0; cfd.driver = driver; cfd.can_call_refresh = can_call_refresh; cfd.cancellable = cancellable; g_hash_table_foreach (driver->priv->folders, close_folder, &cfd); g_hash_table_destroy (driver->priv->folders); driver->priv->folders = g_hash_table_new (g_str_hash, g_str_equal); /* FIXME: status from driver */ return 0; } #if 0 static void free_key (gpointer key, gpointer value, gpointer user_data) { g_free (key); } #endif static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const gchar *desc, ...) { if (driver->priv->logfile) { gchar *str = NULL; if (desc) { va_list ap; va_start (ap, desc); str = g_strdup_vprintf (desc, ap); va_end (ap); } switch (status) { case FILTER_LOG_START: { /* write log header */ const gchar *subject = NULL; const gchar *from = NULL; gchar date[50]; time_t t; /* FIXME: does this need locking? Probably */ from = camel_message_info_get_from (driver->priv->info); subject = camel_message_info_get_subject (driver->priv->info); time (&t); strftime (date, 49, "%Y-%m-%d %H:%M:%S", localtime (&t)); fprintf ( driver->priv->logfile, "%s - Applied filter \"%s\" to " "message from %s - \"%s\"\n", date, str, from ? from : "unknown", subject ? subject : ""); break; } case FILTER_LOG_ACTION: fprintf (driver->priv->logfile, "Action: %s\n", str); break; case FILTER_LOG_INFO: fprintf (driver->priv->logfile, "%s\n", str); break; case FILTER_LOG_END: fprintf (driver->priv->logfile, "\n"); break; default: /* nothing else is loggable */ break; } g_free (str); fflush (driver->priv->logfile); } } struct _run_only_once { CamelFilterDriver *driver; GError *error; }; static gboolean run_only_once (gpointer key, gchar *action, struct _run_only_once *user_data) { CamelFilterDriver *driver = user_data->driver; CamelSExpResult *r; d (printf ("evaluating: %s\n\n", action)); camel_sexp_input_text (driver->priv->eval, action, strlen (action)); if (camel_sexp_parse (driver->priv->eval) == -1) { if (user_data->error == NULL) g_set_error ( &user_data->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Error parsing filter: %s: %s"), camel_sexp_error (driver->priv->eval), action); goto done; } r = camel_sexp_eval (driver->priv->eval); if (r == NULL) { if (user_data->error == NULL) g_set_error ( &user_data->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Error executing filter: %s: %s"), camel_sexp_error (driver->priv->eval), action); goto done; } camel_sexp_result_free (driver->priv->eval, r); done: g_free (key); g_free (action); return TRUE; } /** * camel_filter_driver_flush: * @driver: a #CamelFilterDriver * @error: return location for a #GError, or %NULL * * Flush all of the only-once filter actions. **/ void camel_filter_driver_flush (CamelFilterDriver *driver, GError **error) { struct _run_only_once data; if (!driver->priv->only_once) return; data.driver = driver; data.error = NULL; g_hash_table_foreach_remove (driver->priv->only_once, (GHRFunc) run_only_once, &data); if (data.error != NULL) g_propagate_error (error, data.error); } static gint decode_flags_from_xev (const gchar *xev, CamelMessageInfo *mi) { guint32 uid, flags = 0; gchar *header; /* check for uid/flags */ header = camel_header_token_decode (xev); if (!(header && strlen (header) == strlen ("00000000-0000") && sscanf (header, "%08x-%04x", &uid, &flags) == 2)) { g_free (header); return 0; } g_free (header); camel_message_info_set_flags (mi, ~0, flags); return 0; } /** * camel_filter_driver_filter_mbox: * @driver: CamelFilterDriver * @mbox: mbox filename to be filtered * @original_source_url: (nullable): URI of the @mbox, or %NULL * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Filters an mbox file based on rules defined in the FilterDriver * object. Is more efficient as it doesn't need to open the folder * through Camel directly. * * Returns: -1 if errors were encountered during filtering, * otherwise returns 0. * **/ gint camel_filter_driver_filter_mbox (CamelFilterDriver *driver, const gchar *mbox, const gchar *original_source_url, GCancellable *cancellable, GError **error) { CamelMimeParser *mp = NULL; gchar *source_url = NULL; gint fd = -1; gint i = 0; struct stat st; gint status; goffset last = 0; gint ret = -1; GError *local_error = NULL; fd = g_open (mbox, O_RDONLY | O_BINARY, 0); if (fd == -1) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Unable to open spool folder")); goto fail; } /* to get the filesize */ if (fstat (fd, &st) != 0) st.st_size = 0; mp = camel_mime_parser_new (); camel_mime_parser_scan_from (mp, TRUE); if (camel_mime_parser_init_with_fd (mp, fd) == -1) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Unable to process spool folder")); goto fail; } fd = -1; g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy); g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref); driver->priv->delete_after_transfer = NULL; source_url = g_filename_to_uri (mbox, NULL, NULL); while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) { CamelMessageInfo *info; CamelMimeMessage *message; const CamelNameValueArray *headers; CamelMimePart *mime_part; gint pc = 0; const gchar *xev; if (st.st_size > 0) pc = (gint)(100.0 * ((double) camel_mime_parser_tell (mp) / (double) st.st_size)); if (pc > 0) camel_operation_progress (cancellable, pc); report_status ( driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d (%d%%)"), i, pc); message = camel_mime_message_new (); mime_part = CAMEL_MIME_PART (message); if (!camel_mime_part_construct_from_parser_sync ( mime_part, mp, cancellable, error)) { report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i); g_object_unref (message); goto fail; } headers = camel_medium_get_headers (CAMEL_MEDIUM (mime_part)); info = camel_message_info_new_from_headers (NULL, headers); /* Try and see if it has X-Evolution headers */ xev = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Evolution"); if (xev) decode_flags_from_xev (xev, info); camel_message_info_set_size (info, camel_mime_parser_tell (mp) - last); last = camel_mime_parser_tell (mp); status = filter_driver_filter_message_internal ( driver, FALSE, message, info, NULL, NULL, source_url, original_source_url ? original_source_url : source_url, cancellable, &local_error); g_object_unref (message); if (local_error != NULL || status == -1) { report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Failed on message %d"), i); g_clear_object (&info); g_propagate_error (error, local_error); local_error = NULL; goto fail; } i++; /* skip over the FROM_END state */ camel_mime_parser_step (mp, NULL, NULL); g_clear_object (&info); } if (!filter_driver_process_transfers (driver, cancellable, &local_error)) { report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Failed to transfer messages: %s"), local_error ? local_error->message : _("Unknown error")); g_propagate_error (error, local_error); goto fail; } camel_operation_progress (cancellable, 100); if (driver->priv->defaultfolder) { report_status ( driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder")); camel_folder_synchronize_sync ( driver->priv->defaultfolder, FALSE, cancellable, NULL); } report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete")); ret = 0; fail: g_free (source_url); if (fd != -1) close (fd); if (mp) g_object_unref (mp); return ret; } /** * camel_filter_driver_filter_folder: * @driver: CamelFilterDriver * @folder: CamelFolder to be filtered * @cache: UID cache (needed for POP folders) * @uids: (element-type utf8) (nullable): message uids to be filtered or * %NULL (as a shortcut to filter all messages) * @remove: TRUE to mark filtered messages as deleted * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Filters a folder based on rules defined in the FilterDriver * object. * * Returns: -1 if errors were encountered during filtering, * otherwise returns 0. * **/ gint camel_filter_driver_filter_folder (CamelFilterDriver *driver, CamelFolder *folder, CamelUIDCache *cache, GPtrArray *uids, gboolean remove, GCancellable *cancellable, GError **error) { gboolean freeuids = FALSE; CamelMessageInfo *info; CamelStore *parent_store; const gchar *store_uid; gint status = 0; gint i; GError *local_error = NULL; parent_store = camel_folder_get_parent_store (folder); store_uid = camel_service_get_uid (CAMEL_SERVICE (parent_store)); if (uids == NULL) { uids = camel_folder_get_uids (folder); freeuids = TRUE; } g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy); g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref); driver->priv->delete_after_transfer = NULL; for (i = 0; i < uids->len; i++) { gint pc = (100 * i) / uids->len; camel_operation_progress (cancellable, pc); report_status ( driver, CAMEL_FILTER_STATUS_START, pc, _("Getting message %d of %d"), i + 1, uids->len); if (camel_folder_has_summary_capability (folder)) info = camel_folder_get_message_info (folder, uids->pdata[i]); else info = NULL; status = filter_driver_filter_message_internal ( driver, FALSE, NULL, info, uids->pdata[i], folder, store_uid, store_uid, cancellable, &local_error); if (camel_folder_has_summary_capability (folder)) g_clear_object (&info); if (local_error != NULL || status == -1) { report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Failed at message %d of %d"), i + 1, uids->len); g_propagate_error (error, local_error); local_error = NULL; status = -1; break; } if (remove && !driver->priv->copied && !driver->priv->moved) { camel_folder_set_message_flags ( folder, uids->pdata[i], CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0); } else if (remove) { info = camel_folder_get_message_info (folder, uids->pdata[i]); if (info) driver->priv->delete_after_transfer = g_slist_prepend (driver->priv->delete_after_transfer, info); } if (cache) camel_uid_cache_save_uid (cache, uids->pdata[i]); if (cache && (i % 10) == 0) camel_uid_cache_save (cache); } if (!filter_driver_process_transfers (driver, cancellable, &local_error)) { report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Failed to transfer messages: %s"), local_error ? local_error->message : _("Unknown error")); g_propagate_error (error, local_error); status = -1; } camel_operation_progress (cancellable, 100); /* Save the cache of any pending mails. */ if (cache) camel_uid_cache_save (cache); /* Unset message headers on the infos, which are meant only for filtering, which just finished, thus the memory can be freed now */ for (i = 0; i < uids->len; i++) { info = camel_folder_get_message_info (folder, uids->pdata[i]); if (info) camel_message_info_take_headers (info, NULL); g_clear_object (&info); } if (driver->priv->defaultfolder) { report_status ( driver, CAMEL_FILTER_STATUS_PROGRESS, 100, _("Syncing folder")); camel_folder_synchronize_sync ( driver->priv->defaultfolder, FALSE, cancellable, NULL); } if (i == uids->len && status != -1) report_status ( driver, CAMEL_FILTER_STATUS_END, 100, _("Complete")); if (freeuids) camel_folder_free_uids (folder, uids); close_folders (driver, remove, cancellable); return status; } struct _get_message { struct _CamelFilterDriverPrivate *priv; const gchar *store_uid; }; static CamelMimeMessage * get_message_cb (gpointer data, GCancellable *cancellable, GError **error) { struct _get_message *msgdata = data; CamelMimeMessage *message; if (msgdata->priv->message) { message = g_object_ref (msgdata->priv->message); } else { const gchar *uid; if (msgdata->priv->uid != NULL) uid = msgdata->priv->uid; else uid = camel_message_info_get_uid (msgdata->priv->info); message = camel_folder_get_message_sync ( msgdata->priv->source, uid, cancellable, error); } if (message != NULL && camel_mime_message_get_source (message) == NULL) camel_mime_message_set_source (message, msgdata->store_uid); return message; } static gint filter_driver_filter_message_internal (CamelFilterDriver *driver, gboolean can_process_transfers, CamelMimeMessage *message, CamelMessageInfo *info, const gchar *uid, CamelFolder *source, const gchar *store_uid, const gchar *original_store_uid, GCancellable *cancellable, GError **error) { CamelFilterDriverPrivate *p = driver->priv; gboolean freeinfo = FALSE; gboolean filtered = FALSE; CamelSExpResult *r; GList *list, *link; g_return_val_if_fail (message != NULL || (source != NULL && uid != NULL), -1); if (info == NULL) { const CamelNameValueArray *headers; if (message) { g_object_ref (message); } else { message = camel_folder_get_message_sync ( source, uid, cancellable, error); if (!message) return -1; } headers = camel_medium_get_headers (CAMEL_MEDIUM (message)); info = camel_message_info_new_from_headers (NULL, headers); freeinfo = TRUE; } else { if (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) return 0; uid = camel_message_info_get_uid (info); if (message) g_object_ref (message); } if (can_process_transfers) { g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy); g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref); driver->priv->delete_after_transfer = NULL; } driver->priv->terminated = FALSE; driver->priv->deleted = FALSE; driver->priv->copied = FALSE; driver->priv->moved = FALSE; driver->priv->message = message; driver->priv->info = info; driver->priv->uid = uid; driver->priv->source = source; if (message != NULL && camel_mime_message_get_source (message) == NULL) camel_mime_message_set_source (message, original_store_uid); if (g_strcmp0 (store_uid, "local") == 0 || g_strcmp0 (store_uid, "vfolder") == 0) { store_uid = NULL; } if (g_strcmp0 (original_store_uid, "local") == 0 || g_strcmp0 (original_store_uid, "vfolder") == 0) { original_store_uid = NULL; } list = g_queue_peek_head_link (&driver->priv->rules); filtered = list != NULL; for (link = list; link != NULL; link = g_list_next (link)) { struct _filter_rule *rule = link->data; struct _get_message data; gint result; if (driver->priv->terminated) { camel_filter_driver_log (driver, FILTER_LOG_INFO, "Stopped processing per request"); break; } if (g_cancellable_set_error_if_cancelled (cancellable, &driver->priv->error)) { camel_filter_driver_log (driver, FILTER_LOG_INFO, "Stopped processing on cancel"); goto error; } d (printf ("applying rule %s\naction %s\n", rule->match, rule->action)); data.priv = p; data.store_uid = original_store_uid; if (original_store_uid == NULL) original_store_uid = store_uid; camel_filter_driver_log (driver, FILTER_LOG_START, "%s", rule->name); result = camel_filter_search_match_with_log ( driver->priv->session, get_message_cb, &data, driver->priv->info, original_store_uid, source, rule->match, driver->priv->logfile, cancellable, &driver->priv->error); switch (result) { case CAMEL_SEARCH_ERROR: camel_filter_driver_log (driver, FILTER_LOG_INFO, " Execution of filter '%s' failed: %s\n", rule->name, driver->priv->error ? driver->priv->error->message : "Unknown error"); g_prefix_error ( &driver->priv->error, _("Execution of filter “%s” failed: "), rule->name); goto error; case CAMEL_SEARCH_MATCHED: camel_filter_driver_log (driver, FILTER_LOG_INFO, " Filter '%s' matched\n", rule->name); /* perform necessary filtering actions */ camel_sexp_input_text ( driver->priv->eval, rule->action, strlen (rule->action)); if (camel_sexp_parse (driver->priv->eval) == -1) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Error parsing filter “%s”: %s: %s"), rule->name, camel_sexp_error (driver->priv->eval), rule->action); goto error; } r = camel_sexp_eval (driver->priv->eval); if (driver->priv->error != NULL) { g_prefix_error ( &driver->priv->error, _("Execution of filter “%s” failed: "), rule->name); goto error; } if (r == NULL) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Error executing filter “%s”: %s: %s"), rule->name, camel_sexp_error (driver->priv->eval), rule->action); goto error; } camel_sexp_result_free (driver->priv->eval, r); break; case CAMEL_SEARCH_NOMATCH: camel_filter_driver_log (driver, FILTER_LOG_INFO, " Filter '%s' did not match\n", rule->name); break; default: break; } } if (can_process_transfers) { if (!filter_driver_process_transfers (driver, cancellable, &driver->priv->error)) goto error; } /* *Now* we can set the DELETED flag... */ if (driver->priv->deleted) { if (can_process_transfers || (!driver->priv->moved && !driver->priv->copied)) { if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) camel_folder_set_message_flags ( driver->priv->source, driver->priv->uid, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN, ~0); else camel_message_info_set_flags ( info, CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_FOLDER_FLAGGED, ~0); } else { /* Set the DELETED flag only after the messages are really transferred */ CamelMessageInfo *nfo; if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source)) nfo = camel_folder_get_message_info (driver->priv->source, driver->priv->uid); else nfo = g_object_ref (info); if (nfo) driver->priv->delete_after_transfer = g_slist_prepend (driver->priv->delete_after_transfer, nfo); } } /* Logic: if !Moved and there exists a default folder... */ if (!(driver->priv->copied && driver->priv->deleted) && !driver->priv->moved && driver->priv->defaultfolder) { /* copy it to the default inbox */ filtered = TRUE; camel_filter_driver_log ( driver, FILTER_LOG_ACTION, "Copy to default folder"); if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) { GPtrArray *uids; uids = g_ptr_array_new (); g_ptr_array_add (uids, (gchar *) driver->priv->uid); camel_folder_transfer_messages_to_sync ( driver->priv->source, uids, driver->priv->defaultfolder, FALSE, NULL, cancellable, &driver->priv->error); g_ptr_array_free (uids, TRUE); } else { if (driver->priv->message == NULL) { driver->priv->message = camel_folder_get_message_sync ( source, uid, cancellable, error); if (!driver->priv->message) goto error; } camel_folder_append_message_sync ( driver->priv->defaultfolder, driver->priv->message, driver->priv->info, NULL, cancellable, &driver->priv->error); } } if (driver->priv->message) g_object_unref (driver->priv->message); if (freeinfo) g_clear_object (&info); return 0; error: if (filtered) camel_filter_driver_log (driver, FILTER_LOG_END, NULL); if (driver->priv->message) g_object_unref (driver->priv->message); if (freeinfo) g_clear_object (&info); g_propagate_error (error, driver->priv->error); driver->priv->error = NULL; return -1; } /** * camel_filter_driver_filter_message: * @driver: CamelFilterDriver * @message: (nullable): message to filter or %NULL * @info: (nullable): message info or %NULL * @uid: (nullable): message uid or %NULL * @source: (nullable): source folder or %NULL * @store_uid: (nullable): UID of source store, or %NULL * @original_store_uid: (nullable): UID of source store (pre-movemail), or %NULL * @cancellable: optional #GCancellable object, or %NULL * @error: return location for a #GError, or %NULL * * Filters a message based on rules defined in the FilterDriver * object. If the source folder (@source) and the uid (@uid) are * provided, the filter will operate on the CamelFolder (which in * certain cases is more efficient than using the default * camel_folder_append_message() function). * * Returns: -1 if errors were encountered during filtering, * otherwise returns 0. * **/ gint camel_filter_driver_filter_message (CamelFilterDriver *driver, CamelMimeMessage *message, CamelMessageInfo *info, const gchar *uid, CamelFolder *source, const gchar *store_uid, const gchar *original_store_uid, GCancellable *cancellable, GError **error) { return filter_driver_filter_message_internal (driver, TRUE, message, info, uid, source, store_uid, original_store_uid, cancellable, error); } /** * camel_filter_driver_log_info: * @driver: (nullable): a #CamelFilterDriver, or %NULL * @format: a printf-like format to use for the informational log entry * @...: arguments for @format * * Logs an informational message to a filter log. The function does * nothing when @driver is %NULL or when there is no log file being * set in @driver. * * Since: 3.24 **/ void camel_filter_driver_log_info (CamelFilterDriver *driver, const gchar *format, ...) { gchar *str; va_list ap; if (!driver || !driver->priv->logfile) return; g_return_if_fail (format != NULL); va_start (ap, format); str = g_strdup_vprintf (format, ap); va_end (ap); camel_filter_driver_log (driver, FILTER_LOG_INFO, "%s", str); g_free (str); }