Coverity fixes
[claws.git] / src / matcher.c
index 734a825033ff1dabaa500edd95dfd078520d2c6a..4382c5996aa534ebba0f523f39b38da8edeffc0e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 2002-2011 by the Claws Mail Team and Hiroyuki Yamamoto
+ * Copyright (C) 2002-2012 by the Claws Mail Team and Hiroyuki Yamamoto
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -19,6 +19,7 @@
 
 #ifdef HAVE_CONFIG_H
 #  include "config.h"
+#include "claws-features.h"
 #endif
 
 #include <glib.h>
@@ -46,6 +47,8 @@
 #include "prefs_common.h"
 #include "log.h"
 #include "tags.h"
+#include "folder_item_prefs.h"
+#include "procmsg.h"
 
 /*!
  *\brief       Keyword lookup element
@@ -107,6 +110,8 @@ static const MatchParser matchparser_tab[] = {
        {MATCHCRITERIA_NOT_TAGGED, "~tagged"},
        {MATCHCRITERIA_AGE_GREATER, "age_greater"},
        {MATCHCRITERIA_AGE_LOWER, "age_lower"},
+       {MATCHCRITERIA_AGE_GREATER_HOURS, "age_greater_hours"},
+       {MATCHCRITERIA_AGE_LOWER_HOURS, "age_lower_hours"},
        {MATCHCRITERIA_NEWSGROUPS, "newsgroups"},
        {MATCHCRITERIA_NOT_NEWSGROUPS, "~newsgroups"},
        {MATCHCRITERIA_INREPLYTO, "inreplyto"},
@@ -179,6 +184,50 @@ enum {
        MATCH_ONE = 2
 };
 
+enum {
+       CONTEXT_SUBJECT,
+       CONTEXT_FROM,
+       CONTEXT_TO,
+       CONTEXT_CC,
+       CONTEXT_NEWSGROUPS,
+       CONTEXT_IN_REPLY_TO,
+       CONTEXT_REFERENCES,
+       CONTEXT_HEADER,
+       CONTEXT_HEADER_LINE,
+       CONTEXT_BODY_LINE,
+       CONTEXT_TAG,
+       N_CONTEXT_STRS
+};
+
+static gchar *context_str[N_CONTEXT_STRS];
+
+void matcher_init(void)
+{
+       if (context_str[CONTEXT_SUBJECT] != NULL)
+               return;
+
+       context_str[CONTEXT_SUBJECT] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Subject:"));
+       context_str[CONTEXT_FROM] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
+       context_str[CONTEXT_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
+       context_str[CONTEXT_CC] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
+       context_str[CONTEXT_NEWSGROUPS] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Newsgroups:"));
+       context_str[CONTEXT_IN_REPLY_TO] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("In-Reply-To:"));
+       context_str[CONTEXT_REFERENCES] = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("References:"));
+       context_str[CONTEXT_HEADER] = g_strdup(_("header"));
+       context_str[CONTEXT_HEADER_LINE] = g_strdup(_("header line"));
+       context_str[CONTEXT_BODY_LINE] = g_strdup(_("body line"));
+       context_str[CONTEXT_TAG]  = g_strdup(_("tag"));
+}
+
+void matcher_done(void)
+{
+       int i;
+       for (i = 0; i < N_CONTEXT_STRS; i++) {
+               g_free(context_str[i]);
+               context_str[i] = NULL;
+       }
+}
+
 extern gboolean debug_filtering_session;
 
 /*!
@@ -264,42 +313,9 @@ MatcherProp *matcherprop_new(gint criteria, const gchar *header,
        prop->expr = expr != NULL ? g_strdup(expr) : NULL;
 
        prop->matchtype = matchtype;
+#ifndef G_OS_WIN32
        prop->preg = NULL;
-       prop->value = value;
-       prop->error = 0;
-
-       return prop;
-}
-
-/*!
- *\brief       Allocate a structure for a filtering / scoring
- *             "condition" (a matcher structure)
- *             Same as matcherprop_new, except it doesn't change the expr's 
- *             case.
- *
- *\param       criteria Criteria ID (MATCHCRITERIA_XXXX)
- *\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
- *
- *\return      MatcherProp * Pointer to newly allocated structure
- */
-MatcherProp *matcherprop_new_create(gint criteria, const gchar *header,
-                             gint matchtype, const gchar *expr,
-                             int value)
-{
-       MatcherProp *prop;
-
-       prop = g_new0(MatcherProp, 1);
-       prop->criteria = criteria;
-       prop->header = header != NULL ? g_strdup(header) : NULL;
-
-       prop->expr = expr != NULL ? g_strdup(expr) : NULL;
-
-       prop->matchtype = matchtype;
-       prop->preg = NULL;
+#endif
        prop->value = value;
        prop->error = 0;
 
@@ -316,10 +332,12 @@ void matcherprop_free(MatcherProp *prop)
 {
        g_free(prop->expr);
        g_free(prop->header);
+#ifndef G_OS_WIN32
        if (prop->preg != NULL) {
                regfree(prop->preg);
                g_free(prop->preg);
        }
+#endif
        g_free(prop);
 }
 
@@ -339,7 +357,9 @@ MatcherProp *matcherprop_copy(const MatcherProp *src)
        prop->expr = src->expr ? g_strdup(src->expr) : NULL;
        prop->matchtype = src->matchtype;
        
+#ifndef G_OS_WIN32
        prop->preg = NULL; /* will be re-evaluated */
+#endif
        prop->value = src->value;
        prop->error = src->error;       
        return prop;            
@@ -741,10 +761,11 @@ static gboolean matcherprop_match_test(const MatcherProp *prop,
  *
  *\return      gboolean TRUE if a match
  */
-gboolean matcherprop_match(MatcherProp *prop, 
-                          MsgInfo *info)
+static gboolean matcherprop_match(MatcherProp *prop, 
+                                 MsgInfo *info)
 {
        time_t t;
+       gint age_mult_hours = 1;
 
        switch(prop->criteria) {
        case MATCHCRITERIA_ALL:
@@ -838,93 +859,45 @@ gboolean matcherprop_match(MatcherProp *prop,
        case MATCHCRITERIA_NOT_WATCH_THREAD:
                return !MSG_IS_WATCH_THREAD(info->flags);
        case MATCHCRITERIA_SUBJECT:
-               return matcherprop_string_match(prop, info->subject,
-                                               prefs_common_translated_header_name("Subject:"));
+               return matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
        case MATCHCRITERIA_NOT_SUBJECT:
-               return !matcherprop_string_match(prop, info->subject,
-                                               prefs_common_translated_header_name("Subject:"));
+               return !matcherprop_string_match(prop, info->subject, context_str[CONTEXT_SUBJECT]);
        case MATCHCRITERIA_FROM:
+               return matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
        case MATCHCRITERIA_NOT_FROM:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("From:"));
-               ret = matcherprop_string_match(prop, info->from, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_FROM)? ret : !ret;
-       }
+               return !matcherprop_string_match(prop, info->from, context_str[CONTEXT_FROM]);
        case MATCHCRITERIA_TO:
+               return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
        case MATCHCRITERIA_NOT_TO:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
-               ret = matcherprop_string_match(prop, info->to, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_TO)? ret : !ret;
-       }
+               return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO]);
        case MATCHCRITERIA_CC:
