Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-filter-search.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  *  Authors: Jeffrey Stedfast <fejj@ximian.com>
4  *           Michael Zucchi <NotZed@Ximian.com>
5  *
6  *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.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
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 /* POSIX requires <sys/types.h> be included before <regex.h> */
29 #include <sys/types.h>
30
31 #include <ctype.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <regex.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #include <glib/gi18n-lib.h>
41
42 #ifndef G_OS_WIN32
43 #include <sys/wait.h>
44 #endif
45
46 #include "camel-debug.h"
47 #include "camel-filter-search.h"
48 #include "camel-iconv.h"
49 #include "camel-mime-message.h"
50 #include "camel-multipart.h"
51 #include "camel-provider.h"
52 #include "camel-search-private.h"
53 #include "camel-session.h"
54 #include "camel-stream-fs.h"
55 #include "camel-stream-mem.h"
56 #include "camel-string-utils.h"
57 #include "camel-url.h"
58
59 #define d(x)
60
61 typedef struct {
62         CamelSession *session;
63         CamelFilterSearchGetMessageFunc get_message;
64         gpointer get_message_data;
65         CamelMimeMessage *message;
66         CamelMessageInfo *info;
67         const gchar *source;
68         GError **error;
69 } FilterMessageSearch;
70
71 /* CamelSExp callbacks */
72 static CamelSExpResult *header_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
73 static CamelSExpResult *header_has_words (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
74 static CamelSExpResult *header_matches (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
75 static CamelSExpResult *header_starts_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
76 static CamelSExpResult *header_ends_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
77 static CamelSExpResult *header_exists (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
78 static CamelSExpResult *header_soundex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
79 static CamelSExpResult *header_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
80 static CamelSExpResult *header_full_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
81 static CamelSExpResult *match_all (struct _CamelSExp *f, gint argc, struct _CamelSExpTerm **argv, FilterMessageSearch *fms);
82 static CamelSExpResult *body_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
83 static CamelSExpResult *body_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
84 static CamelSExpResult *user_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
85 static CamelSExpResult *user_tag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
86 static CamelSExpResult *system_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
87 static CamelSExpResult *get_sent_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
88 static CamelSExpResult *get_received_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
89 static CamelSExpResult *get_current_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
90 static CamelSExpResult *get_relative_months (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
91 static CamelSExpResult *header_source (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
92 static CamelSExpResult *get_size (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
93 static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
94 static CamelSExpResult *junk_test (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
95
96 /* builtin functions */
97 static struct {
98         const gchar *name;
99         CamelSExpFunc func;
100         gint type;              /* set to 1 if a function can perform shortcut evaluation, or
101                                    doesn't execute everything, 0 otherwise */
102 } symbols[] = {
103         { "match-all",          (CamelSExpFunc) match_all,          1 },
104         { "body-contains",      (CamelSExpFunc) body_contains,      0 },
105         { "body-regex",         (CamelSExpFunc) body_regex,         0 },
106         { "header-contains",    (CamelSExpFunc) header_contains,    0 },
107         { "header-has-words",   (CamelSExpFunc) header_has_words,   0 },
108         { "header-matches",     (CamelSExpFunc) header_matches,     0 },
109         { "header-starts-with", (CamelSExpFunc) header_starts_with, 0 },
110         { "header-ends-with",   (CamelSExpFunc) header_ends_with,   0 },
111         { "header-exists",      (CamelSExpFunc) header_exists,      0 },
112         { "header-soundex",     (CamelSExpFunc) header_soundex,     0 },
113         { "header-regex",       (CamelSExpFunc) header_regex,       0 },
114         { "header-full-regex",  (CamelSExpFunc) header_full_regex,  0 },
115         { "user-tag",           (CamelSExpFunc) user_tag,           0 },
116         { "user-flag",          (CamelSExpFunc) user_flag,          0 },
117         { "system-flag",        (CamelSExpFunc) system_flag,        0 },
118         { "get-sent-date",      (CamelSExpFunc) get_sent_date,      0 },
119         { "get-received-date",  (CamelSExpFunc) get_received_date,  0 },
120         { "get-current-date",   (CamelSExpFunc) get_current_date,   0 },
121         { "get-relative-months",(CamelSExpFunc) get_relative_months,0 },
122         { "header-source",      (CamelSExpFunc) header_source,      0 },
123         { "get-size",           (CamelSExpFunc) get_size,           0 },
124         { "pipe-message",       (CamelSExpFunc) pipe_message,       0 },
125         { "junk-test",          (CamelSExpFunc) junk_test,          0 },
126 };
127
128 static CamelMimeMessage *
129 camel_filter_search_get_message (FilterMessageSearch *fms,
130                                  struct _CamelSExp *sexp)
131 {
132         if (fms->message)
133                 return fms->message;
134
135         fms->message = fms->get_message (fms->get_message_data, fms->error);
136
137         if (fms->message == NULL)
138                 camel_sexp_fatal_error (sexp, _("Failed to retrieve message"));
139
140         return fms->message;
141 }
142
143 static gboolean
144 check_header_in_message_info (CamelMessageInfo *info,
145                               gint argc,
146                               struct _CamelSExpResult **argv,
147                               camel_search_match_t how,
148                               gboolean *matched)
149 {
150         struct _KnownHeaders {
151                 const gchar *header_name;
152                 guint info_key;
153         } known_headers[] = {
154                 { "Subject", CAMEL_MESSAGE_INFO_SUBJECT },
155                 { "From", CAMEL_MESSAGE_INFO_FROM },
156                 { "To", CAMEL_MESSAGE_INFO_TO },
157                 { "Cc", CAMEL_MESSAGE_INFO_CC }
158         };
159         camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
160         const gchar *name, *value;
161         gboolean found = FALSE;
162         gint ii;
163
164         g_return_val_if_fail (argc > 1, FALSE);
165         g_return_val_if_fail (argv != NULL, FALSE);
166         g_return_val_if_fail (matched != NULL, FALSE);
167
168         if (!info)
169                 return FALSE;
170
171         name = argv[0]->value.string;
172         g_return_val_if_fail (name != NULL, FALSE);
173
174         value = NULL;
175
176         for (ii = 0; ii < G_N_ELEMENTS (known_headers); ii++) {
177                 found = g_ascii_strcasecmp (name, known_headers[ii].header_name) == 0;
178                 if (found) {
179                         value = camel_message_info_ptr (info, known_headers[ii].info_key);
180                         if (known_headers[ii].info_key == CAMEL_MESSAGE_INFO_FROM ||
181                             known_headers[ii].info_key == CAMEL_MESSAGE_INFO_TO ||
182                             known_headers[ii].info_key == CAMEL_MESSAGE_INFO_CC)
183                                 type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
184                         break;
185                 }
186         }
187
188         if (!found || !value)
189                 return FALSE;
190
191         for (ii = 1; ii < argc && !*matched; ii++) {
192                 if (argv[ii]->type == CAMEL_SEXP_RES_STRING)
193                         *matched = camel_search_header_match (value, argv[ii]->value.string, how, type, NULL);
194         }
195
196         return TRUE;
197 }
198
199 static CamelSExpResult *
200 check_header (struct _CamelSExp *f,
201               gint argc,
202               struct _CamelSExpResult **argv,
203               FilterMessageSearch *fms,
204               camel_search_match_t how)
205 {
206         gboolean matched = FALSE;
207         CamelSExpResult *r;
208         gint i;
209
210         if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
211                 gchar *name = argv[0]->value.string;
212
213                 /* shortcut: a match for "" against any header always matches */
214                 for (i = 1; i < argc && !matched; i++)
215                         matched = argv[i]->type == CAMEL_SEXP_RES_STRING && argv[i]->value.string[0] == 0;
216
217                 if (g_ascii_strcasecmp (name, "x-camel-mlist") == 0) {
218                         const gchar *list = camel_message_info_mlist (fms->info);
219
220                         if (list) {
221                                 for (i = 1; i < argc && !matched; i++) {
222                                         if (argv[i]->type == CAMEL_SEXP_RES_STRING)
223                                                 matched = camel_search_header_match (list, argv[i]->value.string, how, CAMEL_SEARCH_TYPE_MLIST, NULL);
224                                 }
225                         }
226                 } else if (fms->message || !check_header_in_message_info (fms->info, argc, argv, how, &matched)) {
227                         CamelMimeMessage *message;
228                         CamelMimePart *mime_part;
229                         struct _camel_header_raw *header;
230                         const gchar *charset = NULL;
231                         camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
232                         CamelContentType *ct;
233
234                         message = camel_filter_search_get_message (fms, f);
235                         mime_part = CAMEL_MIME_PART (message);
236
237                         /* FIXME: what about Resent-To, Resent-Cc and Resent-From? */
238                         if (g_ascii_strcasecmp ("to", name) == 0 || g_ascii_strcasecmp ("cc", name) == 0 || g_ascii_strcasecmp ("from", name) == 0)
239                                 type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
240                         else if (message) {
241                                 ct = camel_mime_part_get_content_type (mime_part);
242                                 if (ct) {
243                                         charset = camel_content_type_param (ct, "charset");
244                                         charset = camel_iconv_charset_name (charset);
245                                 }
246                         }
247
248                         for (header = mime_part->headers; header && !matched; header = header->next) {
249                                 if (!g_ascii_strcasecmp (header->name, name)) {
250                                         for (i = 1; i < argc && !matched; i++) {
251                                                 if (argv[i]->type == CAMEL_SEXP_RES_STRING)
252                                                         matched = camel_search_header_match (header->value, argv[i]->value.string, how, type, charset);
253                                         }
254                                 }
255                         }
256                 }
257         }
258
259         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
260         r->value.boolean = matched;
261
262         return r;
263 }
264
265 static CamelSExpResult *
266 header_contains (struct _CamelSExp *f,
267                  gint argc,
268                  struct _CamelSExpResult **argv,
269                  FilterMessageSearch *fms)
270 {
271         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_CONTAINS);
272 }
273
274 static CamelSExpResult *
275 header_has_words (struct _CamelSExp *f,
276                   gint argc,
277                   struct _CamelSExpResult **argv,
278                   FilterMessageSearch *fms)
279 {
280         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_WORD);
281 }
282
283 static CamelSExpResult *
284 header_matches (struct _CamelSExp *f,
285                 gint argc,
286                 struct _CamelSExpResult **argv,
287                 FilterMessageSearch *fms)
288 {
289         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_EXACT);
290 }
291
292 static CamelSExpResult *
293 header_starts_with (struct _CamelSExp *f,
294                     gint argc,
295                     struct _CamelSExpResult **argv,
296                     FilterMessageSearch *fms)
297 {
298         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_STARTS);
299 }
300
301 static CamelSExpResult *
302 header_ends_with (struct _CamelSExp *f,
303                   gint argc,
304                   struct _CamelSExpResult **argv,
305                   FilterMessageSearch *fms)
306 {
307         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_ENDS);
308 }
309
310 static CamelSExpResult *
311 header_soundex (struct _CamelSExp *f,
312                 gint argc,
313                 struct _CamelSExpResult **argv,
314                 FilterMessageSearch *fms)
315 {
316         return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_SOUNDEX);
317 }
318
319 static CamelSExpResult *
320 header_exists (struct _CamelSExp *f,
321                gint argc,
322                struct _CamelSExpResult **argv,
323                FilterMessageSearch *fms)
324 {
325         CamelMimeMessage *message;
326         gboolean matched = FALSE;
327         CamelSExpResult *r;
328         gint i;
329
330         message = camel_filter_search_get_message (fms, f);
331
332         for (i = 0; i < argc && !matched; i++) {
333                 if (argv[i]->type == CAMEL_SEXP_RES_STRING)
334                         matched = camel_medium_get_header (CAMEL_MEDIUM (message), argv[i]->value.string) != NULL;
335         }
336
337         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
338         r->value.boolean = matched;
339
340         return r;
341 }
342
343 static CamelSExpResult *
344 header_regex (struct _CamelSExp *f,
345               gint argc,
346               struct _CamelSExpResult **argv,
347               FilterMessageSearch *fms)
348 {
349         CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
350         CamelMimeMessage *message;
351         regex_t pattern;
352         const gchar *contents;
353
354         message = camel_filter_search_get_message (fms, f);
355
356         if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING
357             && (contents = camel_medium_get_header (CAMEL_MEDIUM (message), argv[0]->value.string))
358             && camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE, argc - 1, argv + 1, fms->error) == 0) {
359                 r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
360                 regfree (&pattern);
361         } else
362                 r->value.boolean = FALSE;
363
364         return r;
365 }
366
367 static gchar *
368 get_full_header (CamelMimeMessage *message)
369 {
370         CamelMimePart *mime_part;
371         GString *str = g_string_new ("");
372         gchar   *ret;
373         struct _camel_header_raw *h;
374
375         mime_part = CAMEL_MIME_PART (message);
376
377         for (h = mime_part->headers; h; h = h->next) {
378                 if (h->value != NULL) {
379                         g_string_append (str, h->name);
380                         if (isspace (h->value[0]))
381                                 g_string_append (str, ":");
382                         else
383                                 g_string_append (str, ": ");
384                         g_string_append (str, h->value);
385                         g_string_append_c (str, '\n');
386                 }
387         }
388
389         ret = str->str;
390         g_string_free (str, FALSE);
391
392         return ret;
393 }
394
395 static CamelSExpResult *
396 header_full_regex (struct _CamelSExp *f,
397                    gint argc,
398                    struct _CamelSExpResult **argv,
399                    FilterMessageSearch *fms)
400 {
401         CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
402         CamelMimeMessage *message;
403         regex_t pattern;
404         gchar *contents;
405
406         if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_NEWLINE,
407                                            argc, argv, fms->error) == 0) {
408                 message = camel_filter_search_get_message (fms, f);
409                 contents = get_full_header (message);
410                 r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
411                 g_free (contents);
412                 regfree (&pattern);
413         } else
414                 r->value.boolean = FALSE;
415
416         return r;
417 }
418
419 static CamelSExpResult *
420 match_all (struct _CamelSExp *f,
421            gint argc,
422            struct _CamelSExpTerm **argv,
423            FilterMessageSearch *fms)
424 {
425         /* match-all: when dealing with single messages is a no-op */
426         CamelSExpResult *r;
427
428         if (argc > 0)
429                 return camel_sexp_term_eval (f, argv[0]);
430
431         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
432         r->value.boolean = TRUE;
433
434         return r;
435 }
436
437 static CamelSExpResult *
438 body_contains (struct _CamelSExp *f,
439                gint argc,
440                struct _CamelSExpResult **argv,
441                FilterMessageSearch *fms)
442 {
443         CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
444         CamelMimeMessage *message;
445         regex_t pattern;
446
447         if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE, argc, argv, fms->error) == 0) {
448                 message = camel_filter_search_get_message (fms, f);
449                 r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
450                 regfree (&pattern);
451         } else
452                 r->value.boolean = FALSE;
453
454         return r;
455 }
456
457 static CamelSExpResult *
458 body_regex (struct _CamelSExp *f,
459             gint argc,
460             struct _CamelSExpResult **argv,
461             FilterMessageSearch *fms)
462 {
463         CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
464         CamelMimeMessage *message;
465         regex_t pattern;
466
467         if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE,
468                                            argc, argv, fms->error) == 0) {
469                 message = camel_filter_search_get_message (fms, f);
470                 r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
471                 regfree (&pattern);
472         } else
473                 r->value.boolean = FALSE;
474
475         return r;
476 }
477
478 static CamelSExpResult *
479 user_flag (struct _CamelSExp *f,
480            gint argc,
481            struct _CamelSExpResult **argv,
482            FilterMessageSearch *fms)
483 {
484         CamelSExpResult *r;
485         gboolean truth = FALSE;
486         gint i;
487
488         /* performs an OR of all words */
489         for (i = 0; i < argc && !truth; i++) {
490                 if (argv[i]->type == CAMEL_SEXP_RES_STRING
491                     && camel_message_info_user_flag (fms->info, argv[i]->value.string)) {
492                         truth = TRUE;
493                         break;
494                 }
495         }
496
497         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
498         r->value.boolean = truth;
499
500         return r;
501 }
502
503 static CamelSExpResult *
504 system_flag (struct _CamelSExp *f,
505              gint argc,
506              struct _CamelSExpResult **argv,
507              FilterMessageSearch *fms)
508 {
509         CamelSExpResult *r;
510
511         if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
512                 camel_sexp_fatal_error (f, _("Invalid arguments to (system-flag)"));
513
514         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
515         r->value.boolean = camel_system_flag_get (camel_message_info_flags (fms->info), argv[0]->value.string);
516
517         return r;
518 }
519
520 static CamelSExpResult *
521 user_tag (struct _CamelSExp *f,
522           gint argc,
523           struct _CamelSExpResult **argv,
524           FilterMessageSearch *fms)
525 {
526         CamelSExpResult *r;
527         const gchar *tag;
528
529         if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
530                 camel_sexp_fatal_error (f, _("Invalid arguments to (user-tag)"));
531
532         tag = camel_message_info_user_tag (fms->info, argv[0]->value.string);
533
534         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
535         r->value.string = g_strdup (tag ? tag : "");
536
537         return r;
538 }
539
540 static CamelSExpResult *
541 get_sent_date (struct _CamelSExp *f,
542                gint argc,
543                struct _CamelSExpResult **argv,
544                FilterMessageSearch *fms)
545 {
546         CamelMimeMessage *message;
547         CamelSExpResult *r;
548
549         message = camel_filter_search_get_message (fms, f);
550         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
551         r->value.number = camel_mime_message_get_date (message, NULL);
552
553         return r;
554 }
555
556 static CamelSExpResult *
557 get_received_date (struct _CamelSExp *f,
558                    gint argc,
559                    struct _CamelSExpResult **argv,
560                    FilterMessageSearch *fms)
561 {
562         CamelMimeMessage *message;
563         CamelSExpResult *r;
564
565         message = camel_filter_search_get_message (fms, f);
566         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
567         r->value.number = camel_mime_message_get_date_received (message, NULL);
568
569         return r;
570 }
571
572 static CamelSExpResult *
573 get_current_date (struct _CamelSExp *f,
574                   gint argc,
575                   struct _CamelSExpResult **argv,
576                   FilterMessageSearch *fms)
577 {
578         CamelSExpResult *r;
579
580         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
581         r->value.number = time (NULL);
582
583         return r;
584 }
585
586 static CamelSExpResult *
587 get_relative_months (struct _CamelSExp *f,
588                      gint argc,
589                      struct _CamelSExpResult **argv,
590                      FilterMessageSearch *fms)
591 {
592         CamelSExpResult *r;
593
594         if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) {
595                 r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
596                 r->value.boolean = FALSE;
597
598                 g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc);
599         } else {
600                 r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
601                 r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number);
602         }
603
604         return r;
605 }
606
607 static CamelService *
608 ref_service_for_source (CamelSession *session,
609                         const gchar *src)
610 {
611         CamelService *service = NULL;
612
613         /* Source strings are now CamelService UIDs. */
614         if (src != NULL)
615                 service = camel_session_ref_service (session, src);
616
617         /* For backward-compability, also handle CamelService URLs. */
618         if (service == NULL && src != NULL) {
619                 CamelURL *url;
620
621                 url = camel_url_new (src, NULL);
622
623                 if (service == NULL && url != NULL)
624                         service = camel_session_ref_service_by_url (
625                                 session, url, CAMEL_PROVIDER_STORE);
626
627                 if (service == NULL && url != NULL)
628                         service = camel_session_ref_service_by_url (
629                                 session, url, CAMEL_PROVIDER_TRANSPORT);
630
631                 if (url != NULL)
632                         camel_url_free (url);
633         }
634
635         return service;
636 }
637
638 static CamelSExpResult *
639 header_source (struct _CamelSExp *f,
640                gint argc,
641                struct _CamelSExpResult **argv,
642                FilterMessageSearch *fms)
643 {
644         CamelMimeMessage *message;
645         CamelSExpResult *r;
646         const gchar *src;
647         CamelService *msg_source = NULL;
648         gboolean truth = FALSE;
649
650         if (fms->source) {
651                 src = fms->source;
652         } else {
653                 message = camel_filter_search_get_message (fms, f);
654                 src = camel_mime_message_get_source (message);
655         }
656
657         if (src)
658                 msg_source = ref_service_for_source (fms->session, src);
659
660         if (msg_source != NULL) {
661                 gint ii;
662
663                 for (ii = 0; ii < argc && !truth; ii++) {
664                         if (argv[ii]->type == CAMEL_SEXP_RES_STRING) {
665                                 CamelService *candidate;
666
667                                 candidate = ref_service_for_source (
668                                         fms->session,
669                                         argv[ii]->value.string);
670                                 if (candidate != NULL) {
671                                         truth = (msg_source == candidate);
672                                         g_object_unref (candidate);
673                                 }
674                         }
675                 }
676
677                 g_object_unref (msg_source);
678         }
679
680         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
681         r->value.boolean = truth;
682
683         return r;
684 }
685
686 /* remember, the size comparisons are done at Kbytes */
687 static CamelSExpResult *
688 get_size (struct _CamelSExp *f,
689           gint argc,
690           struct _CamelSExpResult **argv,
691           FilterMessageSearch *fms)
692 {
693         CamelSExpResult *r;
694
695         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
696         r->value.number = camel_message_info_size (fms->info) / 1024;
697
698         return r;
699 }
700
701 #ifndef G_OS_WIN32
702 static void
703 child_setup_func (gpointer user_data)
704 {
705         setsid ();
706 }
707 #else
708 #define child_setup_func NULL
709 #endif
710
711 typedef struct {
712         gint child_status;
713         GMainLoop *loop;
714 } child_watch_data_t;
715
716 static void
717 child_watch (GPid pid,
718              gint status,
719              gpointer data)
720 {
721         child_watch_data_t *child_watch_data = data;
722
723         g_spawn_close_pid (pid);
724
725         child_watch_data->child_status = status;
726         g_main_loop_quit (child_watch_data->loop);
727 }
728
729 static gint
730 run_command (struct _CamelSExp *f,
731              gint argc,
732              struct _CamelSExpResult **argv,
733              FilterMessageSearch *fms)
734 {
735         CamelMimeMessage *message;
736         CamelStream *stream;
737         gint i;
738         gint pipe_to_child;
739         GPid child_pid;
740         GError *error = NULL;
741         GPtrArray *args;
742         child_watch_data_t child_watch_data;
743         GSource *source;
744         GMainContext *context;
745
746         if (argc < 1 || argv[0]->value.string[0] == '\0')
747                 return 0;
748
749         args = g_ptr_array_new ();
750         for (i = 0; i < argc; i++)
751                 g_ptr_array_add (args, argv[i]->value.string);
752         g_ptr_array_add (args, NULL);
753
754         if (!g_spawn_async_with_pipes (NULL,
755                                        (gchar **) args->pdata,
756                                        NULL,
757                                        G_SPAWN_DO_NOT_REAP_CHILD |
758                                        G_SPAWN_SEARCH_PATH |
759                                        G_SPAWN_STDOUT_TO_DEV_NULL |
760                                        G_SPAWN_STDERR_TO_DEV_NULL,
761                                        child_setup_func,
762                                        NULL,
763                                        &child_pid,
764                                        &pipe_to_child,
765                                        NULL,
766                                        NULL,
767                                        &error)) {
768                 g_ptr_array_free (args, TRUE);
769
770                 g_set_error (
771                         fms->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
772                         _("Failed to create child process '%s': %s"),
773                         argv[0]->value.string, error->message);
774                 g_error_free (error);
775                 return -1;
776         }
777
778         g_ptr_array_free (args, TRUE);
779
780         message = camel_filter_search_get_message (fms, f);
781
782         stream = camel_stream_fs_new_with_fd (pipe_to_child);
783         camel_data_wrapper_write_to_stream_sync (
784                 CAMEL_DATA_WRAPPER (message), stream, NULL, NULL);
785         camel_stream_flush (stream, NULL, NULL);
786         g_object_unref (stream);
787
788         context = g_main_context_new ();
789         child_watch_data.loop = g_main_loop_new (context, FALSE);
790         g_main_context_unref (context);
791
792         source = g_child_watch_source_new (child_pid);
793         g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
794         g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
795         g_source_unref (source);
796
797         g_main_loop_run (child_watch_data.loop);
798         g_main_loop_unref (child_watch_data.loop);
799
800 #ifndef G_OS_WIN32
801         if (WIFEXITED (child_watch_data.child_status))
802                 return WEXITSTATUS (child_watch_data.child_status);
803         else
804                 return -1;
805 #else
806         return child_watch_data.child_status;
807 #endif
808 }
809
810 static CamelSExpResult *
811 pipe_message (struct _CamelSExp *f,
812               gint argc,
813               struct _CamelSExpResult **argv,
814               FilterMessageSearch *fms)
815 {
816         CamelSExpResult *r;
817         gint retval, i;
818
819         /* make sure all args are strings */
820         for (i = 0; i < argc; i++) {
821                 if (argv[i]->type != CAMEL_SEXP_RES_STRING) {
822                         retval = -1;
823                         goto done;
824                 }
825         }
826
827         retval = run_command (f, argc, argv, fms);
828
829  done:
830         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
831         r->value.number = retval;
832
833         return r;
834 }
835
836 static CamelSExpResult *
837 junk_test (struct _CamelSExp *f,
838            gint argc,
839            struct _CamelSExpResult **argv,
840            FilterMessageSearch *fms)
841 {
842         CamelSExpResult *r;
843         CamelMessageInfo *info = fms->info;
844         CamelJunkFilter *junk_filter;
845         CamelMessageFlags flags;
846         CamelMimeMessage *message;
847         CamelJunkStatus status;
848         const GHashTable *ht;
849         const struct _camel_header_param *node;
850         gboolean sender_is_known;
851         gboolean message_is_junk = FALSE;
852         GError *error = NULL;
853
854         junk_filter = camel_session_get_junk_filter (fms->session);
855         if (junk_filter == NULL)
856                 goto exit;
857
858         /* Check if the message is already classified. */
859
860         flags = camel_message_info_flags (info);
861
862         if (flags & CAMEL_MESSAGE_JUNK) {
863                 if (camel_debug ("junk"))
864                         printf (
865                                 "Message has a Junk flag set already, "
866                                 "skipping junk test...\n");
867                 goto exit;
868         }
869
870         if (flags & CAMEL_MESSAGE_NOTJUNK) {
871                 if (camel_debug ("junk"))
872                         printf (
873                                 "Message has a NotJunk flag set already, "
874                                 "skipping junk test...\n");
875                 goto exit;
876         }
877
878         /* Check the headers for a junk designation. */
879
880         ht = camel_session_get_junk_headers (fms->session);
881         node = camel_message_info_headers (info);
882
883         while (node != NULL) {
884                 const gchar *value = NULL;
885
886                 if (node->name != NULL)
887                         value = g_hash_table_lookup (
888                                 (GHashTable *) ht, node->name);
889
890                 message_is_junk =
891                         (value != NULL) &&
892                         (camel_strstrcase (node->value, value) != NULL);
893
894                 if (message_is_junk) {
895                         if (camel_debug ("junk"))
896                                 printf (
897                                         "Message contains \"%s: %s\"",
898                                         node->name, value);
899                         goto done;
900                 }
901
902                 node = node->next;
903         }
904
905         /* If the sender is known, the message is not junk. */
906
907         sender_is_known = camel_session_lookup_addressbook (
908                 fms->session, camel_message_info_from (info));
909         if (camel_debug ("junk"))
910                 printf (
911                         "Sender '%s' in book? %d\n",
912                         camel_message_info_from (info),
913                         sender_is_known);
914         if (sender_is_known)
915                 goto done;
916
917         /* Consult 3rd party junk filtering software. */
918
919         message = camel_filter_search_get_message (fms, f);
920         status = camel_junk_filter_classify (
921                 junk_filter, message, NULL, &error);
922
923         if (error == NULL) {
924                 const gchar *status_desc;
925
926                 switch (status) {
927                         case CAMEL_JUNK_STATUS_INCONCLUSIVE:
928                                 status_desc = "inconclusive";
929                                 message_is_junk = FALSE;
930                                 break;
931                         case CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK:
932                                 status_desc = "junk";
933                                 message_is_junk = TRUE;
934                                 break;
935                         case CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK:
936                                 status_desc = "not junk";
937                                 message_is_junk = FALSE;
938                                 break;
939                         default:
940                                 g_warn_if_reached ();
941                                 status_desc = "invalid";
942                                 message_is_junk = FALSE;
943                                 break;
944                 }
945
946                 if (camel_debug ("junk"))
947                         g_print (
948                                 "Junk filter classification: %s\n",
949                                 status_desc);
950         } else {
951                 g_warn_if_fail (status == CAMEL_JUNK_STATUS_ERROR);
952                 g_warning ("%s: %s", G_STRFUNC, error->message);
953                 g_error_free (error);
954                 message_is_junk = FALSE;
955         }
956
957 done:
958         if (camel_debug ("junk"))
959                 printf (
960                         "Message is determined to be %s\n",
961                         message_is_junk ? "*JUNK*" : "clean");
962
963 exit:
964         r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
965         r->value.number = message_is_junk;
966
967         return r;
968 }
969
970 /**
971  * camel_filter_search_match:
972  * @session:
973  * @get_message: function to retrieve the message if necessary
974  * @data: data for above
975  * @info:
976  * @source:
977  * @expression:
978  * @error: return location for a #GError, or %NULL
979  *
980  * Returns: one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or
981  * CAMEL_SEARCH_ERROR.
982  **/
983 gint
984 camel_filter_search_match (CamelSession *session,
985                            CamelFilterSearchGetMessageFunc get_message,
986                            gpointer data,
987                            CamelMessageInfo *info,
988                            const gchar *source,
989                            const gchar *expression,
990                            GError **error)
991 {
992         FilterMessageSearch fms;
993         CamelSExp *sexp;
994         CamelSExpResult *result;
995         gboolean retval;
996         gint i;
997
998         fms.session = session;
999         fms.get_message = get_message;
1000         fms.get_message_data = data;
1001         fms.message = NULL;
1002         fms.info = info;
1003         fms.source = source;
1004         fms.error = error;
1005
1006         sexp = camel_sexp_new ();
1007
1008         for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
1009                 if (symbols[i].type == 1)
1010                         camel_sexp_add_ifunction (sexp, 0, symbols[i].name, (CamelSExpIFunc) symbols[i].func, &fms);
1011                 else
1012                         camel_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &fms);
1013         }
1014
1015         camel_sexp_input_text (sexp, expression, strlen (expression));
1016         if (camel_sexp_parse (sexp) == -1) {
1017                 /* A filter search is a search through your filters,
1018                  * ie. your filters is the corpus being searched thru. */
1019                 g_set_error (
1020                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1021                         _("Error executing filter search: %s: %s"),
1022                         camel_sexp_error (sexp), expression);
1023                 goto error;
1024         }
1025
1026         result = camel_sexp_eval (sexp);
1027         if (result == NULL) {
1028                 g_set_error (
1029                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1030                         _("Error executing filter search: %s: %s"),
1031                         camel_sexp_error (sexp), expression);
1032                 goto error;
1033         }
1034
1035         if (result->type == CAMEL_SEXP_RES_BOOL)
1036                 retval = result->value.boolean ? CAMEL_SEARCH_MATCHED : CAMEL_SEARCH_NOMATCH;
1037         else
1038                 retval = CAMEL_SEARCH_NOMATCH;
1039
1040         camel_sexp_result_free (sexp, result);
1041         g_object_unref (sexp);
1042
1043         if (fms.message)
1044                 g_object_unref (fms.message);
1045
1046         return retval;
1047
1048  error:
1049         if (fms.message)
1050                 g_object_unref (fms.message);
1051
1052         g_object_unref (sexp);
1053
1054         return CAMEL_SEARCH_ERROR;
1055 }