summaryrefslogtreecommitdiffstats
path: root/pattern.c
diff options
context:
space:
mode:
authorThomas Roessler <roessler@does-not-exist.org>1998-06-08 09:16:03 +0000
committerThomas Roessler <roessler@does-not-exist.org>1998-06-08 09:16:03 +0000
commit1a5381e07e97fe482c2b3a7c75f99938f0b105d4 (patch)
treeb4fa4088bbbf5fc9217ee6f87ab60034175e6899 /pattern.c
Initial revision
Diffstat (limited to 'pattern.c')
-rw-r--r--pattern.c1075
1 files changed, 1075 insertions, 0 deletions
diff --git a/pattern.c b/pattern.c
new file mode 100644
index 00000000..0435ac76
--- /dev/null
+++ b/pattern.c
@@ -0,0 +1,1075 @@
+/*
+ * Copyright (C) 1996-8 Michael R. Elkins <me@cs.hmc.edu>
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "mutt.h"
+#include "mapping.h"
+#include "keymap.h"
+#include "mailbox.h"
+#include "copy.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+
+#ifdef _PGPPATH
+#include "pgp.h"
+#endif
+
+
+
+static int eat_regexp (pattern_t *pat, BUFFER *, BUFFER *);
+static int eat_date (pattern_t *pat, BUFFER *, BUFFER *);
+static int eat_range (pattern_t *pat, BUFFER *, BUFFER *);
+
+struct pattern_flags
+{
+ int tag; /* character used to represent this op */
+ int op; /* operation to perform */
+ int class;
+ int (*eat_arg) (pattern_t *, BUFFER *, BUFFER *);
+}
+Flags[] =
+{
+ { 'A', M_ALL, 0, NULL },
+ { 'b', M_BODY, M_FULL_MSG, eat_regexp },
+ { 'B', M_WHOLE_MSG, M_FULL_MSG, eat_regexp },
+ { 'c', M_CC, 0, eat_regexp },
+ { 'C', M_RECIPIENT, 0, eat_regexp },
+ { 'd', M_DATE, 0, eat_date },
+ { 'D', M_DELETED, 0, NULL },
+ { 'e', M_SENDER, 0, eat_regexp },
+ { 'E', M_EXPIRED, 0, NULL },
+ { 'f', M_FROM, 0, eat_regexp },
+ { 'F', M_FLAG, 0, NULL },
+ { 'h', M_HEADER, M_FULL_MSG, eat_regexp },
+ { 'i', M_ID, 0, eat_regexp },
+ { 'L', M_ADDRESS, 0, eat_regexp },
+ { 'l', M_LIST, 0, NULL },
+ { 'm', M_MESSAGE, 0, eat_range },
+ { 'n', M_SCORE, 0, eat_range },
+ { 'N', M_NEW, 0, NULL },
+ { 'O', M_OLD, 0, NULL },
+ { 'p', M_PERSONAL_RECIP, 0, NULL },
+ { 'P', M_PERSONAL_FROM, 0, NULL },
+ { 'Q', M_REPLIED, 0, NULL },
+ { 'R', M_READ, 0, NULL },
+ { 'r', M_DATE_RECEIVED, 0, eat_date },
+ { 's', M_SUBJECT, 0, eat_regexp },
+ { 'S', M_SUPERSEDED, 0, NULL },
+ { 'T', M_TAG, 0, NULL },
+ { 't', M_TO, 0, eat_regexp },
+ { 'U', M_UNREAD, 0, NULL },
+ { 'x', M_REFERENCE, 0, eat_regexp },
+ { 'z', M_SIZE, 0, eat_range },
+ { 0 }
+};
+
+static pattern_t *SearchPattern = NULL; /* current search pattern */
+static char LastSearch[STRING] = { 0 }; /* last pattern searched for */
+static char LastSearchExpn[LONG_STRING] = { 0 }; /* expanded version of
+ LastSearch */
+
+#define M_MAXRANGE -1
+
+int mutt_getvaluebychar (char ch, struct mapping_t *table)
+{
+ int i;
+
+ for (i = 0; table[i].name; i++)
+ {
+ if (ch == table[i].name[0])
+ return table[i].value;
+ }
+
+ return (-1);
+}
+
+/* if no uppercase letters are given, do a case-insensitive search */
+int mutt_which_case (const char *s)
+{
+ while (*s)
+ {
+ if (isalpha (*s) && isupper (*s))
+ return 0; /* case-sensitive */
+ s++;
+ }
+ return REG_ICASE; /* case-insensitive */
+}
+
+static int
+msg_search (CONTEXT *ctx, regex_t *rx, char *buf, size_t blen, int op, int msgno)
+{
+ char tempfile[_POSIX_PATH_MAX];
+ MESSAGE *msg = NULL;
+ STATE s;
+ struct stat st;
+ FILE *fp = NULL;
+ long lng = 0;
+ int match = 0;
+ HEADER *h = ctx->hdrs[msgno];
+
+ if ((msg = mx_open_message (ctx, msgno)) != NULL)
+ {
+ if (option (OPTTHOROUGHSRC))
+ {
+ /* decode the header / body */
+ memset (&s, 0, sizeof (s));
+ s.fpin = msg->fp;
+ mutt_mktemp (tempfile);
+ if ((s.fpout = safe_fopen (tempfile, "w+")) == NULL)
+ {
+ mutt_perror (tempfile);
+ return (0);
+ }
+
+ if (op != M_BODY)
+ mutt_copy_header (msg->fp, h, s.fpout, CH_FROM | CH_DECODE, NULL);
+
+ if (op != M_HEADER)
+ {
+ mutt_parse_mime_message (ctx, h);
+
+
+
+#ifdef _PGPPATH
+ if (h->pgp & PGPENCRYPT && !pgp_valid_passphrase())
+ {
+ mx_close_message (&msg);
+ fclose (fp);
+ unlink (tempfile);
+ return (0);
+ }
+#endif
+
+
+
+ fseek (msg->fp, h->offset, 0);
+ mutt_body_handler (h->content, &s);
+ }
+
+ fp = s.fpout;
+ fflush (fp);
+ fseek (fp, 0, 0);
+ fstat (fileno (fp), &st);
+ lng = (long) st.st_size;
+ }
+ else
+ {
+ /* raw header / body */
+ fp = msg->fp;
+ if (op != M_BODY)
+ {
+ fseek (fp, h->offset, 0);
+ lng = h->content->offset - h->offset;
+ }
+ if (op != M_HEADER)
+ {
+ if (op == M_BODY)
+ fseek (fp, h->content->offset, 0);
+ lng += h->content->length;
+ }
+ }
+
+ /* search the file "fp" */
+ while (lng > 0)
+ {
+ if (fgets (buf, blen - 1, fp) == NULL)
+ break; /* don't loop forever */
+ if (regexec (rx, buf, 0, NULL, 0) == 0)
+ {
+ match = 1;
+ break;
+ }
+ lng -= strlen (buf);
+ }
+
+ mx_close_message (&msg);
+
+ if (option (OPTTHOROUGHSRC))
+ {
+ fclose (fp);
+ unlink (tempfile);
+ }
+ }
+
+ return match;
+}
+
+int eat_regexp (pattern_t *pat, BUFFER *s, BUFFER *err)
+{
+ BUFFER buf;
+ int r;
+
+ memset (&buf, 0, sizeof (buf));
+ if (mutt_extract_token (&buf, s, M_TOKEN_PATTERN | M_TOKEN_COMMENT) != 0 ||
+ !buf.data)
+ {
+ snprintf (err->data, err->dsize, "Error in expression: %s", s->dptr);
+ return (-1);
+ }
+ pat->rx = safe_malloc (sizeof (regex_t));
+ r = REGCOMP (pat->rx, buf.data, REG_NEWLINE | REG_NOSUB | mutt_which_case (buf.data));
+ FREE (&buf.data);
+ if (r)
+ {
+ regerror (r, pat->rx, err->data, err->dsize);
+ regfree (pat->rx);
+ safe_free ((void **) &pat->rx);
+ return (-1);
+ }
+ return 0;
+}
+
+int eat_range (pattern_t *pat, BUFFER *s, BUFFER *err)
+{
+ char *tmp;
+
+ if ((*s->dptr != '-') && (*s->dptr != '<'))
+ {
+ /* range minimum */
+ if (*s->dptr == '>')
+ {
+ pat->max = M_MAXRANGE;
+ pat->min = strtol (s->dptr + 1, &tmp, 0);
+ }
+ else
+ pat->min = strtol (s->dptr, &tmp, 0);
+ if (toupper (*tmp) == 'K') /* is there a prefix? */
+ {
+ pat->min *= 1024;
+ tmp++;
+ }
+ else if (toupper (*tmp) == 'M')
+ {
+ pat->min *= 1048576;
+ tmp++;
+ }
+ if (*s->dptr == '>')
+ {
+ s->dptr = tmp;
+ return 0;
+ }
+ if (*tmp != '-')
+ {
+ /* exact value */
+ pat->max = pat->min;
+ s->dptr = tmp;
+ return 0;
+ }
+ tmp++;
+ }
+ else
+ {
+ s->dptr++;
+ tmp = s->dptr;
+ }
+
+ if (isdigit (*tmp))
+ {
+ /* range maximum */
+ pat->max = strtol (tmp, &tmp, 0);
+ if (toupper (*tmp) == 'K')
+ {
+ pat->max *= 1024;
+ tmp++;
+ }
+ else if (toupper (*tmp) == 'M')
+ {
+ pat->max *= 1048576;
+ tmp++;
+ }
+ }
+ else
+ pat->max = M_MAXRANGE;
+
+ s->dptr = tmp;
+ return 0;
+}
+
+static const char *getDate (const char *s, struct tm *t, BUFFER *err)
+{
+ char *p;
+ time_t now = time (NULL);
+ struct tm *tm = localtime (&now);
+
+ t->tm_mday = strtol (s, &p, 0);
+ if (t->tm_mday < 1 || t->tm_mday > 31)
+ {
+ snprintf (err->data, err->dsize, "Invalid day of month: %s", s);
+ return NULL;
+ }
+ if (*p != '/')
+ {
+ /* fill in today's month and year */
+ t->tm_mon = tm->tm_mon;
+ t->tm_year = tm->tm_year;
+ return p;
+ }
+ p++;
+ t->tm_mon = strtol (p, &p, 0) - 1;
+ if (t->tm_mon < 0 || t->tm_mon > 11)
+ {
+ snprintf (err->data, err->dsize, "Invalid month: %s", p);
+ return NULL;
+ }
+ if (*p != '/')
+ {
+ t->tm_year = tm->tm_year;
+ return p;
+ }
+ p++;
+ t->tm_year = strtol (p, &p, 0);
+ if (t->tm_year < 70) /* year 2000+ */
+ t->tm_year += 100;
+ else if (t->tm_year > 1900)
+ t->tm_year -= 1900;
+ return p;
+}
+
+/* Ny years
+ Nm months
+ Nw weeks
+ Nd days */
+static const char *get_offset (struct tm *tm, const char *s)
+{
+ char *ps;
+ int offset = strtol (s, &ps, 0);
+
+ switch (*ps)
+ {
+ case 'y':
+ tm->tm_year -= offset;
+ break;
+ case 'm':
+ tm->tm_mon -= offset;
+ break;
+ case 'w':
+ tm->tm_mday -= 7 * offset;
+ break;
+ case 'd':
+ tm->tm_mday -= offset;
+ break;
+ }
+ mutt_normalize_time (tm);
+ return (ps + 1);
+}
+
+static int eat_date (pattern_t *pat, BUFFER *s, BUFFER *err)
+{
+ BUFFER buffer;
+ struct tm min, max;
+
+ memset (&buffer, 0, sizeof (buffer));
+ if (mutt_extract_token (&buffer, s, M_TOKEN_COMMENT | M_TOKEN_PATTERN) != 0
+ || !buffer.data)
+ {
+ strfcpy (err->data, "error in expression", err->dsize);
+ return (-1);
+ }
+
+ memset (&min, 0, sizeof (min));
+ /* the `0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
+ when doing timezone conversion, we use Jan 2, 1970 UTC as the base
+ here */
+ min.tm_mday = 2;
+ min.tm_year = 70;
+
+ memset (&max, 0, sizeof (max));
+
+ /* Arbitrary year in the future. Don't set this too high
+ or mutt_mktime() returns something larger than will
+ fit in a time_t on some systems */
+ max.tm_year = 130;
+ max.tm_mon = 11;
+ max.tm_mday = 31;
+ max.tm_hour = 23;
+ max.tm_min = 59;
+ max.tm_sec = 59;
+
+ if (strchr ("<>=", buffer.data[0]))
+ {
+ /* offset from current time
+ <3d less than three days ago
+ >3d more than three days ago
+ =3d exactly three days ago */
+ time_t now = time (NULL);
+ struct tm *tm = localtime (&now);
+ int exact = 0;
+
+ if (buffer.data[0] == '<')
+ {
+ memcpy (&min, tm, sizeof (min));
+ tm = &min;
+ }
+ else
+ {
+ memcpy (&max, tm, sizeof (max));
+ tm = &max;
+
+ if (buffer.data[0] == '=')
+ exact++;
+ }
+ tm->tm_hour = 23;
+ tm->tm_min = max.tm_sec = 59;
+
+ get_offset (tm, buffer.data + 1);
+
+ if (exact)
+ {
+ /* start at the beginning of the day in question */
+ memcpy (&min, &max, sizeof (max));
+ min.tm_hour = min.tm_sec = min.tm_min = 0;
+ }
+ }
+ else
+ {
+ const char *pc = buffer.data;
+
+ if (*pc != '-')
+ {
+ /* mininum date specified */
+ if ((pc = getDate (pc, &min, err)) == NULL)
+ {
+ FREE (&buffer.data);
+ return (-1);
+ }
+ }
+
+ if (*pc && *pc == '-')
+ {
+ /* max date */
+ pc++; /* skip the `-' */
+ SKIPWS (pc);
+ if (*pc)
+ if (!getDate (pc, &max, err))
+ {
+ FREE (&buffer.data);
+ return (-1);
+ }
+ }
+ else
+ {
+ /* search for messages on a specific day */
+ max.tm_year = min.tm_year;
+ max.tm_mon = min.tm_mon;
+ max.tm_mday = min.tm_mday;
+ }
+ }
+
+ pat->min = mutt_mktime (&min, 1);
+ pat->max = mutt_mktime (&max, 1);
+
+ FREE (&buffer.data);
+
+ return 0;
+}
+
+static struct pattern_flags *lookup_tag (char tag)
+{
+ int i;
+
+ for (i = 0; Flags[i].tag; i++)
+ if (Flags[i].tag == tag)
+ return (&Flags[i]);
+ return NULL;
+}
+
+static /* const */ char *find_matching_paren (/* const */ char *s)
+{
+ int level = 1;
+
+ for (; *s; s++)
+ {
+ if (*s == '(')
+ level++;
+ else if (*s == ')')
+ {
+ level--;
+ if (!level)
+ break;
+ }
+ }
+ return s;
+}
+
+void mutt_pattern_free (pattern_t **pat)
+{
+ pattern_t *tmp;
+
+ while (*pat)
+ {
+ tmp = *pat;
+ *pat = (*pat)->next;
+
+ if (tmp->rx)
+ {
+ regfree (tmp->rx);
+ safe_free ((void **) &tmp->rx);
+ }
+ if (tmp->child)
+ mutt_pattern_free (&tmp->child);
+ safe_free ((void **) &tmp);
+ }
+}
+
+pattern_t *mutt_pattern_comp (/* const */ char *s, int flags, BUFFER *err)
+{
+ pattern_t *curlist = NULL;
+ pattern_t *tmp;
+ pattern_t *last = NULL;
+ int not = 0;
+ int or = 0;
+ int implicit = 1; /* used to detect logical AND operator */
+ struct pattern_flags *entry;
+ char *p;
+ char *buf;
+ BUFFER ps;
+
+ memset (&ps, 0, sizeof (ps));
+ ps.dptr = s;
+ ps.dsize = strlen (s);
+
+ while (*ps.dptr)
+ {
+ SKIPWS (ps.dptr);
+ switch (*ps.dptr)
+ {
+ case '!':
+ ps.dptr++;
+ not = !not;
+ break;
+ case '|':
+ if (!or)
+ {
+ if (!curlist)
+ {
+ snprintf (err->data, err->dsize, "error in pattern at: %s", ps.dptr);
+ return NULL;
+ }
+ if (curlist->next)
+ {
+ /* A & B | C == (A & B) | C */
+ tmp = new_pattern ();
+ tmp->op = M_AND;
+ tmp->child = curlist;
+
+ curlist = tmp;
+ last = curlist;
+ }
+
+ or = 1;
+ }
+ ps.dptr++;
+ implicit = 0;
+ not = 0;
+ break;
+ case '~':
+ if (implicit && or)
+ {
+ /* A | B & C == (A | B) & C */
+ tmp = new_pattern ();
+ tmp->op = M_OR;
+ tmp->child = curlist;
+ curlist = tmp;
+ last = tmp;
+ or = 0;
+ }
+
+ tmp = new_pattern ();
+ tmp->not = not;
+ not = 0;
+
+ if (last)
+ last->next = tmp;
+ else
+ curlist = tmp;
+ last = tmp;
+
+ ps.dptr++; /* move past the ~ */
+ if ((entry = lookup_tag (*ps.dptr)) == NULL)
+ {
+ snprintf (err->data, err->dsize, "%c: invalid command", *ps.dptr);
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ if (entry->class && (flags & entry->class) == 0)
+ {
+ snprintf (err->data, err->dsize, "%c: not supported in this mode", *ps.dptr);
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ tmp->op = entry->op;
+
+ ps.dptr++; /* eat the operator and any optional whitespace */
+ SKIPWS (ps.dptr);
+
+ if (entry->eat_arg)
+ {
+ if (!*ps.dptr)
+ {
+ snprintf (err->data, err->dsize, "missing parameter");
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ if (entry->eat_arg (tmp, &ps, err) == -1)
+ {
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ }
+ implicit = 1;
+ break;
+ case '(':
+ p = find_matching_paren (ps.dptr + 1);
+ if (*p != ')')
+ {
+ snprintf (err->data, err->dsize, "mismatched parenthesis: %s", ps.dptr);
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ /* compile the sub-expression */
+ buf = mutt_substrdup (ps.dptr + 1, p);
+ if ((tmp = mutt_pattern_comp (buf, flags, err)) == NULL)
+ {
+ FREE (&buf);
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ FREE (&buf);
+ if (last)
+ last->next = tmp;
+ else
+ curlist = tmp;
+ last = tmp;
+ tmp->not = not;
+ not = 0;
+ ps.dptr = p + 1; /* restore location */
+ break;
+ default:
+ snprintf (err->data, err->dsize, "error in pattern at: %s", ps.dptr);
+ mutt_pattern_free (&curlist);
+ return NULL;
+ }
+ }
+ if (!curlist)
+ {
+ strfcpy (err->data, "empty pattern", err->dsize);
+ return NULL;
+ }
+ if (curlist->next)
+ {
+ tmp = new_pattern ();
+ tmp->op = or ? M_OR : M_AND;
+ tmp->child = curlist;
+ curlist = tmp;
+ }
+ return (curlist);
+}
+
+static int
+perform_and (pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr)
+{
+ for (; pat; pat = pat->next)
+ if (mutt_pattern_exec (pat, flags, ctx, hdr) <= 0)
+ return 0;
+ return 1;
+}
+
+static int
+perform_or (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *hdr)
+{
+ for (; pat; pat = pat->next)
+ if (mutt_pattern_exec (pat, flags, ctx, hdr) > 0)
+ return 1;
+ return 0;
+}
+
+static int match_adrlist (regex_t *rx, int match_personal, ADDRESS *a)
+{
+ for (; a; a = a->next)
+ {
+ if ((a->mailbox && regexec (rx, a->mailbox, 0, NULL, 0) == 0) ||
+ (match_personal && a->personal && regexec (rx, a->personal, 0, NULL, 0) == 0))
+ return 1;
+ }
+ return 0;
+}
+
+static int match_reference (regex_t *rx, LIST *refs)
+{
+ for (; refs; refs = refs->next)
+ if (regexec (rx, refs->data, 0, NULL, 0) == 0)
+ return 1;
+ return 0;
+}
+
+static int match_user (ADDRESS *p)
+{
+ for (; p; p = p->next)
+ if (mutt_addr_is_user (p))
+ return 1;
+ return 0;
+}
+
+/* flags
+ M_MATCH_FULL_ADDRESS match both personal and machine address */
+int
+mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, HEADER *h)
+{
+ char buf[STRING];
+
+ switch (pat->op)
+ {
+ case M_AND:
+ return (pat->not ^ (perform_and (pat->child, flags, ctx, h) > 0));
+ case M_OR:
+ return (pat->not ^ (perform_or (pat->child, flags, ctx, h) > 0));
+ case M_ALL:
+ return (!pat->not);
+ case M_EXPIRED:
+ return (pat->not ^ h->expired);
+ case M_SUPERSEDED:
+ return (pat->not ^ h->superseded);
+ case M_FLAG:
+ return (pat->not ^ h->flagged);
+ case M_TAG:
+ return (pat->not ^ h->tagged);
+ case M_NEW:
+ return (pat->not ? h->old || h->read : !(h->old || h->read));
+ case M_UNREAD:
+ return (pat->not ? h->read : !h->read);
+ case M_REPLIED:
+ return (pat->not ^ h->replied);
+ case M_OLD:
+ return (pat->not ? (!h->old || h->read) : (h->old && !h->read));
+ case M_READ:
+ return (pat->not ^ h->read);
+ case M_DELETED:
+ return (pat->not ^ h->deleted);
+ case M_MESSAGE:
+ return (pat->not ^ (h->msgno >= pat->min - 1 && (pat->max == M_MAXRANGE ||
+ h->msgno <= pat->max - 1)));
+ case M_DATE:
+ return (pat->not ^ (h->date_sent >= pat->min && h->date_sent <= pat->max));
+ case M_DATE_RECEIVED:
+ return (pat->not ^ (h->received >= pat->min && h->received <= pat->max));
+ case M_BODY:
+ case M_HEADER:
+ case M_WHOLE_MSG:
+ return (pat->not ^ msg_search (ctx, pat->rx, buf, sizeof (buf), pat->op, h->msgno));
+ case M_SENDER:
+ return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->sender));
+ case M_FROM:
+ return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->from));
+ case M_TO:
+ return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to));
+ case M_CC:
+ return (pat->not ^ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc));
+ case M_SUBJECT:
+ return (pat->not ^ (h->env->subject && regexec (pat->rx, h->env->subject, 0, NULL, 0) == 0));
+ case M_ID:
+ return (pat->not ^ (h->env->message_id && regexec (pat->rx, h->env->message_id, 0, NULL, 0) == 0));
+ case M_SCORE:
+ return (pat->not ^ (h->score >= pat->min && (pat->max == M_MAXRANGE ||
+ h->score <= pat->max)));
+ case M_SIZE:
+ return (pat->not ^ (h->content->length > pat->min && (pat->max == M_MAXRANGE || h->content->length < pat->max)));
+ case M_REFERENCE:
+ return (pat->not ^ match_reference (pat->rx, h->env->references));
+ case M_ADDRESS:
+ return (pat->not ^ (match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->from) ||
+ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->sender) ||
+ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to) ||
+ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc)));
+ break;
+ case M_RECIPIENT:
+ return (pat->not ^ (match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->to) ||
+ match_adrlist (pat->rx, flags & M_MATCH_FULL_ADDRESS, h->env->cc)));
+ break;
+ case M_LIST:
+ return (pat->not ^ (mutt_is_list_recipient (h->env->to) ||
+ mutt_is_list_recipient (h->env->cc)));
+ case M_PERSONAL_RECIP:
+ return (pat->not ^ (match_user (h->env->to) || match_user (h->env->cc)));
+ break;
+ case M_PERSONAL_FROM:
+ return (pat->not ^ (match_user (h->env->from)));
+ break;
+ }
+ mutt_error ("error: unknown op %d (report this error).", pat->op);
+ return (-1);
+}
+
+/* convert a simple search into a real request */
+void mutt_check_simple (char *s, size_t len, const char *simple)
+{
+ char tmp[LONG_STRING];
+
+ if (!strchr (s, '~')) /* yup, so spoof a real request */
+ {
+ /* convert old tokens into the new format */
+ if (strcasecmp ("all", s) == 0 ||
+ !strcmp ("^", s) || !strcmp (".", s)) /* ~A is more efficient */
+ strfcpy (s, "~A", len);
+ else if (strcasecmp ("del", s) == 0)
+ strfcpy (s, "~D", len);
+ else if (strcasecmp ("flag", s) == 0)
+ strfcpy (s, "~F", len);
+ else if (strcasecmp ("new", s) == 0)
+ strfcpy (s, "~N", len);
+ else if (strcasecmp ("old", s) == 0)
+ strfcpy (s, "~O", len);
+ else if (strcasecmp ("repl", s) == 0)
+ strfcpy (s, "~Q", len);
+ else if (strcasecmp ("read", s) == 0)
+ strfcpy (s, "~R", len);
+ else if (strcasecmp ("tag", s) == 0)
+ strfcpy (s, "~T", len);
+ else if (strcasecmp ("unread", s) == 0)
+ strfcpy (s, "~U", len);
+ else
+ {
+ const char *p = s;
+ int i = 0;
+
+ tmp[i++] = '"';
+ while (*p && i < sizeof (tmp) - 2)
+ {
+ if (*p == '\\' || *p == '"')
+ tmp[i++] = '\\';
+ tmp[i++] = *p++;
+ }
+ tmp[i++] = '"';
+ tmp[i] = 0;
+ mutt_expand_fmt (s, len, simple, tmp);
+ }
+ }
+}
+
+int mutt_pattern_func (int op, char *prompt, HEADER *hdr)
+{
+ pattern_t *pat;
+ char buf[LONG_STRING] = "", *simple, error[STRING];
+ BUFFER err;
+ int i;
+
+ if (mutt_get_field (prompt, buf, sizeof (buf), 0) != 0 || !buf[0])
+ return (-1);
+
+ mutt_message ("Compiling search pattern...");
+
+ simple = safe_strdup (buf);
+ mutt_check_simple (buf, sizeof (buf), NONULL (SimpleSearch));
+
+ err.data = error;
+ err.dsize = sizeof (error);
+ if ((pat = mutt_pattern_comp (buf, M_FULL_MSG, &err)) == NULL)
+ {
+ FREE (&simple);
+ mutt_error ("%s", err.data);
+ return (-1);
+ }
+
+ mutt_message ("Executing command on matching messages...");
+
+ if (op == M_LIMIT)
+ {
+ for (i = 0; i < Context->msgcount; i++)
+ Context->hdrs[i]->virtual = -1;
+ Context->vcount = 0;
+ Context->vsize = 0;
+ }
+
+#define this_body Context->hdrs[i]->content
+
+ for (i = 0; i < Context->msgcount; i++)
+ if (mutt_pattern_exec (pat, M_MATCH_FULL_ADDRESS, Context, Context->hdrs[i]))
+ {
+ switch (op)
+ {
+ case M_DELETE:
+ mutt_set_flag (Context, Context->hdrs[i], M_DELETE, 1);
+ break;
+ case M_UNDELETE:
+ mutt_set_flag (Context, Context->hdrs[i], M_DELETE, 0);
+ break;
+ case M_TAG:
+ mutt_set_flag (Context, Context->hdrs[i], M_TAG, 1);
+ break;
+ case M_UNTAG:
+ mutt_set_flag (Context, Context->hdrs[i], M_TAG, 0);
+ break;
+ case M_LIMIT:
+ Context->hdrs[i]->virtual = Context->vcount;
+ Context->v2r[Context->vcount] = i;
+ Context->vcount++;
+ Context->vsize+=this_body->length + this_body->offset -
+ this_body->hdr_offset;
+ break;
+ }
+ }
+#undef this_body
+
+ mutt_clear_error ();
+
+ if (op == M_LIMIT)
+ {
+ safe_free ((void **) &Context->pattern);
+ if (Context->limit_pattern)
+ mutt_pattern_free (&Context->limit_pattern);
+ if (!Context->vcount)
+ {
+ mutt_error ("No messages matched criteria.");
+ /* restore full display */
+ for (i = 0; i < Context->msgcount; i++)
+ {
+ Context->hdrs[i]->virtual = i;
+ Context->v2r[i] = i;
+ }
+
+ Context->vcount = Context->msgcount;
+ }
+ else if (strncmp (buf, "~A", 2) != 0)
+ {
+ Context->pattern = simple;
+ simple = NULL; /* don't clobber it */
+ Context->limit_pattern = mutt_pattern_comp (buf, M_FULL_MSG, &err);
+ }
+ }
+ FREE (&simple);
+ mutt_pattern_free (&pat);
+ return 0;
+}
+
+int mutt_search_command (int cur, int op)
+{
+ int i, j;
+ char buf[STRING];
+ char temp[LONG_STRING];
+ char error[STRING];
+ BUFFER err;
+ int incr;
+ HEADER *h;
+
+ if (op != OP_SEARCH_NEXT && op != OP_SEARCH_OPPOSITE)
+ {
+ strfcpy (buf, LastSearch, sizeof (buf));
+ if (mutt_get_field ((op == OP_SEARCH) ? "Search for: " : "Reverse search for: ",
+ buf, sizeof (buf), M_CLEAR) != 0 || !buf[0])
+ return (-1);
+
+ if (op == OP_SEARCH)
+ unset_option (OPTSEARCHREVERSE);
+ else
+ set_option (OPTSEARCHREVERSE);
+
+ /* compare the *expanded* version of the search pattern in case
+ $simple_search has changed while we were searching */
+ strfcpy (temp, buf, sizeof (temp));
+ mutt_check_simple (temp, sizeof (temp), NONULL (SimpleSearch));
+
+ if (!SearchPattern || strcmp (temp, LastSearchExpn))
+ {
+ set_option (OPTSEARCHINVALID);
+ strfcpy (LastSearch, buf, sizeof (LastSearch));
+ mutt_message ("Compiling search pattern...");
+ mutt_pattern_free (&SearchPattern);
+ err.data = error;
+ err.dsize = sizeof (error);
+ if ((SearchPattern = mutt_pattern_comp (temp, M_FULL_MSG, &err)) == NULL)
+ {
+ mutt_error ("%s", error);
+ return (-1);
+ }
+ mutt_clear_error ();
+ }
+ }
+ else if (!SearchPattern)
+ {
+ mutt_error ("No search pattern.");
+ return (-1);
+ }
+
+ if (option (OPTSEARCHINVALID))
+ {
+ for (i = 0; i < Context->msgcount; i++)
+ Context->hdrs[i]->searched = 0;
+ unset_option (OPTSEARCHINVALID);
+ }
+
+ incr = (option (OPTSEARCHREVERSE)) ? -1 : 1;
+ if (op == OP_SEARCH_OPPOSITE)
+ incr = -incr;
+
+ for (i = cur + incr, j = 0 ; j != Context->vcount; j++)
+ {
+ if (i > Context->vcount - 1)
+ {
+ i = 0;
+ if (option (OPTWRAPSEARCH))
+ mutt_message ("Search wrapped to top.");
+ else
+ {
+ mutt_message ("Search hit bottom without finding match");
+ return (-1);
+ }
+ }
+ else if (i < 0)
+ {
+ i = Context->vcount - 1;
+ if (option (OPTWRAPSEARCH))
+ mutt_message ("Search wrapped to bottom.");
+ else
+ {
+ mutt_message ("Search hit top without finding match");
+ return (-1);
+ }
+ }
+
+ h = Context->hdrs[Context->v2r[i]];
+ if (h->searched)
+ {
+ /* if we've already evaulated this message, use the cached value */
+ if (h->matched)
+ return i;
+ }
+ else
+ {
+ /* remember that we've already searched this message */
+ h->searched = 1;
+ if ((h->matched = (mutt_pattern_exec (SearchPattern, M_MATCH_FULL_ADDRESS, Context, h) > 0)))
+ return i;
+ }
+
+ if (Signals & S_INTERRUPT)
+ {
+ mutt_error ("Search interrupted.");
+ Signals &= ~S_INTERRUPT;
+ return (-1);
+ }
+
+ i += incr;
+ }
+
+ mutt_error ("Not found.");
+ return (-1);
+}