2006-09-26 [colin] 2.5.1cvs5
[claws.git] / src / matcher.c
index 8d86aeadf205815c8c930cfb755c39e3d1501d11..f6d4c2415f216d7643e9385054fec4008ac64994 100644 (file)
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
 #include <glib.h>
 #include <glib/gi18n.h>
 #include <ctype.h>
 #include <stdlib.h>
 #include <errno.h>
 
+#ifdef USE_PTHREAD
+#include <pthread.h>
+#endif
+
 #include "defs.h"
 #include "utils.h"
 #include "procheader.h"
@@ -33,6 +41,7 @@
 #include "addr_compl.h"
 #include "codeconv.h"
 #include "quoted-printable.h"
+#include "sylpheed.h"
 #include <ctype.h>
 
 /*!
@@ -94,6 +103,8 @@ static const MatchParser matchparser_tab[] = {
        {MATCHCRITERIA_SCORE_EQUAL, "score_equal"},
        {MATCHCRITERIA_PARTIAL, "partial"},
        {MATCHCRITERIA_NOT_PARTIAL, "~partial"},
+       {MATCHCRITERIA_FOUND_IN_ADDRESSBOOK, "found_in_addressbook"},
+       {MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK, "~found_in_addressbook"},
 
        {MATCHCRITERIA_SIZE_GREATER, "size_greater"},
        {MATCHCRITERIA_SIZE_SMALLER, "size_smaller"},
@@ -116,8 +127,6 @@ static const MatchParser matchparser_tab[] = {
        {MATCHTYPE_MATCH, "match"},
        {MATCHTYPE_REGEXPCASE, "regexpcase"},
        {MATCHTYPE_REGEXP, "regexp"},
-       {MATCHTYPE_ANY_IN_ADDRESSBOOK, "any_in_addressbook"},
-       {MATCHTYPE_ALL_IN_ADDRESSBOOK, "all_in_addressbook"},
 
        /* actions */
        {MATCHACTION_SCORE, "score"},    /* for backward compatibility */
@@ -142,6 +151,12 @@ static const MatchParser matchparser_tab[] = {
        {MATCHACTION_IGNORE, "ignore"},
 };
 
+enum {
+       MATCH_ANY = 0,
+       MATCH_ALL = 1,
+       MATCH_ONE = 2
+};
+
 /*!
  *\brief       Look up table with keywords defined in \sa matchparser_tab
  */
@@ -204,7 +219,8 @@ gint get_matchparser_tab_id(const gchar *str)
  *             "condition" (a matcher structure)
  *
  *\param       criteria Criteria ID (MATCHCRITERIA_XXXX)
- *\param       header Header string (if criteria is MATCHCRITERIA_HEADER)
+ *\param       header Header string (if criteria is MATCHCRITERIA_HEADER
+                       or MATCHCRITERIA_FOUND_IN_ADDRESSBOOK)
  *\param       matchtype Type of action (MATCHTYPE_XXX)
  *\param       expr String value or expression to check
  *\param       value Integer value to check
