1377 lines
28 KiB
C
1377 lines
28 KiB
C
/* $NetBSD: list.c,v 1.25 2009/04/10 13:08:25 christos Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1980, 1993
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
#ifndef lint
|
|
#if 0
|
|
static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95";
|
|
#else
|
|
__RCSID("$NetBSD: list.c,v 1.25 2009/04/10 13:08:25 christos Exp $");
|
|
#endif
|
|
#endif /* not lint */
|
|
|
|
#include <assert.h>
|
|
#include <regex.h>
|
|
#include <util.h>
|
|
|
|
#include "rcv.h"
|
|
#include "extern.h"
|
|
#include "format.h"
|
|
#include "thread.h"
|
|
#include "mime.h"
|
|
|
|
/*
|
|
* Mail -- a mail program
|
|
*
|
|
* Message list handling.
|
|
*/
|
|
|
|
/*
|
|
* Token values returned by the scanner used for argument lists.
|
|
* Also, sizes of scanner-related things.
|
|
*/
|
|
enum token_e {
|
|
TEOL, /* End of the command line */
|
|
TNUMBER, /* A message number or range of numbers */
|
|
TDASH, /* A simple dash */
|
|
TSTRING, /* A string (possibly containing '-') */
|
|
TDOT, /* A "." */
|
|
TUP, /* An "^" */
|
|
TDOLLAR, /* A "$" */
|
|
TSTAR, /* A "*" */
|
|
TOPEN, /* An '(' */
|
|
TCLOSE, /* A ')' */
|
|
TPLUS, /* A '+' */
|
|
TAND, /* A '&' */
|
|
TOR, /* A '|' */
|
|
TXOR, /* A logical '^' */
|
|
TNOT, /* A '!' */
|
|
TERROR /* A lexical error */
|
|
};
|
|
|
|
#define REGDEP 2 /* Maximum regret depth. */
|
|
#define STRINGLEN 1024 /* Maximum length of string token */
|
|
|
|
static int lexnumber; /* Number of TNUMBER from scan() */
|
|
static char lexstring[STRINGLEN]; /* String from TSTRING, scan() */
|
|
static int regretp; /* Pointer to TOS of regret tokens */
|
|
static int regretstack[REGDEP]; /* Stack of regretted tokens */
|
|
static char *string_stack[REGDEP]; /* Stack of regretted strings */
|
|
static int numberstack[REGDEP]; /* Stack of regretted numbers */
|
|
|
|
/*
|
|
* Scan out the list of string arguments, shell style
|
|
* for a RAWLIST.
|
|
*/
|
|
PUBLIC int
|
|
getrawlist(const char line[], char **argv, int argc)
|
|
{
|
|
char c, *cp2, quotec;
|
|
const char *cp;
|
|
int argn;
|
|
char linebuf[LINESIZE];
|
|
|
|
argn = 0;
|
|
cp = line;
|
|
for (;;) {
|
|
cp = skip_WSP(cp);
|
|
if (*cp == '\0')
|
|
break;
|
|
if (argn >= argc - 1) {
|
|
(void)printf(
|
|
"Too many elements in the list; excess discarded.\n");
|
|
break;
|
|
}
|
|
cp2 = linebuf;
|
|
quotec = '\0';
|
|
while ((c = *cp) != '\0') {
|
|
cp++;
|
|
if (quotec != '\0') {
|
|
if (c == quotec)
|
|
quotec = '\0';
|
|
else if (quotec != '\'' && c == '\\')
|
|
switch (c = *cp++) {
|
|
case '\0':
|
|
*cp2++ = '\\';
|
|
cp--;
|
|
break;
|
|
case '0': case '1': case '2': case '3':
|
|
case '4': case '5': case '6': case '7':
|
|
c -= '0';
|
|
if (*cp >= '0' && *cp <= '7')
|
|
c = c * 8 + *cp++ - '0';
|
|
if (*cp >= '0' && *cp <= '7')
|
|
c = c * 8 + *cp++ - '0';
|
|
*cp2++ = c;
|
|
break;
|
|
case 'b':
|
|
*cp2++ = '\b';
|
|
break;
|
|
case 'f':
|
|
*cp2++ = '\f';
|
|
break;
|
|
case 'n':
|
|
*cp2++ = '\n';
|
|
break;
|
|
case 'r':
|
|
*cp2++ = '\r';
|
|
break;
|
|
case 't':
|
|
*cp2++ = '\t';
|
|
break;
|
|
case 'v':
|
|
*cp2++ = '\v';
|
|
break;
|
|
default:
|
|
*cp2++ = c;
|
|
}
|
|
else if (c == '^') {
|
|
c = *cp++;
|
|
if (c == '?')
|
|
*cp2++ = '\177';
|
|
/* null doesn't show up anyway */
|
|
else if ((c >= 'A' && c <= '_') ||
|
|
(c >= 'a' && c <= 'z'))
|
|
*cp2++ = c & 037;
|
|
else {
|
|
*cp2++ = '^';
|
|
cp--;
|
|
}
|
|
} else
|
|
*cp2++ = c;
|
|
} else if (c == '"' || c == '\'')
|
|
quotec = c;
|
|
else if (is_WSP(c))
|
|
break;
|
|
else
|
|
*cp2++ = c;
|
|
}
|
|
*cp2 = '\0';
|
|
argv[argn++] = savestr(linebuf);
|
|
}
|
|
argv[argn] = NULL;
|
|
return argn;
|
|
}
|
|
|
|
/*
|
|
* Mark all messages that the user wanted from the command
|
|
* line in the message structure. Return 0 on success, -1
|
|
* on error.
|
|
*/
|
|
|
|
/*
|
|
* Bit values for colon modifiers.
|
|
*/
|
|
#define CMBOX 0x001 /* Unread messages */
|
|
#define CMDELETED 0x002 /* Deleted messages */
|
|
#define CMMODIFY 0x004 /* Unread messages */
|
|
#define CMNEW 0x008 /* New messages */
|
|
#define CMOLD 0x010 /* Old messages */
|
|
#define CMPRESERVE 0x020 /* Unread messages */
|
|
#define CMREAD 0x040 /* Read messages */
|
|
#define CMSAVED 0x080 /* Saved messages */
|
|
#define CMTAGGED 0x100 /* Tagged messages */
|
|
#define CMUNREAD 0x200 /* Unread messages */
|
|
#define CMNEGATE 0x400 /* Negate the match */
|
|
#define CMMASK 0x7ff /* Mask the valid bits */
|
|
|
|
/*
|
|
* The following table describes the letters which can follow
|
|
* the colon and gives the corresponding modifier bit.
|
|
*/
|
|
|
|
static const struct coltab {
|
|
char co_char; /* What to find past : */
|
|
int co_bit; /* Associated modifier bit */
|
|
int co_mask; /* m_status bits to mask */
|
|
int co_equal; /* ... must equal this */
|
|
} coltab[] = {
|
|
{ '!', CMNEGATE, 0, 0 },
|
|
{ 'd', CMDELETED, MDELETED, MDELETED },
|
|
{ 'e', CMMODIFY, MMODIFY, MMODIFY },
|
|
{ 'm', CMBOX, MBOX, MBOX },
|
|
{ 'n', CMNEW, MNEW, MNEW },
|
|
{ 'o', CMOLD, MNEW, 0 },
|
|
{ 'p', CMPRESERVE, MPRESERVE, MPRESERVE },
|
|
{ 'r', CMREAD, MREAD, MREAD },
|
|
{ 's', CMSAVED, MSAVED, MSAVED },
|
|
{ 't', CMTAGGED, MTAGGED, MTAGGED },
|
|
{ 'u', CMUNREAD, MREAD|MNEW, 0 },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
static int lastcolmod;
|
|
|
|
static int
|
|
ignore_message(int m_flag, int colmod)
|
|
{
|
|
int ignore_msg;
|
|
const struct coltab *colp;
|
|
|
|
ignore_msg = !(colmod & CMNEGATE);
|
|
colmod &= (~CMNEGATE & CMMASK);
|
|
|
|
for (colp = &coltab[0]; colp->co_char; colp++)
|
|
if (colp->co_bit & colmod &&
|
|
(m_flag & colp->co_mask) == colp->co_equal)
|
|
return !ignore_msg;
|
|
return ignore_msg;
|
|
}
|
|
|
|
/*
|
|
* Turn the character after a colon modifier into a bit
|
|
* value.
|
|
*/
|
|
static int
|
|
evalcol(int col)
|
|
{
|
|
const struct coltab *colp;
|
|
|
|
if (col == 0)
|
|
return lastcolmod;
|
|
for (colp = &coltab[0]; colp->co_char; colp++)
|
|
if (colp->co_char == col)
|
|
return colp->co_bit;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
get_colmod(int colmod, char *cp)
|
|
{
|
|
if ((cp[0] == '\0') ||
|
|
(cp[0] == '!' && cp[1] == '\0'))
|
|
colmod |= lastcolmod;
|
|
|
|
for (/*EMPTY*/; *cp; cp++) {
|
|
int colresult;
|
|
if ((colresult = evalcol(*cp)) == 0) {
|
|
(void)printf("Unknown colon modifier \"%s\"\n", lexstring);
|
|
return -1;
|
|
}
|
|
if (colresult == CMNEGATE)
|
|
colmod ^= CMNEGATE;
|
|
else
|
|
colmod |= colresult;
|
|
}
|
|
return colmod;
|
|
}
|
|
|
|
static int
|
|
syntax_error(const char *msg)
|
|
{
|
|
(void)printf("Syntax error: %s\n", msg);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* scan out a single lexical item and return its token number,
|
|
* updating the string pointer passed **p. Also, store the value
|
|
* of the number or string scanned in lexnumber or lexstring as
|
|
* appropriate. In any event, store the scanned `thing' in lexstring.
|
|
*/
|
|
static enum token_e
|
|
scan(char **sp)
|
|
{
|
|
static const struct lex {
|
|
char l_char;
|
|
enum token_e l_token;
|
|
} singles[] = {
|
|
{ '$', TDOLLAR },
|
|
{ '.', TDOT },
|
|
{ '^', TUP },
|
|
{ '*', TSTAR },
|
|
{ '-', TDASH },
|
|
{ '+', TPLUS },
|
|
{ '(', TOPEN },
|
|
{ ')', TCLOSE },
|
|
{ '&', TAND },
|
|
{ '|', TOR },
|
|
{ '!', TNOT },
|
|
{ 0, 0 }
|
|
};
|
|
const struct lex *lp;
|
|
char *cp, *cp2;
|
|
int c;
|
|
int quotec;
|
|
|
|
if (regretp >= 0) {
|
|
(void)strcpy(lexstring, string_stack[regretp]);
|
|
lexnumber = numberstack[regretp];
|
|
return regretstack[regretp--];
|
|
}
|
|
cp = *sp;
|
|
cp2 = lexstring;
|
|
lexstring[0] = '\0';
|
|
|
|
/*
|
|
* strip away leading white space.
|
|
*/
|
|
cp = skip_WSP(cp);
|
|
|
|
/*
|
|
* If no characters remain, we are at end of line,
|
|
* so report that.
|
|
*/
|
|
if (*cp == '\0') {
|
|
*sp = cp;
|
|
return TEOL;
|
|
}
|
|
|
|
/*
|
|
* If the leading character is a digit, scan
|
|
* the number and convert it on the fly.
|
|
* Return TNUMBER when done.
|
|
*/
|
|
c = (unsigned char)*cp++;
|
|
if (isdigit(c)) {
|
|
lexnumber = 0;
|
|
while (isdigit(c)) {
|
|
lexnumber = lexnumber * 10 + c - '0';
|
|
*cp2++ = c;
|
|
c = (unsigned char)*cp++;
|
|
}
|
|
*cp2 = '\0';
|
|
*sp = --cp;
|
|
return TNUMBER;
|
|
}
|
|
|
|
/*
|
|
* Check for single character tokens; return such
|
|
* if found.
|
|
*/
|
|
for (lp = &singles[0]; lp->l_char != 0; lp++)
|
|
if (c == lp->l_char) {
|
|
lexstring[0] = c;
|
|
lexstring[1] = '\0';
|
|
*sp = cp;
|
|
return lp->l_token;
|
|
}
|
|
|
|
/*
|
|
* We've got a string! Copy all the characters
|
|
* of the string into lexstring, until we see
|
|
* a null, space, or tab.
|
|
* Respect quoting and quoted pairs.
|
|
*/
|
|
quotec = 0;
|
|
while (c != '\0') {
|
|
if (c == quotec) {
|
|
quotec = 0;
|
|
c = *cp++;
|
|
continue;
|
|
}
|
|
if (quotec) {
|
|
if (c == '\\' && (*cp == quotec || *cp == '\\'))
|
|
c = *cp++;
|
|
}
|
|
else {
|
|
switch (c) {
|
|
case '\'':
|
|
case '"':
|
|
quotec = c;
|
|
c = *cp++;
|
|
continue;
|
|
case ' ':
|
|
case '\t':
|
|
c = '\0'; /* end of token! */
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (cp2 - lexstring < STRINGLEN - 1)
|
|
*cp2++ = c;
|
|
c = *cp++;
|
|
}
|
|
if (quotec && c == 0) {
|
|
(void)fprintf(stderr, "Missing %c\n", quotec);
|
|
return TERROR;
|
|
}
|
|
*sp = --cp;
|
|
*cp2 = '\0';
|
|
return TSTRING;
|
|
}
|
|
|
|
/*
|
|
* Unscan the named token by pushing it onto the regret stack.
|
|
*/
|
|
static void
|
|
regret(int token)
|
|
{
|
|
if (++regretp >= REGDEP)
|
|
errx(1, "Too many regrets");
|
|
regretstack[regretp] = token;
|
|
lexstring[sizeof(lexstring) - 1] = '\0';
|
|
string_stack[regretp] = savestr(lexstring);
|
|
numberstack[regretp] = lexnumber;
|
|
}
|
|
|
|
/*
|
|
* Reset all the scanner global variables.
|
|
*/
|
|
static void
|
|
scaninit(void)
|
|
{
|
|
regretp = -1;
|
|
}
|
|
|
|
#define DELIM " \t," /* list of string delimiters */
|
|
static int
|
|
is_substr(const char *big, const char *little)
|
|
{
|
|
const char *cp;
|
|
if ((cp = strstr(big, little)) == NULL)
|
|
return 0;
|
|
|
|
return strchr(DELIM, cp[strlen(little)]) != 0 &&
|
|
(cp == big || strchr(DELIM, cp[-1]) != 0);
|
|
}
|
|
#undef DELIM
|
|
|
|
|
|
/*
|
|
* Look for (compiled regex) pattern in a line.
|
|
* Returns:
|
|
* 1 if match found.
|
|
* 0 if no match found.
|
|
* -1 on error
|
|
*/
|
|
static int
|
|
regexcmp(void *pattern, char *line, size_t len)
|
|
{
|
|
regmatch_t pmatch[1];
|
|
regmatch_t *pmp;
|
|
int eflags;
|
|
int rval;
|
|
regex_t *preg;
|
|
|
|
preg = pattern;
|
|
|
|
if (line == NULL)
|
|
return 0;
|
|
|
|
if (len == 0) {
|
|
pmp = NULL;
|
|
eflags = 0;
|
|
}
|
|
else {
|
|
pmatch[0].rm_so = 0;
|
|
pmatch[0].rm_eo = line[len - 1] == '\n' ? len - 1 : len;
|
|
pmp = pmatch;
|
|
eflags = REG_STARTEND;
|
|
}
|
|
|
|
switch ((rval = regexec(preg, line, 0, pmp, eflags))) {
|
|
case 0:
|
|
case REG_NOMATCH:
|
|
return rval == 0;
|
|
|
|
default: {
|
|
char errbuf[LINESIZE];
|
|
(void)regerror(rval, preg, errbuf, sizeof(errbuf));
|
|
(void)printf("regexec failed: '%s': %s\n", line, errbuf);
|
|
return -1;
|
|
}}
|
|
}
|
|
|
|
/*
|
|
* Look for (string) pattern in line.
|
|
* Return 1 if match found.
|
|
*/
|
|
static int
|
|
substrcmp(void *pattern, char *line, size_t len)
|
|
{
|
|
char *substr;
|
|
substr = pattern;
|
|
|
|
if (line == NULL)
|
|
return 0;
|
|
|
|
if (len) {
|
|
if (line[len - 1] == '\n') {
|
|
line[len - 1] = '\0';
|
|
}
|
|
else {
|
|
char *cp;
|
|
cp = salloc(len + 1);
|
|
(void)strlcpy(cp, line, len + 1);
|
|
line = cp;
|
|
}
|
|
}
|
|
return strcasestr(line, substr) != NULL;
|
|
}
|
|
|
|
/*
|
|
* Look for NULL line. Used to find non-existent fields.
|
|
* Return 1 if match found.
|
|
*/
|
|
static int
|
|
hasfieldcmp(void *pattern __unused, char *line, size_t len __unused)
|
|
{
|
|
#ifdef __lint__
|
|
pattern = pattern;
|
|
len = len;
|
|
#endif
|
|
return line != NULL;
|
|
}
|
|
|
|
static regex_t preg;
|
|
/*
|
|
* Determine the compare function and its argument based on the
|
|
* "regex-search" variable.
|
|
*/
|
|
static int (*
|
|
get_cmpfn(void **pattern, char *str)
|
|
)(void *, char *, size_t)
|
|
{
|
|
char *val;
|
|
int cflags;
|
|
int e;
|
|
|
|
if (*str == 0) {
|
|
*pattern = NULL;
|
|
return hasfieldcmp;
|
|
}
|
|
|
|
if ((val = value(ENAME_REGEX_SEARCH)) != NULL) {
|
|
cflags = REG_NOSUB;
|
|
val = skip_WSP(val);
|
|
if (*val) {
|
|
if (is_substr(val, "icase"))
|
|
cflags |= REG_ICASE;
|
|
if (is_substr(val, "extended"))
|
|
cflags |= REG_EXTENDED;
|
|
/*
|
|
* NOTE: regcomp() will fail if "nospec" and
|
|
* "extended" are used together.
|
|
*/
|
|
if (is_substr(val, "nospec"))
|
|
cflags |= REG_NOSPEC;
|
|
}
|
|
if ((e = regcomp(&preg, str, cflags)) != 0) {
|
|
char errbuf[LINESIZE];
|
|
(void)regerror(e, &preg, errbuf, sizeof(errbuf));
|
|
(void)printf("regcomp failed: '%s': %s\n", str, errbuf);
|
|
return NULL;
|
|
}
|
|
*pattern = &preg;
|
|
return regexcmp;
|
|
}
|
|
|
|
*pattern = str;
|
|
return substrcmp;
|
|
}
|
|
|
|
/*
|
|
* Free any memory allocated by get_cmpfn()
|
|
*/
|
|
static void
|
|
free_cmparg(void *pattern)
|
|
{
|
|
if (pattern == &preg)
|
|
regfree(&preg);
|
|
}
|
|
|
|
/*
|
|
* Check the message body for the pattern.
|
|
*/
|
|
static int
|
|
matchbody(int (*cmpfn)(void *, char *, size_t),
|
|
void *pattern, struct message *mp, char const *fieldname __unused)
|
|
{
|
|
FILE *fp;
|
|
char *line;
|
|
size_t len;
|
|
int gotmatch;
|
|
|
|
#ifdef __lint__
|
|
fieldname = fieldname;
|
|
#endif
|
|
/*
|
|
* Get a temporary file.
|
|
*/
|
|
{
|
|
char *tempname;
|
|
int fd;
|
|
|
|
(void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
|
|
fp = NULL;
|
|
if ((fd = mkstemp(tempname)) != -1) {
|
|
(void)unlink(tempname);
|
|
if ((fp = Fdopen(fd, "w+")) == NULL)
|
|
(void)close(fd);
|
|
}
|
|
if (fp == NULL) {
|
|
warn("%s", tempname);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Pump the (decoded) message body into the temp file.
|
|
*/
|
|
{
|
|
#ifdef MIME_SUPPORT
|
|
struct mime_info *mip;
|
|
int retval;
|
|
|
|
mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
|
|
: NULL;
|
|
|
|
retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
|
|
mime_decode_close(mip);
|
|
if (retval == -1)
|
|
#else
|
|
if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
|
|
#endif
|
|
{
|
|
warn("matchbody: mesg=%d", get_msgnum(mp));
|
|
return -1;
|
|
}
|
|
}
|
|
/*
|
|
* XXX - should we read the entire body into a buffer so we
|
|
* can search across lines?
|
|
*/
|
|
rewind(fp);
|
|
gotmatch = 0;
|
|
while ((line = fgetln(fp, &len)) != NULL && len > 0) {
|
|
gotmatch = cmpfn(pattern, line, len);
|
|
if (gotmatch)
|
|
break;
|
|
}
|
|
(void)Fclose(fp);
|
|
|
|
return gotmatch;
|
|
}
|
|
|
|
/*
|
|
* Check the "To:", "Cc:", and "Bcc" fields for the pattern.
|
|
*/
|
|
static int
|
|
matchto(int (*cmpfn)(void *, char *, size_t),
|
|
void *pattern, struct message *mp, char const *fieldname __unused)
|
|
{
|
|
static const char *to_fields[] = { "to", "cc", "bcc", 0 };
|
|
const char **to;
|
|
int gotmatch;
|
|
|
|
#ifdef __lint__
|
|
fieldname = fieldname;
|
|
#endif
|
|
gotmatch = 0;
|
|
for (to = to_fields; *to; to++) {
|
|
char *field;
|
|
field = hfield(*to, mp);
|
|
gotmatch = cmpfn(pattern, field, 0);
|
|
if (gotmatch)
|
|
break;
|
|
}
|
|
return gotmatch;
|
|
}
|
|
|
|
/*
|
|
* Check a field for the pattern.
|
|
*/
|
|
static int
|
|
matchfield(int (*cmpfn)(void *, char *, size_t),
|
|
void *pattern, struct message *mp, char const *fieldname)
|
|
{
|
|
char *field;
|
|
|
|
#ifdef __lint__
|
|
fieldname = fieldname;
|
|
#endif
|
|
field = hfield(fieldname, mp);
|
|
return cmpfn(pattern, field, 0);
|
|
}
|
|
|
|
/*
|
|
* Check the headline for the pattern.
|
|
*/
|
|
static int
|
|
matchfrom(int (*cmpfn)(void *, char *, size_t),
|
|
void *pattern, struct message *mp, char const *fieldname __unused)
|
|
{
|
|
char headline[LINESIZE];
|
|
char *field;
|
|
|
|
#ifdef __lint__
|
|
fieldname = fieldname;
|
|
#endif
|
|
(void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
|
|
field = savestr(headline);
|
|
if (strncmp(field, "From ", 5) != 0)
|
|
return 1;
|
|
|
|
return cmpfn(pattern, field + 5, 0);
|
|
}
|
|
|
|
/*
|
|
* Check the sender for the pattern.
|
|
*/
|
|
static int
|
|
matchsender(int (*cmpfn)(void *, char *, size_t),
|
|
void *pattern, struct message *mp, char const *fieldname __unused)
|
|
{
|
|
char *field;
|
|
|
|
#ifdef __lint__
|
|
fieldname = fieldname;
|
|
#endif
|
|
field = nameof(mp, 0);
|
|
return cmpfn(pattern, field, 0);
|
|
}
|
|
|
|
/*
|
|
* Interpret 'str' and check each message (1 thru 'msgCount') for a match.
|
|
* The 'str' has the format: [/[[x]:]y with the following meanings:
|
|
*
|
|
* y pattern 'y' is compared against the senders address.
|
|
* /y pattern 'y' is compared with the subject field. If 'y' is empty,
|
|
* the last search 'str' is used.
|
|
* /:y pattern 'y' is compared with the subject field.
|
|
* /x:y pattern 'y' is compared with the specified header field 'x' or
|
|
* the message body if 'x' == "body".
|
|
*
|
|
* The last two forms require "searchheaders" to be defined.
|
|
*/
|
|
static int
|
|
match_string(int *markarray, char *str, int msgCount)
|
|
{
|
|
int i;
|
|
int rval;
|
|
int (*matchfn)(int (*)(void *, char *, size_t),
|
|
void *, struct message *, char const *);
|
|
int (*cmpfn)(void *, char *, size_t);
|
|
void *cmparg;
|
|
char const *fieldname;
|
|
|
|
if (*str != '/') {
|
|
matchfn = matchsender;
|
|
fieldname = NULL;
|
|
}
|
|
else {
|
|
static char lastscan[STRINGLEN];
|
|
char *cp;
|
|
|
|
str++;
|
|
if (*str == '\0')
|
|
str = lastscan;
|
|
else
|
|
(void)strlcpy(lastscan, str, sizeof(lastscan));
|
|
|
|
if (value(ENAME_SEARCHHEADERS) == NULL ||
|
|
(cp = strchr(str, ':')) == NULL) {
|
|
matchfn = matchfield;
|
|
fieldname = "subject";
|
|
/* str = str; */
|
|
}
|
|
else {
|
|
static const struct matchtbl_s {
|
|
char const *key;
|
|
size_t len;
|
|
char const *fieldname;
|
|
int (*matchfn)(int (*)(void *, char *, size_t),
|
|
void *, struct message *, char const *);
|
|
} matchtbl[] = {
|
|
#define X(a) a, sizeof(a) - 1
|
|
#define X_NULL NULL, 0
|
|
{ X(":"), "subject", matchfield },
|
|
{ X("body:"), NULL, matchbody },
|
|
{ X("from:"), NULL, matchfrom },
|
|
{ X("to:"), NULL, matchto },
|
|
{ X_NULL, NULL, matchfield }
|
|
#undef X_NULL
|
|
#undef X
|
|
};
|
|
const struct matchtbl_s *mtp;
|
|
size_t len;
|
|
/*
|
|
* Check for special cases!
|
|
* These checks are case sensitive so the true fields
|
|
* can be grabbed as mentioned in the manpage.
|
|
*/
|
|
cp++;
|
|
len = cp - str;
|
|
for (mtp = matchtbl; mtp->key; mtp++) {
|
|
if (len == mtp->len &&
|
|
strncmp(str, mtp->key, len) == 0)
|
|
break;
|
|
}
|
|
matchfn = mtp->matchfn;
|
|
if (mtp->key)
|
|
fieldname = mtp->fieldname;
|
|
else {
|
|
char *p;
|
|
p = salloc(len);
|
|
(void)strlcpy(p, str, len);
|
|
fieldname = p;
|
|
}
|
|
str = cp;
|
|
}
|
|
}
|
|
|
|
cmpfn = get_cmpfn(&cmparg, str);
|
|
if (cmpfn == NULL)
|
|
return -1;
|
|
|
|
rval = 0;
|
|
for (i = 1; i <= msgCount; i++) {
|
|
struct message *mp;
|
|
mp = get_message(i);
|
|
rval = matchfn(cmpfn, cmparg, mp, fieldname);
|
|
if (rval == -1)
|
|
break;
|
|
if (rval)
|
|
markarray[i - 1] = 1;
|
|
rval = 0;
|
|
}
|
|
|
|
free_cmparg(cmparg); /* free any memory allocated by get_cmpfn() */
|
|
|
|
return rval;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the message number corresponding to the passed meta character.
|
|
*/
|
|
static int
|
|
metamess(int meta, int f)
|
|
{
|
|
int c, m;
|
|
struct message *mp;
|
|
|
|
c = meta;
|
|
switch (c) {
|
|
case '^':
|
|
/*
|
|
* First 'good' message left.
|
|
*/
|
|
for (mp = get_message(1); mp; mp = next_message(mp))
|
|
if ((mp->m_flag & MDELETED) == f)
|
|
return get_msgnum(mp);
|
|
(void)printf("No applicable messages\n");
|
|
return -1;
|
|
|
|
case '$':
|
|
/*
|
|
* Last 'good message left.
|
|
*/
|
|
for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
|
|
if ((mp->m_flag & MDELETED) == f)
|
|
return get_msgnum(mp);
|
|
(void)printf("No applicable messages\n");
|
|
return -1;
|
|
|
|
case '.':
|
|
/*
|
|
* Current message.
|
|
*/
|
|
if (dot == NULL) {
|
|
(void)printf("No applicable messages\n");
|
|
return -1;
|
|
}
|
|
m = get_msgnum(dot);
|
|
if ((dot->m_flag & MDELETED) != f) {
|
|
(void)printf("%d: Inappropriate message\n", m);
|
|
return -1;
|
|
}
|
|
return m;
|
|
|
|
default:
|
|
(void)printf("Unknown metachar (%c)\n", c);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the passed message number for legality and proper flags.
|
|
* If f is MDELETED, then either kind will do. Otherwise, the message
|
|
* has to be undeleted.
|
|
*/
|
|
static int
|
|
check(int mesg, int f)
|
|
{
|
|
struct message *mp;
|
|
|
|
if ((mp = get_message(mesg)) == NULL) {
|
|
(void)printf("%d: Invalid message number\n", mesg);
|
|
return -1;
|
|
}
|
|
if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
|
|
(void)printf("%d: Inappropriate message\n", mesg);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
markall_core(int *markarray, char **bufp, int f, int level)
|
|
{
|
|
enum token_e tok;
|
|
enum logic_op_e {
|
|
LOP_AND,
|
|
LOP_OR,
|
|
LOP_XOR
|
|
} logic_op; /* binary logic operation */
|
|
int logic_invert; /* invert the result */
|
|
int *tmparray; /* temporarly array with result */
|
|
int msgCount; /* tmparray length and message count */
|
|
int beg; /* first value of a range */
|
|
int colmod; /* the colon-modifier for this group */
|
|
int got_not; /* for syntax checking of '!' */
|
|
int got_one; /* we have a message spec, valid or not */
|
|
int got_bin; /* we have a pending binary operation */
|
|
int i;
|
|
|
|
logic_op = LOP_OR;
|
|
logic_invert = 0;
|
|
colmod = 0;
|
|
|
|
msgCount = get_msgCount();
|
|
tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
|
|
|
|
beg = 0;
|
|
got_one = 0;
|
|
got_not = 0;
|
|
got_bin = 0;
|
|
|
|
while ((tok = scan(bufp)) != TEOL) {
|
|
if (tok == TERROR)
|
|
return -1;
|
|
|
|
/*
|
|
* Do some syntax checking.
|
|
*/
|
|
switch (tok) {
|
|
case TDASH:
|
|
case TPLUS:
|
|
case TDOLLAR:
|
|
case TUP:
|
|
case TDOT:
|
|
case TNUMBER:
|
|
break;
|
|
|
|
case TAND:
|
|
case TOR:
|
|
case TXOR:
|
|
if (!got_one)
|
|
return syntax_error("missing left operand");
|
|
/*FALLTHROUGH*/
|
|
default:
|
|
if (beg)
|
|
return syntax_error("end of range missing");
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* The main tok switch.
|
|
*/
|
|
switch (tok) {
|
|
struct message *mp;
|
|
|
|
case TERROR: /* trapped above */
|
|
case TEOL:
|
|
assert(/*CONSTCOND*/0);
|
|
break;
|
|
|
|
case TUP:
|
|
if (got_one) { /* a possible logical xor */
|
|
enum token_e t;
|
|
t = scan(bufp); /* peek ahead */
|
|
regret(t);
|
|
lexstring[0] = '^'; /* restore lexstring */
|
|
lexstring[1] = '\0';
|
|
if (t != TDASH && t != TEOL && t != TCLOSE) {
|
|
/* convert tok to TXOR and put
|
|
* it back on the stack so we
|
|
* can handle it consistently */
|
|
tok = TXOR;
|
|
regret(tok);
|
|
continue;
|
|
}
|
|
}
|
|
/* FALLTHROUGH */
|
|
case TDOLLAR:
|
|
case TDOT:
|
|
lexnumber = metamess(lexstring[0], f);
|
|
if (lexnumber == -1)
|
|
return -1;
|
|
/* FALLTHROUGH */
|
|
case TNUMBER:
|
|
if (check(lexnumber, f))
|
|
return -1;
|
|
number:
|
|
got_one = 1;
|
|
if (beg != 0) {
|
|
if (lexnumber < beg) {
|
|
(void)printf("invalid range: %d-%d\n", beg, lexnumber);
|
|
return -1;
|
|
}
|
|
for (i = beg; i <= lexnumber; i++)
|
|
tmparray[i - 1] = 1;
|
|
|
|
beg = 0;
|
|
break;
|
|
}
|
|
beg = lexnumber; /* start of a range */
|
|
tok = scan(bufp);
|
|
if (tok == TDASH) {
|
|
continue;
|
|
}
|
|
else {
|
|
regret(tok);
|
|
tmparray[beg - 1] = 1;
|
|
beg = 0;
|
|
}
|
|
break;
|
|
|
|
case TDASH:
|
|
for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
|
|
if ((mp->m_flag & MDELETED) == 0)
|
|
break;
|
|
}
|
|
if (mp == NULL) {
|
|
(void)printf("Referencing before 1\n");
|
|
return -1;
|
|
}
|
|
lexnumber = get_msgnum(mp);
|
|
goto number;
|
|
|
|
case TPLUS:
|
|
for (mp = next_message(dot); mp; mp = next_message(mp)) {
|
|
if ((mp->m_flag & MDELETED) == 0)
|
|
break;
|
|
}
|
|
if (mp == NULL) {
|
|
(void)printf("Referencing beyond EOF\n");
|
|
return -1;
|
|
}
|
|
lexnumber = get_msgnum(mp);
|
|
goto number;
|
|
|
|
case TSTRING:
|
|
if (lexstring[0] == ':') { /* colon modifier! */
|
|
colmod = get_colmod(colmod, lexstring + 1);
|
|
if (colmod == -1)
|
|
return -1;
|
|
continue;
|
|
}
|
|
got_one = 1;
|
|
if (match_string(tmparray, lexstring, msgCount) == -1)
|
|
return -1;
|
|
break;
|
|
|
|
case TSTAR:
|
|
got_one = 1;
|
|
for (i = 1; i <= msgCount; i++)
|
|
tmparray[i - 1] = 1;
|
|
break;
|
|
|
|
|
|
/**************
|
|
* Parentheses.
|
|
*/
|
|
case TOPEN:
|
|
if (markall_core(tmparray, bufp, f, level + 1) == -1)
|
|
return -1;
|
|
break;
|
|
|
|
case TCLOSE:
|
|
if (level == 0)
|
|
return syntax_error("extra ')'");
|
|
goto done;
|
|
|
|
|
|
/*********************
|
|
* Logical operations.
|
|
*/
|
|
case TNOT:
|
|
got_not = 1;
|
|
logic_invert = ! logic_invert;
|
|
continue;
|
|
|
|
/*
|
|
* Binary operations.
|
|
*/
|
|
case TAND:
|
|
if (got_not)
|
|
return syntax_error("'!' precedes '&'");
|
|
got_bin = 1;
|
|
logic_op = LOP_AND;
|
|
continue;
|
|
|
|
case TOR:
|
|
if (got_not)
|
|
return syntax_error("'!' precedes '|'");
|
|
got_bin = 1;
|
|
logic_op = LOP_OR;
|
|
continue;
|
|
|
|
case TXOR:
|
|
if (got_not)
|
|
return syntax_error("'!' precedes logical '^'");
|
|
got_bin = 1;
|
|
logic_op = LOP_XOR;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Do the logic operations.
|
|
*/
|
|
if (logic_invert)
|
|
for (i = 0; i < msgCount; i++)
|
|
tmparray[i] = ! tmparray[i];
|
|
|
|
switch (logic_op) {
|
|
case LOP_AND:
|
|
for (i = 0; i < msgCount; i++)
|
|
markarray[i] &= tmparray[i];
|
|
break;
|
|
|
|
case LOP_OR:
|
|
for (i = 0; i < msgCount; i++)
|
|
markarray[i] |= tmparray[i];
|
|
break;
|
|
|
|
case LOP_XOR:
|
|
for (i = 0; i < msgCount; i++)
|
|
markarray[i] ^= tmparray[i];
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Clear the temporary array and reset the logic
|
|
* operations.
|
|
*/
|
|
for (i = 0; i < msgCount; i++)
|
|
tmparray[i] = 0;
|
|
|
|
logic_op = LOP_OR;
|
|
logic_invert = 0;
|
|
got_not = 0;
|
|
got_bin = 0;
|
|
}
|
|
|
|
if (beg)
|
|
return syntax_error("end of range missing");
|
|
|
|
if (level)
|
|
return syntax_error("missing ')'");
|
|
|
|
done:
|
|
if (got_not)
|
|
return syntax_error("trailing '!'");
|
|
|
|
if (got_bin)
|
|
return syntax_error("missing right operand");
|
|
|
|
if (colmod != 0) {
|
|
/*
|
|
* If we have colon-modifiers but no messages
|
|
* specifiec, then assume '*' was given.
|
|
*/
|
|
if (got_one == 0)
|
|
for (i = 1; i <= msgCount; i++)
|
|
markarray[i - 1] = 1;
|
|
|
|
for (i = 1; i <= msgCount; i++) {
|
|
struct message *mp;
|
|
if ((mp = get_message(i)) != NULL &&
|
|
ignore_message(mp->m_flag, colmod))
|
|
markarray[i - 1] = 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
markall(char buf[], int f)
|
|
{
|
|
int i;
|
|
int mc;
|
|
int *markarray;
|
|
int msgCount;
|
|
struct message *mp;
|
|
|
|
msgCount = get_msgCount();
|
|
|
|
/*
|
|
* Clear all the previous message marks.
|
|
*/
|
|
for (i = 1; i <= msgCount; i++)
|
|
if ((mp = get_message(i)) != NULL)
|
|
mp->m_flag &= ~MMARK;
|
|
|
|
buf = skip_WSP(buf);
|
|
if (*buf == '\0')
|
|
return 0;
|
|
|
|
scaninit();
|
|
markarray = csalloc((size_t)msgCount, sizeof(*markarray));
|
|
if (markall_core(markarray, &buf, f, 0) == -1)
|
|
return -1;
|
|
|
|
/*
|
|
* Transfer the markarray values to the messages.
|
|
*/
|
|
mc = 0;
|
|
for (i = 1; i <= msgCount; i++) {
|
|
if (markarray[i - 1] &&
|
|
(mp = get_message(i)) != NULL &&
|
|
(f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
|
|
mp->m_flag |= MMARK;
|
|
mc++;
|
|
}
|
|
}
|
|
|
|
if (mc == 0) {
|
|
(void)printf("No applicable messages.\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Convert the user string of message numbers and
|
|
* store the numbers into vector.
|
|
*
|
|
* Returns the count of messages picked up or -1 on error.
|
|
*/
|
|
PUBLIC int
|
|
getmsglist(char *buf, int *vector, int flags)
|
|
{
|
|
int *ip;
|
|
struct message *mp;
|
|
|
|
if (get_msgCount() == 0) {
|
|
*vector = 0;
|
|
return 0;
|
|
}
|
|
if (markall(buf, flags) < 0)
|
|
return -1;
|
|
ip = vector;
|
|
for (mp = get_message(1); mp; mp = next_message(mp))
|
|
if (mp->m_flag & MMARK)
|
|
*ip++ = get_msgnum(mp);
|
|
*ip = 0;
|
|
return (int)(ip - vector);
|
|
}
|
|
|
|
/*
|
|
* Find the first message whose flags & m == f and return
|
|
* its message number.
|
|
*/
|
|
PUBLIC int
|
|
first(int f, int m)
|
|
{
|
|
struct message *mp;
|
|
|
|
if (get_msgCount() == 0)
|
|
return 0;
|
|
f &= MDELETED;
|
|
m &= MDELETED;
|
|
for (mp = dot; mp; mp = next_message(mp))
|
|
if ((mp->m_flag & m) == f)
|
|
return get_msgnum(mp);
|
|
for (mp = prev_message(dot); mp; mp = prev_message(mp))
|
|
if ((mp->m_flag & m) == f)
|
|
return get_msgnum(mp);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Show all headers without paging. (-H flag)
|
|
*/
|
|
__dead
|
|
PUBLIC int
|
|
show_headers_and_exit(int flags)
|
|
{
|
|
struct message *mp;
|
|
|
|
/* We are exiting anyway, so use the default signal handler. */
|
|
if (signal(SIGINT, SIG_DFL) == SIG_IGN)
|
|
(void)signal(SIGINT, SIG_IGN);
|
|
|
|
flags &= CMMASK;
|
|
for (mp = get_message(1); mp; mp = next_message(mp))
|
|
if (flags == 0 || !ignore_message(mp->m_flag, flags))
|
|
printhead(get_msgnum(mp));
|
|
|
|
exit(0);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/*
|
|
* A hack so -H can have an optional modifier as -H[:flags].
|
|
*
|
|
* This depends a bit on the internals of getopt(). In particular,
|
|
* for flags expecting an argument, argv[optind-1] must contain the
|
|
* optarg and optarg must point to a substring of argv[optind-1] not a
|
|
* copy of it.
|
|
*/
|
|
PUBLIC int
|
|
get_Hflag(char **argv)
|
|
{
|
|
int flags;
|
|
|
|
flags = ~CMMASK;
|
|
|
|
if (optarg == NULL) /* We had an error, just get the flags. */
|
|
return flags;
|
|
|
|
if (*optarg != ':' || optarg == argv[optind - 1]) {
|
|
optind--;
|
|
optreset = 1;
|
|
if (optarg != argv[optind]) {
|
|
static char temparg[LINESIZE];
|
|
size_t optlen;
|
|
size_t arglen;
|
|
char *p;
|
|
|
|
optlen = strlen(optarg);
|
|
arglen = strlen(argv[optind]);
|
|
p = argv[optind] + arglen - optlen;
|
|
optlen = MIN(optlen, sizeof(temparg) - 1);
|
|
temparg[0] = '-';
|
|
(void)memmove(temparg + 1, p, optlen + 1);
|
|
argv[optind] = temparg;
|
|
}
|
|
}
|
|
else {
|
|
flags = get_colmod(flags, optarg + 1);
|
|
}
|
|
return flags;
|
|
}
|