/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Jeffrey Stedfast
* Michael Zucchi
*/
#include "evolution-data-server-config.h"
/* POSIX requires be included before */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef G_OS_WIN32
#include
#endif
#include "camel-debug.h"
#include "camel-filter-search.h"
#include "camel-iconv.h"
#include "camel-mime-message.h"
#include "camel-multipart.h"
#include "camel-provider.h"
#include "camel-search-private.h"
#include "camel-session.h"
#include "camel-stream-fs.h"
#include "camel-stream-mem.h"
#include "camel-string-utils.h"
#include "camel-url.h"
#define d(x)
typedef struct {
CamelSession *session;
CamelFilterSearchGetMessageFunc get_message;
gpointer get_message_data;
CamelMimeMessage *message;
CamelMessageInfo *info;
CamelFolder *folder;
const gchar *source;
FILE *logfile;
GCancellable *cancellable;
GError **error;
} FilterMessageSearch;
/* CamelSExp callbacks */
static CamelSExpResult *header_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_has_words (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_matches (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_starts_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_ends_with (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_exists (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_soundex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_full_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *match_all (struct _CamelSExp *f, gint argc, struct _CamelSExpTerm **argv, FilterMessageSearch *fms);
static CamelSExpResult *body_contains (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *body_regex (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *user_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *user_tag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *system_flag (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *get_sent_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *get_received_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *get_current_date (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *get_relative_months (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *header_source (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *get_size (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *pipe_message (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *junk_test (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *message_location (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *make_time_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *compare_date_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
static CamelSExpResult *addressbook_contains_func (struct _CamelSExp *f, gint argc, struct _CamelSExpResult **argv, FilterMessageSearch *fms);
/* builtin functions */
static struct {
const gchar *name;
CamelSExpFunc func;
gint type; /* set to 1 if a function can perform shortcut evaluation, or
doesn't execute everything, 0 otherwise */
} symbols[] = {
{ "match-all", (CamelSExpFunc) match_all, 1 },
{ "body-contains", (CamelSExpFunc) body_contains, 0 },
{ "body-regex", (CamelSExpFunc) body_regex, 0 },
{ "header-contains", (CamelSExpFunc) header_contains, 0 },
{ "header-has-words", (CamelSExpFunc) header_has_words, 0 },
{ "header-matches", (CamelSExpFunc) header_matches, 0 },
{ "header-starts-with", (CamelSExpFunc) header_starts_with, 0 },
{ "header-ends-with", (CamelSExpFunc) header_ends_with, 0 },
{ "header-exists", (CamelSExpFunc) header_exists, 0 },
{ "header-soundex", (CamelSExpFunc) header_soundex, 0 },
{ "header-regex", (CamelSExpFunc) header_regex, 0 },
{ "header-full-regex", (CamelSExpFunc) header_full_regex, 0 },
{ "user-tag", (CamelSExpFunc) user_tag, 0 },
{ "user-flag", (CamelSExpFunc) user_flag, 0 },
{ "system-flag", (CamelSExpFunc) system_flag, 0 },
{ "get-sent-date", (CamelSExpFunc) get_sent_date, 0 },
{ "get-received-date", (CamelSExpFunc) get_received_date, 0 },
{ "get-current-date", (CamelSExpFunc) get_current_date, 0 },
{ "get-relative-months",(CamelSExpFunc) get_relative_months,0 },
{ "header-source", (CamelSExpFunc) header_source, 0 },
{ "get-size", (CamelSExpFunc) get_size, 0 },
{ "pipe-message", (CamelSExpFunc) pipe_message, 0 },
{ "junk-test", (CamelSExpFunc) junk_test, 0 },
{ "message-location", (CamelSExpFunc) message_location, 0 },
{ "make-time", (CamelSExpFunc) make_time_func, 0 },
{ "compare-date", (CamelSExpFunc) compare_date_func, 0 },
{ "addressbook-contains",(CamelSExpFunc) addressbook_contains_func, 0 }
};
static void
camel_filter_search_log (FilterMessageSearch *fms,
const gchar *format,
...) G_GNUC_PRINTF (2, 3);
static void
camel_filter_search_log (FilterMessageSearch *fms,
const gchar *format,
...)
{
gchar *str;
va_list ap;
if (!fms || !fms->logfile || !format)
return;
va_start (ap, format);
str = g_strdup_vprintf (format, ap);
va_end (ap);
fprintf (fms->logfile, " %s\n", str);
fflush (fms->logfile);
g_free (str);
}
static CamelMimeMessage *
camel_filter_search_get_message (FilterMessageSearch *fms,
struct _CamelSExp *sexp)
{
GError *local_error = NULL;
if (fms->message)
return fms->message;
fms->message = fms->get_message (fms->get_message_data, fms->cancellable, &local_error);
if (fms->message == NULL) {
camel_filter_search_log (fms, "Failed to retrieve message: %s", local_error ? local_error->message : "Unknown error");
camel_sexp_fatal_error (sexp, _("Failed to retrieve message"));
}
if (local_error)
g_propagate_error (fms->error, local_error);
return fms->message;
}
static const gchar *
camel_filter_search_get_header (FilterMessageSearch *fms,
struct _CamelSExp *sexp,
const gchar *name)
{
const CamelNameValueArray *headers;
CamelMimeMessage *message;
const gchar *value;
g_return_val_if_fail (fms != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
headers = fms->info ? camel_message_info_get_headers (fms->info) : NULL;
if (headers) {
value = camel_name_value_array_get_named (headers, CAMEL_COMPARE_CASE_INSENSITIVE, name);
while (value && g_ascii_isspace (*value))
value++;
return value;
}
message = camel_filter_search_get_message (fms, sexp);
if (!message)
return NULL;
return camel_medium_get_header (CAMEL_MEDIUM (message), name);
}
static const CamelNameValueArray *
camel_filter_search_get_headers (FilterMessageSearch *fms,
struct _CamelSExp *sexp)
{
const CamelNameValueArray *headers;
g_return_val_if_fail (fms != NULL, NULL);
headers = fms->info ? camel_message_info_get_headers (fms->info) : NULL;
if (!headers) {
CamelMimeMessage *message;
message = camel_filter_search_get_message (fms, sexp);
if (message)
headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
}
return headers;
}
static gboolean
check_header_in_message_info (FilterMessageSearch *fms,
gint argc,
struct _CamelSExpResult **argv,
camel_search_match_t how,
gboolean *matched)
{
struct _KnownHeaders {
const gchar *header_name;
const gchar *info_name;
camel_search_t type;
} known_headers[] = {
{ "Subject", "subject", CAMEL_SEARCH_TYPE_ENCODED },
{ "From", "from", CAMEL_SEARCH_TYPE_ADDRESS_ENCODED },
{ "To", "to", CAMEL_SEARCH_TYPE_ADDRESS_ENCODED },
{ "Cc", "cc", CAMEL_SEARCH_TYPE_ADDRESS_ENCODED }
};
CamelMessageInfo *info;
const gchar *name;
gchar *value;
gboolean found = FALSE;
camel_search_t use_type;
gint ii;
g_return_val_if_fail (fms != NULL, FALSE);
g_return_val_if_fail (argc > 1, FALSE);
g_return_val_if_fail (argv != NULL, FALSE);
g_return_val_if_fail (matched != NULL, FALSE);
info = fms->info;
/* If there are headers available, then prefer them, thus the addresses are used
as received, not as they are stored in the folder summary. */
if (!info || camel_message_info_get_headers (info))
return FALSE;
name = argv[0]->value.string;
g_return_val_if_fail (name != NULL, FALSE);
/* test against any header */
if (!*name) {
gint jj;
for (jj = 0; jj < G_N_ELEMENTS (known_headers); jj++) {
value = NULL;
g_object_get (G_OBJECT (info), known_headers[jj].info_name, &value, NULL);
if (!value)
continue;
for (ii = 1; ii < argc && !*matched; ii++) {
if (argv[ii]->type == CAMEL_SEXP_RES_STRING) {
*matched = camel_search_header_match (value, argv[ii]->value.string, how, known_headers[jj].type, NULL);
camel_filter_search_log (fms, "Info value '%s' of header '%s' does %smatch '%s'",
value, known_headers[jj].header_name, *matched ? "" : "not ", argv[ii]->value.string);
}
}
g_free (value);
if (*matched)
return TRUE;
}
return FALSE;
}
value = NULL;
for (ii = 0; ii < G_N_ELEMENTS (known_headers); ii++) {
found = g_ascii_strcasecmp (name, known_headers[ii].header_name) == 0;
if (found) {
g_object_get (G_OBJECT (info), known_headers[ii].info_name, &value, NULL);
use_type = known_headers[ii].type;
break;
}
}
if (!found || !value) {
g_free (value);
return FALSE;
}
for (ii = 1; ii < argc && !*matched; ii++) {
if (argv[ii]->type == CAMEL_SEXP_RES_STRING) {
*matched = camel_search_header_match (value, argv[ii]->value.string, how, use_type, NULL);
camel_filter_search_log (fms, "Info value '%s' of header '%s' does %smatch '%s'",
value, name, *matched ? "" : "not ", argv[ii]->value.string);
}
}
g_free (value);
return TRUE;
}
static CamelSExpResult *
check_header (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms,
camel_search_match_t how)
{
gboolean matched = FALSE;
CamelSExpResult *r;
gint i;
if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING) {
gchar *name = argv[0]->value.string;
/* shortcut: a match for "" against any header always matches */
for (i = 1; i < argc && !matched; i++)
matched = argv[i]->type == CAMEL_SEXP_RES_STRING && argv[i]->value.string[0] == 0;
if (g_ascii_strcasecmp (name, "x-camel-mlist") == 0) {
const gchar *list = camel_message_info_get_mlist (fms->info);
if (list && *list) {
for (i = 1; i < argc && !matched; i++) {
if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
matched = camel_search_header_match (list, argv[i]->value.string, how, CAMEL_SEARCH_TYPE_MLIST, NULL);
camel_filter_search_log (fms, "Mailing list header value '%s' does %smatch '%s'", list, matched ? "" : "not ", argv[i]->value.string);
}
}
}
} else if (fms->message || !check_header_in_message_info (fms, argc, argv, how, &matched)) {
const CamelNameValueArray *headers;
const gchar *charset = NULL;
camel_search_t type = CAMEL_SEARCH_TYPE_ENCODED;
guint ii;
const gchar *header_name = NULL, *header_value = NULL;
headers = camel_filter_search_get_headers (fms, f);
if (camel_search_header_is_address (name)) {
type = CAMEL_SEARCH_TYPE_ADDRESS_ENCODED;
} else {
charset = camel_search_get_default_charset_from_headers (headers);
}
for (ii = 0; !matched && camel_name_value_array_get (headers, ii, &header_name, &header_value); ii++) {
/* empty name means any header */
if (!name || !*name || !g_ascii_strcasecmp (header_name, name)) {
for (i = 1; i < argc && !matched; i++) {
if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
matched = camel_search_header_match (header_value, argv[i]->value.string, how, type, charset);
camel_filter_search_log (fms, "Header '%s' value '%s' does %smatch '%s'",
header_name, header_value, matched ? "" : "not ", argv[i]->value.string);
}
}
}
}
}
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = matched;
return r;
}
static CamelSExpResult *
header_contains (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_CONTAINS);
}
static CamelSExpResult *
header_has_words (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_WORD);
}
static CamelSExpResult *
header_matches (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_EXACT);
}
static CamelSExpResult *
header_starts_with (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_STARTS);
}
static CamelSExpResult *
header_ends_with (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_ENDS);
}
static CamelSExpResult *
header_soundex (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
return check_header (f, argc, argv, fms, CAMEL_SEARCH_MATCH_SOUNDEX);
}
static CamelSExpResult *
header_exists (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
gboolean matched = FALSE;
CamelSExpResult *r;
gint i;
for (i = 0; i < argc && !matched; i++) {
if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
const gchar *name = argv[i]->value.string;
matched = camel_filter_search_get_header (fms, f, name) != NULL;
camel_filter_search_log (fms, "Header '%s' does %sexist", name, matched ? "" : "not ");
}
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = matched;
return r;
}
static CamelSExpResult *
header_regex (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
regex_t pattern;
gchar *contents = NULL;
if (argc > 1 && argv[0]->type == CAMEL_SEXP_RES_STRING
&& (contents = camel_search_get_header_decoded (argv[0]->value.string,
camel_filter_search_get_header (fms, f, argv[0]->value.string),
camel_search_get_default_charset_from_headers (camel_filter_search_get_headers (fms, f))))
&& camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE, argc - 1, argv + 1, fms->error) == 0) {
r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
camel_filter_search_log (fms, "Regex on header '%s' does %smatch value '%s'",
argv[0]->value.string, r->value.boolean ? "" : "not ", contents);
regfree (&pattern);
} else {
camel_filter_search_log (fms, "Regex on header not tested, skipping");
r->value.boolean = FALSE;
}
g_free (contents);
return r;
}
static CamelSExpResult *
header_full_regex (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
regex_t pattern;
if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_NEWLINE,
argc, argv, fms->error) == 0) {
const CamelNameValueArray *headers = camel_filter_search_get_headers (fms, f);
gchar *contents;
contents = camel_search_get_headers_decoded (headers, NULL);
r->value.boolean = regexec (&pattern, contents, 0, NULL, 0) == 0;
g_free (contents);
regfree (&pattern);
} else
r->value.boolean = FALSE;
camel_filter_search_log (fms, "Full regex on headers does %smatch", r->value.boolean ? "" : "not ");
return r;
}
static CamelSExpResult *
match_all (struct _CamelSExp *f,
gint argc,
struct _CamelSExpTerm **argv,
FilterMessageSearch *fms)
{
/* match-all: when dealing with single messages is a no-op */
CamelSExpResult *r;
if (argc > 0)
return camel_sexp_term_eval (f, argv[0]);
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = TRUE;
return r;
}
static CamelSExpResult *
body_contains (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
CamelMimeMessage *message;
regex_t pattern;
if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE, argc, argv, fms->error) == 0) {
message = camel_filter_search_get_message (fms, f);
r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
regfree (&pattern);
} else
r->value.boolean = FALSE;
camel_filter_search_log (fms, "Body contains does %smatch", r->value.boolean ? "" : "not ");
return r;
}
static CamelSExpResult *
body_regex (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
CamelMimeMessage *message;
regex_t pattern;
if (camel_search_build_match_regex (&pattern, CAMEL_SEARCH_MATCH_ICASE | CAMEL_SEARCH_MATCH_REGEX | CAMEL_SEARCH_MATCH_NEWLINE,
argc, argv, fms->error) == 0) {
message = camel_filter_search_get_message (fms, f);
r->value.boolean = camel_search_message_body_contains ((CamelDataWrapper *) message, &pattern);
regfree (&pattern);
} else
r->value.boolean = FALSE;
camel_filter_search_log (fms, "Body regex does %smatch", r->value.boolean ? "" : "not ");
return r;
}
static CamelSExpResult *
user_flag (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
gboolean truth = FALSE, have_any = FALSE;
gint i;
/* performs an OR of all words */
for (i = 0; i < argc && !truth; i++) {
if (argv[i]->type == CAMEL_SEXP_RES_STRING
&& camel_message_info_get_user_flag (fms->info, argv[i]->value.string)) {
camel_filter_search_log (fms, "User flag '%s' found", argv[i]->value.string);
have_any = TRUE;
truth = TRUE;
break;
} else if (argv[i]->type == CAMEL_SEXP_RES_STRING) {
have_any = TRUE;
camel_filter_search_log (fms, "User flag '%s' not found", argv[i]->value.string);
}
}
if (!have_any)
camel_filter_search_log (fms, "None user flag tried (possibly invalid filter rule definition)");
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = truth;
return r;
}
static CamelSExpResult *
system_flag (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
camel_sexp_fatal_error (f, _("Invalid arguments to (system-flag)"));
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = camel_system_flag_get (camel_message_info_get_flags (fms->info), argv[0]->value.string);
camel_filter_search_log (fms, "System flag '%s' does %smatch", argv[0]->value.string, r->value.boolean ? "" : "not ");
return r;
}
static CamelSExpResult *
user_tag (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
const gchar *tag;
if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
camel_sexp_fatal_error (f, _("Invalid arguments to (user-tag)"));
tag = camel_message_info_get_user_tag (fms->info, argv[0]->value.string);
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_STRING);
r->value.string = g_strdup (tag ? tag : "");
camel_filter_search_log (fms, "Got user tag '%s' with value '%s'", argv[0]->value.string, r->value.string);
return r;
}
static CamelSExpResult *
get_sent_date (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
gint64 sent_date;
CamelSExpResult *r;
if (fms->info) {
sent_date = camel_message_info_get_date_sent (fms->info);
} else {
CamelMimeMessage *message;
message = camel_filter_search_get_message (fms, f);
sent_date = camel_mime_message_get_date (message, NULL);
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = sent_date;
camel_filter_search_log (fms, "Got sent date '%" G_GINT64_FORMAT "'", (gint64) r->value.number);
return r;
}
static CamelSExpResult *
get_received_date (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
gint64 received_date;
CamelSExpResult *r;
if (fms->info) {
received_date = camel_message_info_get_date_received (fms->info);
} else {
CamelMimeMessage *message;
message = camel_filter_search_get_message (fms, f);
received_date = camel_mime_message_get_date_received (message, NULL);
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = received_date;
camel_filter_search_log (fms, "Got received date '%" G_GINT64_FORMAT "'", (gint64) r->value.number);
return r;
}
static CamelSExpResult *
get_current_date (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = time (NULL);
camel_filter_search_log (fms, "Got current date '%" G_GINT64_FORMAT "'", (gint64) r->value.number);
return r;
}
static CamelSExpResult *
get_relative_months (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_INT) {
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = FALSE;
g_debug ("%s: Expecting 1 argument, an integer, but got %d arguments", G_STRFUNC, argc);
camel_filter_search_log (fms, "Failed relative months: Expecting 1 argument, an integer, but got %d arguments", argc);
} else {
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = camel_folder_search_util_add_months (time (NULL), argv[0]->value.number);
camel_filter_search_log (fms, "Got relative months '%" G_GINT64_FORMAT "' for value '%d'", (gint64) r->value.number, argv[0]->value.number);
}
return r;
}
static CamelService *
ref_service_for_source (CamelSession *session,
const gchar *src)
{
CamelService *service = NULL;
/* Source strings are now CamelService UIDs. */
if (src != NULL)
service = camel_session_ref_service (session, src);
/* For backward-compability, also handle CamelService URLs. */
if (service == NULL && src != NULL) {
CamelURL *url;
url = camel_url_new (src, NULL);
if (service == NULL && url != NULL)
service = camel_session_ref_service_by_url (
session, url, CAMEL_PROVIDER_STORE);
if (service == NULL && url != NULL)
service = camel_session_ref_service_by_url (
session, url, CAMEL_PROVIDER_TRANSPORT);
if (url != NULL)
camel_url_free (url);
}
return service;
}
static CamelSExpResult *
header_source (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelMimeMessage *message;
CamelSExpResult *r;
const gchar *src;
CamelService *msg_source = NULL;
gboolean truth = FALSE;
if (fms->source) {
src = fms->source;
} else {
message = camel_filter_search_get_message (fms, f);
src = camel_mime_message_get_source (message);
}
if (src)
msg_source = ref_service_for_source (fms->session, src);
if (msg_source != NULL) {
gint ii;
for (ii = 0; ii < argc && !truth; ii++) {
if (argv[ii]->type == CAMEL_SEXP_RES_STRING) {
CamelService *candidate;
candidate = ref_service_for_source (
fms->session,
argv[ii]->value.string);
if (candidate != NULL) {
truth = (msg_source == candidate);
g_object_unref (candidate);
camel_filter_search_log (fms, "Message source '%s' does %smatch requested source '%s'",
src, truth ? "" : "not ", argv[ii]->value.string ? argv[ii]->value.string : "NULL");
} else {
camel_filter_search_log (fms, "Unknown requested message source '%s' in rule",
argv[ii]->value.string ? argv[ii]->value.string : "NULL");
}
}
}
g_object_unref (msg_source);
} else {
camel_filter_search_log (fms, "No message source service found for '%s'", src ? src : "NULL");
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = truth;
return r;
}
/* remember, the size comparisons are done at Kbytes */
static CamelSExpResult *
get_size (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = camel_message_info_get_size (fms->info) / 1024;
camel_filter_search_log (fms, "Got message size '%" G_GINT64_FORMAT "' (from %" G_GINT64_FORMAT ")",
(gint64) r->value.number, (gint64) camel_message_info_get_size (fms->info));
return r;
}
#ifndef G_OS_WIN32
static void
child_setup_func (gpointer user_data)
{
setsid ();
}
#else
#define child_setup_func NULL
#endif
typedef struct {
gint child_status;
GMainLoop *loop;
} child_watch_data_t;
static void
child_watch (GPid pid,
gint status,
gpointer data)
{
child_watch_data_t *child_watch_data = data;
g_spawn_close_pid (pid);
child_watch_data->child_status = status;
g_main_loop_quit (child_watch_data->loop);
}
static gint
run_command (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelMimeMessage *message;
CamelStream *stream;
gint i;
gint pipe_to_child;
GPid child_pid;
GError *error = NULL;
GPtrArray *args;
child_watch_data_t child_watch_data;
GSource *source;
GMainContext *context;
if (argc < 1 || argv[0]->value.string[0] == '\0')
return 0;
args = g_ptr_array_new ();
for (i = 0; i < argc; i++)
g_ptr_array_add (args, argv[i]->value.string);
g_ptr_array_add (args, NULL);
if (!g_spawn_async_with_pipes (NULL,
(gchar **) args->pdata,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_SEARCH_PATH |
G_SPAWN_STDOUT_TO_DEV_NULL |
G_SPAWN_STDERR_TO_DEV_NULL,
child_setup_func,
NULL,
&child_pid,
&pipe_to_child,
NULL,
NULL,
&error)) {
g_ptr_array_free (args, TRUE);
g_set_error (
fms->error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Failed to create child process ā%sā: %s"),
argv[0]->value.string, error->message);
g_error_free (error);
return -1;
}
g_ptr_array_free (args, TRUE);
message = camel_filter_search_get_message (fms, f);
stream = camel_stream_fs_new_with_fd (pipe_to_child);
camel_data_wrapper_write_to_stream_sync (
CAMEL_DATA_WRAPPER (message), stream, fms->cancellable, NULL);
camel_stream_flush (stream, fms->cancellable, NULL);
g_object_unref (stream);
context = g_main_context_new ();
child_watch_data.loop = g_main_loop_new (context, FALSE);
g_main_context_unref (context);
source = g_child_watch_source_new (child_pid);
g_source_set_callback (source, (GSourceFunc) child_watch, &child_watch_data, NULL);
g_source_attach (source, g_main_loop_get_context (child_watch_data.loop));
g_source_unref (source);
g_main_loop_run (child_watch_data.loop);
g_main_loop_unref (child_watch_data.loop);
#ifndef G_OS_WIN32
if (WIFEXITED (child_watch_data.child_status))
return WEXITSTATUS (child_watch_data.child_status);
else
return -1;
#else
return child_watch_data.child_status;
#endif
}
static CamelSExpResult *
pipe_message (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
gint retval, i;
/* make sure all args are strings */
for (i = 0; i < argc; i++) {
if (argv[i]->type != CAMEL_SEXP_RES_STRING) {
retval = -1;
goto done;
}
}
retval = run_command (f, argc, argv, fms);
done:
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_INT);
r->value.number = retval;
camel_filter_search_log (fms, "Pipe message result: %" G_GINT64_FORMAT, (gint64) r->value.number);
return r;
}
static CamelSExpResult *
junk_test (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
CamelMessageInfo *info = fms->info;
CamelJunkFilter *junk_filter;
CamelMessageFlags flags;
CamelMimeMessage *message;
CamelJunkStatus status;
const GHashTable *ht;
const CamelNameValueArray *info_headers;
gboolean sender_is_known;
gboolean message_is_junk = FALSE;
GError *error = NULL;
/* Check if the message is already classified. */
flags = camel_message_info_get_flags (info);
if (flags & CAMEL_MESSAGE_JUNK) {
message_is_junk = TRUE;
if (camel_debug ("junk"))
printf ("Message '%s' has a Junk flag set already, skipping junk test...\n", camel_message_info_get_uid (info));
camel_filter_search_log (fms, "Message '%s' has a Junk flag set already, skipping junk test", camel_message_info_get_uid (info));
goto done;
}
if (flags & CAMEL_MESSAGE_NOTJUNK) {
if (camel_debug ("junk"))
printf ("Message '%s' has a NotJunk flag set already, skipping junk test...\n", camel_message_info_get_uid (info));
camel_filter_search_log (fms, "Message '%s' has a NotJunk flag set already, skipping junk test", camel_message_info_get_uid (info));
goto done;
}
/* If the sender is known, the message is not junk.
Do this before header test, to be able to override server-side set headers. */
sender_is_known = camel_session_lookup_addressbook (
fms->session, camel_message_info_get_from (info));
camel_filter_search_log (fms, "Sender '%s' of message '%s' is %sin any address book",
camel_message_info_get_from (info),
camel_message_info_get_uid (info),
sender_is_known ? "" : "not ");
if (camel_debug ("junk"))
printf ("Sender '%s' of message '%s' in book? %d\n",
camel_message_info_get_from (info),
camel_message_info_get_uid (info),
sender_is_known);
if (sender_is_known)
goto done;
/* Check the headers for a junk designation. */
ht = camel_session_get_junk_headers (fms->session);
camel_message_info_property_lock (info);
info_headers = camel_message_info_get_headers (info);
if (info_headers) {
guint len, ii;
len = camel_name_value_array_get_length (info_headers);
for (ii = 0; ii < len; ii++) {
const gchar *hdr_name = NULL;
const gchar *hdr_value = NULL;
const gchar *junk_value = NULL;
if (!camel_name_value_array_get (info_headers, ii, &hdr_name, &hdr_value))
continue;
if (!hdr_name || !hdr_value)
continue;
junk_value = g_hash_table_lookup ((GHashTable *) ht, hdr_name);
message_is_junk =
(junk_value != NULL) &&
(camel_strstrcase (hdr_value, junk_value) != NULL);
if (message_is_junk) {
if (camel_debug ("junk"))
printf ("Message '%s' contains \"%s: %s\"",
camel_message_info_get_uid (info),
hdr_name, junk_value);
camel_filter_search_log (fms, "Message '%s' is junk, because contains header '%s' with value '%s'",
camel_message_info_get_uid (info), hdr_name, junk_value);
camel_message_info_property_unlock (info);
goto done;
}
}
}
camel_message_info_property_unlock (info);
/* Not every message info has headers available, thus try headers of the message itself */
message = camel_filter_search_get_message (fms, f);
if (message) {
const CamelNameValueArray *headers;
const gchar *raw_name = NULL, *raw_value = NULL;
guint ii;
headers = camel_medium_get_headers (CAMEL_MEDIUM (message));
for (ii = 0; camel_name_value_array_get (headers, ii, &raw_name, &raw_value); ii++) {
const gchar *value;
if (!raw_name)
continue;
value = g_hash_table_lookup ((GHashTable *) ht, raw_name);
if (!value)
continue;
message_is_junk = camel_strstrcase (raw_value, value) != NULL;
if (message_is_junk) {
if (camel_debug ("junk")) {
printf ("Message '%s' contains \"%s: %s\"",
camel_message_info_get_uid (info),
raw_name, value);
}
camel_filter_search_log (fms, "Message '%s' is junk, because contains header '%s' with value '%s'",
camel_message_info_get_uid (info), raw_name, value);
goto done;
}
}
} else {
goto done;
}
/* Consult 3rd party junk filtering software. */
junk_filter = camel_session_get_junk_filter (fms->session);
if (junk_filter == NULL) {
camel_filter_search_log (fms, "No junk filter set");
goto done;
}
status = camel_junk_filter_classify (
junk_filter, message, fms->cancellable, &error);
if (error == NULL) {
const gchar *status_desc;
switch (status) {
case CAMEL_JUNK_STATUS_INCONCLUSIVE:
status_desc = "inconclusive";
message_is_junk = FALSE;
break;
case CAMEL_JUNK_STATUS_MESSAGE_IS_JUNK:
status_desc = "junk";
message_is_junk = TRUE;
break;
case CAMEL_JUNK_STATUS_MESSAGE_IS_NOT_JUNK:
status_desc = "not junk";
message_is_junk = FALSE;
break;
default:
g_warn_if_reached ();
status_desc = "invalid";
message_is_junk = FALSE;
break;
}
camel_filter_search_log (fms, "Junk filter classified message '%s' as '%s'", camel_message_info_get_uid (info), status_desc);
if (camel_debug ("junk"))
printf (
"Junk filter classification for message '%s': %s\n",
camel_message_info_get_uid (info),
status_desc);
} else {
g_warn_if_fail (status == CAMEL_JUNK_STATUS_ERROR);
camel_filter_search_log (fms, "Junk classify failed for message '%s' with error '%s'", camel_message_info_get_uid (info), error->message);
if (camel_debug ("junk"))
printf ("Junk classify failed for message '%s' with error: %s\n", camel_message_info_get_uid (info), error->message);
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("%s: %s", G_STRFUNC, error->message);
g_error_free (error);
message_is_junk = FALSE;
}
done:
camel_filter_search_log (fms, "Finish message '%s' junk classify as %sJunk", camel_message_info_get_uid (info), message_is_junk ? "" : "not ");
if (camel_debug ("junk"))
printf (
"Message '%s' is determined to be %s\n",
camel_message_info_get_uid (info),
message_is_junk ? "*JUNK*" : "clean");
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.number = message_is_junk;
return r;
}
/* this is copied from Evolution's libemail-engine/e-mail-folder-utils.c */
static gchar *
mail_folder_uri_build (CamelStore *store,
const gchar *folder_name)
{
const gchar *uid;
gchar *encoded_name;
gchar *encoded_uid;
gchar *uri;
g_return_val_if_fail (CAMEL_IS_STORE (store), NULL);
g_return_val_if_fail (folder_name != NULL, NULL);
/* Skip the leading slash, if present. */
if (*folder_name == '/')
folder_name++;
uid = camel_service_get_uid (CAMEL_SERVICE (store));
encoded_uid = camel_url_encode (uid, ":;@/");
encoded_name = camel_url_encode (folder_name, "#");
uri = g_strdup_printf ("folder://%s/%s", encoded_uid, encoded_name);
g_free (encoded_uid);
g_free (encoded_name);
return uri;
}
static CamelSExpResult *
message_location (struct _CamelSExp *f,
gint argc,
struct _CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *r;
gboolean same = FALSE;
if (argc != 1 || argv[0]->type != CAMEL_SEXP_RES_STRING)
camel_sexp_fatal_error (f, _("Invalid arguments to (message-location)"));
if (fms->folder && argv[0]->value.string) {
CamelStore *store;
const gchar *name;
gchar *uri;
store = camel_folder_get_parent_store (fms->folder);
name = camel_folder_get_full_name (fms->folder);
uri = mail_folder_uri_build (store, name);
same = g_str_equal (uri, argv[0]->value.string);
camel_filter_search_log (fms, "Message location '%s' is %ssame as requested '%s'",
uri, same ? "" : "not ", argv[0]->value.string);
g_free (uri);
} else {
camel_filter_search_log (fms, "Message location cannot check, have %sfolder or %srequest",
fms->folder ? "" : "no ", argv[0]->value.string ? "" : "no ");
}
r = camel_sexp_result_new (f, CAMEL_SEXP_RES_BOOL);
r->value.boolean = same;
return r;
}
static CamelSExpResult *
make_time_func (CamelSExp *sexp,
gint argc,
CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *res;
camel_filter_search_log (fms, "Calling 'make-time'");
res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_TIME);
res->value.time = camel_folder_search_util_make_time (argc, argv);
return res;
}
static CamelSExpResult *
compare_date_func (CamelSExp *sexp,
gint argc,
CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *res;
res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_INT);
res->value.number = 0;
if (argc == 2) {
gint64 t1, t2;
if (argv[0]->type == CAMEL_SEXP_RES_INT)
t1 = argv[0]->value.number;
else if (argv[0]->type == CAMEL_SEXP_RES_TIME)
t1 = (gint64) argv[0]->value.time;
else {
camel_filter_search_log (fms, "compare-date result:%d (incorrect first argument type)", res->value.number);
return res;
}
if (argv[1]->type == CAMEL_SEXP_RES_INT)
t2 = argv[1]->value.number;
else if (argv[1]->type == CAMEL_SEXP_RES_TIME)
t2 = (gint64) argv[1]->value.time;
else {
camel_filter_search_log (fms, "compare-date result:%d (incorrect second argument type)", res->value.number);
return res;
}
res->value.number = camel_folder_search_util_compare_date (t1, t2);
}
camel_filter_search_log (fms, "compare-date result:%d", res->value.number);
return res;
}
static CamelSExpResult *
addressbook_contains_func (CamelSExp *sexp,
gint argc,
CamelSExpResult **argv,
FilterMessageSearch *fms)
{
CamelSExpResult *res;
res = camel_sexp_result_new (sexp, CAMEL_SEXP_RES_BOOL);
res->value.boolean = FALSE;
if (argc == 2) {
GError *local_error = NULL;
const gchar *book_uid, *header, *email_address = NULL;
gboolean address_set = FALSE;
if (argv[0]->type != CAMEL_SEXP_RES_STRING) {
camel_filter_search_log (fms, "addressbook-contains result:%d (incorrect first argument type, expects string)", res->value.boolean);
return res;
}
if (argv[1]->type != CAMEL_SEXP_RES_STRING) {
camel_filter_search_log (fms, "addressbook-contains result:%d (incorrect second argument type, expects string)", res->value.boolean);
return res;
}
book_uid = argv[0]->value.string;
header = argv[1]->value.string;
if (header && g_ascii_strcasecmp (header, "From") == 0) {
email_address = camel_message_info_get_from (fms->info);
address_set = TRUE;
} else if (header && g_ascii_strcasecmp (header, "To") == 0) {
email_address = camel_message_info_get_to (fms->info);
address_set = TRUE;
} else if (header && g_ascii_strcasecmp (header, "Cc") == 0) {
email_address = camel_message_info_get_cc (fms->info);
address_set = TRUE;
}
res->value.boolean = email_address && camel_session_addressbook_contains_sync (fms->session,
book_uid, email_address, fms->cancellable, &local_error);
if (!address_set)
camel_filter_search_log (fms, "addressbook-contains result:%d unsupported header '%s'", res->value.boolean, header);
else if (local_error)
camel_filter_search_log (fms, "addressbook-contains result:%d for '%s' in '%s' error:%s", res->value.boolean, email_address, book_uid, local_error->message);
else
camel_filter_search_log (fms, "addressbook-contains result:%d for '%s' in '%s' ", res->value.boolean, email_address, book_uid);
g_clear_error (&local_error);
} else {
camel_filter_search_log (fms, "addressbook-contains result:%d expects two arguments, received %d", res->value.boolean, argc);
}
return res;
}
static const gchar *
camel_search_result_to_string (gint value)
{
return value == CAMEL_SEARCH_ERROR ? "ERROR" :
value == CAMEL_SEARCH_NOMATCH ? "NOMATCH" :
value == CAMEL_SEARCH_MATCHED ? "MATCHED" : "???";
}
/**
* camel_filter_search_match_with_log:
* @session:
* @get_message: (scope async): function to retrieve the message if necessary
* @user_data: data for above
* @info:
* @source:
* @folder: in which folder the message is stored
* @expression:
* @logfile: (nullable): an optional log file to write logging information to, or %NULL
* @cancellable: a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Returns: one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or
* CAMEL_SEARCH_ERROR.
*
* Since 3.24
**/
gint
camel_filter_search_match_with_log (CamelSession *session,
CamelFilterSearchGetMessageFunc get_message,
gpointer user_data,
CamelMessageInfo *info,
const gchar *source,
CamelFolder *folder,
const gchar *expression,
FILE *logfile,
GCancellable *cancellable,
GError **error)
{
FilterMessageSearch fms;
CamelSExp *sexp;
CamelSExpResult *result;
gint retval;
GError *local_error = NULL;
gint i;
fms.session = session;
fms.get_message = get_message;
fms.get_message_data = user_data;
fms.message = NULL;
fms.info = info;
fms.source = source;
fms.folder = folder;
fms.logfile = logfile;
fms.cancellable = cancellable;
fms.error = &local_error;
sexp = camel_sexp_new ();
for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
if (symbols[i].type == 1)
camel_sexp_add_ifunction (sexp, 0, symbols[i].name, (CamelSExpIFunc) symbols[i].func, &fms);
else
camel_sexp_add_function (sexp, 0, symbols[i].name, symbols[i].func, &fms);
}
camel_sexp_input_text (sexp, expression, strlen (expression));
if (camel_sexp_parse (sexp) == -1) {
if (!local_error) {
/* A filter search is a search through your filters,
* ie. your filters is the corpus being searched thru. */
g_set_error (
&local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Error executing filter search: %s: %s"),
camel_sexp_error (sexp), expression);
}
goto error;
}
result = camel_sexp_eval (sexp);
if (result == NULL) {
if (!local_error)
g_set_error (
&local_error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Error executing filter search: %s: %s"),
camel_sexp_error (sexp), expression);
goto error;
}
if (local_error) {
camel_sexp_result_free (sexp, result);
goto error;
}
if (result->type == CAMEL_SEXP_RES_BOOL)
retval = result->value.boolean ? CAMEL_SEARCH_MATCHED : CAMEL_SEARCH_NOMATCH;
else
retval = CAMEL_SEARCH_NOMATCH;
camel_sexp_result_free (sexp, result);
g_object_unref (sexp);
if (fms.message)
g_object_unref (fms.message);
if (logfile) {
camel_filter_search_log (&fms, "Finished test of message uid:%s subject:'%s' from '%s : %s' as %s",
camel_message_info_get_uid (info), camel_message_info_get_subject (info),
folder ? camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))) : "NULL",
folder ? camel_folder_get_full_name (folder) : "NULL",
camel_search_result_to_string (retval));
}
return retval;
error:
if (fms.message)
g_object_unref (fms.message);
g_object_unref (sexp);
if (logfile) {
camel_filter_search_log (&fms, "Finished test of message uid:%s subject:'%s' from '%s : %s' as ERROR: '%s'",
camel_message_info_get_uid (info), camel_message_info_get_subject (info),
folder ? camel_service_get_display_name (CAMEL_SERVICE (camel_folder_get_parent_store (folder))) : "NULL",
folder ? camel_folder_get_full_name (folder) : "NULL",
local_error ? local_error->message : "Unknown error");
}
if (local_error)
g_propagate_error (error, local_error);
return CAMEL_SEARCH_ERROR;
}
/**
* camel_filter_search_match:
* @session:
* @get_message: (scope async): function to retrieve the message if necessary
* @user_data: data for above
* @info:
* @source:
* @folder: in which folder the message is stored
* @expression:
* @cancellable: a #GCancellable, or %NULL
* @error: return location for a #GError, or %NULL
*
* Returns: one of CAMEL_SEARCH_MATCHED, CAMEL_SEARCH_NOMATCH, or
* CAMEL_SEARCH_ERROR.
**/
gint
camel_filter_search_match (CamelSession *session,
CamelFilterSearchGetMessageFunc get_message,
gpointer user_data,
CamelMessageInfo *info,
const gchar *source,
CamelFolder *folder,
const gchar *expression,
GCancellable *cancellable,
GError **error)
{
return camel_filter_search_match_with_log (session, get_message, user_data, info,
source, folder, expression, NULL, cancellable, error);
}