@@ -237,10 +253,8 @@ MatcherProp *matcherprop_new(gint criteria, const gchar *header,
  */
 void matcherprop_free(MatcherProp *prop)
 {
-       if (prop->expr) 
-               g_free(prop->expr);
-       if (prop->header)
-               g_free(prop->header);
+       g_free(prop->expr);
+       g_free(prop->header);
        if (prop->preg != NULL) {
                regfree(prop->preg);
                g_free(prop->preg);
@@ -273,38 +287,59 @@ MatcherProp *matcherprop_copy(const MatcherProp *src)
 /* ************** match ******************************/
 
 static gboolean match_with_addresses_in_addressbook
-       (MatcherProp *prop, const gchar *str, gint type)
+       (MatcherProp *prop, GSList *address_list, gint type,
+        gchar* folderpath, gint match)
 {
-       GSList *address_list = NULL;
-       GSList *walk;
-       gboolean res = FALSE;
+       GSList *walk = NULL;
+       gboolean found = FALSE;
+       gchar *path = NULL;
 
-       if (str == NULL || *str == 0) 
-               return FALSE;
+       g_return_val_if_fail(address_list != NULL, FALSE);
+
+       debug_print("match_with_addresses_in_addressbook(%d, %s)\n",
+                               g_slist_length(address_list), folderpath);
+
+       if (folderpath == NULL ||
+               strcasecmp(folderpath, _("Any")) == 0 ||
+               *folderpath == '\0')
+               path = NULL;
+       else
+               path = folderpath;
        
-       /* XXX: perhaps complete with comments too */
-       address_list = address_list_append(address_list, str);
-       if (!address_list) 
-               return FALSE;
+       start_address_completion(path);
 
-       start_address_completion();             
-       res = FALSE;
        for (walk = address_list; walk != NULL; walk = walk->next) {
-               gboolean found = complete_address(walk->data) ? TRUE : FALSE;
-               
+               /* exact matching of email address */
+               guint num_addr = complete_address(walk->data);
+               found = FALSE;
+               if (num_addr > 1) {
+                       /* skip first item (this is the search string itself) */
+                       int i = 1;
+                       for (; i < num_addr && !found; i++) {
+                               gchar *addr = get_complete_address(i);
+                               extract_address(addr);
+                               if (strcasecmp(addr, walk->data) == 0)
+                                       found = TRUE;
+                               g_free(addr);
+                       }
+               }
                g_free(walk->data);
-               if (!found && type == MATCHTYPE_ALL_IN_ADDRESSBOOK) {
-                       res = FALSE;
+
+               if (match == MATCH_ALL) {
+                       /* if matching all addresses, stop if one doesn't match */
+                       if (!found)
                        break;
-               } else if (found) 
-                       res = TRUE;
+               } else if (match == MATCH_ANY) {
+                       /* if matching any address, stop if one does match */
+                       if (found)
+                               break;
+       }
+               /* MATCH_ONE: there should be only one loop iteration */
        }
-
-       g_slist_free(address_list);
 
        end_address_completion();
        
-       return res;
+       return found;
 }
 
 /*!
@@ -347,11 +382,6 @@ static gboolean matcherprop_string_match(MatcherProp *prop, const gchar *str)
                else
                        return FALSE;
                        
-       case MATCHTYPE_ALL_IN_ADDRESSBOOK:      
-       case MATCHTYPE_ANY_IN_ADDRESSBOOK:
-               return match_with_addresses_in_addressbook
-                       (prop, str, prop->matchtype);
-
        case MATCHTYPE_MATCH:
                return (strstr(str, prop->expr) != NULL);
 
@@ -392,7 +422,8 @@ static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *
                res = matcherprop_string_match(prop, tmp);
        }
        
-       if (res == FALSE && strchr(prop->expr, '=')) {
+       if (res == FALSE && (strchr(prop->expr, '=') || strchr(prop->expr, '_')
+                           || strchr(str, '=') || strchr(str, '_'))) {
                /* if searching for something with an equal char, maybe 
                 * we should try to match the non-decoded string. 
                 * In case it was not qp-encoded. */
@@ -401,6 +432,7 @@ static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *
                                (str, conv_get_locale_charset_str_no_utf8(),
                                 CS_INTERNAL);
                        res = matcherprop_string_match(prop, utf);
+                       g_free(utf);
                } else {
                        res = matcherprop_string_match(prop, str);
                }
@@ -412,6 +444,29 @@ static gboolean matcherprop_string_decode_match(MatcherProp *prop, const gchar *
        return res;
 }
 
+#ifdef USE_PTHREAD
+typedef struct _thread_data {
+       const gchar *cmd;
+       gboolean done;
+} thread_data;
+#endif
+
+#ifdef USE_PTHREAD
+void *matcher_test_thread(void *data)
+{
+       thread_data *td = (thread_data *)data;
+       int result = -1;
+
+       pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+       pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
+
+       result = system(td->cmd);
+       if (result) perror("system");
+       td->done = TRUE; /* let the caller thread join() */
+       return GINT_TO_POINTER(result);
+}
+#endif
+
 /*!
  *\brief       Execute a command defined in the matcher structure
  *
@@ -426,6 +481,12 @@ static gboolean matcherprop_match_test(const MatcherProp *prop,
        gchar *file;
        gchar *cmd;
        gint retval;
+#ifdef USE_PTHREAD
+       pthread_t pt;
+       thread_data *td = g_new0(thread_data, 1);
+       void *res = NULL;
+       time_t start_time = time(NULL);
+#endif
 
        file = procmsg_get_message_file(info);
        if (file == NULL)
@@ -436,7 +497,31 @@ static gboolean matcherprop_match_test(const MatcherProp *prop,
        if (cmd == NULL)
                return FALSE;
 
+#if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
+       td->cmd = cmd;
+       td->done = FALSE;
+       if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE, 
+                       matcher_test_thread, td) != 0)
+               retval = system(cmd);
+       else {
+               printf("waiting for test thread\n");
+               while(!td->done) {
+                       /* don't let the interface freeze while waiting */
+                       sylpheed_do_idle();
+                       if (time(NULL) - start_time > 30) {
+                               pthread_cancel(pt);
+                               td->done = TRUE;
+                               retval = -1;
+                       }
+               }
+               pthread_join(pt, &res);
+               retval = GPOINTER_TO_INT(res);
+               printf(" test thread returned %d\n", retval);
+       }
+       g_free(td);
+#else
        retval = system(cmd);
