1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Michael Zucchi <notzed@ximian.com>
18 * Jeffrey Stedfast <fejj@ximian.com>
19 */
20
21 #include "evolution-data-server-config.h"
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <time.h>
29
30 #include <glib/gstdio.h>
31 #include <glib/gi18n-lib.h>
32
33 #ifndef G_OS_WIN32
34 #include <sys/wait.h>
35 #endif
36
37 #include "camel-debug.h"
38 #include "camel-file-utils.h"
39 #include "camel-filter-driver.h"
40 #include "camel-filter-search.h"
41 #include "camel-mime-message.h"
42 #include "camel-service.h"
43 #include "camel-session.h"
44 #include "camel-sexp.h"
45 #include "camel-store.h"
46 #include "camel-stream-fs.h"
47 #include "camel-stream-mem.h"
48 #include "camel-string-utils.h"
49
50 #define d(x)
51
52 /* an invalid pointer */
53 #define FOLDER_INVALID ((gpointer)~0)
54
55 /* type of status for a log report */
56 enum filter_log_t {
57 FILTER_LOG_NONE,
58 FILTER_LOG_START, /* start of new log entry */
59 FILTER_LOG_ACTION, /* an action performed */
60 FILTER_LOG_INFO, /* a generic information log */
61 FILTER_LOG_END /* end of log */
62 };
63
64 /* list of rule nodes */
65 struct _filter_rule {
66 gchar *match;
67 gchar *action;
68 gchar *name;
69 };
70
71 struct _CamelFilterDriverPrivate {
72 GHashTable *globals; /* global variables */
73
74 CamelSession *session;
75
76 CamelFolder *defaultfolder; /* defualt folder */
77
78 CamelFilterStatusFunc statusfunc; /* status callback */
79 gpointer statusdata; /* status callback data */
80
81 CamelFilterShellFunc shellfunc; /* execute shell command callback */
82 gpointer shelldata; /* execute shell command callback data */
83
84 CamelFilterPlaySoundFunc playfunc; /* play-sound command callback */
85 gpointer playdata; /* play-sound command callback data */
86
87 CamelFilterSystemBeepFunc beep; /* system beep callback */
88 gpointer beepdata; /* system beep callback data */
89
90 /* for callback */
91 CamelFilterGetFolderFunc get_folder;
92 gpointer data;
93
94 /* run-time data */
95 GHashTable *folders; /* folders that message has been copied to */
96 gint closed; /* close count */
97 GHashTable *only_once; /* actions to run only-once */
98
99 gboolean terminated; /* message processing was terminated */
100 gboolean deleted; /* message was marked for deletion */
101 gboolean copied; /* message was copied to some folder or another */
102 gboolean moved; /* message was moved to some folder or another */
103
104 CamelMimeMessage *message; /* input message */
105 CamelMessageInfo *info; /* message summary info */
106 const gchar *uid; /* message uid */
107 CamelFolder *source; /* message source folder */
108 gboolean modified; /* has the input message been modified? */
109
110 FILE *logfile; /* log file */
111
112 GQueue rules; /* queue of _filter_rule structs */
113
114 GError *error;
115
116 GHashTable *transfers; /* CamelFolder * ~> MessageTransferData * */
117 GSList *delete_after_transfer; /* CamelMessageInfo * to delete after transfers are done */
118
119 /* evaluator */
120 CamelSExp *eval;
121 };
122
123 static void camel_filter_driver_log (CamelFilterDriver *driver, enum filter_log_t status, const gchar *desc, ...);
124
125 static CamelFolder *open_folder (CamelFilterDriver *d, const gchar *folder_url);
126 static gint close_folders (CamelFilterDriver *d, gboolean can_call_refresh, GCancellable *cancellable);
127
128 static CamelSExpResult *do_delete (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
129 static CamelSExpResult *do_forward_to (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
130 static CamelSExpResult *do_copy (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
131 static CamelSExpResult *do_move (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
132 static CamelSExpResult *do_stop (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
133 static CamelSExpResult *do_label (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
134 static CamelSExpResult *do_color (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
135 static CamelSExpResult *do_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
136 static CamelSExpResult *do_adjust_score (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
137 static CamelSExpResult *set_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
138 static CamelSExpResult *unset_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
139 static CamelSExpResult *do_shell (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
140 static CamelSExpResult *do_beep (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
141 static CamelSExpResult *play_sound (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
142 static CamelSExpResult *do_only_once (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
143 static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, CamelFilterDriver *);
144
145 static gint
146 filter_driver_filter_message_internal (CamelFilterDriver *driver,
147 gboolean can_process_transfers,
148 CamelMimeMessage *message,
149 CamelMessageInfo *info,
150 const gchar *uid,
151 CamelFolder *source,
152 const gchar *store_uid,
153 const gchar *original_store_uid,
154 GCancellable *cancellable,
155 GError **error);
156
157 /* these are our filter actions - each must have a callback */
158 static struct {
159 const gchar *name;
160 CamelSExpFunc func;
161 gint type; /* set to 1 if a function can perform shortcut evaluation, or
162 doesn't execute everything, 0 otherwise */
163 } symbols[] = {
164 { "delete", (CamelSExpFunc) do_delete, 0 },
165 { "forward-to", (CamelSExpFunc) do_forward_to, 0 },
166 { "copy-to", (CamelSExpFunc) do_copy, 0 },
167 { "move-to", (CamelSExpFunc) do_move, 0 },
168 { "stop", (CamelSExpFunc) do_stop, 0 },
169 { "set-label", (CamelSExpFunc) do_label, 0 },
170 { "set-color", (CamelSExpFunc) do_color, 0 },
171 { "set-score", (CamelSExpFunc) do_score, 0 },
172 { "adjust-score", (CamelSExpFunc) do_adjust_score, 0 },
173 { "set-system-flag", (CamelSExpFunc) set_flag, 0 },
174 { "unset-system-flag", (CamelSExpFunc) unset_flag, 0 },
175 { "pipe-message", (CamelSExpFunc) pipe_message, 0 },
176 { "shell", (CamelSExpFunc) do_shell, 0 },
177 { "beep", (CamelSExpFunc) do_beep, 0 },
178 { "play-sound", (CamelSExpFunc) play_sound, 0 },
179 { "only-once", (CamelSExpFunc) do_only_once, 0 }
180 };
181
182 G_DEFINE_TYPE_WITH_PRIVATE (CamelFilterDriver, camel_filter_driver, G_TYPE_OBJECT)
183
184 typedef struct _MessageTransferData {
185 GPtrArray *copy_uids;
186 GPtrArray *move_uids;
187 } MessageTransferData;
188
189 static MessageTransferData *
message_transfer_data_new(void)190 message_transfer_data_new (void)
191 {
192 return g_slice_new0 (MessageTransferData);
193 }
194
195 static void
message_transfer_data_free(gpointer ptr)196 message_transfer_data_free (gpointer ptr)
197 {
198 MessageTransferData *mtd = ptr;
199
200 if (mtd) {
201 if (mtd->copy_uids)
202 g_ptr_array_free (mtd->copy_uids, TRUE);
203 if (mtd->move_uids)
204 g_ptr_array_free (mtd->move_uids, TRUE);
205 g_slice_free (MessageTransferData, mtd);
206 }
207 }
208
209 static void
filter_driver_add_to_transfers(CamelFilterDriver * driver,CamelFolder * destination,const gchar * uid,gboolean is_move)210 filter_driver_add_to_transfers (CamelFilterDriver *driver,
211 CamelFolder *destination,
212 const gchar *uid,
213 gboolean is_move)
214 {
215 MessageTransferData *mtd;
216 GPtrArray **parray;
217
218 g_return_if_fail (CAMEL_IS_FILTER_DRIVER (driver));
219 g_return_if_fail (CAMEL_IS_FOLDER (destination));
220 g_return_if_fail (uid);
221
222 if (!driver->priv->transfers)
223 driver->priv->transfers = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, message_transfer_data_free);
224
225 mtd = g_hash_table_lookup (driver->priv->transfers, destination);
226
227 if (!mtd) {
228 mtd = message_transfer_data_new ();
229 g_hash_table_insert (driver->priv->transfers, g_object_ref (destination), mtd);
230 }
231
232 if (is_move)
233 parray = &(mtd->move_uids);
234 else
235 parray = &(mtd->copy_uids);
236
237 if (!*parray)
238 *parray = g_ptr_array_new_with_free_func ((GDestroyNotify) camel_pstring_free);
239
240 g_ptr_array_add (*parray, (gpointer) camel_pstring_strdup (uid));
241 }
242
243 static gboolean
filter_driver_process_transfers(CamelFilterDriver * driver,GCancellable * cancellable,GError ** error)244 filter_driver_process_transfers (CamelFilterDriver *driver,
245 GCancellable *cancellable,
246 GError **error)
247 {
248 gboolean success = TRUE;
249 GSList *link;
250
251 g_return_val_if_fail (CAMEL_IS_FILTER_DRIVER (driver), FALSE);
252
253 if (driver->priv->transfers) {
254 CamelStore *parent_store;
255 GHashTableIter iter;
256 gpointer key, value;
257 guint ii, sz;
258
259 parent_store = camel_folder_get_parent_store (driver->priv->source);
260
261 /* Translators: The first “%s” is replaced with an account name and the second “%s”
262 is replaced with a full path name. The spaces around “:” are intentional, as
263 the whole “%s : %s” is meant as an absolute identification of the folder. */
264 camel_operation_push_message (cancellable, _("Transferring filtered messages in “%s : %s”"),
265 camel_service_get_display_name (CAMEL_SERVICE (parent_store)),
266 camel_folder_get_full_name (driver->priv->source));
267
268 ii = 0;
269 sz = g_hash_table_size (driver->priv->transfers);
270
271 if (!sz)
272 sz = 1;
273
274 camel_operation_progress (cancellable, ii * 100 / sz);
275
276 g_hash_table_iter_init (&iter, driver->priv->transfers);
277 while (success && g_hash_table_iter_next (&iter, &key, &value)) {
278 CamelFolder *destination = key;
279 MessageTransferData *mtd = value;
280
281 g_warn_if_fail (CAMEL_IS_FOLDER (destination));
282 g_warn_if_fail (mtd != NULL);
283
284 if (mtd->copy_uids) {
285 success = camel_folder_transfer_messages_to_sync (driver->priv->source,
286 mtd->copy_uids, destination, FALSE, NULL, cancellable, error);
287 }
288
289 if (success && mtd->move_uids) {
290 success = camel_folder_transfer_messages_to_sync (driver->priv->source,
291 mtd->move_uids, destination, TRUE, NULL, cancellable, error);
292 }
293
294 ii++;
295 camel_operation_progress (cancellable, ii * 100 / sz);
296 }
297
298 camel_operation_pop_message (cancellable);
299
300 g_hash_table_destroy (driver->priv->transfers);
301 driver->priv->transfers = NULL;
302 }
303
304 for (link = driver->priv->delete_after_transfer; link && success; link = g_slist_next (link)) {
305 CamelMessageInfo *nfo = link->data;
306
307 camel_message_info_set_flags (
308 nfo,
309 CAMEL_MESSAGE_DELETED |
310 CAMEL_MESSAGE_SEEN |
311 CAMEL_MESSAGE_FOLDER_FLAGGED,
312 ~0);
313 }
314
315 g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref);
316 driver->priv->delete_after_transfer = NULL;
317
318 return success;
319 }
320
321 static void
free_hash_strings(gpointer key,gpointer value,gpointer data)322 free_hash_strings (gpointer key,
323 gpointer value,
324 gpointer data)
325 {
326 g_free (key);
327 g_free (value);
328 }
329
330 static gint
filter_rule_compare_by_name(struct _filter_rule * rule,const gchar * name)331 filter_rule_compare_by_name (struct _filter_rule *rule,
332 const gchar *name)
333 {
334 return g_strcmp0 (rule->name, name);
335 }
336
337 static void
filter_driver_dispose(GObject * object)338 filter_driver_dispose (GObject *object)
339 {
340 CamelFilterDriverPrivate *priv;
341
342 priv = CAMEL_FILTER_DRIVER (object)->priv;
343 g_clear_pointer (&priv->transfers, g_hash_table_destroy);
344
345 g_slist_free_full (priv->delete_after_transfer, g_object_unref);
346 priv->delete_after_transfer = NULL;
347
348 if (priv->defaultfolder != NULL) {
349 camel_folder_thaw (priv->defaultfolder);
350 g_object_unref (priv->defaultfolder);
351 priv->defaultfolder = NULL;
352 }
353
354 g_clear_object (&priv->session);
355
356 /* Chain up to parent's dispose() method. */
357 G_OBJECT_CLASS (camel_filter_driver_parent_class)->dispose (object);
358 }
359
360 static void
filter_driver_finalize(GObject * object)361 filter_driver_finalize (GObject *object)
362 {
363 CamelFilterDriverPrivate *priv;
364 struct _filter_rule *node;
365
366 priv = CAMEL_FILTER_DRIVER (object)->priv;
367
368 /* close all folders that were opened for appending */
369 close_folders (CAMEL_FILTER_DRIVER (object), FALSE, NULL);
370 g_hash_table_destroy (priv->folders);
371
372 g_hash_table_foreach (priv->globals, free_hash_strings, object);
373 g_hash_table_destroy (priv->globals);
374
375 g_hash_table_foreach (priv->only_once, free_hash_strings, object);
376 g_hash_table_destroy (priv->only_once);
377
378 g_object_unref (priv->eval);
379
380 while ((node = g_queue_pop_head (&priv->rules)) != NULL) {
381 g_free (node->match);
382 g_free (node->action);
383 g_free (node->name);
384 g_free (node);
385 }
386
387 /* Chain up to parent's finalize() method. */
388 G_OBJECT_CLASS (camel_filter_driver_parent_class)->finalize (object);
389 }
390
391 static void
camel_filter_driver_class_init(CamelFilterDriverClass * class)392 camel_filter_driver_class_init (CamelFilterDriverClass *class)
393 {
394 GObjectClass *object_class;
395
396 object_class = G_OBJECT_CLASS (class);
397 object_class->dispose = filter_driver_dispose;
398 object_class->finalize = filter_driver_finalize;
399 }
400
401 static void
camel_filter_driver_init(CamelFilterDriver * filter_driver)402 camel_filter_driver_init (CamelFilterDriver *filter_driver)
403 {
404 gint ii;
405
406 filter_driver->priv = camel_filter_driver_get_instance_private (filter_driver);
407
408 g_queue_init (&filter_driver->priv->rules);
409
410 filter_driver->priv->eval = camel_sexp_new ();
411
412 /* Load in builtin symbols */
413 for (ii = 0; ii < G_N_ELEMENTS (symbols); ii++) {
414 if (symbols[ii].type == 1) {
415 camel_sexp_add_ifunction (
416 filter_driver->priv->eval, 0,
417 symbols[ii].name, (CamelSExpIFunc)
418 symbols[ii].func, filter_driver);
419 } else {
420 camel_sexp_add_function (
421 filter_driver->priv->eval, 0,
422 symbols[ii].name, symbols[ii].func,
423 filter_driver);
424 }
425 }
426
427 filter_driver->priv->globals =
428 g_hash_table_new (g_str_hash, g_str_equal);
429
430 filter_driver->priv->folders =
431 g_hash_table_new (g_str_hash, g_str_equal);
432
433 filter_driver->priv->only_once =
434 g_hash_table_new (g_str_hash, g_str_equal);
435 }
436
437 /**
438 * camel_filter_driver_new:
439 * @session: (type CamelSession):
440 *
441 * Returns: A new CamelFilterDriver object
442 **/
443 CamelFilterDriver *
camel_filter_driver_new(CamelSession * session)444 camel_filter_driver_new (CamelSession *session)
445 {
446 CamelFilterDriver *d;
447
448 d = g_object_new (CAMEL_TYPE_FILTER_DRIVER, NULL);
449 d->priv->session = g_object_ref (session);
450
451 return d;
452 }
453
454 /**
455 * camel_filter_driver_set_folder_func:
456 * @d: a #CamelFilterDriver
457 * @get_folder: (scope call) (closure user_data): a callback to get a folder
458 * @user_data: user data to pass to @get_folder
459 *
460 * Sets a callback (of type #CamelFilterGetFolderFunc) to get a folder.
461 **/
462 void
camel_filter_driver_set_folder_func(CamelFilterDriver * d,CamelFilterGetFolderFunc get_folder,gpointer user_data)463 camel_filter_driver_set_folder_func (CamelFilterDriver *d,
464 CamelFilterGetFolderFunc get_folder,
465 gpointer user_data)
466 {
467 d->priv->get_folder = get_folder;
468 d->priv->data = user_data;
469 }
470
471 /**
472 * camel_filter_driver_set_logfile:
473 * @d: a #CamelFilterDriver
474 * @logfile: (nullable): a FILE handle where to write logging
475 *
476 * Sets a log file to use for logging.
477 **/
478 void
camel_filter_driver_set_logfile(CamelFilterDriver * d,FILE * logfile)479 camel_filter_driver_set_logfile (CamelFilterDriver *d,
480 FILE *logfile)
481 {
482 d->priv->logfile = logfile;
483 }
484
485 /**
486 * camel_filter_driver_set_status_func:
487 * @d: a #CamelFilterDriver
488 * @func: (scope call) (closure user_data): a callback to report progress
489 * @user_data: user data to pass to @func
490 *
491 * Sets a status callback, which is used to report progress/status.
492 **/
493 void
camel_filter_driver_set_status_func(CamelFilterDriver * d,CamelFilterStatusFunc func,gpointer user_data)494 camel_filter_driver_set_status_func (CamelFilterDriver *d,
495 CamelFilterStatusFunc func,
496 gpointer user_data)
497 {
498 d->priv->statusfunc = func;
499 d->priv->statusdata = user_data;
500 }
501
502 /**
503 * camel_filter_driver_set_shell_func:
504 * @d: a #CamelFilterDriver
505 * @func: (scope call) (closure user_data): a shell command callback
506 * @user_data: user data to pass to @func
507 *
508 * Sets a shell command callback, which is called when a shell command
509 * execution is requested.
510 **/
511 void
camel_filter_driver_set_shell_func(CamelFilterDriver * d,CamelFilterShellFunc func,gpointer user_data)512 camel_filter_driver_set_shell_func (CamelFilterDriver *d,
513 CamelFilterShellFunc func,
514 gpointer user_data)
515 {
516 d->priv->shellfunc = func;
517 d->priv->shelldata = user_data;
518 }
519
520 /**
521 * camel_filter_driver_set_play_sound_func:
522 * @d: a #CamelFilterDriver
523 * @func: (scope call) (closure user_data): a callback to play a sound
524 * @user_data: user data to pass to @func
525 *
526 * Sets a callback to call when a play of a sound is requested.
527 **/
528 void
camel_filter_driver_set_play_sound_func(CamelFilterDriver * d,CamelFilterPlaySoundFunc func,gpointer user_data)529 camel_filter_driver_set_play_sound_func (CamelFilterDriver *d,
530 CamelFilterPlaySoundFunc func,
531 gpointer user_data)
532 {
533 d->priv->playfunc = func;
534 d->priv->playdata = user_data;
535 }
536
537 /**
538 * camel_filter_driver_set_system_beep_func:
539 * @d: a #CamelFilterDriver
540 * @func: (scope call) (closure user_data): a system beep callback
541 * @user_data: user data to pass to @func
542 *
543 * Sets a callback to use for system beep.
544 **/
545 void
camel_filter_driver_set_system_beep_func(CamelFilterDriver * d,CamelFilterSystemBeepFunc func,gpointer user_data)546 camel_filter_driver_set_system_beep_func (CamelFilterDriver *d,
547 CamelFilterSystemBeepFunc func,
548 gpointer user_data)
549 {
550 d->priv->beep = func;
551 d->priv->beepdata = user_data;
552 }
553
554 /**
555 * camel_filter_driver_set_default_folder:
556 * @d: a #CamelFilterDriver
557 * @def: (nullable): a default #CamelFolder
558 *
559 * Sets a default folder for the driver. The function adds
560 * its own reference for the folder.
561 **/
562 void
camel_filter_driver_set_default_folder(CamelFilterDriver * d,CamelFolder * def)563 camel_filter_driver_set_default_folder (CamelFilterDriver *d,
564 CamelFolder *def)
565 {
566 if (d->priv->defaultfolder == def)
567 return;
568
569 if (d->priv->defaultfolder) {
570 camel_folder_thaw (d->priv->defaultfolder);
571 g_object_unref (d->priv->defaultfolder);
572 }
573
574 d->priv->defaultfolder = def;
575
576 if (d->priv->defaultfolder) {
577 camel_folder_freeze (d->priv->defaultfolder);
578 g_object_ref (d->priv->defaultfolder);
579 }
580 }
581
582 /**
583 * camel_filter_driver_add_rule:
584 * @d: a #CamelFilterDriver
585 * @name: name of the rule
586 * @match: a code (#CamelSExp) to execute to check whether the rule can be applied
587 * @action: an action code (#CamelSExp) to execute, when the @match evaluates to %TRUE
588 *
589 * Adds a new rule to set of rules to process by the filter driver.
590 **/
591 void
camel_filter_driver_add_rule(CamelFilterDriver * d,const gchar * name,const gchar * match,const gchar * action)592 camel_filter_driver_add_rule (CamelFilterDriver *d,
593 const gchar *name,
594 const gchar *match,
595 const gchar *action)
596 {
597 struct _filter_rule *node;
598
599 node = g_malloc (sizeof (*node));
600 node->match = g_strdup (match);
601 node->action = g_strdup (action);
602 node->name = g_strdup (name);
603
604 g_queue_push_tail (&d->priv->rules, node);
605 }
606
607 /**
608 * camel_filter_driver_remove_rule_by_name:
609 * @d: a #CamelFilterDriver
610 * @name: rule name
611 *
612 * Removes a rule by name, added by camel_filter_driver_add_rule().
613 *
614 * Returns: Whether the rule had been found and removed.
615 **/
616 gboolean
camel_filter_driver_remove_rule_by_name(CamelFilterDriver * d,const gchar * name)617 camel_filter_driver_remove_rule_by_name (CamelFilterDriver *d,
618 const gchar *name)
619 {
620 GList *link;
621
622 link = g_queue_find_custom (
623 &d->priv->rules, name,
624 (GCompareFunc) filter_rule_compare_by_name);
625
626 if (link != NULL) {
627 struct _filter_rule *rule = link->data;
628
629 g_queue_delete_link (&d->priv->rules, link);
630
631 g_free (rule->match);
632 g_free (rule->action);
633 g_free (rule->name);
634 g_free (rule);
635
636 return TRUE;
637 }
638
639 return FALSE;
640 }
641
642 static void
report_status(CamelFilterDriver * driver,enum camel_filter_status_t status,gint pc,const gchar * desc,...)643 report_status (CamelFilterDriver *driver,
644 enum camel_filter_status_t status,
645 gint pc,
646 const gchar *desc,
647 ...)
648 {
649 /* call user-defined status report function */
650 va_list ap;
651 gchar *str;
652
653 if (driver->priv->statusfunc) {
654 va_start (ap, desc);
655 str = g_strdup_vprintf (desc, ap);
656 va_end (ap);
657 driver->priv->statusfunc (driver, status, pc, str, driver->priv->statusdata);
658 g_free (str);
659 }
660 }
661
662 #if 0
663 void
664 camel_filter_driver_set_global (CamelFilterDriver *d,
665 const gchar *name,
666 const gchar *value)
667 {
668 gchar *oldkey, *oldvalue;
669
670 if (g_hash_table_lookup_extended (d->priv->globals, name, (gpointer) &oldkey, (gpointer) &oldvalue)) {
671 g_free (oldvalue);
672 g_hash_table_insert (d->priv->globals, oldkey, g_strdup (value));
673 } else {
674 g_hash_table_insert (d->priv->globals, g_strdup (name), g_strdup (value));
675 }
676 }
677 #endif
678
679 static CamelSExpResult *
do_delete(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)680 do_delete (struct _CamelSExp *f,
681 gint argc,
682 struct _CamelSExpResult **argv,
683 CamelFilterDriver *driver)
684 {
685 d (fprintf (stderr, "doing delete\n"));
686 driver->priv->deleted = TRUE;
687 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Delete");
688
689 return NULL;
690 }
691
692 static CamelSExpResult *
do_forward_to(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)693 do_forward_to (struct _CamelSExp *f,
694 gint argc,
695 struct _CamelSExpResult **argv,
696 CamelFilterDriver *driver)
697 {
698 const gchar *forward_with;
699
700 d (fprintf (stderr, "marking message for forwarding\n"));
701
702 /* requires one parameter, string with a destination address */
703 if (argc < 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
704 return NULL;
705
706 /* make sure we have the message... */
707 if (driver->priv->message == NULL) {
708 /* FIXME Pass a GCancellable */
709 driver->priv->message = camel_folder_get_message_sync (
710 driver->priv->source,
711 driver->priv->uid, NULL,
712 &driver->priv->error);
713 if (driver->priv->message == NULL)
714 return NULL;
715 }
716
717 forward_with = (argc > 1 && argv[1]->type == CAMEL_SEXP_RES_STRING) ? argv[1]->value.string : NULL;
718
719 if (forward_with && !*forward_with)
720 forward_with = NULL;
721
722 if (forward_with) {
723 camel_medium_set_header (CAMEL_MEDIUM (driver->priv->message), "X-Evolution-Forward-With", forward_with);
724 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward message to '%s' with '%s'", argv[0]->value.string, forward_with);
725 } else {
726 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Forward message to '%s'", argv[0]->value.string);
727 }
728
729 /* XXX Not cancellable. */
730 camel_session_forward_to_sync (
731 driver->priv->session,
732 driver->priv->source,
733 driver->priv->message,
734 argv[0]->value.string,
735 NULL,
736 &driver->priv->error);
737
738 if (forward_with)
739 camel_medium_remove_header (CAMEL_MEDIUM (driver->priv->message), "X-Evolution-Forward-With");
740
741 return NULL;
742 }
743
744 static CamelSExpResult *
do_copy(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)745 do_copy (struct _CamelSExp *f,
746 gint argc,
747 struct _CamelSExpResult **argv,
748 CamelFilterDriver *driver)
749 {
750 gint i;
751
752 d (fprintf (stderr, "copying message...\n"));
753
754 for (i = 0; i < argc; i++) {
755 if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
756 /* open folders we intent to copy to */
757 gchar *folder = argv[i]->value.string;
758 CamelFolder *outbox;
759
760 outbox = open_folder (driver, folder);
761 if (!outbox)
762 break;
763
764 if (outbox == driver->priv->source)
765 break;
766
767 if (driver->priv->message == NULL)
768 /* FIXME Pass a GCancellable */
769 driver->priv->message = camel_folder_get_message_sync (
770 driver->priv->source,
771 driver->priv->uid, NULL,
772 &driver->priv->error);
773
774 if (!driver->priv->message)
775 continue;
776
777 /* FIXME Pass a GCancellable */
778 camel_folder_append_message_sync (
779 outbox, driver->priv->message,
780 driver->priv->info, NULL, NULL,
781 &driver->priv->error);
782
783 if (driver->priv->error == NULL)
784 driver->priv->copied = TRUE;
785
786 camel_filter_driver_log (
787 driver, FILTER_LOG_ACTION,
788 "Copy to folder %s", folder);
789 }
790 }
791
792 return NULL;
793 }
794
795 static CamelSExpResult *
do_move(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)796 do_move (struct _CamelSExp *f,
797 gint argc,
798 struct _CamelSExpResult **argv,
799 CamelFilterDriver *driver)
800 {
801 gint i;
802
803 d (fprintf (stderr, "moving message...\n"));
804
805 for (i = 0; i < argc; i++) {
806 if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
807 /* open folders we intent to move to */
808 gchar *folder = argv[i]->value.string;
809 CamelFolder *outbox;
810 gint last;
811
812 outbox = open_folder (driver, folder);
813 if (!outbox)
814 break;
815
816 if (outbox == driver->priv->source)
817 break;
818
819 /* only delete on last folder (only 1 can ever be supplied by ui currently) */
820 last = (i == argc - 1);
821
822 if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) {
823 filter_driver_add_to_transfers (driver, outbox, driver->priv->uid, last);
824 } else {
825 if (driver->priv->message == NULL)
826 /* FIXME Pass a GCancellable */
827 driver->priv->message = camel_folder_get_message_sync (
828 driver->priv->source, driver->priv->uid, NULL, &driver->priv->error);
829
830 if (!driver->priv->message)
831 continue;
832
833 /* FIXME Pass a GCancellable */
834 camel_folder_append_message_sync (
835 outbox, driver->priv->message, driver->priv->info,
836 NULL, NULL, &driver->priv->error);
837
838 if (driver->priv->error == NULL && last) {
839 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
840 camel_folder_set_message_flags (
841 driver->priv->source, driver->priv->uid,
842 CAMEL_MESSAGE_DELETED |
843 CAMEL_MESSAGE_SEEN, ~0);
844 else
845 camel_message_info_set_flags (
846 driver->priv->info,
847 CAMEL_MESSAGE_DELETED |
848 CAMEL_MESSAGE_SEEN |
849 CAMEL_MESSAGE_FOLDER_FLAGGED,
850 ~0);
851 }
852 }
853
854 if (driver->priv->error == NULL) {
855 driver->priv->moved = TRUE;
856 camel_filter_driver_log (
857 driver, FILTER_LOG_ACTION,
858 "Move to folder %s", folder);
859 }
860 }
861 }
862
863 /* implicit 'stop' with 'move' */
864 camel_filter_driver_log (
865 driver, FILTER_LOG_ACTION,
866 "Stopped processing");
867 driver->priv->terminated = TRUE;
868
869 return NULL;
870 }
871
872 static CamelSExpResult *
do_stop(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)873 do_stop (struct _CamelSExp *f,
874 gint argc,
875 struct _CamelSExpResult **argv,
876 CamelFilterDriver *driver)
877 {
878 camel_filter_driver_log (
879 driver, FILTER_LOG_ACTION,
880 "Stopped processing");
881 d (fprintf (stderr, "terminating message processing\n"));
882 driver->priv->terminated = TRUE;
883
884 return NULL;
885 }
886
887 static CamelSExpResult *
do_label(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)888 do_label (struct _CamelSExp *f,
889 gint argc,
890 struct _CamelSExpResult **argv,
891 CamelFilterDriver *driver)
892 {
893 d (fprintf (stderr, "setting label tag\n"));
894 if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
895 /* This is a list of new labels, we should used these in case of passing in old names.
896 * This all is required only because backward compatibility. */
897 const gchar *new_labels[] = { "$Labelimportant", "$Labelwork", "$Labelpersonal", "$Labeltodo", "$Labellater", NULL};
898 const gchar *label;
899 gint i;
900
901 label = argv[0]->value.string;
902
903 for (i = 0; new_labels[i]; i++) {
904 if (label && strcmp (new_labels[i] + 6, label) == 0) {
905 label = new_labels[i];
906 break;
907 }
908 }
909
910 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
911 camel_folder_set_message_user_flag (driver->priv->source, driver->priv->uid, label, TRUE);
912 else
913 camel_message_info_set_user_flag (driver->priv->info, label, TRUE);
914 camel_filter_driver_log (
915 driver, FILTER_LOG_ACTION,
916 "Set label to %s", label);
917 }
918
919 return NULL;
920 }
921
922 static CamelSExpResult *
do_color(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)923 do_color (struct _CamelSExp *f,
924 gint argc,
925 struct _CamelSExpResult **argv,
926 CamelFilterDriver *driver)
927 {
928 d (fprintf (stderr, "setting color tag\n"));
929 if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
930 const gchar *color = argv[0]->value.string;
931
932 if (color && !*color)
933 color = NULL;
934
935 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
936 camel_folder_set_message_user_tag (driver->priv->source, driver->priv->uid, "color", color);
937 else
938 camel_message_info_set_user_tag (driver->priv->info, "color", color);
939 camel_filter_driver_log (
940 driver, FILTER_LOG_ACTION,
941 "Set color to %s", color ? color : "None");
942 }
943
944 return NULL;
945 }
946
947 static CamelSExpResult *
do_score(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)948 do_score (struct _CamelSExp *f,
949 gint argc,
950 struct _CamelSExpResult **argv,
951 CamelFilterDriver *driver)
952 {
953 d (fprintf (stderr, "setting score tag\n"));
954 if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) {
955 gchar *value;
956
957 value = g_strdup_printf ("%d", argv[0]->value.number);
958 camel_message_info_set_user_tag (driver->priv->info, "score", value);
959 camel_filter_driver_log (
960 driver, FILTER_LOG_ACTION,
961 "Set score to %d", argv[0]->value.number);
962 g_free (value);
963 }
964
965 return NULL;
966 }
967
968 static CamelSExpResult *
do_adjust_score(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)969 do_adjust_score (struct _CamelSExp *f,
970 gint argc,
971 struct _CamelSExpResult **argv,
972 CamelFilterDriver *driver)
973 {
974 d (fprintf (stderr, "adjusting score tag\n"));
975 if (argc > 0 && argv[0]->type == CAMEL_SEXP_RES_INT) {
976 gchar *value;
977 gint old;
978
979 value = (gchar *) camel_message_info_get_user_tag (driver->priv->info, "score");
980 old = value ? atoi (value) : 0;
981 value = g_strdup_printf ("%d", old + argv[0]->value.number);
982 camel_message_info_set_user_tag (driver->priv->info, "score", value);
983 camel_filter_driver_log (
984 driver, FILTER_LOG_ACTION,
985 "Adjust score (%d) to %s",
986 argv[0]->value.number, value);
987 g_free (value);
988 }
989
990 return NULL;
991 }
992
993 static CamelSExpResult *
set_flag(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)994 set_flag (struct _CamelSExp *f,
995 gint argc,
996 struct _CamelSExpResult **argv,
997 CamelFilterDriver *driver)
998 {
999 guint32 flags;
1000
1001 d (fprintf (stderr, "setting flag\n"));
1002 if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
1003 flags = camel_system_flag (argv[0]->value.string);
1004 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
1005 camel_folder_set_message_flags (
1006 driver->priv->source, driver->priv->uid, flags, ~0);
1007 else
1008 camel_message_info_set_flags (
1009 driver->priv->info, flags |
1010 CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
1011 camel_filter_driver_log (
1012 driver, FILTER_LOG_ACTION,
1013 "Set %s flag", argv[0]->value.string);
1014 }
1015
1016 return NULL;
1017 }
1018
1019 static CamelSExpResult *
unset_flag(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1020 unset_flag (struct _CamelSExp *f,
1021 gint argc,
1022 struct _CamelSExpResult **argv,
1023 CamelFilterDriver *driver)
1024 {
1025 guint32 flags;
1026
1027 d (fprintf (stderr, "unsetting flag\n"));
1028 if (argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
1029 flags = camel_system_flag (argv[0]->value.string);
1030 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
1031 camel_folder_set_message_flags (
1032 driver->priv->source, driver->priv->uid, flags, 0);
1033 else
1034 camel_message_info_set_flags (
1035 driver->priv->info, flags |
1036 CAMEL_MESSAGE_FOLDER_FLAGGED, 0);
1037 camel_filter_driver_log (
1038 driver, FILTER_LOG_ACTION,
1039 "Unset %s flag", argv[0]->value.string);
1040 }
1041
1042 return NULL;
1043 }
1044
1045 #ifndef G_OS_WIN32
1046 static void
child_setup_func(gpointer user_data)1047 child_setup_func (gpointer user_data)
1048 {
1049 setsid ();
1050 }
1051 #else
1052 #define child_setup_func NULL
1053 #endif
1054
1055 typedef struct {
1056 gint child_status;
1057 GMainLoop *loop;
1058 } child_watch_data_t;
1059
1060 static void
child_watch(GPid pid,gint status,gpointer user_data)1061 child_watch (GPid pid,
1062 gint status,
1063 gpointer user_data)
1064 {
1065 child_watch_data_t *child_watch_data = user_data;
1066
1067 g_spawn_close_pid (pid);
1068
1069 child_watch_data->child_status = status;
1070
1071 g_main_loop_quit (child_watch_data->loop);
1072 }
1073
1074 static gint
pipe_to_system(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1075 pipe_to_system (struct _CamelSExp *f,
1076 gint argc,
1077 struct _CamelSExpResult **argv,
1078 CamelFilterDriver *driver)
1079 {
1080 gint i, pipe_to_child, pipe_from_child;
1081 CamelMimeMessage *message = NULL;
1082 CamelMimeParser *parser;
1083 CamelStream *stream, *mem;
1084 GPid child_pid;
1085 GError *error = NULL;
1086 GByteArray *bytes;
1087 GPtrArray *args;
1088 child_watch_data_t child_watch_data;
1089 GSource *source;
1090 GMainContext *context;
1091
1092 if (argc < 1 || argv[0]->value.string[0] == '\0')
1093 return 0;
1094
1095 /* make sure we have the message... */
1096 if (driver->priv->message == NULL) {
1097 /* FIXME Pass a GCancellable */
1098 driver->priv->message = camel_folder_get_message_sync (
1099 driver->priv->source, driver->priv->uid, NULL, &driver->priv->error);
1100 if (driver->priv->message == NULL)
1101 return -1;
1102 }
1103
1104 args = g_ptr_array_new ();
1105 for (i = 0; i < argc; i++)
1106 g_ptr_array_add (args, argv[i]->value.string);
1107 g_ptr_array_add (args, NULL);
1108
1109 if (!g_spawn_async_with_pipes (NULL,
1110 (gchar **) args->pdata,
1111 NULL,
1112 G_SPAWN_DO_NOT_REAP_CHILD |
1113 G_SPAWN_SEARCH_PATH |
1114 G_SPAWN_STDERR_TO_DEV_NULL,
1115 child_setup_func,
1116 NULL,
1117 &child_pid,
1118 &pipe_to_child,
1119 &pipe_from_child,
1120 NULL,
1121 &error)) {
1122 g_ptr_array_free (args, TRUE);
1123
1124 g_set_error (
1125 &driver->priv->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1126 _("Failed to create child process “%s”: %s"),
1127 argv[0]->value.string, error->message);
1128 g_error_free (error);
1129 return -1;
1130 }
1131
1132 g_ptr_array_free (args, TRUE);
1133
1134 stream = camel_stream_fs_new_with_fd (pipe_to_child);
1135 if (camel_data_wrapper_write_to_stream_sync (
1136 CAMEL_DATA_WRAPPER (driver->priv->message), stream, NULL, NULL) == -1) {
1137 g_object_unref (stream);
1138 close (pipe_from_child);
1139 goto wait;
1140 }
1141
1142 if (camel_stream_flush (stream, NULL, &error) == -1) {
1143 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
1144 g_warning ("%s: Failed to flush output stream: %s", G_STRFUNC, error->message);
1145 /* Ignore flush errors, it can be due to calling fsync() on the pipe */
1146 g_clear_error (&error);
1147 }
1148
1149 g_object_unref (stream);
1150
1151 stream = camel_stream_fs_new_with_fd (pipe_from_child);
1152 mem = camel_stream_mem_new ();
1153 if (camel_stream_write_to_stream (stream, mem, NULL, NULL) == -1) {
1154 g_object_unref (stream);
1155 g_object_unref (mem);
1156 goto wait;
1157 }
1158
1159 g_object_unref (stream);
1160
1161 bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
1162
1163 if (!bytes || !bytes->len)
1164 goto wait;
1165
1166 g_seekable_seek (G_SEEKABLE (mem), 0, G_SEEK_SET, NULL, NULL);
1167
1168 parser = camel_mime_parser_new ();
1169 camel_mime_parser_init_with_stream (parser, mem, NULL);
1170 camel_mime_parser_scan_from (parser, FALSE);
1171 g_object_unref (mem);
1172
1173 message = camel_mime_message_new ();
1174 if (!camel_mime_part_construct_from_parser_sync (
1175 (CamelMimePart *) message, parser, NULL, NULL)) {
1176 gint err = camel_mime_parser_errno (parser);
1177 g_set_error (
1178 &driver->priv->error, G_IO_ERROR,
1179 g_io_error_from_errno (err),
1180 _("Invalid message stream received from %s: %s"),
1181 argv[0]->value.string, g_strerror (err));
1182 g_object_unref (message);
1183 message = NULL;
1184 } else {
1185 g_object_unref (driver->priv->message);
1186 driver->priv->message = message;
1187 driver->priv->modified = TRUE;
1188 }
1189
1190 g_object_unref (parser);
1191
1192 wait:
1193 context = g_main_context_new ();
1194 child_watch_data.loop = g_main_loop_new (context, FALSE);
1195 g_main_context_unref (context);
1196
1197 source = g_child_watch_source_new (child_pid);
1198 g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
1199 g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
1200 g_source_unref (source);
1201
1202 g_main_loop_run (child_watch_data.loop);
1203 g_main_loop_unref (child_watch_data.loop);
1204
1205 #ifndef G_OS_WIN32
1206 if (message && WIFEXITED (child_watch_data.child_status))
1207 return WEXITSTATUS (child_watch_data.child_status);
1208 else
1209 return -1;
1210 #else
1211 return child_watch_data.child_status;
1212 #endif
1213 }
1214
1215 static CamelSExpResult *
pipe_message(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1216 pipe_message (struct _CamelSExp *f,
1217 gint argc,
1218 struct _CamelSExpResult **argv,
1219 CamelFilterDriver *driver)
1220 {
1221 gint i;
1222
1223 /* make sure all args are strings */
1224 for (i = 0; i < argc; i++) {
1225 if (argv[i]->type != CAMEL_SEXP_RES_STRING)
1226 return NULL;
1227 }
1228
1229 camel_filter_driver_log (
1230 driver, FILTER_LOG_ACTION,
1231 "Piping message to %s", argv[0]->value.string);
1232 pipe_to_system (f, argc, argv, driver);
1233
1234 return NULL;
1235 }
1236
1237 static CamelSExpResult *
do_shell(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1238 do_shell (struct _CamelSExp *f,
1239 gint argc,
1240 struct _CamelSExpResult **argv,
1241 CamelFilterDriver *driver)
1242 {
1243 GString *command;
1244 GPtrArray *args;
1245 gint i;
1246
1247 d (fprintf (stderr, "executing shell command\n"));
1248
1249 command = g_string_new ("");
1250
1251 args = g_ptr_array_new ();
1252
1253 /* make sure all args are strings */
1254 for (i = 0; i < argc; i++) {
1255 if (argv[i]->type != CAMEL_SEXP_RES_STRING)
1256 goto done;
1257
1258 g_ptr_array_add (args, argv[i]->value.string);
1259
1260 g_string_append (command, argv[i]->value.string);
1261 g_string_append_c (command, ' ');
1262 }
1263
1264 g_string_truncate (command, command->len - 1);
1265
1266 if (driver->priv->shellfunc && argc >= 1) {
1267 /* NULL-terminate the array, but do not count it into the argc */
1268 g_ptr_array_add (args, NULL);
1269
1270 driver->priv->shellfunc (driver, argc, (gchar **) args->pdata, driver->priv->shelldata);
1271 camel_filter_driver_log (
1272 driver, FILTER_LOG_ACTION,
1273 "Executing shell command: [%s]", command->str);
1274 }
1275
1276 done:
1277
1278 g_ptr_array_free (args, TRUE);
1279 g_string_free (command, TRUE);
1280
1281 return NULL;
1282 }
1283
1284 static CamelSExpResult *
do_beep(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1285 do_beep (struct _CamelSExp *f,
1286 gint argc,
1287 struct _CamelSExpResult **argv,
1288 CamelFilterDriver *driver)
1289 {
1290 d (fprintf (stderr, "beep\n"));
1291
1292 if (driver->priv->beep) {
1293 driver->priv->beep (driver, driver->priv->beepdata);
1294 camel_filter_driver_log (driver, FILTER_LOG_ACTION, "Beep");
1295 }
1296
1297 return NULL;
1298 }
1299
1300 static CamelSExpResult *
play_sound(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1301 play_sound (struct _CamelSExp *f,
1302 gint argc,
1303 struct _CamelSExpResult **argv,
1304 CamelFilterDriver *driver)
1305 {
1306 d (fprintf (stderr, "play sound\n"));
1307
1308 if (driver->priv->playfunc && argc == 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
1309 driver->priv->playfunc (driver, argv[0]->value.string, driver->priv->playdata);
1310 camel_filter_driver_log (
1311 driver, FILTER_LOG_ACTION, "Play sound");
1312 }
1313
1314 return NULL;
1315 }
1316
1317 static CamelSExpResult *
do_only_once(struct _CamelSExp * f,gint argc,struct _CamelSExpResult ** argv,CamelFilterDriver * driver)1318 do_only_once (struct _CamelSExp *f,
1319 gint argc,
1320 struct _CamelSExpResult **argv,
1321 CamelFilterDriver *driver)
1322 {
1323 d (fprintf (stderr, "only once\n"));
1324
1325 if (argc == 2 && !g_hash_table_lookup (driver->priv->only_once, argv[0]->value.string))
1326 g_hash_table_insert (
1327 driver->priv->only_once,
1328 g_strdup (argv[0]->value.string),
1329 g_strdup (argv[1]->value.string));
1330
1331 return NULL;
1332 }
1333
1334 static CamelFolder *
open_folder(CamelFilterDriver * driver,const gchar * folder_url)1335 open_folder (CamelFilterDriver *driver,
1336 const gchar *folder_url)
1337 {
1338 CamelFolder *camelfolder;
1339
1340 /* we have a lookup table of currently open folders */
1341 camelfolder = g_hash_table_lookup (driver->priv->folders, folder_url);
1342 if (camelfolder)
1343 return camelfolder == FOLDER_INVALID ? NULL : camelfolder;
1344
1345 /* if we have a default folder, ignore exceptions. This is so
1346 * a bad filter rule on pop or local delivery doesn't result
1347 * in duplicate mails, just mail going to inbox. Otherwise,
1348 * we want to know about exceptions and abort processing */
1349 if (driver->priv->defaultfolder) {
1350 camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, NULL);
1351 } else {
1352 camelfolder = driver->priv->get_folder (driver, folder_url, driver->priv->data, &driver->priv->error);
1353 }
1354
1355 if (camelfolder) {
1356 g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), camelfolder);
1357 camel_folder_freeze (camelfolder);
1358 } else {
1359 g_hash_table_insert (driver->priv->folders, g_strdup (folder_url), FOLDER_INVALID);
1360 }
1361
1362 return camelfolder;
1363 }
1364
1365 typedef struct _CloseFolderData {
1366 CamelFilterDriver *driver;
1367 gboolean can_call_refresh;
1368 GCancellable *cancellable;
1369 } CloseFolderData;
1370
1371 static void
close_folder(gpointer key,gpointer value,gpointer user_data)1372 close_folder (gpointer key,
1373 gpointer value,
1374 gpointer user_data)
1375 {
1376 CamelFolder *folder = value;
1377 CamelFilterDriver *driver;
1378 CloseFolderData *cfd = user_data;
1379
1380 g_return_if_fail (cfd != NULL);
1381
1382 driver = cfd->driver;
1383
1384 driver->priv->closed++;
1385 g_free (key);
1386
1387 if (folder != FOLDER_INVALID) {
1388 if (camel_folder_synchronize_sync (folder, FALSE, cfd->cancellable,
1389 (driver->priv->error != NULL) ? NULL : &driver->priv->error)) {
1390 if (cfd->can_call_refresh && cfd->cancellable) {
1391 camel_folder_refresh_info_sync (
1392 folder, cfd->cancellable,
1393 (driver->priv->error != NULL) ? NULL : &driver->priv->error);
1394 }
1395 }
1396
1397 camel_folder_thaw (folder);
1398 g_object_unref (folder);
1399 }
1400
1401 report_status (
1402 driver, CAMEL_FILTER_STATUS_PROGRESS,
1403 g_hash_table_size (driver->priv->folders) * 100 /
1404 driver->priv->closed, _("Syncing folders"));
1405 }
1406
1407 /* flush/close all folders */
1408 static gint
close_folders(CamelFilterDriver * driver,gboolean can_call_refresh,GCancellable * cancellable)1409 close_folders (CamelFilterDriver *driver,
1410 gboolean can_call_refresh,
1411 GCancellable *cancellable)
1412 {
1413 CloseFolderData cfd;
1414
1415 report_status (
1416 driver, CAMEL_FILTER_STATUS_PROGRESS,
1417 0, _("Syncing folders"));
1418
1419 driver->priv->closed = 0;
1420
1421 cfd.driver = driver;
1422 cfd.can_call_refresh = can_call_refresh;
1423 cfd.cancellable = cancellable;
1424
1425 g_hash_table_foreach (driver->priv->folders, close_folder, &cfd);
1426 g_hash_table_destroy (driver->priv->folders);
1427 driver->priv->folders = g_hash_table_new (g_str_hash, g_str_equal);
1428
1429 /* FIXME: status from driver */
1430 return 0;
1431 }
1432
1433 #if 0
1434 static void
1435 free_key (gpointer key,
1436 gpointer value,
1437 gpointer user_data)
1438 {
1439 g_free (key);
1440 }
1441 #endif
1442
1443 static void
camel_filter_driver_log(CamelFilterDriver * driver,enum filter_log_t status,const gchar * desc,...)1444 camel_filter_driver_log (CamelFilterDriver *driver,
1445 enum filter_log_t status,
1446 const gchar *desc,
1447 ...)
1448 {
1449 if (driver->priv->logfile) {
1450 gchar *str = NULL;
1451
1452 if (desc) {
1453 va_list ap;
1454
1455 va_start (ap, desc);
1456 str = g_strdup_vprintf (desc, ap);
1457 va_end (ap);
1458 }
1459
1460 switch (status) {
1461 case FILTER_LOG_START: {
1462 /* write log header */
1463 const gchar *subject = NULL;
1464 const gchar *from = NULL;
1465 gchar date[50];
1466 time_t t;
1467
1468 /* FIXME: does this need locking? Probably */
1469
1470 from = camel_message_info_get_from (driver->priv->info);
1471 subject = camel_message_info_get_subject (driver->priv->info);
1472
1473 time (&t);
1474 strftime (date, 49, "%Y-%m-%d %H:%M:%S", localtime (&t));
1475 fprintf (
1476 driver->priv->logfile,
1477 "%s - Applied filter \"%s\" to "
1478 "message from %s - \"%s\"\n",
1479 date, str, from ? from : "unknown",
1480 subject ? subject : "");
1481
1482 break;
1483 }
1484 case FILTER_LOG_ACTION:
1485 fprintf (driver->priv->logfile, "Action: %s\n", str);
1486 break;
1487 case FILTER_LOG_INFO:
1488 fprintf (driver->priv->logfile, "%s\n", str);
1489 break;
1490 case FILTER_LOG_END:
1491 fprintf (driver->priv->logfile, "\n");
1492 break;
1493 default:
1494 /* nothing else is loggable */
1495 break;
1496 }
1497
1498 g_free (str);
1499
1500 fflush (driver->priv->logfile);
1501 }
1502 }
1503
1504 struct _run_only_once {
1505 CamelFilterDriver *driver;
1506 GError *error;
1507 };
1508
1509 static gboolean
run_only_once(gpointer key,gchar * action,struct _run_only_once * user_data)1510 run_only_once (gpointer key,
1511 gchar *action,
1512 struct _run_only_once *user_data)
1513 {
1514 CamelFilterDriver *driver = user_data->driver;
1515 CamelSExpResult *r;
1516
1517 d (printf ("evaluating: %s\n\n", action));
1518
1519 camel_sexp_input_text (driver->priv->eval, action, strlen (action));
1520 if (camel_sexp_parse (driver->priv->eval) == -1) {
1521 if (user_data->error == NULL)
1522 g_set_error (
1523 &user_data->error,
1524 CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1525 _("Error parsing filter: %s: %s"),
1526 camel_sexp_error (driver->priv->eval), action);
1527 goto done;
1528 }
1529
1530 r = camel_sexp_eval (driver->priv->eval);
1531 if (r == NULL) {
1532 if (user_data->error == NULL)
1533 g_set_error (
1534 &user_data->error,
1535 CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1536 _("Error executing filter: %s: %s"),
1537 camel_sexp_error (driver->priv->eval), action);
1538 goto done;
1539 }
1540
1541 camel_sexp_result_free (driver->priv->eval, r);
1542
1543 done:
1544
1545 g_free (key);
1546 g_free (action);
1547
1548 return TRUE;
1549 }
1550
1551 /**
1552 * camel_filter_driver_flush:
1553 * @driver: a #CamelFilterDriver
1554 * @error: return location for a #GError, or %NULL
1555 *
1556 * Flush all of the only-once filter actions.
1557 **/
1558 void
camel_filter_driver_flush(CamelFilterDriver * driver,GError ** error)1559 camel_filter_driver_flush (CamelFilterDriver *driver,
1560 GError **error)
1561 {
1562 struct _run_only_once data;
1563
1564 if (!driver->priv->only_once)
1565 return;
1566
1567 data.driver = driver;
1568 data.error = NULL;
1569
1570 g_hash_table_foreach_remove (driver->priv->only_once, (GHRFunc) run_only_once, &data);
1571
1572 if (data.error != NULL)
1573 g_propagate_error (error, data.error);
1574 }
1575
1576 static gint
decode_flags_from_xev(const gchar * xev,CamelMessageInfo * mi)1577 decode_flags_from_xev (const gchar *xev,
1578 CamelMessageInfo *mi)
1579 {
1580 guint32 uid, flags = 0;
1581 gchar *header;
1582
1583 /* check for uid/flags */
1584 header = camel_header_token_decode (xev);
1585 if (!(header && strlen (header) == strlen ("00000000-0000")
1586 && sscanf (header, "%08x-%04x", &uid, &flags) == 2)) {
1587 g_free (header);
1588 return 0;
1589 }
1590 g_free (header);
1591
1592 camel_message_info_set_flags (mi, ~0, flags);
1593
1594 return 0;
1595 }
1596
1597 /**
1598 * camel_filter_driver_filter_mbox:
1599 * @driver: CamelFilterDriver
1600 * @mbox: mbox filename to be filtered
1601 * @original_source_url: (nullable): URI of the @mbox, or %NULL
1602 * @cancellable: optional #GCancellable object, or %NULL
1603 * @error: return location for a #GError, or %NULL
1604 *
1605 * Filters an mbox file based on rules defined in the FilterDriver
1606 * object. Is more efficient as it doesn't need to open the folder
1607 * through Camel directly.
1608 *
1609 * Returns: -1 if errors were encountered during filtering,
1610 * otherwise returns 0.
1611 *
1612 **/
1613 gint
camel_filter_driver_filter_mbox(CamelFilterDriver * driver,const gchar * mbox,const gchar * original_source_url,GCancellable * cancellable,GError ** error)1614 camel_filter_driver_filter_mbox (CamelFilterDriver *driver,
1615 const gchar *mbox,
1616 const gchar *original_source_url,
1617 GCancellable *cancellable,
1618 GError **error)
1619 {
1620 CamelMimeParser *mp = NULL;
1621 gchar *source_url = NULL;
1622 gint fd = -1;
1623 gint i = 0;
1624 struct stat st;
1625 gint status;
1626 goffset last = 0;
1627 gint ret = -1;
1628 GError *local_error = NULL;
1629
1630 fd = g_open (mbox, O_RDONLY | O_BINARY, 0);
1631 if (fd == -1) {
1632 g_set_error (
1633 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1634 _("Unable to open spool folder"));
1635 goto fail;
1636 }
1637 /* to get the filesize */
1638 if (fstat (fd, &st) != 0)
1639 st.st_size = 0;
1640
1641 mp = camel_mime_parser_new ();
1642 camel_mime_parser_scan_from (mp, TRUE);
1643 if (camel_mime_parser_init_with_fd (mp, fd) == -1) {
1644 g_set_error (
1645 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1646 _("Unable to process spool folder"));
1647 goto fail;
1648 }
1649 fd = -1;
1650
1651 g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy);
1652
1653 g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref);
1654 driver->priv->delete_after_transfer = NULL;
1655
1656 source_url = g_filename_to_uri (mbox, NULL, NULL);
1657
1658 while (camel_mime_parser_step (mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
1659 CamelMessageInfo *info;
1660 CamelMimeMessage *message;
1661 const CamelNameValueArray *headers;
1662 CamelMimePart *mime_part;
1663 gint pc = 0;
1664 const gchar *xev;
1665
1666 if (st.st_size > 0)
1667 pc = (gint)(100.0 * ((double) camel_mime_parser_tell (mp) / (double) st.st_size));
1668
1669 if (pc > 0)
1670 camel_operation_progress (cancellable, pc);
1671
1672 report_status (
1673 driver, CAMEL_FILTER_STATUS_START,
1674 pc, _("Getting message %d (%d%%)"), i, pc);
1675
1676 message = camel_mime_message_new ();
1677 mime_part = CAMEL_MIME_PART (message);
1678
1679 if (!camel_mime_part_construct_from_parser_sync (
1680 mime_part, mp, cancellable, error)) {
1681 report_status (
1682 driver, CAMEL_FILTER_STATUS_END,
1683 100, _("Failed on message %d"), i);
1684 g_object_unref (message);
1685 goto fail;
1686 }
1687
1688 headers = camel_medium_get_headers (CAMEL_MEDIUM (mime_part));
1689 info = camel_message_info_new_from_headers (NULL, headers);
1690 /* Try and see if it has X-Evolution headers */
1691 xev = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, "X-Evolution");
1692 if (xev)
1693 decode_flags_from_xev (xev, info);
1694
1695 camel_message_info_set_size (info, camel_mime_parser_tell (mp) - last);
1696
1697 last = camel_mime_parser_tell (mp);
1698 status = filter_driver_filter_message_internal (
1699 driver, FALSE, message, info, NULL, NULL, source_url,
1700 original_source_url ? original_source_url :
1701 source_url, cancellable, &local_error);
1702 g_object_unref (message);
1703 if (local_error != NULL || status == -1) {
1704 report_status (
1705 driver, CAMEL_FILTER_STATUS_END,
1706 100, _("Failed on message %d"), i);
1707 g_clear_object (&info);
1708 g_propagate_error (error, local_error);
1709 local_error = NULL;
1710 goto fail;
1711 }
1712
1713 i++;
1714
1715 /* skip over the FROM_END state */
1716 camel_mime_parser_step (mp, NULL, NULL);
1717
1718 g_clear_object (&info);
1719 }
1720
1721 if (!filter_driver_process_transfers (driver, cancellable, &local_error)) {
1722 report_status (
1723 driver, CAMEL_FILTER_STATUS_END,
1724 100, _("Failed to transfer messages: %s"), local_error ? local_error->message : _("Unknown error"));
1725 g_propagate_error (error, local_error);
1726 goto fail;
1727 }
1728
1729 camel_operation_progress (cancellable, 100);
1730
1731 if (driver->priv->defaultfolder) {
1732 report_status (
1733 driver, CAMEL_FILTER_STATUS_PROGRESS,
1734 100, _("Syncing folder"));
1735 camel_folder_synchronize_sync (
1736 driver->priv->defaultfolder, FALSE, cancellable, NULL);
1737 }
1738
1739 report_status (driver, CAMEL_FILTER_STATUS_END, 100, _("Complete"));
1740
1741 ret = 0;
1742 fail:
1743 g_free (source_url);
1744 if (fd != -1)
1745 close (fd);
1746 if (mp)
1747 g_object_unref (mp);
1748
1749 return ret;
1750 }
1751
1752 /**
1753 * camel_filter_driver_filter_folder:
1754 * @driver: CamelFilterDriver
1755 * @folder: CamelFolder to be filtered
1756 * @cache: UID cache (needed for POP folders)
1757 * @uids: (element-type utf8): message uids to be filtered or NULL (as a
1758 * shortcut to filter all messages)
1759 * @remove: TRUE to mark filtered messages as deleted
1760 * @cancellable: optional #GCancellable object, or %NULL
1761 * @error: return location for a #GError, or %NULL
1762 *
1763 * Filters a folder based on rules defined in the FilterDriver
1764 * object.
1765 *
1766 * Returns: -1 if errors were encountered during filtering,
1767 * otherwise returns 0.
1768 *
1769 **/
1770 gint
camel_filter_driver_filter_folder(CamelFilterDriver * driver,CamelFolder * folder,CamelUIDCache * cache,GPtrArray * uids,gboolean remove,GCancellable * cancellable,GError ** error)1771 camel_filter_driver_filter_folder (CamelFilterDriver *driver,
1772 CamelFolder *folder,
1773 CamelUIDCache *cache,
1774 GPtrArray *uids,
1775 gboolean remove,
1776 GCancellable *cancellable,
1777 GError **error)
1778 {
1779 gboolean freeuids = FALSE;
1780 CamelMessageInfo *info;
1781 CamelStore *parent_store;
1782 const gchar *store_uid;
1783 gint status = 0;
1784 gint i;
1785 GError *local_error = NULL;
1786
1787 parent_store = camel_folder_get_parent_store (folder);
1788 store_uid = camel_service_get_uid (CAMEL_SERVICE (parent_store));
1789
1790 if (uids == NULL) {
1791 uids = camel_folder_get_uids (folder);
1792 freeuids = TRUE;
1793 }
1794
1795 g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy);
1796
1797 g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref);
1798 driver->priv->delete_after_transfer = NULL;
1799
1800 for (i = 0; i < uids->len; i++) {
1801 gint pc = (100 * i) / uids->len;
1802
1803 camel_operation_progress (cancellable, pc);
1804
1805 report_status (
1806 driver, CAMEL_FILTER_STATUS_START,
1807 pc, _("Getting message %d of %d"),
1808 i + 1, uids->len);
1809
1810 if (camel_folder_has_summary_capability (folder))
1811 info = camel_folder_get_message_info (folder, uids->pdata[i]);
1812 else
1813 info = NULL;
1814
1815 status = filter_driver_filter_message_internal (
1816 driver, FALSE, NULL, info, uids->pdata[i], folder,
1817 store_uid, store_uid, cancellable, &local_error);
1818
1819 if (camel_folder_has_summary_capability (folder))
1820 g_clear_object (&info);
1821
1822 if (local_error != NULL || status == -1) {
1823 report_status (
1824 driver, CAMEL_FILTER_STATUS_END, 100,
1825 _("Failed at message %d of %d"),
1826 i + 1, uids->len);
1827 g_propagate_error (error, local_error);
1828 local_error = NULL;
1829 status = -1;
1830 break;
1831 }
1832
1833 if (remove && !driver->priv->copied && !driver->priv->moved) {
1834 camel_folder_set_message_flags (
1835 folder, uids->pdata[i],
1836 CAMEL_MESSAGE_DELETED |
1837 CAMEL_MESSAGE_SEEN, ~0);
1838 } else if (remove) {
1839 info = camel_folder_get_message_info (folder, uids->pdata[i]);
1840 if (info)
1841 driver->priv->delete_after_transfer = g_slist_prepend (driver->priv->delete_after_transfer, info);
1842 }
1843
1844 if (cache)
1845 camel_uid_cache_save_uid (cache, uids->pdata[i]);
1846 if (cache && (i % 10) == 0)
1847 camel_uid_cache_save (cache);
1848 }
1849
1850 if (!filter_driver_process_transfers (driver, cancellable, &local_error)) {
1851 report_status (
1852 driver, CAMEL_FILTER_STATUS_END,
1853 100, _("Failed to transfer messages: %s"), local_error ? local_error->message : _("Unknown error"));
1854 g_propagate_error (error, local_error);
1855 status = -1;
1856 }
1857
1858 camel_operation_progress (cancellable, 100);
1859
1860 /* Save the cache of any pending mails. */
1861 if (cache)
1862 camel_uid_cache_save (cache);
1863
1864 if (driver->priv->defaultfolder) {
1865 report_status (
1866 driver, CAMEL_FILTER_STATUS_PROGRESS,
1867 100, _("Syncing folder"));
1868 camel_folder_synchronize_sync (
1869 driver->priv->defaultfolder, FALSE, cancellable, NULL);
1870 }
1871
1872 if (i == uids->len && status != -1)
1873 report_status (
1874 driver, CAMEL_FILTER_STATUS_END,
1875 100, _("Complete"));
1876
1877 if (freeuids)
1878 camel_folder_free_uids (folder, uids);
1879
1880 close_folders (driver, remove, cancellable);
1881
1882 return status;
1883 }
1884
1885 struct _get_message {
1886 struct _CamelFilterDriverPrivate *priv;
1887 const gchar *store_uid;
1888 };
1889
1890 static CamelMimeMessage *
get_message_cb(gpointer data,GCancellable * cancellable,GError ** error)1891 get_message_cb (gpointer data,
1892 GCancellable *cancellable,
1893 GError **error)
1894 {
1895 struct _get_message *msgdata = data;
1896 CamelMimeMessage *message;
1897
1898 if (msgdata->priv->message) {
1899 message = g_object_ref (msgdata->priv->message);
1900 } else {
1901 const gchar *uid;
1902
1903 if (msgdata->priv->uid != NULL)
1904 uid = msgdata->priv->uid;
1905 else
1906 uid = camel_message_info_get_uid (msgdata->priv->info);
1907
1908 message = camel_folder_get_message_sync (
1909 msgdata->priv->source, uid, cancellable, error);
1910 }
1911
1912 if (message != NULL && camel_mime_message_get_source (message) == NULL)
1913 camel_mime_message_set_source (message, msgdata->store_uid);
1914
1915 return message;
1916 }
1917
1918 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)1919 filter_driver_filter_message_internal (CamelFilterDriver *driver,
1920 gboolean can_process_transfers,
1921 CamelMimeMessage *message,
1922 CamelMessageInfo *info,
1923 const gchar *uid,
1924 CamelFolder *source,
1925 const gchar *store_uid,
1926 const gchar *original_store_uid,
1927 GCancellable *cancellable,
1928 GError **error)
1929 {
1930 CamelFilterDriverPrivate *p = driver->priv;
1931 gboolean freeinfo = FALSE;
1932 gboolean filtered = FALSE;
1933 CamelSExpResult *r;
1934 GList *list, *link;
1935 gint result;
1936
1937 g_return_val_if_fail (message != NULL || (source != NULL && uid != NULL), -1);
1938
1939 if (info == NULL) {
1940 const CamelNameValueArray *headers;
1941
1942 if (message) {
1943 g_object_ref (message);
1944 } else {
1945 message = camel_folder_get_message_sync (
1946 source, uid, cancellable, error);
1947 if (!message)
1948 return -1;
1949 }
1950
1951 headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
1952 info = camel_message_info_new_from_headers (NULL, headers);
1953 freeinfo = TRUE;
1954 } else {
1955 if (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED)
1956 return 0;
1957
1958 uid = camel_message_info_get_uid (info);
1959
1960 if (message)
1961 g_object_ref (message);
1962 }
1963
1964 if (can_process_transfers) {
1965 g_clear_pointer (&driver->priv->transfers, g_hash_table_destroy);
1966
1967 g_slist_free_full (driver->priv->delete_after_transfer, g_object_unref);
1968 driver->priv->delete_after_transfer = NULL;
1969 }
1970
1971 driver->priv->terminated = FALSE;
1972 driver->priv->deleted = FALSE;
1973 driver->priv->copied = FALSE;
1974 driver->priv->moved = FALSE;
1975 driver->priv->message = message;
1976 driver->priv->info = info;
1977 driver->priv->uid = uid;
1978 driver->priv->source = source;
1979
1980 if (message != NULL && camel_mime_message_get_source (message) == NULL)
1981 camel_mime_message_set_source (message, original_store_uid);
1982
1983 if (g_strcmp0 (store_uid, "local") == 0 ||
1984 g_strcmp0 (store_uid, "vfolder") == 0) {
1985 store_uid = NULL;
1986 }
1987
1988 if (g_strcmp0 (original_store_uid, "local") == 0 ||
1989 g_strcmp0 (original_store_uid, "vfolder") == 0) {
1990 original_store_uid = NULL;
1991 }
1992
1993 list = g_queue_peek_head_link (&driver->priv->rules);
1994 result = CAMEL_SEARCH_NOMATCH;
1995 filtered = list != NULL;
1996
1997 for (link = list; link != NULL; link = g_list_next (link)) {
1998 struct _filter_rule *rule = link->data;
1999 struct _get_message data;
2000
2001 if (driver->priv->terminated) {
2002 camel_filter_driver_log (driver, FILTER_LOG_INFO, "Stopped processing per request");
2003 break;
2004 }
2005
2006 if (g_cancellable_set_error_if_cancelled (cancellable, &driver->priv->error)) {
2007 camel_filter_driver_log (driver, FILTER_LOG_INFO, "Stopped processing on cancel");
2008 goto error;
2009 }
2010
2011 d (printf ("applying rule %s\naction %s\n", rule->match, rule->action));
2012
2013 data.priv = p;
2014 data.store_uid = original_store_uid;
2015
2016 if (original_store_uid == NULL)
2017 original_store_uid = store_uid;
2018
2019 camel_filter_driver_log (driver, FILTER_LOG_START, "%s", rule->name);
2020
2021 result = camel_filter_search_match_with_log (
2022 driver->priv->session, get_message_cb, &data, driver->priv->info,
2023 original_store_uid, source, rule->match, driver->priv->logfile, cancellable, &driver->priv->error);
2024
2025 switch (result) {
2026 case CAMEL_SEARCH_ERROR:
2027 camel_filter_driver_log (driver, FILTER_LOG_INFO, " Execution of filter '%s' failed: %s\n",
2028 rule->name, driver->priv->error ? driver->priv->error->message : "Unknown error");
2029
2030 g_prefix_error (
2031 &driver->priv->error,
2032 _("Execution of filter “%s” failed: "),
2033 rule->name);
2034 goto error;
2035 case CAMEL_SEARCH_MATCHED:
2036 camel_filter_driver_log (driver, FILTER_LOG_INFO, " Filter '%s' matched\n", rule->name);
2037
2038 /* perform necessary filtering actions */
2039 camel_sexp_input_text (
2040 driver->priv->eval,
2041 rule->action, strlen (rule->action));
2042 if (camel_sexp_parse (driver->priv->eval) == -1) {
2043 g_set_error (
2044 error, CAMEL_ERROR,
2045 CAMEL_ERROR_GENERIC,
2046 _("Error parsing filter “%s”: %s: %s"),
2047 rule->name,
2048 camel_sexp_error (driver->priv->eval),
2049 rule->action);
2050 goto error;
2051 }
2052 r = camel_sexp_eval (driver->priv->eval);
2053 if (driver->priv->error != NULL) {
2054 g_prefix_error (
2055 &driver->priv->error,
2056 _("Execution of filter “%s” failed: "),
2057 rule->name);
2058 goto error;
2059 }
2060
2061 if (r == NULL) {
2062 g_set_error (
2063 error, CAMEL_ERROR,
2064 CAMEL_ERROR_GENERIC,
2065 _("Error executing filter “%s”: %s: %s"),
2066 rule->name,
2067 camel_sexp_error (driver->priv->eval),
2068 rule->action);
2069 goto error;
2070 }
2071 camel_sexp_result_free (driver->priv->eval, r);
2072 break;
2073 case CAMEL_SEARCH_NOMATCH:
2074 camel_filter_driver_log (driver, FILTER_LOG_INFO, " Filter '%s' did not match\n", rule->name);
2075 break;
2076 default:
2077 break;
2078 }
2079 }
2080
2081 if (can_process_transfers) {
2082 if (!filter_driver_process_transfers (driver, cancellable, &driver->priv->error))
2083 goto error;
2084 }
2085
2086 /* *Now* we can set the DELETED flag... */
2087 if (driver->priv->deleted) {
2088 if (can_process_transfers || (!driver->priv->moved && !driver->priv->copied)) {
2089 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
2090 camel_folder_set_message_flags (
2091 driver->priv->source, driver->priv->uid,
2092 CAMEL_MESSAGE_DELETED |
2093 CAMEL_MESSAGE_SEEN, ~0);
2094 else
2095 camel_message_info_set_flags (
2096 info, CAMEL_MESSAGE_DELETED |
2097 CAMEL_MESSAGE_SEEN |
2098 CAMEL_MESSAGE_FOLDER_FLAGGED, ~0);
2099 } else {
2100 /* Set the DELETED flag only after the messages are really transferred */
2101 CamelMessageInfo *nfo;
2102
2103 if (driver->priv->source && driver->priv->uid && camel_folder_has_summary_capability (driver->priv->source))
2104 nfo = camel_folder_get_message_info (driver->priv->source, driver->priv->uid);
2105 else
2106 nfo = g_object_ref (info);
2107
2108 if (nfo)
2109 driver->priv->delete_after_transfer = g_slist_prepend (driver->priv->delete_after_transfer, nfo);
2110 }
2111 }
2112
2113 /* Logic: if !Moved and there exists a default folder... */
2114 if (!(driver->priv->copied && driver->priv->deleted) && !driver->priv->moved && driver->priv->defaultfolder) {
2115 /* copy it to the default inbox */
2116 filtered = TRUE;
2117 camel_filter_driver_log (
2118 driver, FILTER_LOG_ACTION,
2119 "Copy to default folder");
2120
2121 if (!driver->priv->modified && driver->priv->uid && driver->priv->source && camel_folder_has_summary_capability (driver->priv->source)) {
2122 GPtrArray *uids;
2123
2124 uids = g_ptr_array_new ();
2125 g_ptr_array_add (uids, (gchar *) driver->priv->uid);
2126 camel_folder_transfer_messages_to_sync (
2127 driver->priv->source, uids, driver->priv->defaultfolder,
2128 FALSE, NULL, cancellable, &driver->priv->error);
2129 g_ptr_array_free (uids, TRUE);
2130 } else {
2131 if (driver->priv->message == NULL) {
2132 driver->priv->message = camel_folder_get_message_sync (
2133 source, uid, cancellable, error);
2134 if (!driver->priv->message)
2135 goto error;
2136 }
2137
2138 camel_folder_append_message_sync (
2139 driver->priv->defaultfolder,
2140 driver->priv->message,
2141 driver->priv->info, NULL,
2142 cancellable,
2143 &driver->priv->error);
2144 }
2145 }
2146
2147 if (driver->priv->message)
2148 g_object_unref (driver->priv->message);
2149
2150 if (freeinfo)
2151 g_clear_object (&info);
2152
2153 return 0;
2154
2155 error:
2156 if (filtered)
2157 camel_filter_driver_log (driver, FILTER_LOG_END, NULL);
2158
2159 if (driver->priv->message)
2160 g_object_unref (driver->priv->message);
2161
2162 if (freeinfo)
2163 g_clear_object (&info);
2164
2165 g_propagate_error (error, driver->priv->error);
2166 driver->priv->error = NULL;
2167
2168 return -1;
2169 }
2170
2171 /**
2172 * camel_filter_driver_filter_message:
2173 * @driver: CamelFilterDriver
2174 * @message: message to filter or NULL
2175 * @info: message info or NULL
2176 * @uid: message uid or NULL
2177 * @source: source folder or NULL
2178 * @store_uid: UID of source store, or %NULL
2179 * @original_store_uid: UID of source store (pre-movemail), or %NULL
2180 * @cancellable: optional #GCancellable object, or %NULL
2181 * @error: return location for a #GError, or %NULL
2182 *
2183 * Filters a message based on rules defined in the FilterDriver
2184 * object. If the source folder (@source) and the uid (@uid) are
2185 * provided, the filter will operate on the CamelFolder (which in
2186 * certain cases is more efficient than using the default
2187 * camel_folder_append_message() function).
2188 *
2189 * Returns: -1 if errors were encountered during filtering,
2190 * otherwise returns 0.
2191 *
2192 **/
2193 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)2194 camel_filter_driver_filter_message (CamelFilterDriver *driver,
2195 CamelMimeMessage *message,
2196 CamelMessageInfo *info,
2197 const gchar *uid,
2198 CamelFolder *source,
2199 const gchar *store_uid,
2200 const gchar *original_store_uid,
2201 GCancellable *cancellable,
2202 GError **error)
2203 {
2204 return filter_driver_filter_message_internal (driver, TRUE, message,
2205 info, uid, source, store_uid, original_store_uid, cancellable, error);
2206 }
2207
2208 /**
2209 * camel_filter_driver_log_info:
2210 * @driver: (nullable): a #CamelFilterDriver, or %NULL
2211 * @format: a printf-like format to use for the informational log entry
2212 * @...: arguments for @format
2213 *
2214 * Logs an informational message to a filter log. The function does
2215 * nothing when @driver is %NULL or when there is no log file being
2216 * set in @driver.
2217 *
2218 * Since: 3.24
2219 **/
2220 void
camel_filter_driver_log_info(CamelFilterDriver * driver,const gchar * format,...)2221 camel_filter_driver_log_info (CamelFilterDriver *driver,
2222 const gchar *format,
2223 ...)
2224 {
2225 gchar *str;
2226 va_list ap;
2227
2228 if (!driver || !driver->priv->logfile)
2229 return;
2230
2231 g_return_if_fail (format != NULL);
2232
2233 va_start (ap, format);
2234 str = g_strdup_vprintf (format, ap);
2235 va_end (ap);
2236
2237 camel_filter_driver_log (driver, FILTER_LOG_INFO, "%s", str);
2238
2239 g_free (str);
2240 }
2241