1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Authors: Jeffrey Stedfast <fejj@ximian.com>
4 * Michael Zucchi <NotZed@Ximian.com>
6 * Copyright 2000 Ximian, Inc. (www.ximian.com)
7 * Copyright 2001 Ximian Inc. (www.ximian.com)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU Lesser General Public
11 * License as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this program; if not, write to the
20 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
29 /* POSIX requires <sys/types.h> be included before <regex.h> */
30 #include <sys/types.h>
46 #include <glib/gi18n-lib.h>
48 #include <libedataserver/e-iconv.h>
49 #include <libedataserver/e-sexp.h>
51 #include "camel-debug.h"
52 #include "camel-exception.h"
53 #include "camel-filter-search.h"
54 #include "camel-mime-message.h"
55 #include "camel-multipart.h"
56 #include "camel-provider.h"
57 #include "camel-search-private.h"
58 #include "camel-session.h"
59 #include "camel-stream-fs.h"
60 #include "camel-stream-mem.h"
61 #include "camel-url.h"
66 CamelSession *session;
67 CamelFilterSearchGetMessageFunc get_message;
68 void *get_message_data;
69 CamelMimeMessage *message;
70 CamelMessageInfo *info;
73 } FilterMessageSearch;
76 static ESExpResult *header_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
77 static ESExpResult *header_matches (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
78 static ESExpResult *header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
79 static ESExpResult *header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
80 static ESExpResult *header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
81 static ESExpResult *header_soundex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
82 static ESExpResult *header_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
83 static ESExpResult *header_full_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
84 static ESExpResult *match_all (struct _ESExp *f, int argc, struct _ESExpTerm **argv, FilterMessageSearch *fms);
85 static ESExpResult *body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
86 static ESExpResult *body_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
87 static ESExpResult *user_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
88 static ESExpResult *user_tag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
89 static ESExpResult *system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
90 static ESExpResult *get_sent_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
91 static ESExpResult *get_received_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
92 static ESExpResult *get_current_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
93 static ESExpResult *header_source (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
94 static ESExpResult *get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
95 static ESExpResult *pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
96 static ESExpResult *junk_test (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms);
98 /* builtin functions */
102 int type; /* set to 1 if a function can perform shortcut evaluation, or
103 doesn't execute everything, 0 otherwise */
105 { "match-all", (ESExpFunc *) match_all, 1 },
106 { "body-contains", (ESExpFunc *) body_contains, 0 },
107 { "body-regex", (ESExpFunc *) body_regex, 0 },
108 { "header-contains", (ESExpFunc *) header_contains, 0 },
109 { "header-matches", (ESExpFunc *) header_matches, 0 },
110 { "header-starts-with", (ESExpFunc *) header_starts_with, 0 },
111 { "header-ends-with", (ESExpFunc *) header_ends_with, 0 },
112 { "header-exists", (ESExpFunc *) header_exists, 0 },
113 { "header-soundex", (ESExpFunc *) header_soundex, 0 },
114 { "header-regex", (ESExpFunc *) header_regex, 0 },
115 { "header-full-regex", (ESExpFunc *) header_full_regex, 0 },
116 { "user-tag", (ESExpFunc *) user_tag, 0 },
117 { "user-flag", (ESExpFunc *) user_flag, 0 },
118 { "system-flag", (ESExpFunc *) system_flag, 0 },
119 { "get-sent-date", (ESExpFunc *) get_sent_date, 0 },
120 { "get-received-date", (ESExpFunc *) get_received_date, 0 },
121 { "get-current-date", (ESExpFunc *) get_current_date, 0 },
122 { "header-source", (ESExpFunc *) header_source, 0 },
123 { "get-size", (ESExpFunc *) get_size, 0 },
124 { "pipe-message", (ESExpFunc *) pipe_message, 0 },
125 { "junk-test", (ESExpFunc *) junk_test, 0 },
129 static CamelMimeMessage *
130 camel_filter_search_get_message (FilterMessageSearch *fms, struct _ESExp *sexp)
135 fms->message = fms->get_message (fms->get_message_data, fms->ex);
137 if (fms->message == NULL)
138 e_sexp_fatal_error (sexp, _("Failed to retrieve message"));
144 check_header (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms, camel_search_match_t how)
146 gboolean matched = FALSE;
150 if (argc > 1 && argv[0]->type == ESEXP_RES_STRING) {
151 char *name = argv[0]->value.string;
153 /* shortcut: a match for "" against any header always matches */
154 for (i=1; i<argc && !matched; i++)
155 matched = argv[i]->type == ESEXP_RES_STRING && argv[i]->value.string[0] == 0;
157 if (g_ascii_strcasecmp(name, "x-camel-mlist") == 0) {
158 const char *list = camel_message_info_mlist(fms->info);
161 for (i=1; i<argc && !matched; i++) {
162 if (argv[i]->type == ESEXP_RES_STRING)
163 matched = camel_search_header_match(list, argv[i]->value.string, how, CAMEL_SEARCH_TYPE_MLIST, NULL);
167 CamelMimeMessage *message = camel_filter_search_get_message (fms, f);
168 struct _camel_header_raw *header;
169 const char *charset = NULL;
170 camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
171 CamelContentType *ct;
173 /* FIXME: what about Resent-To, Resent-Cc and Resent-From? */
174 if (g_ascii_strcasecmp("to", name) == 0 || g_ascii_strcasecmp("cc", name) == 0 || g_ascii_strcasecmp("from", name) == 0)
175 type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
177 ct = camel_mime_part_get_content_type (CAMEL_MIME_PART (message));
179 charset = camel_content_type_param (ct, "charset");
180 charset = e_iconv_charset_name (charset);
184 for (header = ((CamelMimePart *)message)->headers; header && !matched; header = header->next) {
185 if (!g_ascii_strcasecmp(header->name, name)) {
186 for (i=1; i<argc && !matched; i++) {
187 if (argv[i]->type == ESEXP_RES_STRING)
188 matched = camel_search_header_match(header->value, argv[i]->value.string, how, type, charset);
195 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
196 r->value.bool = matched;
202 header_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
204 return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_CONTAINS);
209 header_matches (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
211 return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_EXACT);
215 header_starts_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
217 return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_STARTS);
221 header_ends_with (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
223 return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_ENDS);
227 header_soundex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
229 return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_SOUNDEX);
233 header_exists (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
235 CamelMimeMessage *message;
236 gboolean matched = FALSE;
240 message = camel_filter_search_get_message (fms, f);
242 for (i = 0; i < argc && !matched; i++) {
243 if (argv[i]->type == ESEXP_RES_STRING)
244 matched = camel_medium_get_header (CAMEL_MEDIUM (message), argv[i]->value.string) != NULL;
247 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
248 r->value.bool = matched;
254 header_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
256 ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
257 CamelMimeMessage *message;
259 const char *contents;
261 message = camel_filter_search_get_message (fms, f);
263 if (argc > 1 && argv[0]->type == ESEXP_RES_STRING
264 && (contents = camel_medium_get_header (CAMEL_MEDIUM (message), argv[0]->value.string))
265 && camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE, argc-1, argv+1, fms->ex) == 0) {
266 r->value.bool = regexec (&pattern, contents, 0, NULL, 0) == 0;
269 r->value.bool = FALSE;
275 get_full_header (CamelMimeMessage *message)
277 CamelMimePart *mp = CAMEL_MIME_PART (message);
278 GString *str = g_string_new ("");
280 struct _camel_header_raw *h;
282 for (h = mp->headers; h; h = h->next) {
283 if (h->value != NULL) {
284 g_string_append (str, h->name);
285 if (isspace (h->value[0]))
286 g_string_append (str, ":");
288 g_string_append (str, ": ");
289 g_string_append (str, h->value);
290 g_string_append_c(str, '\n');
295 g_string_free (str, FALSE);
301 header_full_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
303 ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
304 CamelMimeMessage *message;
308 if (camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_NEWLINE,
309 argc, argv, fms->ex) == 0) {
310 message = camel_filter_search_get_message (fms, f);
311 contents = get_full_header (message);
312 r->value.bool = regexec (&pattern, contents, 0, NULL, 0) == 0;
316 r->value.bool = FALSE;
322 match_all (struct _ESExp *f, int argc, struct _ESExpTerm **argv, FilterMessageSearch *fms)
324 /* match-all: when dealing with single messages is a no-op */
328 return e_sexp_term_eval (f, argv[0]);
330 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
331 r->value.bool = TRUE;
337 body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
339 ESExpResult *r = e_sexp_result_new (f, ESEXP_RES_BOOL);
340 CamelMimeMessage *message;
343 if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE, argc, argv, fms->ex) == 0) {
344 message = camel_filter_search_get_message (fms, f);
345 r->value.bool = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
348 r->value.bool = FALSE;
354 body_regex (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
356 ESExpResult *r = e_sexp_result_new(f, ESEXP_RES_BOOL);
357 CamelMimeMessage *message;
360 if (camel_search_build_match_regex(&pattern, CAMEL_SEARCH_MATCH_ICASE|CAMEL_SEARCH_MATCH_REGEX|CAMEL_SEARCH_MATCH_NEWLINE,
361 argc, argv, fms->ex) == 0) {
362 message = camel_filter_search_get_message (fms, f);
363 r->value.bool = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
366 r->value.bool = FALSE;
372 user_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
375 gboolean truth = FALSE;
378 /* performs an OR of all words */
379 for (i = 0; i < argc && !truth; i++) {
380 if (argv[i]->type == ESEXP_RES_STRING
381 && camel_message_info_user_flag(fms->info, argv[i]->value.string)) {
387 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
388 r->value.bool = truth;
394 system_flag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
398 if (argc != 1 || argv[0]->type != ESEXP_RES_STRING)
399 e_sexp_fatal_error(f, _("Invalid arguments to (system-flag)"));
401 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
402 r->value.bool = camel_system_flag_get (camel_message_info_flags(fms->info), argv[0]->value.string);
408 user_tag (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
413 if (argc != 1 || argv[0]->type != ESEXP_RES_STRING)
414 e_sexp_fatal_error(f, _("Invalid arguments to (user-tag)"));
416 tag = camel_message_info_user_tag(fms->info, argv[0]->value.string);
418 r = e_sexp_result_new (f, ESEXP_RES_STRING);
419 r->value.string = g_strdup (tag ? tag : "");
425 get_sent_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
427 CamelMimeMessage *message;
430 message = camel_filter_search_get_message (fms, f);
431 r = e_sexp_result_new (f, ESEXP_RES_INT);
432 r->value.number = camel_mime_message_get_date (message, NULL);
438 get_received_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
440 CamelMimeMessage *message;
443 message = camel_filter_search_get_message (fms, f);
444 r = e_sexp_result_new (f, ESEXP_RES_INT);
445 r->value.number = camel_mime_message_get_date_received (message, NULL);
451 get_current_date (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
455 r = e_sexp_result_new (f, ESEXP_RES_INT);
456 r->value.number = time (NULL);
462 header_source (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
464 CamelMimeMessage *message;
467 int truth = FALSE, i;
468 CamelProvider *provider;
469 CamelURL *uria, *urib;
474 message = camel_filter_search_get_message(fms, f);
475 src = camel_mime_message_get_source(message);
479 && (provider = camel_provider_get(src, NULL))
480 && provider->url_equal) {
481 uria = camel_url_new(src, NULL);
483 for (i=0;i<argc && !truth;i++) {
484 if (argv[i]->type == ESEXP_RES_STRING
485 && (urib = camel_url_new(argv[i]->value.string, NULL))) {
486 truth = provider->url_equal(uria, urib);
487 camel_url_free(urib);
490 camel_url_free(uria);
494 r = e_sexp_result_new(f, ESEXP_RES_BOOL);
495 r->value.bool = truth;
500 /* remember, the size comparisons are done at Kbytes */
502 get_size (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
506 r = e_sexp_result_new(f, ESEXP_RES_INT);
507 r->value.number = camel_message_info_size(fms->info) / 1024;
514 child_setup_func (gpointer user_data)
519 #define child_setup_func NULL
525 } child_watch_data_t;
528 child_watch (GPid pid,
532 child_watch_data_t *child_watch_data = data;
534 g_spawn_close_pid (pid);
536 child_watch_data->child_status = status;
537 g_main_loop_quit (child_watch_data->loop);
541 run_command (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
543 CamelMimeMessage *message;
548 GError *error = NULL;
550 child_watch_data_t child_watch_data;
552 GMainContext *context;
554 if (argc < 1 || argv[0]->value.string[0] == '\0')
557 args = g_ptr_array_new ();
558 for (i = 0; i < argc; i++)
559 g_ptr_array_add (args, argv[i]->value.string);
560 g_ptr_array_add (args, NULL);
562 if (!g_spawn_async_with_pipes (NULL,
563 (gchar **) args->pdata,
565 G_SPAWN_DO_NOT_REAP_CHILD |
566 G_SPAWN_SEARCH_PATH |
567 G_SPAWN_STDOUT_TO_DEV_NULL |
568 G_SPAWN_STDERR_TO_DEV_NULL,
576 g_ptr_array_free (args, TRUE);
578 camel_exception_setv (fms->ex, CAMEL_EXCEPTION_SYSTEM,
579 _("Failed to create create child process '%s': %s"),
580 argv[0]->value.string, error->message);
581 g_error_free (error);
585 g_ptr_array_free (args, TRUE);
587 message = camel_filter_search_get_message (fms, f);
589 stream = camel_stream_fs_new_with_fd (pipe_to_child);
590 camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream);
591 camel_stream_flush (stream);
592 camel_object_unref (stream);
594 context = g_main_context_new ();
595 child_watch_data.loop = g_main_loop_new (context, FALSE);
596 g_main_context_unref (context);
598 source = g_child_watch_source_new (child_pid);
599 g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
600 g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
601 g_source_unref (source);
603 g_main_loop_run (child_watch_data.loop);
604 g_main_loop_unref (child_watch_data.loop);
607 if (WIFEXITED (child_watch_data.child_status))
608 return WEXITSTATUS (child_watch_data.child_status);
612 return child_watch_data.child_status;
617 pipe_message (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
622 /* make sure all args are strings */
623 for (i = 0; i < argc; i++) {
624 if (argv[i]->type != ESEXP_RES_STRING) {
630 retval = run_command (f, argc, argv, fms);
633 r = e_sexp_result_new (f, ESEXP_RES_INT);
634 r->value.number = retval;
640 junk_test (struct _ESExp *f, int argc, struct _ESExpResult **argv, FilterMessageSearch *fms)
643 gboolean retval = FALSE;
645 if (fms->session->junk_plugin != NULL) {
646 retval = camel_junk_plugin_check_junk (fms->session->junk_plugin, camel_filter_search_get_message (fms, f));
648 if (camel_debug ("junk"))
649 printf("junk filter => %s\n", retval ? "*JUNK*" : "clean");
652 r = e_sexp_result_new (f, ESEXP_RES_BOOL);
653 r->value.number = retval;
659 * camel_filter_search_match:
661 * @get_message: function to retrieve the message if necessary
662 * @data: data for above
668 * Returns one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or CAMEL_SEARCH_ERROR.
671 camel_filter_search_match (CamelSession *session,
672 CamelFilterSearchGetMessageFunc get_message, void *data,
673 CamelMessageInfo *info, const char *source,
674 const char *expression, CamelException *ex)
676 FilterMessageSearch fms;
682 fms.session = session;
683 fms.get_message = get_message;
684 fms.get_message_data = data;
690 sexp = e_sexp_new ();
692 for (i = 0; i < sizeof (symbols) / sizeof (symbols[0]); i++) {
693 if (symbols[i].type == 1)
694 e_sexp_add_ifunction (sexp, 0, symbols[i].name, (ESExpIFunc *)symbols[i].func, &fms);
696 e_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &fms);
699 e_sexp_input_text (sexp, expression, strlen (expression));
700 if (e_sexp_parse (sexp) == -1) {
701 if (!camel_exception_is_set (ex))
702 /* A filter search is a search through your filters, ie. your filters is the corpus being searched thru. */
703 camel_exception_setv (ex, 1, _("Error executing filter search: %s: %s"),
704 e_sexp_error (sexp), expression);
708 result = e_sexp_eval (sexp);
709 if (result == NULL) {
710 if (!camel_exception_is_set (ex))
711 camel_exception_setv (ex, 1, _("Error executing filter search: %s: %s"),
712 e_sexp_error (sexp), expression);
716 if (result->type == ESEXP_RES_BOOL)
717 retval = result->value.bool ? CAMEL_SEARCH_MATCHED : CAMEL_SEARCH_NOMATCH;
719 retval = CAMEL_SEARCH_NOMATCH;
721 e_sexp_result_free (sexp, result);
725 camel_object_unref (fms.message);
731 camel_object_unref (fms.message);
735 return CAMEL_SEARCH_ERROR;