+#endif
        debug_print("Command exit code: %d\n", retval);
 
        g_free(cmd);
@@ -520,14 +605,14 @@ gboolean matcherprop_match(MatcherProp *prop,
                || matcherprop_string_match(prop, info->cc));
        case MATCHCRITERIA_AGE_GREATER:
                t = time(NULL);
-               return ((t - info->date_t) / (60 * 60 * 24)) >= prop->value;
+               return ((t - info->date_t) / (60 * 60 * 24)) > prop->value;
        case MATCHCRITERIA_AGE_LOWER:
                t = time(NULL);
-               return ((t - info->date_t) / (60 * 60 * 24)) <= prop->value;
+               return ((t - info->date_t) / (60 * 60 * 24)) < prop->value;
        case MATCHCRITERIA_SCORE_GREATER:
-               return info->score >= prop->value;
+               return info->score > prop->value;
        case MATCHCRITERIA_SCORE_LOWER:
-               return info->score <= prop->value;
+               return info->score < prop->value;
        case MATCHCRITERIA_SCORE_EQUAL:
                return info->score == prop->value;
        case MATCHCRITERIA_SIZE_GREATER:
@@ -564,7 +649,7 @@ gboolean matcherprop_match(MatcherProp *prop,
        case MATCHCRITERIA_NOT_TEST:
                return !matcherprop_match_test(prop, info);
        default:
-               return 0;
+               return FALSE;
        }
 }
 
