ca13337dfe
- Remove all longjmp(3) calls from signal handlers. Instead, we post to an internal signal queue and check that periodically. All signal related code is now in sig.c, except for the SIGCHLD handler which remains in popen.c as it is intimately tied to routines there. - Handle SIGPIPE in type1() regardless of mime support, or else the handler in execute() will prevent our error code from being returned resulting in 'sawcom' not being set on the first command as it should. This only affected the initial behavior of the "next" command without mime support. - Add the 'T' flag to many commands in cmdtab.c that should not look like the first command. E.g., start mail on a mailbox with multiple messages, run "set foo", then "next", and watch the second message get displayed rather than the first as is the case without the first "set" command. - Add file descriptor and file handle leak detection. Enabled by DEBUG_FILE_LEAK. This will likely disappear in the future. - Fix a long standing (since import in 1993) longjmp() bug in edstop(): the jmpbuf was invalid when quit() is called at the end of main. - Fix a long standing bug (since import in 1993) in snarf() where it didn't strip whitespace correctly if the line consisted only of whitespace. - Lint cleanup. - New Feature: "Header" command. This allows miscellaneous header fields to be added to the header, e.g., "X-Organization:" or "Reply-To:" fields. - New Feature: "page-also" variable. This allows the specification of additional commands to page. It is more flexible than "crt". - Document the "pager-off" variable: if set, it disables paging entirely.
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;
|
|
}
|