+               return matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
        case MATCHCRITERIA_NOT_CC:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
-               ret = matcherprop_string_match(prop, info->cc, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_CC)? ret : !ret;
-       }
+               return !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
        case MATCHCRITERIA_TO_OR_CC:
-       {
-               gchar *context1, *context2;
-               gboolean ret;
-
-               context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
-               context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
-               ret = matcherprop_string_match(prop, info->to, context1)
-                       || matcherprop_string_match(prop, info->cc, context2);
-               g_free(context1);
-               g_free(context2);
-               return ret;
-       }
+               return matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
+                    || matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
        case MATCHCRITERIA_NOT_TO_AND_NOT_CC:
-       {
-               gchar *context1, *context2;
-               gboolean ret;
-
-               context1 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("To:"));
-               context2 = g_strdup_printf(_("%s header"), prefs_common_translated_header_name("Cc:"));
-               ret = !(matcherprop_string_match(prop, info->to, context1)
-                       || matcherprop_string_match(prop, info->cc, context2));
-               g_free(context1);
-               g_free(context2);
-               return ret;
-       }
+               return !matcherprop_string_match(prop, info->to, context_str[CONTEXT_TO])
+                    && !matcherprop_string_match(prop, info->cc, context_str[CONTEXT_CC]);
        case MATCHCRITERIA_TAG:
+               return matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
        case MATCHCRITERIA_NOT_TAG:
-       {
-               gboolean ret;
-
-               ret = matcherprop_tag_match(prop, info, _("Tag"));
-               return (prop->criteria == MATCHCRITERIA_TAG)? ret : !ret;
-       }
+               return !matcherprop_tag_match(prop, info, context_str[CONTEXT_TAG]);
        case MATCHCRITERIA_TAGGED:
