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