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