summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands.c10
-rw-r--r--copy.c2
-rw-r--r--copy.h1
-rw-r--r--doc/manual.sgml.head98
-rw-r--r--doc/muttrc.man.head10
-rw-r--r--globals.h3
-rw-r--r--handler.c6
-rw-r--r--hdrline.c12
-rw-r--r--init.c96
-rw-r--r--init.h22
-rw-r--r--mutt.h16
-rw-r--r--muttlib.c120
-rw-r--r--parse.c44
-rw-r--r--pattern.c3
-rw-r--r--protos.h4
-rw-r--r--send.c2
-rw-r--r--sort.c53
-rw-r--r--sort.h9
18 files changed, 502 insertions, 9 deletions
diff --git a/commands.c b/commands.c
index 38abc5ea..95a5334f 100644
--- a/commands.c
+++ b/commands.c
@@ -501,9 +501,9 @@ int mutt_select_sort (int reverse)
int method = Sort; /* save the current method in case of abort */
switch (mutt_multi_choice (reverse ?
- _("Rev-Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore?: ") :
- _("Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore?: "),
- _("dfrsotuzc")))
+ _("Rev-Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: ") :
+ _("Sort (d)ate/(f)rm/(r)ecv/(s)ubj/t(o)/(t)hread/(u)nsort/si(z)e/s(c)ore/s(p)am?: "),
+ _("dfrsotuzcp")))
{
case -1: /* abort - don't resort */
return -1;
@@ -543,6 +543,10 @@ int mutt_select_sort (int reverse)
case 9: /* s(c)ore */
Sort = SORT_SCORE;
break;
+
+ case 10: /* s(p)am */
+ Sort = SORT_SPAM;
+ break;
}
if (reverse)
Sort |= SORT_REVERSE;
diff --git a/copy.c b/copy.c
index ff889cde..9e77ba2f 100644
--- a/copy.c
+++ b/copy.c
@@ -586,6 +586,8 @@ _mutt_copy_message (FILE *fpout, FILE *fpin, HEADER *hdr, BODY *body,
s.flags |= M_WEED;
if (flags & M_CM_CHARCONV)
s.flags |= M_CHARCONV;
+ if (flags & M_CM_REPLYING)
+ s.flags |= M_REPLYING;
if (WithCrypto && flags & M_CM_VERIFY)
s.flags |= M_VERIFY;
diff --git a/copy.h b/copy.h
index c03d5f85..67568380 100644
--- a/copy.h
+++ b/copy.h
@@ -25,6 +25,7 @@
#define M_CM_WEED (1<<5) /* weed message/rfc822 attachment headers */
#define M_CM_CHARCONV (1<<6) /* perform character set conversions */
#define M_CM_PRINTING (1<<7) /* printing the message - display light */
+#define M_CM_REPLYING (1<<8) /* replying the message */
#define M_CM_DECODE_PGP (1<<8) /* used for decoding PGP messages */
diff --git a/doc/manual.sgml.head b/doc/manual.sgml.head
index 0ced0e50..3dfb89a3 100644
--- a/doc/manual.sgml.head
+++ b/doc/manual.sgml.head
@@ -1499,6 +1499,97 @@ specify the same pattern specified in the <tt/score/ command for it to be
removed. The pattern ``*'' is a special token which means to clear the list
of all score entries.
+<sect1>Spam detection<label id="spam">
+<p>
+Usage: <tt/spam/ <em/pattern/ <em/format/
+Usage: <tt/nospam/ <em/pattern/
+
+Mutt has generalized support for external spam-scoring filters.
+By defining your spam patterns with the <tt/spam/ and <tt/nospam/
+commands, you can <em/limit/, <em/search/, and <em/sort/ your
+mail based on its spam attributes, as determined by the external
+filter. You also can display the spam attributes in your index
+display using the <tt/%H/ selector in the <ref id="index_format"
+name="&dollar;index&lowbar;format"> variable. (Tip: try <tt/%?H?[%H] ?/
+to display spam tags only when they are defined for a given message.)
+
+Your first step is to define your external filter's spam patterns using
+the <tt/spam/ command. <em/pattern/ should be a regular expression
+that matches a header in a mail message. If any message in the mailbox
+matches this regular expression, it will receive a ``spam tag'' or
+``spam attribute'' (unless it also matches a <tt/nospam/ pattern -- see
+below.) The appearance of this attribute is entirely up to you, and is
+governed by the <em/format/ parameter. <em/format/ can be any static
+text, but it also can include back-references from the <em/pattern/
+expression. (A regular expression ``back-reference'' refers to a
+sub-expression contained within parentheses.) <tt/%1/ is replaced with
+the first back-reference in the regex, <tt/%2/ with the second, etc.
+
+If you're using multiple spam filters, a message can have more than
+one spam-related header. You can define <tt/spam/ patterns for each
+filter you use. If a message matches two or more of these patterns, and
+the &dollar;spam&lowbar;separator variable is set to a string, then the
+message's spam tag will consist of all the <em/format/ strings joined
+together, with the value of &dollar;spam&lowbar;separator separating
+them.
+
+For example, suppose I use DCC, SpamAssassin, and PureMessage. I might
+define these spam settings:
+<tscreen><verb>
+spam "X-DCC-.*-Metrics:.*(....)=many" "90+/DCC-%1"
+spam "X-Spam-Status: Yes" "90+/SA"
+spam "X-PerlMX-Spam: .*Probability=([0-9]+)%" "%1/PM"
+set spam_separator=", "
+</verb></tscreen>
+
+If I then received a message that DCC registered with ``many'' hits
+under the ``Fuz2'' checksum, and that PureMessage registered with a
+97% probability of being spam, that message's spam tag would read
+<tt>90+/DCC-Fuz2, 97/PM</tt>. (The four characters before ``=many'' in a
+DCC report indicate the checksum used -- in this case, ``Fuz2''.)
+
+If the &dollar;spam&lowbar;separator variable is unset, then each
+spam pattern match supercedes the previous one. Instead of getting
+joined <em/format/ strings, you'll get only the last one to match.
+
+The spam tag is what will be displayed in the index when you use
+<tt/%H/ in the <tt/&dollar;index&lowbar;format/ variable. It's also the
+string that the <tt/~H/ pattern-matching expression matches against for
+<em/search/ and <em/limit/ functions. And it's what sorting by spam
+attribute will use as a sort key.
+
+That's a pretty complicated example, and most people's actual
+environments will have only one spam filter. The simpler your
+configuration, the more effective mutt can be, especially when it comes
+to sorting.
+
+Generally, when you sort by spam tag, mutt will sort <em/lexically/ --
+that is, by ordering strings alphnumerically. However, if a spam tag
+begins with a number, mutt will sort numerically first, and lexically
+only when two numbers are equal in value. (This is like UNIX's
+<tt/sort -n/.) A message with no spam attributes at all -- that is, one
+that didn't match <em/any/ of your <tt/spam/ patterns -- is sorted at
+lowest priority. Numbers are sorted next, beginning with 0 and ranging
+upward. Finally, non-numeric strings are sorted, with ``a'' taking lower
+priority than ``z''. Clearly, in general, sorting by spam tags is most
+effective when you can coerce your filter to give you a raw number. But
+in case you can't, mutt can still do something useful.
+
+Finally, the <tt/nospam/ command can be used to write exceptions to
+<tt/spam/ patterns. If a header pattern matches something in a <tt/spam/
+command, but you nonetheless do not want it to receive a spam tag,
+you can list a more precise pattern under a <tt/nospam/ command.
+
+You can have as many <tt/spam/ or <tt/nospam/ commands as you like.
+You can even do your own primitive spam detection within mutt -- for
+example, if you consider all mail from MAILER-DAEMON to be spam, you can
+use a <tt/spam/ command like this:
+
+<tscreen><verb>
+spam "^From: .*MAILER-DAEMON" "999"
+</verb></tscreen>
+
+
<sect1>Setting variables<label id="set">
<p>
Usage: <tt/set/ &lsqb;no|inv&rsqb;<em/variable/&lsqb;=<em/value/&rsqb; &lsqb; <em/variable/ ... &rsqb;<newline>
@@ -1766,6 +1857,7 @@ messages:
~f USER messages originating from USER
~g cryptographically signed messages
~G cryptographically encrypted messages
+~H EXPR messages with a spam attribute matching EXPR
~h EXPR messages which contain EXPR in the message header
~k message contains PGP key material
~i ID message which match ID in the ``Message-ID'' field
@@ -2399,7 +2491,7 @@ account-hook imap://host2/ 'set tunnel="ssh host2 /usr/libexec/imapd"'
<sect1>Start a WWW Browser on URLs (EXTERNAL)<label id="urlview">
<p>
-If a message contains URLs (<em/unified ressource locator/ = address in the
+If a message contains URLs (<em/unified resource locator/ = address in the
WWW space like <em>http://www.mutt.org/</em>), it is efficient to get
a menu with all the URLs and start a WWW browser on one of them. This
functionality is provided by the external urlview program which can be
@@ -3063,6 +3155,10 @@ The following are the commands understood by mutt.
<item>
<tt><ref id="source" name="source"></tt> <em/filename/
<item>
+<tt><ref id="spam" name="spam"></tt> <em/pattern/ <em/format/
+<item>
+<tt><ref id="spam" name="nospam"></tt> <em/pattern/
+<item>
<tt><ref id="lists" name="subscribe"></tt> <em/regexp/ &lsqb; <em/regexp/ ... &rsqb;
<item>
<tt><ref id="lists" name="unsubscribe"></tt> <em/regexp/ &lsqb; <em/regexp/ ... &rsqb;
diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head
index b52b81b8..03a9d880 100644
--- a/doc/muttrc.man.head
+++ b/doc/muttrc.man.head
@@ -338,6 +338,15 @@ variables will reset to their system defaults.
\fBsource\fP \fIfilename\fP
The given file will be evaluated as a configuration file.
.TP
+.nf
+\fBspam\fP \fIpattern\fP \fIformat\fP
+\fBnospam\fP \fIpattern\fP
+.fi
+These commands define spam-detection patterns from external spam
+filters, so that mutt can sort, limit, and search on
+``spam tags'' or ``spam attributes'', or display them
+in the index. See the Mutt manual for details.
+.TP
\fBunhook\fP [\fB * \fP | \fIhook-type\fP ]
This command will remove all hooks of a given type, or all hooks
when \(lq\fB*\fP\(rq is used as an argument. \fIhook-type\fP
@@ -386,6 +395,7 @@ l l.
~f \fIEXPR\fP messages originating from \fIEXPR\fP
~g PGP signed messages
~G PGP encrypted messages
+~H \fIEXPR\fP messages with spam tags matching \fIEXPR\fP
~h \fIEXPR\fP messages which contain \fIEXPR\fP in the message header
~k message contains PGP key material
~i \fIEXPR\fP message which match \fIEXPR\fP in the \(lqMessage-ID\(rq field
diff --git a/globals.h b/globals.h
index 8841c854..4f3adbb5 100644
--- a/globals.h
+++ b/globals.h
@@ -103,6 +103,7 @@ WHERE char *Shell;
WHERE char *Signature;
WHERE char *SimpleSearch;
WHERE char *Spoolfile;
+WHERE char *SpamSep;
#if defined(USE_SSL) || defined(USE_NSS)
WHERE char *SslCertFile INITVAL (NULL);
WHERE char *SslEntropyFile INITVAL (NULL);
@@ -126,6 +127,8 @@ WHERE LIST *UnIgnore INITVAL(0);
WHERE RX_LIST *Alternates INITVAL(0);
WHERE RX_LIST *MailLists INITVAL(0);
WHERE RX_LIST *SubscribedLists INITVAL(0);
+WHERE SPAM_LIST *SpamList INITVAL(0);
+WHERE RX_LIST *NoSpamList INITVAL(0);
/* bit vector for boolean variables */
#ifdef MAIN_C
diff --git a/handler.c b/handler.c
index 85040595..d425c222 100644
--- a/handler.c
+++ b/handler.c
@@ -1485,6 +1485,9 @@ void multipart_handler (BODY *a, STATE *s)
}
mutt_body_handler (p, s);
state_putc ('\n', s);
+ if ((s->flags & M_REPLYING)
+ && (option (OPTINCLUDEONLYFIRST)) && (s->flags & M_FIRSTDONE))
+ break;
}
if (a->encoding == ENCBASE64 || a->encoding == ENCQUOTEDPRINTABLE ||
@@ -1918,6 +1921,7 @@ void mutt_body_handler (BODY *b, STATE *s)
s->fpin = fp;
}
}
+ s->flags |= M_FIRSTDONE;
}
else if (s->flags & M_DISPLAY)
{
@@ -1935,5 +1939,5 @@ void mutt_body_handler (BODY *b, STATE *s)
}
bail:
- s->flags = oflags;
+ s->flags = oflags | (s->flags & M_FIRSTDONE);
}
diff --git a/hdrline.c b/hdrline.c
index 931bd428..e2d9f983 100644
--- a/hdrline.c
+++ b/hdrline.c
@@ -433,6 +433,18 @@ hdr_format_str (char *dest,
optional = 0;
break;
+ case 'H':
+ /* (Hormel) spam score */
+ if (optional)
+ optional = hdr->env->spam ? 1 : 0;
+
+ if (hdr->env->spam)
+ mutt_format_s (dest, destlen, prefix, NONULL (hdr->env->spam->data));
+ else
+ mutt_format_s (dest, destlen, prefix, "");
+
+ break;
+
case 'i':
mutt_format_s (dest, destlen, prefix, hdr->env->message_id ? hdr->env->message_id : "<no.id>");
break;
diff --git a/init.c b/init.c
index 764c23ed..4196aa0b 100644
--- a/init.c
+++ b/init.c
@@ -366,6 +366,73 @@ static int add_to_rx_list (RX_LIST **list, const char *s, int flags, BUFFER *err
}
+static int add_to_spam_list (SPAM_LIST **list, const char *pat, const char *templ, BUFFER *err)
+{
+ SPAM_LIST *t, *last = NULL;
+ REGEXP *rx;
+ int n;
+ const char *p;
+
+ if (!pat || !*pat || !templ)
+ return 0;
+
+ if (!(rx = mutt_compile_regexp (pat, REG_ICASE)))
+ {
+ snprintf (err->data, err->dsize, _("Bad regexp: %s"), pat);
+ return -1;
+ }
+
+ /* check to make sure the item is not already on this list */
+ for (last = *list; last; last = last->next)
+ {
+ if (ascii_strcasecmp (rx->pattern, last->rx->pattern) == 0)
+ {
+ /* already on the list, so just ignore it */
+ last = NULL;
+ break;
+ }
+ if (!last->next)
+ break;
+ }
+
+ if (!*list || last)
+ {
+ t = mutt_new_spam_list();
+ t->rx = rx;
+ t->template = safe_strdup(templ);
+
+ /* find highest match number in template string */
+ t->nmatch = 0;
+ for (p = templ; *p;)
+ {
+ if (*p == '%')
+ {
+ n = atoi(++p);
+ if (n > t->nmatch)
+ t->nmatch = n;
+ while (*p && isdigit(*p))
+ ++p;
+ }
+ else
+ ++p;
+ }
+ t->nmatch++; /* match 0 is always the whole expr */
+
+ if (last)
+ {
+ last->next = t;
+ last = last->next;
+ }
+ else
+ *list = last = t;
+ }
+ else /* duplicate */
+ mutt_free_regexp (&rx);
+
+ return 0;
+}
+
+
static void remove_from_list (LIST **l, const char *str)
{
LIST *p, *last = NULL;
@@ -504,6 +571,35 @@ static int parse_rx_unlist (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *
return 0;
}
+static int parse_spam_list (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+{
+ BUFFER templ;
+
+ memset(&templ, 0, sizeof(templ));
+
+ if (!MoreArgs(s))
+ {
+ strfcpy(err->data, _("spam: no matching pattern"), err->dsize);
+ return -1;
+ }
+ mutt_extract_token (buf, s, 0);
+
+ if (MoreArgs(s))
+ {
+ mutt_extract_token (&templ, s, 0);
+ }
+ else
+ {
+ templ.data = NULL;
+ templ.dsize = 0;
+ }
+
+ if (add_to_spam_list ((SPAM_LIST **) data, buf->data, templ.data, err) != 0)
+ return -1;
+
+ return 0;
+}
+
static int parse_unlist (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
{
do
diff --git a/init.h b/init.h
index 8a67a153..301e7c50 100644
--- a/init.h
+++ b/init.h
@@ -884,6 +884,12 @@ struct option_t MuttVars[] = {
** Controls whether or not a copy of the message(s) you are replying to
** is included in your reply.
*/
+ { "include_onlyfirst", DT_BOOL, R_NONE, OPTINCLUDEONLYFIRST, 0},
+ /*
+ ** .pp
+ ** Controls whether or not Mutt includes only the first attachment
+ ** of the message you are replying.
+ */
{ "indent_string", DT_STR, R_NONE, UL &Prefix, UL "> " },
/*
** .pp
@@ -919,6 +925,7 @@ struct option_t MuttVars[] = {
** .dt %E .dd number of messages in current thread
** .dt %f .dd entire From: line (address + real name)
** .dt %F .dd author name, or recipient name if the message is from you
+ ** .dt %H .dd spam attribute(s) of this message
** .dt %i .dd message-id of the current message
** .dt %l .dd number of lines in the message (does not work with maildir,
** mh, and possibly IMAP folders)
@@ -2354,6 +2361,7 @@ struct option_t MuttVars[] = {
** . mailbox-order (unsorted)
** . score
** . size
+ ** . spam
** . subject
** . threads
** . to
@@ -2419,6 +2427,15 @@ struct option_t MuttVars[] = {
** the message whether or not this is the case, as long as the
** non-``$$reply_regexp'' parts of both messages are identical.
*/
+ { "spam_separator", DT_STR, R_NONE, UL &SpamSep, UL 0 },
+ /*
+ ** .pp
+ ** ``$spam_separator'' controls what happens when multiple spam headers
+ ** are matched: if unset, each successive header will overwrite any
+ ** previous matches value for the spam label. If set, each successive
+ ** match will append to the previous, using ``$spam_separator'' as a
+ ** separator.
+ */
{ "spoolfile", DT_PATH, R_NONE, UL &Spoolfile, 0 },
/*
** .pp
@@ -2718,6 +2735,7 @@ const struct mapping_t SortMethods[] = {
{ "threads", SORT_THREADS },
{ "to", SORT_TO },
{ "score", SORT_SCORE },
+ { "spam", SORT_SPAM },
{ NULL, 0 }
};
@@ -2736,6 +2754,7 @@ const struct mapping_t SortAuxMethods[] = {
*/
{ "to", SORT_TO },
{ "score", SORT_SCORE },
+ { "spam", SORT_SPAM },
{ NULL, 0 }
};
@@ -2768,6 +2787,7 @@ const struct mapping_t SortKeyMethods[] = {
static int parse_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
static int parse_rx_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+static int parse_spam_list (BUFFER *, BUFFER *, unsigned long, BUFFER *);
static int parse_unlist (BUFFER *, BUFFER *, unsigned long, BUFFER *);
static int parse_rx_unlist (BUFFER *, BUFFER *, unsigned long, BUFFER *);
@@ -2833,6 +2853,8 @@ struct command_t Commands[] = {
{ "send-hook", mutt_parse_hook, M_SENDHOOK },
{ "set", parse_set, 0 },
{ "source", parse_source, 0 },
+ { "spam", parse_spam_list, UL &SpamList },
+ { "nospam", parse_rx_list, UL &NoSpamList },
{ "subscribe", parse_subscribe, 0 },
{ "toggle", parse_set, M_SET_INV },
{ "unalias", parse_unalias, 0 },
diff --git a/mutt.h b/mutt.h
index 45597ece..85afda8f 100644
--- a/mutt.h
+++ b/mutt.h
@@ -223,6 +223,7 @@ enum
M_ID,
M_BODY,
M_HEADER,
+ M_HORMEL,
M_WHOLE_MSG,
M_SENDER,
M_MESSAGE,
@@ -373,6 +374,7 @@ enum
OPTSSLSYSTEMCERTS,
#endif
OPTIMPLICITAUTOVIEW,
+ OPTINCLUDEONLYFIRST,
OPTKEEPFLAGGED,
OPTMAILCAPSANITIZE,
OPTMAILDIRTRASH,
@@ -408,6 +410,7 @@ enum
OPTSIGDASHES,
OPTSIGONTOP,
OPTSORTRE,
+ OPTSPAMSEP,
OPTSTATUSONTOP,
OPTSTRICTTHREADS,
OPTSUSPEND,
@@ -518,10 +521,20 @@ typedef struct rx_list_t
struct rx_list_t *next;
} RX_LIST;
+typedef struct spam_list_t
+{
+ REGEXP *rx;
+ int nmatch;
+ char *template;
+ struct spam_list_t *next;
+} SPAM_LIST;
+
#define mutt_new_list() safe_calloc (1, sizeof (LIST))
#define mutt_new_rx_list() safe_calloc (1, sizeof (RX_LIST))
+#define mutt_new_spam_list() safe_calloc (1, sizeof (SPAM_LIST))
void mutt_free_list (LIST **);
void mutt_free_rx_list (RX_LIST **);
+void mutt_free_spam_list (SPAM_LIST **);
int mutt_matches_ignore (const char *, LIST *);
/* add an element to a list */
@@ -556,6 +569,7 @@ typedef struct envelope
char *supersedes;
char *date;
char *x_label;
+ BUFFER *spam;
LIST *references; /* message references (in reverse order) */
LIST *in_reply_to; /* in-reply-to header content */
LIST *userhdrs; /* user defined headers */
@@ -845,6 +859,8 @@ typedef struct
#define M_WEED (1<<3) /* weed headers even when not in display mode */
#define M_CHARCONV (1<<4) /* Do character set conversions */
#define M_PRINTING (1<<5) /* are we printing? - M_DISPLAY "light" */
+#define M_REPLYING (1<<6) /* are we replying? */
+#define M_FIRSTDONE (1<<7) /* the first attachment has been done */
#define state_set_prefix(s) ((s)->flags |= M_PENDINGPREFIX)
#define state_reset_prefix(s) ((s)->flags &= ~M_PENDINGPREFIX)
diff --git a/muttlib.c b/muttlib.c
index e02b44dc..896e9982 100644
--- a/muttlib.c
+++ b/muttlib.c
@@ -1292,6 +1292,60 @@ void mutt_sleep (short s)
sleep (s);
}
+/*
+ * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
+ * just initializes. Frees anything already in the buffer.
+ *
+ * Disregards the 'destroy' flag, which seems reserved for caller.
+ * This is bad, but there's no apparent protocol for it.
+ */
+BUFFER * mutt_buffer_init(BUFFER *b)
+{
+ if (!b)
+ {
+ b = safe_malloc(sizeof(BUFFER));
+ if (!b)
+ return NULL;
+ }
+ else
+ {
+ safe_free(b->data);
+ }
+ memset(b, 0, sizeof(BUFFER));
+ return b;
+}
+
+/*
+ * Creates and initializes a BUFFER*. If passed an existing BUFFER*,
+ * just initializes. Frees anything already in the buffer. Copies in
+ * the seed string.
+ *
+ * Disregards the 'destroy' flag, which seems reserved for caller.
+ * This is bad, but there's no apparent protocol for it.
+ */
+BUFFER * mutt_buffer_from(BUFFER *b, char *seed)
+{
+ int n;
+
+ if (!seed)
+ return NULL;
+
+ b = mutt_buffer_init(b);
+ b->data = safe_strdup (seed);
+ b->dsize = mutt_strlen (seed);
+ b->dptr = (char *) b->data + b->dsize;
+ return b;
+}
+
+void mutt_buffer_free(BUFFER **b)
+{
+ if (!b)
+ return;
+ if ((*b)->data)
+ safe_free(&((*b)->data));
+ safe_free(b);
+}
+
void mutt_buffer_addstr (BUFFER* buf, const char* s)
{
mutt_buffer_add (buf, s, mutt_strlen (s));
@@ -1388,6 +1442,21 @@ void mutt_free_rx_list (RX_LIST **list)
}
}
+void mutt_free_spam_list (SPAM_LIST **list)
+{
+ SPAM_LIST *p;
+
+ if (!list) return;
+ while (*list)
+ {
+ p = *list;
+ *list = (*list)->next;
+ mutt_free_regexp (&p->rx);
+ safe_free(&p->template);
+ FREE (&p);
+ }
+}
+
int mutt_match_rx_list (const char *s, RX_LIST *l)
{
if (!s) return 0;
@@ -1403,3 +1472,54 @@ int mutt_match_rx_list (const char *s, RX_LIST *l)
return 0;
}
+
+int mutt_match_spam_list (const char *s, SPAM_LIST *l, char *text, int x)
+{
+ static regmatch_t *pmatch = NULL;
+ static int nmatch = 0;
+ int i, n, tlen;
+ char *p;
+
+ if (!s) return 0;
+
+ tlen = 0;
+
+ for (; l; l = l->next)
+ {
+ /* If this pattern needs more matches, expand pmatch. */
+ if (l->nmatch > nmatch)
+ {
+ safe_realloc ((void**) &pmatch, l->nmatch * sizeof(regmatch_t));
+ nmatch = l->nmatch;
+ }
+
+ /* Does this pattern match? */
+ if (regexec (l->rx->rx, s, (size_t) l->nmatch, (regmatch_t *) pmatch, (int) 0) == 0)
+ {
+ dprint (5, (debugfile, "mutt_match_spam_list: %s matches %s\n", s, l->rx->pattern));
+ dprint (5, (debugfile, "mutt_match_spam_list: %d subs\n", l->rx->rx->re_nsub));
+
+ /* Copy template into text, with substitutions. */
+ for (p = l->template; *p;)
+ {
+ if (*p == '%')
+ {
+ n = atoi(++p); /* find pmatch index */
+ while (isdigit(*p))
+ ++p; /* skip subst token */
+ for (i = pmatch[n].rm_so; (i < pmatch[n].rm_eo) && (tlen < x); i++)
+ text[tlen++] = s[i];
+ }
+ else
+ {
+ text[tlen++] = *p++;
+ }
+ }
+ text[tlen] = '\0';
+ dprint (5, (debugfile, "mutt_match_spam_list: \"%s\"\n", text));
+ return 1;
+ }
+ }
+
+ return 0;
+}
diff --git a/parse.c b/parse.c
index 471c55e5..53ecbfda 100644
--- a/parse.c
+++ b/parse.c
@@ -1269,6 +1269,7 @@ ENVELOPE *mutt_read_rfc822_header (FILE *f, HEADER *hdr, short user_hdrs,
long loc;
int matched;
size_t linelen = LONG_STRING;
+ char buf[LONG_STRING+1];
if (hdr)
{
@@ -1312,6 +1313,49 @@ ENVELOPE *mutt_read_rfc822_header (FILE *f, HEADER *hdr, short user_hdrs,
break; /* end of header */
}
+ *buf = '\0';
+
+ if (mutt_match_spam_list(line, SpamList, buf, sizeof(buf)))
+ {
+ if (!mutt_match_rx_list(line, NoSpamList))
+ {
+
+ /* if spam tag already exists, figure out how to amend it */
+ if (e->spam && *buf)
+ {
+ /* If SpamSep defined, append with separator */
+ if (SpamSep)
+ {
+ mutt_buffer_addstr(e->spam, SpamSep);
+ mutt_buffer_addstr(e->spam, buf);
+ }
+
+ /* else overwrite */
+ else
+ {
+ e->spam->dptr = e->spam->data;
+ *e->spam->dptr = '\0';
+ mutt_buffer_addstr(e->spam, buf);
+ }
+ }
+
+ /* spam tag is new, and match expr is non-empty; copy */
+ else if (!e->spam && *buf)
+ {
+ e->spam = mutt_buffer_from(NULL, buf);
+ }
+
+ /* match expr is empty; plug in null string if no existing tag */
+ else if (!e->spam)
+ {
+ e->spam = mutt_buffer_from(NULL, "");
+ }
+
+ if (e->spam && e->spam->data)
+ dprint(5, (debugfile, "p822: spam = %s\n", e->spam->data));
+ }
+ }
+
*p = 0;
p++;
SKIPWS (p);
diff --git a/pattern.c b/pattern.c
index 70130af0..6ee3e67d 100644
--- a/pattern.c
+++ b/pattern.c
@@ -58,6 +58,7 @@ Flags[] =
{ 'g', M_CRYPT_SIGN, 0, NULL },
{ 'G', M_CRYPT_ENCRYPT, 0, NULL },
{ 'h', M_HEADER, M_FULL_MSG, eat_regexp },
+ { 'H', M_HORMEL, 0, eat_regexp },
{ 'i', M_ID, 0, eat_regexp },
{ 'k', M_PGP_KEY, 0, NULL },
{ 'L', M_ADDRESS, 0, eat_regexp },
@@ -1046,6 +1047,8 @@ mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx,
return (pat->not ^ ((h->security & APPLICATION_PGP) && (h->security & PGPKEY)));
case M_XLABEL:
return (pat->not ^ (h->env->x_label && regexec (pat->rx, h->env->x_label, 0, NULL, 0) == 0));
+ case M_HORMEL:
+ return (pat->not ^ (h->env->spam && h->env->spam->data && regexec (pat->rx, h->env->spam->data, 0, NULL, 0) == 0));
case M_DUPLICATED:
return (pat->not ^ (h->thread && h->thread->duplicate_thread));
case M_UNREFERENCED:
diff --git a/protos.h b/protos.h
index 5aab3d53..a481e4f4 100644
--- a/protos.h
+++ b/protos.h
@@ -32,6 +32,9 @@ void _mutt_make_string (char *, size_t, const char *, CONTEXT *,
HEADER *, format_flag);
int mutt_extract_token (BUFFER *, BUFFER *, int);
+BUFFER * mutt_buffer_init (BUFFER *);
+BUFFER * mutt_buffer_from (BUFFER *, char *);
+void mutt_buffer_free(BUFFER **);
void mutt_buffer_add (BUFFER*, const char*, size_t);
void mutt_buffer_addstr (BUFFER*, const char*);
void mutt_buffer_addch (BUFFER*, char);
@@ -291,6 +294,7 @@ int mutt_is_text_part (BODY *);
int mutt_is_valid_mailbox (const char *);
int mutt_lookup_mime_type (BODY *, const char *);
int mutt_match_rx_list (const char *, RX_LIST *);
+int mutt_match_spam_list (const char *, SPAM_LIST *, char *, int);
int mutt_messages_in_thread (CONTEXT *, HEADER *, int);
int mutt_multi_choice (char *prompt, char *letters);
int mutt_needs_mailcap (BODY *);
diff --git a/send.c b/send.c
index cc171e76..1d791934 100644
--- a/send.c
+++ b/send.c
@@ -403,7 +403,7 @@ void mutt_make_post_indent (CONTEXT *ctx, HEADER *cur, FILE *out)
static int include_reply (CONTEXT *ctx, HEADER *cur, FILE *out)
{
- int cmflags = M_CM_PREFIX | M_CM_DECODE | M_CM_CHARCONV;
+ int cmflags = M_CM_PREFIX | M_CM_DECODE | M_CM_CHARCONV | M_CM_REPLYING;
int chflags = CH_DECODE;
if (WithCrypto && (cur->security & ENCRYPT))
diff --git a/sort.c b/sort.c
index cec5b3c7..17e49f99 100644
--- a/sort.c
+++ b/sort.c
@@ -149,6 +149,57 @@ int compare_order (const void *a, const void *b)
return (SORTCODE ((*ha)->index - (*hb)->index));
}
+int compare_spam (const void *a, const void *b)
+{
+ HEADER **ppa = (HEADER **) a;
+ HEADER **ppb = (HEADER **) b;
+ char *aptr, *bptr;
+ int ahas, bhas;
+ int result = 0;
+
+ /* Firstly, require spam attributes for both msgs */
+ /* to compare. Determine which msgs have one. */
+ ahas = (*ppa)->env && (*ppa)->env->spam;
+ bhas = (*ppb)->env && (*ppb)->env->spam;
+
+ /* If one msg has spam attr but other does not, sort the one with first. */
+ if (ahas && !bhas)
+ return (SORTCODE(1));
+ if (!ahas && bhas)
+ return (SORTCODE(-1));
+
+ /* Else, if neither has a spam attr, presume equality. Fall back on aux. */
+ if (!ahas && !bhas)
+ {
+ AUXSORT(result, a, b);
+ return (SORTCODE(result));
+ }
+
+
+ /* Both have spam attrs. */
+
+ /* preliminary numeric examination */
+ result = (strtoul((*ppa)->env->spam->data, &aptr, 10) -
+ strtoul((*ppb)->env->spam->data, &bptr, 10));
+
+ /* If either aptr or bptr is equal to data, there is no numeric */
+ /* value for that spam attribute. In this case, compare lexically. */
+ if ((aptr == (*ppa)->env->spam->data) || (bptr == (*ppb)->env->spam->data))
+ return (SORTCODE(strcmp(aptr, bptr)));
+
+ /* Otherwise, we have numeric value for both attrs. If these values */
+ /* are equal, then we first fall back upon string comparison, then */
+ /* upon auxiliary sort. */
+ if (result == 0)
+ {
+ result = strcmp(aptr, bptr);
+ if (result == 0)
+ AUXSORT(result, a, b);
+ }
+
+ return (SORTCODE(result));
+}
+
sort_t *mutt_get_sort_func (int method)
{
switch (method & SORT_MASK)
@@ -169,6 +220,8 @@ sort_t *mutt_get_sort_func (int method)
return (compare_to);
case SORT_SCORE:
return (compare_score);
+ case SORT_SPAM:
+ return (compare_spam);
default:
return (NULL);
}
diff --git a/sort.h b/sort.h
index 87af0592..3986c33e 100644
--- a/sort.h
+++ b/sort.h
@@ -29,9 +29,12 @@
#define SORT_ADDRESS 11
#define SORT_KEYID 12
#define SORT_TRUST 13
-#define SORT_MASK 0xf
-#define SORT_REVERSE (1<<4)
-#define SORT_LAST (1<<5)
+#define SORT_SPAM 14
+/* dgc: Sort & SortAux are shorts, so I'm bumping these bitflags up from
+ * bits 4 & 5 to bits 8 & 9 to make room for more sort keys in the future. */
+#define SORT_MASK 0xff
+#define SORT_REVERSE (1<<8)
+#define SORT_LAST (1<<9)
typedef int sort_t (const void *, const void *);
sort_t *mutt_get_sort_func (int);