@@ -630,8 +715,8 @@ static void matcherlist_skip_headers(FILE *fp)
 static gboolean matcherprop_match_one_header(MatcherProp *matcher,
                                             gchar *buf)
 {
-       gboolean result;
-       Header *header;
+       gboolean result = FALSE;
+       Header *header = NULL;
 
        switch (matcher->criteria) {
        case MATCHCRITERIA_HEADER:
@@ -660,7 +745,57 @@ static gboolean matcherprop_match_one_header(MatcherProp *matcher,
                return !matcherprop_string_decode_match(matcher, buf);
        case MATCHCRITERIA_NOT_HEADERS_PART:
                return !matcherprop_string_match(matcher, buf);
+       case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
+       case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
+               {
+                       GSList *address_list = NULL;
+                       gint match = MATCH_ONE;
+                       gboolean found = FALSE;
+
+                       /* how many address headers are me trying to mach? */
+                       if (strcasecmp(matcher->header, _("Any")) == 0)
+                               match = MATCH_ANY;
+                       else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
+                                       match = MATCH_ALL;
+
+                       if (match == MATCH_ONE) {
+                               /* matching one address header exactly, is that the right one? */
+                               header = procheader_parse_header(buf);
+                               if (!header ||
+                                               !procheader_headername_equal(header->name, matcher->header))
+                                       return FALSE;
+                               address_list = address_list_append(address_list, header->body);
+                               if (address_list == NULL)
+                                       return FALSE;
+
+                       } else {
+                               header = procheader_parse_header(buf);
+                               if (!header)
+                                       return FALSE;
+                               /* address header is one of the headers we have to match when checking
+                                  for any address header or all address headers? */
+                               if (procheader_headername_equal(header->name, "From") ||
+                                        procheader_headername_equal(header->name, "To") ||
+                                        procheader_headername_equal(header->name, "Cc") ||
+                                        procheader_headername_equal(header->name, "Reply-To") ||
+                                        procheader_headername_equal(header->name, "Sender"))
+                                       address_list = address_list_append(address_list, header->body);
+                               if (address_list == NULL)
+                                       return FALSE;
+                       }
+
+                       found = match_with_addresses_in_addressbook
+                                                       (matcher, address_list, matcher->criteria,
+                                                        matcher->expr, match);
+                       g_slist_free(address_list);
+
+                       if (matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK)
+                               return !found;
+                       else
+                               return found;
        }
+       }
+
        return FALSE;
 }
 
@@ -680,6 +815,8 @@ static gboolean matcherprop_criteria_headers(const MatcherProp *matcher)
        case MATCHCRITERIA_NOT_HEADER:
        case MATCHCRITERIA_HEADERS_PART:
        case MATCHCRITERIA_NOT_HEADERS_PART:
+       case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
+       case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
                return TRUE;
        default:
                return FALSE;
@@ -725,14 +862,46 @@ static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
        while (procheader_get_one_field(buf, sizeof(buf), fp, NULL) != -1) {
                for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
                        MatcherProp *matcher = (MatcherProp *) l->data;
+                       gint match = MATCH_ANY;
 
                        if (matcher->done)
                                continue;
 
-                       /* if the criteria is ~headers_part or ~message, ZERO lines
-                        * must NOT match for the rule to match. */
+                       /* determine the match range (all, any are our concern here) */
                        if (matcher->criteria == MATCHCRITERIA_NOT_HEADERS_PART ||
                            matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
+                               match = MATCH_ALL;
+
+                       } else if (matcher->criteria == MATCHCRITERIA_FOUND_IN_ADDRESSBOOK ||
+                                          matcher->criteria == MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK) {
+                               Header *header = NULL;
+
+                               /* address header is one of the headers we have to match when checking
+                                  for any address header or all address headers? */
+                               header = procheader_parse_header(buf);
+                               if (header &&
+                                       (procheader_headername_equal(header->name, "From") ||
+                                        procheader_headername_equal(header->name, "To") ||
+                                        procheader_headername_equal(header->name, "Cc") ||
+                                        procheader_headername_equal(header->name, "Reply-To") ||
+                                        procheader_headername_equal(header->name, "Sender"))) {
+
+                                       if (strcasecmp(matcher->header, _("Any")) == 0)
+                                               match = MATCH_ANY;
+                                       else if (strcasecmp(matcher->header, Q_("Filtering Matcher Menu|All")) == 0)
+                                               match = MATCH_ALL;
+                                       else
+                                               match = MATCH_ONE;
+                               } else {
+                                       /* further call to matcherprop_match_one_header() can't match
+                                          and it irrelevant, so: don't alter the match result */
+                                       continue;
+                               }
+                       }
+
+                       /* ZERO line must NOT match for the rule to match.
+                        */
+                       if (match == MATCH_ALL) {
                                if (matcherprop_match_one_header(matcher, buf)) {
                                        matcher->result = TRUE;
                                } else {
@@ -742,7 +911,7 @@ static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
                        /* else, just one line matching is enough for the rule to match
                         */
                        } else if (matcherprop_criteria_headers(matcher) ||
-                                  matcherprop_criteria_message(matcher)){
+                                  matcherprop_criteria_message(matcher)) {
                                if (matcherprop_match_one_header(matcher, buf)) {
                                        matcher->result = TRUE;
                                        matcher->done = TRUE;
@@ -757,6 +926,7 @@ static gboolean matcherlist_match_headers(MatcherList *matchers, FILE *fp)
                        }
                }
        }
+
        return FALSE;
 }
 
@@ -1120,6 +1290,7 @@ gchar *matcherprop_to_string(MatcherProp *matcher)
        const gchar *matchtype_str;
        int i;
        gchar * quoted_expr;
+       gchar * quoted_header;
        
        criteria_str = NULL;
        for (i = 0; i < (int) (sizeof(matchparser_tab) / sizeof(MatchParser)); i++) {
@@ -1168,6 +1339,15 @@ gchar *matcherprop_to_string(MatcherProp *matcher)
                                              criteria_str, quoted_expr);
                g_free(quoted_expr);
                 return matcher_str;
+       case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
+       case MATCHCRITERIA_NOT_FOUND_IN_ADDRESSBOOK:
+               quoted_header = matcher_quote_str(matcher->header);
+               quoted_expr = matcher_quote_str(matcher->expr);
+               matcher_str = g_strdup_printf("%s \"%s\" in \"%s\"",
+                                             criteria_str, quoted_header, quoted_expr);
+               g_free(quoted_header);
+               g_free(quoted_expr);
+               return matcher_str;
        }
 
        matchtype_str = NULL;