+               return info->tags != NULL;
        case MATCHCRITERIA_NOT_TAGGED:
-       {
-               gboolean ret;
-
-               ret = (info->tags != NULL);
-               return (prop->criteria == MATCHCRITERIA_TAGGED)? ret : !ret;
-       }
+               return info->tags == NULL;
        case MATCHCRITERIA_AGE_GREATER:
+               age_mult_hours = 24;
+               /* Fallthrough intended */
+       case MATCHCRITERIA_AGE_GREATER_HOURS:
        {
                gboolean ret;
                gint age;
 
                t = time(NULL);
-               age = ((t - info->date_t) / (60 * 60 * 24));
+               age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
                ret = (age >= prop->value);
 
                /* debug output */
@@ -943,12 +916,15 @@ gboolean matcherprop_match(MatcherProp *prop,
                return ret;
        }
        case MATCHCRITERIA_AGE_LOWER:
+               age_mult_hours = 24;
+               /* Fallthrough intended */
+       case MATCHCRITERIA_AGE_LOWER_HOURS:
        {
                gboolean ret;
                gint age;
 
                t = time(NULL);
-               age = ((t - info->date_t) / (60 * 60 * 24));
+               age = ((t - info->date_t) / (60 * 60 * age_mult_hours));
                ret = (age < prop->value);
 
                /* debug output */
@@ -1122,41 +1098,17 @@ gboolean matcherprop_match(MatcherProp *prop,
                return ret;
        }
        case MATCHCRITERIA_NEWSGROUPS:
+               return matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
        case MATCHCRITERIA_NOT_NEWSGROUPS:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"),
-                                               prefs_common_translated_header_name("Newsgroups:"));
-               ret = matcherprop_string_match(prop, info->newsgroups, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_NEWSGROUPS)? ret : !ret;
-       }
+               return !matcherprop_string_match(prop, info->newsgroups, context_str[CONTEXT_NEWSGROUPS]);
        case MATCHCRITERIA_INREPLYTO:
+               return matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
        case MATCHCRITERIA_NOT_INREPLYTO:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"),
-                                               prefs_common_translated_header_name("In-Reply-To:"));
-               ret = matcherprop_string_match(prop, info->inreplyto, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_INREPLYTO)? ret : !ret;
-       }
+               return !matcherprop_string_match(prop, info->inreplyto, context_str[CONTEXT_IN_REPLY_TO]);
        case MATCHCRITERIA_REFERENCES:
+               return matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
        case MATCHCRITERIA_NOT_REFERENCES:
-       {
-               gchar *context;
-               gboolean ret;
-
-               context = g_strdup_printf(_("%s header"),
-                                               prefs_common_translated_header_name("References:"));
-               ret = matcherprop_list_match(prop, info->references, context);
-               g_free(context);
-               return (prop->criteria == MATCHCRITERIA_REFERENCES)? ret : !ret;
-       }
+               return !matcherprop_list_match(prop, info->references, context_str[CONTEXT_REFERENCES]);
        case MATCHCRITERIA_TEST:
                return matcherprop_match_test(prop, info);
        case MATCHCRITERIA_NOT_TEST:
@@ -1188,6 +1140,107 @@ MatcherList *matcherlist_new(GSList *matchers, gboolean bool_and)
        return cond;
 }
 
+#ifdef G_OS_UNIX
+/*!
+ *\brief       Builds a single regular expresion from an array of srings.
+ *
+ *\param       strings The lines containing the different sub-regexp.
+ *
+ *\return      The newly allocated regexp string.
+ */
+static gchar *build_complete_regexp(gchar **strings)
+{
+       int i = 0;
+       gchar *expr = NULL;
+       while (strings && strings[i] && *strings[i]) {
+               int old_len = expr ? strlen(expr):0;
+               int new_len = 0;
+               gchar *tmpstr = NULL;
+
+               if (g_utf8_validate(strings[i], -1, NULL))
+                       tmpstr = g_strdup(strings[i]);
+               else
+                       tmpstr = conv_codeset_strdup(strings[i], 
+                                       conv_get_locale_charset_str_no_utf8(),
+                                       CS_INTERNAL);
+
+               if (strstr(tmpstr, "\n"))
+                       *(strstr(tmpstr, "\n")) = '\0';
+
+               new_len = strlen(tmpstr);
+
+               expr = g_realloc(expr, 
+                       expr ? (old_len + strlen("|()") + new_len + 1)
+                            : (strlen("()") + new_len + 1));
+               
+               if (old_len) {
+                       strcpy(expr + old_len, "|(");
+                       strcpy(expr + old_len + 2, tmpstr);
+                       strcpy(expr + old_len + 2 + new_len, ")");
+               } else {
+                       strcpy(expr+old_len, "(");
+                       strcpy(expr+old_len + 1, tmpstr);
+                       strcpy(expr+old_len + 1 + new_len, ")");
+               }
+               g_free(tmpstr);
+               i++;
+       }
+       return expr;
+}
+#endif
+
+/*!
+ *\brief       Create a new list of matchers from a multi-line string
+ *
+ *\param       lines String with "\n"-separated expressions
+ *\param       bool_and Operator
+ *\param       case_sensitive If the matching is case sensitive or not
+ *
+ *\return      MatcherList * New matcher list
+ */
+MatcherList *matcherlist_new_from_lines(gchar *lines, gboolean bool_and, 
+                                       gboolean case_sensitive)
+{
+       MatcherProp *m = NULL;
+       GSList *matchers = NULL;
+       gchar **strings = g_strsplit(lines, "\n", -1);
+
+#ifdef G_OS_UNIX
+       gchar *expr = NULL;
+       expr = build_complete_regexp(strings);
+       debug_print("building matcherprop for expr '%s'\n", expr?expr:"NULL");
+       
+       m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
+                       case_sensitive? MATCHTYPE_REGEXP: MATCHTYPE_REGEXPCASE,
+                       expr, 0);
+       if (m == NULL) {
+               /* print error message */
+               debug_print("failed to allocate memory for matcherprop\n");
+       } else {
+               matchers = g_slist_append(matchers, m);
+       }
+
+       g_free(expr);
+#else
+       int i = 0;
+       while (strings && strings[i] && *strings[i]) {
+               m = matcherprop_new(MATCHCRITERIA_SUBJECT, NULL,
+                       case_sensitive? MATCHTYPE_MATCH: MATCHTYPE_MATCHCASE,
+                       strings[i], 0);
+               if (m == NULL) {
+                       /* print error message */
+                       debug_print("failed to allocate memory for matcherprop\n");
+               } else {
+                       matchers = g_slist_append(matchers, m);
+               }
+               i++;
+       }
+#endif
+       g_strfreev(strings);
+
+       return matcherlist_new(matchers, bool_and);
+}
+
 /*!
  *\brief       Frees a list of matchers
  *
@@ -1241,9 +1294,9 @@ static gboolean matcherprop_match_one_header(MatcherProp *matcher,
                if (procheader_headername_equal(header->name,
                                                matcher->header)) {
                        if (matcher->criteria == MATCHCRITERIA_HEADER)
-                               result = matcherprop_string_match(matcher, header->body, _("header"));
+                               result = matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
                        else
-                               result = !matcherprop_string_match(matcher, header->body, _("header"));
+                               result = !matcherprop_string_match(matcher, header->body, context_str[CONTEXT_HEADER]);
                        procheader_header_free(header);
                        return result;
                }
@@ -1257,7 +1310,7 @@ static gboolean matcherprop_match_one_header(MatcherProp *matcher,
                if (!header)
                        return FALSE;
                result = matcherprop_header_line_match(matcher, 
-                              header->name, header->body, _("header line"));
+                              header->name, header->body, context_str[CONTEXT_HEADER_LINE]);
                procheader_header_free(header);
                return result;
        case MATCHCRITERIA_NOT_HEADERS_PART:
@@ -1266,7 +1319,7 @@ static gboolean matcherprop_match_one_header(MatcherProp *matcher,
                if (!header)
                        return FALSE;
                result = !matcherprop_header_line_match(matcher, 
-                              header->name, header->body, _("header line"));
+                              header->name, header->body, context_str[CONTEXT_HEADER_LINE]);
                procheader_header_free(header);
                return result;
        case MATCHCRITERIA_FOUND_IN_ADDRESSBOOK:
@@ -1471,6 +1524,127 @@ static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
                return FALSE;
        }
 }
+       
+static gboolean matcherlist_match_binary_content(MatcherList *matchers, MimeInfo *partinfo)
+{
+       FILE *outfp;
+       gchar buf[BUFFSIZE];
+       GSList *l;
+
+       if (!partinfo || partinfo->type == MIMETYPE_TEXT)
+               return FALSE;
+       else
+               outfp = procmime_get_binary_content(partinfo);
+
+       if (!outfp)
+               return FALSE;
+
+       while (fgets(buf, sizeof(buf), outfp) != NULL) {
+               strretchomp(buf);
+
+               for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
+                       MatcherProp *matcher = (MatcherProp *) l->data;
+
+                       if (matcher->done) 
+                               continue;
+
+                       /* Don't scan non-text parts when looking in body, only
+                        * when looking in whole message
+                        */
+                       if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
+                           matcher->criteria == MATCHCRITERIA_BODY_PART)
+                               continue;
+
+                       /* if the criteria is ~body_part or ~message, ZERO lines
+                        * must match for the rule to match.
+                        */
+                       if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
+                           matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
+                               if (matcherprop_string_match(matcher, buf, 
+                                                       context_str[CONTEXT_BODY_LINE])) {
+                                       matcher->result = FALSE;
+                                       matcher->done = TRUE;
+                               } else
+                                       matcher->result = TRUE;
+                       /* else, just one line has to match */
+                       } else if (matcherprop_criteria_body(matcher) ||
+                                  matcherprop_criteria_message(matcher)) {
+                               if (matcherprop_string_match(matcher, buf,
+                                                       context_str[CONTEXT_BODY_LINE])) {
+                                       matcher->result = TRUE;
+                                       matcher->done = TRUE;
+                               }
+                       }
+
+                       /* if the matchers are OR'ed and the rule matched,
+                        * no need to check the others. */
+                       if (matcher->result && matcher->done) {
+                               if (!matchers->bool_and) {
+                                       fclose(outfp);
+                                       return TRUE;
+                               }
+                       }
+               }
+       }
+
+       fclose(outfp);
+       return FALSE;
+}
+
+static gboolean match_content_cb(const gchar *buf, gpointer data)
+{
+       MatcherList *matchers = (MatcherList *)data;
+       gboolean all_done = TRUE;
+       GSList *l;
+
+       for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
+               MatcherProp *matcher = (MatcherProp *) l->data;
+
+               if (matcher->done) 
+                       continue;
+
+               /* if the criteria is ~body_part or ~message, ZERO lines
+                * must match for the rule to match.
+                */
+               if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
+                   matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
+                       if (matcherprop_string_match(matcher, buf, 
+                                               context_str[CONTEXT_BODY_LINE])) {
+                               matcher->result = FALSE;
+                               matcher->done = TRUE;
+                       } else
+                               matcher->result = TRUE;
+               /* else, just one line has to match */
+               } else if (matcherprop_criteria_body(matcher) ||
+                          matcherprop_criteria_message(matcher)) {
+                       if (matcherprop_string_match(matcher, buf,
+                                               context_str[CONTEXT_BODY_LINE])) {
+                               matcher->result = TRUE;
+                               matcher->done = TRUE;
+                       }
+               }
+
+               /* if the matchers are OR'ed and the rule matched,
+                * no need to check the others. */
+               if (matcher->result && matcher->done) {
+                       if (!matchers->bool_and) {
+                               return TRUE;
+                       }
+               }
+
+               if (!matcher->done)
+                       all_done = FALSE;
+       }
+       return all_done;
+}
+
+static gboolean matcherlist_match_text_content(MatcherList *matchers, MimeInfo *partinfo)
+{
+       if (partinfo->type != MIMETYPE_TEXT)
+               return FALSE;
+
+       return procmime_scan_text_content(partinfo, match_content_cb, matchers);
+}
 
 /*!
  *\brief       Check if a line in a message file's body matches
@@ -1483,12 +1657,9 @@ static gboolean matcherprop_criteria_body(const MatcherProp *matcher)
  */
 static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only, MsgInfo *info)
 {
-       GSList *l;
        MimeInfo *mimeinfo = NULL;
        MimeInfo *partinfo = NULL;
-       gchar buf[BUFFSIZE];
        gboolean first_text_found = FALSE;
-       FILE *outfp = NULL;
 
        cm_return_val_if_fail(info != NULL, FALSE);
 
@@ -1504,65 +1675,14 @@ static gboolean matcherlist_match_body(MatcherList *matchers, gboolean body_only
 
                if (partinfo->type == MIMETYPE_TEXT) {
                        first_text_found = TRUE;
-                       outfp = procmime_get_text_content(partinfo);
-               } else
-                       outfp = procmime_get_binary_content(partinfo);
-
-               if (!outfp) {
-                       procmime_mimeinfo_free_all(mimeinfo);
-                       return FALSE;
-               }
-
-               while (fgets(buf, sizeof(buf), outfp) != NULL) {
-                       strretchomp(buf);
-
-                       for (l = matchers->matchers ; l != NULL ; l = g_slist_next(l)) {
-                               MatcherProp *matcher = (MatcherProp *) l->data;
-
-                               if (matcher->done) 
-                                       continue;
-
-                               /* Don't scan non-text parts when looking in body, only
-                                * when looking in whole message
-                                */
-                               if (partinfo && partinfo->type != MIMETYPE_TEXT &&
-                               (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
-                               matcher->criteria == MATCHCRITERIA_BODY_PART))
-                                       continue;
-
-                               /* if the criteria is ~body_part or ~message, ZERO lines
-                                * must match for the rule to match.
-                                */
-                               if (matcher->criteria == MATCHCRITERIA_NOT_BODY_PART ||
-                                   matcher->criteria == MATCHCRITERIA_NOT_MESSAGE) {
-                                       if (matcherprop_string_match(matcher, buf, 
-                                                               _("body line"))) {
-                                               matcher->result = FALSE;
-                                               matcher->done = TRUE;
-                                       } else
-                                               matcher->result = TRUE;
-                               /* else, just one line has to match */
-                               } else if (matcherprop_criteria_body(matcher) ||
-                                          matcherprop_criteria_message(matcher)) {
-                                       if (matcherprop_string_match(matcher, buf,
-                                                               _("body line"))) {
-                                               matcher->result = TRUE;
-                                               matcher->done = TRUE;
-                                       }
-                               }
-
-                               /* if the matchers are OR'ed and the rule matched,
-                                * no need to check the others. */
-                               if (matcher->result && matcher->done) {
-                                       if (!matchers->bool_and) {
-                                               procmime_mimeinfo_free_all(mimeinfo);
-                                               fclose(outfp);
-                                               return TRUE;
-                                       }
-                               }
+                       if (matcherlist_match_text_content(matchers, partinfo)) {
+                               procmime_mimeinfo_free_all(mimeinfo);
+                               return TRUE;
                        }
+               } else if (matcherlist_match_binary_content(matchers, partinfo)) {
+                       procmime_mimeinfo_free_all(mimeinfo);
+                       return TRUE;
                }
-               fclose(outfp);
 
                if (body_only && first_text_found)
                        break;
@@ -1743,6 +1863,8 @@ gboolean matcherlist_match(MatcherList *matchers, MsgInfo *info)
                case MATCHCRITERIA_NOT_TAGGED:
                case MATCHCRITERIA_AGE_GREATER:
                case MATCHCRITERIA_AGE_LOWER:
+               case MATCHCRITERIA_AGE_GREATER_HOURS:
+               case MATCHCRITERIA_AGE_LOWER_HOURS:
                case MATCHCRITERIA_NEWSGROUPS:
                case MATCHCRITERIA_NOT_NEWSGROUPS:
                case MATCHCRITERIA_INREPLYTO:
@@ -1890,6 +2012,8 @@ gchar *matcherprop_to_string(MatcherProp *matcher)
        switch (matcher->criteria) {
        case MATCHCRITERIA_AGE_GREATER:
        case MATCHCRITERIA_AGE_LOWER:
+       case MATCHCRITERIA_AGE_GREATER_HOURS:
+       case MATCHCRITERIA_AGE_LOWER_HOURS:
        case MATCHCRITERIA_SCORE_GREATER:
        case MATCHCRITERIA_SCORE_LOWER:
        case MATCHCRITERIA_SCORE_EQUAL:
@@ -2411,6 +2535,7 @@ static void matcher_add_rulenames(const gchar *rcpath)
        FILE *dst = g_fopen(newpath, "wb");
        gchar buf[BUFFSIZE];
        int r;
+
        if (src == NULL) {
                perror("fopen");
                if (dst)
@@ -2433,8 +2558,14 @@ static void matcher_add_rulenames(const gchar *rcpath)
                && strncmp(buf, "disabled rulename \"", 18)) {
                        r = fwrite("enabled rulename \"\" ",
                                strlen("enabled rulename \"\" "), 1, dst);
+                       if (r != 1) {
+                               g_message("cannot fwrite rulename\n");
+                       }
                }
                r = fwrite(buf, strlen(buf), 1, dst);
+               if (r != 1) {
+                       g_message("cannot fwrite rule\n");
+               }
        }
        fclose(dst);
        fclose(src);