@@ -1184,12 +1364,8 @@ gchar *matcherprop_to_string(MatcherProp *matcher)
        case MATCHTYPE_MATCHCASE:
        case MATCHTYPE_REGEXP:
        case MATCHTYPE_REGEXPCASE:
-       case MATCHTYPE_ALL_IN_ADDRESSBOOK:
-       case MATCHTYPE_ANY_IN_ADDRESSBOOK:
                quoted_expr = matcher_quote_str(matcher->expr);
                if (matcher->header) {
-                       gchar * quoted_header;
-                       
                        quoted_header = matcher_quote_str(matcher->header);
                        matcher_str = g_strdup_printf
                                        ("%s \"%s\" %s \"%s\"",
@@ -1428,21 +1604,33 @@ gchar *matching_build_command(const gchar *cmd, MsgInfo *info)
  */
 static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
 {
-       GSList *cur;
+       GSList *cur = NULL;
 
        for (cur = prefs_filtering; cur != NULL; cur = cur->next) {
-               gchar *filtering_str;
+               gchar *filtering_str = NULL;
                gchar *tmp_name = NULL;
-               FilteringProp *prop;
+               FilteringProp *prop = NULL;
 
                if (NULL == (prop = (FilteringProp *) cur->data))
                        continue;
                
                if (NULL == (filtering_str = filteringprop_to_string(prop)))
                        continue;
-                               
+
+               if (prop->enabled) {
+                       if (fputs("enabled ", fp) == EOF) {
+                               FILE_OP_ERROR("filtering config", "fputs");
+                               return;
+                       }
+               } else {
+                       if (fputs("disabled ", fp) == EOF) {
+                               FILE_OP_ERROR("filtering config", "fputs");
+                               return;
+                       }
+               }
+
                if (fputs("rulename \"", fp) == EOF) {
-                       FILE_OP_ERROR("filtering config", "fputs || fputc");
+                       FILE_OP_ERROR("filtering config", "fputs");
                        g_free(filtering_str);
                        return;
                }
@@ -1450,22 +1638,39 @@ static void prefs_filtering_write(FILE *fp, GSList *prefs_filtering)
                while (tmp_name && *tmp_name != '\0') {
                        if (*tmp_name != '"') {
                                if (fputc(*tmp_name, fp) == EOF) {
-                                       FILE_OP_ERROR("filtering config", "fputc");
+                                       FILE_OP_ERROR("filtering config", "fputs || fputc");
                                        g_free(filtering_str);
                                        return;
                                }
                        } else if (*tmp_name == '"') {
                                if (fputc('\\', fp) == EOF ||
                                    fputc('"', fp) == EOF) {
-                                       FILE_OP_ERROR("filtering config", "fputc");
+                                       FILE_OP_ERROR("filtering config", "fputs || fputc");
                                        g_free(filtering_str);
                                        return;
                                }
                        }
                        tmp_name ++;
                }
-               if(fputs("\" ", fp) == EOF ||
-                   fputs(filtering_str, fp) == EOF ||
+               if (fputs("\" ", fp) == EOF) {
+                       FILE_OP_ERROR("filtering config", "fputs");
+                       g_free(filtering_str);
+                       return;
+               }
+
+               if (prop->account_id != 0) {
+                       gchar *tmp = NULL;
+
+                       tmp = g_strdup_printf("account %d ", prop->account_id);
+                       if (fputs(tmp, fp) == EOF) {
+                               FILE_OP_ERROR("filtering config", "fputs");
+                               g_free(tmp);
+                               return;
+                       }
+                       g_free(tmp);
+               }
+
+               if(fputs(filtering_str, fp) == EOF ||
                    fputc('\n', fp) == EOF) {
                        FILE_OP_ERROR("filtering config", "fputs || fputc");
                        g_free(filtering_str);
@@ -1636,7 +1841,7 @@ void prefs_matcher_read_config(void)
                fclose(matcher_parserin);
        }
        else {
-               /* previous version compatibily */
+               /* previous version compatibility */
 
                /* printf("reading filtering\n"); */
                rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,