Home | History | Annotate | Line # | Download | only in mail
list.c revision 1.28
      1  1.28  christos /*	$NetBSD: list.c,v 1.28 2017/11/09 20:27:50 christos Exp $	*/
      2   1.4  christos 
      3   1.1       cgd /*
      4   1.3   deraadt  * Copyright (c) 1980, 1993
      5   1.3   deraadt  *	The Regents of the University of California.  All rights reserved.
      6   1.1       cgd  *
      7   1.1       cgd  * Redistribution and use in source and binary forms, with or without
      8   1.1       cgd  * modification, are permitted provided that the following conditions
      9   1.1       cgd  * are met:
     10   1.1       cgd  * 1. Redistributions of source code must retain the above copyright
     11   1.1       cgd  *    notice, this list of conditions and the following disclaimer.
     12   1.1       cgd  * 2. Redistributions in binary form must reproduce the above copyright
     13   1.1       cgd  *    notice, this list of conditions and the following disclaimer in the
     14   1.1       cgd  *    documentation and/or other materials provided with the distribution.
     15  1.13       agc  * 3. Neither the name of the University nor the names of its contributors
     16   1.1       cgd  *    may be used to endorse or promote products derived from this software
     17   1.1       cgd  *    without specific prior written permission.
     18   1.1       cgd  *
     19   1.1       cgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20   1.1       cgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21   1.1       cgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22   1.1       cgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23   1.1       cgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24   1.1       cgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25   1.1       cgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26   1.1       cgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27   1.1       cgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28   1.1       cgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29   1.1       cgd  * SUCH DAMAGE.
     30   1.1       cgd  */
     31   1.1       cgd 
     32   1.8     lukem #include <sys/cdefs.h>
     33   1.1       cgd #ifndef lint
     34   1.4  christos #if 0
     35   1.5       tls static char sccsid[] = "@(#)list.c	8.4 (Berkeley) 5/1/95";
     36   1.4  christos #else
     37  1.28  christos __RCSID("$NetBSD: list.c,v 1.28 2017/11/09 20:27:50 christos Exp $");
     38   1.4  christos #endif
     39   1.1       cgd #endif /* not lint */
     40   1.1       cgd 
     41  1.18  christos #include <assert.h>
     42  1.18  christos #include <regex.h>
     43  1.18  christos #include <util.h>
     44  1.18  christos 
     45   1.1       cgd #include "rcv.h"
     46   1.3   deraadt #include "extern.h"
     47  1.17  christos #include "format.h"
     48  1.18  christos #include "thread.h"
     49  1.19  christos #include "mime.h"
     50   1.8     lukem 
     51   1.1       cgd /*
     52   1.1       cgd  * Mail -- a mail program
     53   1.1       cgd  *
     54   1.1       cgd  * Message list handling.
     55   1.1       cgd  */
     56   1.1       cgd 
     57   1.1       cgd /*
     58  1.18  christos  * Token values returned by the scanner used for argument lists.
     59  1.18  christos  * Also, sizes of scanner-related things.
     60   1.1       cgd  */
     61  1.18  christos enum token_e {
     62  1.18  christos 	TEOL,			/* End of the command line */
     63  1.18  christos 	TNUMBER,		/* A message number or range of numbers */
     64  1.18  christos 	TDASH,			/* A simple dash */
     65  1.18  christos 	TSTRING,		/* A string (possibly containing '-') */
     66  1.18  christos 	TDOT,			/* A "." */
     67  1.18  christos 	TUP,			/* An "^" */
     68  1.18  christos 	TDOLLAR,		/* A "$" */
     69  1.18  christos 	TSTAR,			/* A "*" */
     70  1.18  christos 	TOPEN,			/* An '(' */
     71  1.18  christos 	TCLOSE,			/* A ')' */
     72  1.18  christos 	TPLUS,			/* A '+' */
     73  1.18  christos 	TAND,			/* A '&' */
     74  1.18  christos 	TOR,			/* A '|' */
     75  1.18  christos 	TXOR,			/* A logical '^' */
     76  1.18  christos 	TNOT,			/* A '!' */
     77  1.18  christos 	TERROR			/* A lexical error */
     78   1.1       cgd };
     79   1.1       cgd 
     80  1.18  christos #define	REGDEP		2		/* Maximum regret depth. */
     81  1.18  christos #define	STRINGLEN	1024		/* Maximum length of string token */
     82   1.1       cgd 
     83  1.18  christos static int	lexnumber;		/* Number of TNUMBER from scan() */
     84  1.18  christos static char	lexstring[STRINGLEN];	/* String from TSTRING, scan() */
     85  1.18  christos static int	regretp;		/* Pointer to TOS of regret tokens */
     86  1.18  christos static int	regretstack[REGDEP];	/* Stack of regretted tokens */
     87  1.18  christos static char	*string_stack[REGDEP];	/* Stack of regretted strings */
     88  1.18  christos static int	numberstack[REGDEP];	/* Stack of regretted numbers */
     89   1.1       cgd 
     90   1.1       cgd /*
     91   1.1       cgd  * Scan out the list of string arguments, shell style
     92   1.1       cgd  * for a RAWLIST.
     93   1.1       cgd  */
     94  1.18  christos PUBLIC int
     95  1.14  christos getrawlist(const char line[], char **argv, int argc)
     96   1.1       cgd {
     97  1.14  christos 	char c, *cp2, quotec;
     98  1.14  christos 	const char *cp;
     99   1.1       cgd 	int argn;
    100  1.18  christos 	char linebuf[LINESIZE];
    101   1.1       cgd 
    102   1.1       cgd 	argn = 0;
    103   1.1       cgd 	cp = line;
    104   1.1       cgd 	for (;;) {
    105  1.21  christos 		cp = skip_WSP(cp);
    106   1.1       cgd 		if (*cp == '\0')
    107   1.1       cgd 			break;
    108   1.1       cgd 		if (argn >= argc - 1) {
    109  1.15  christos 			(void)printf(
    110   1.1       cgd 			"Too many elements in the list; excess discarded.\n");
    111   1.1       cgd 			break;
    112   1.1       cgd 		}
    113   1.1       cgd 		cp2 = linebuf;
    114   1.1       cgd 		quotec = '\0';
    115   1.1       cgd 		while ((c = *cp) != '\0') {
    116   1.1       cgd 			cp++;
    117   1.1       cgd 			if (quotec != '\0') {
    118   1.1       cgd 				if (c == quotec)
    119   1.1       cgd 					quotec = '\0';
    120  1.24  christos 				else if (quotec != '\'' && c == '\\')
    121   1.1       cgd 					switch (c = *cp++) {
    122   1.1       cgd 					case '\0':
    123   1.3   deraadt 						*cp2++ = '\\';
    124   1.3   deraadt 						cp--;
    125   1.1       cgd 						break;
    126   1.1       cgd 					case '0': case '1': case '2': case '3':
    127   1.1       cgd 					case '4': case '5': case '6': case '7':
    128   1.1       cgd 						c -= '0';
    129   1.1       cgd 						if (*cp >= '0' && *cp <= '7')
    130   1.1       cgd 							c = c * 8 + *cp++ - '0';
    131   1.1       cgd 						if (*cp >= '0' && *cp <= '7')
    132   1.1       cgd 							c = c * 8 + *cp++ - '0';
    133   1.1       cgd 						*cp2++ = c;
    134   1.1       cgd 						break;
    135   1.1       cgd 					case 'b':
    136   1.1       cgd 						*cp2++ = '\b';
    137   1.1       cgd 						break;
    138   1.1       cgd 					case 'f':
    139   1.1       cgd 						*cp2++ = '\f';
    140   1.1       cgd 						break;
    141   1.1       cgd 					case 'n':
    142   1.1       cgd 						*cp2++ = '\n';
    143   1.1       cgd 						break;
    144   1.1       cgd 					case 'r':
    145   1.1       cgd 						*cp2++ = '\r';
    146   1.1       cgd 						break;
    147   1.1       cgd 					case 't':
    148   1.1       cgd 						*cp2++ = '\t';
    149   1.1       cgd 						break;
    150   1.1       cgd 					case 'v':
    151   1.1       cgd 						*cp2++ = '\v';
    152   1.1       cgd 						break;
    153   1.3   deraadt 					default:
    154   1.3   deraadt 						*cp2++ = c;
    155   1.1       cgd 					}
    156   1.1       cgd 				else if (c == '^') {
    157   1.1       cgd 					c = *cp++;
    158   1.1       cgd 					if (c == '?')
    159   1.1       cgd 						*cp2++ = '\177';
    160   1.1       cgd 					/* null doesn't show up anyway */
    161   1.4  christos 					else if ((c >= 'A' && c <= '_') ||
    162   1.4  christos 						 (c >= 'a' && c <= 'z'))
    163   1.3   deraadt 						*cp2++ = c & 037;
    164   1.3   deraadt 					else {
    165   1.3   deraadt 						*cp2++ = '^';
    166   1.3   deraadt 						cp--;
    167   1.3   deraadt 					}
    168   1.1       cgd 				} else
    169   1.1       cgd 					*cp2++ = c;
    170   1.1       cgd 			} else if (c == '"' || c == '\'')
    171   1.1       cgd 				quotec = c;
    172  1.21  christos 			else if (is_WSP(c))
    173   1.1       cgd 				break;
    174   1.1       cgd 			else
    175   1.1       cgd 				*cp2++ = c;
    176   1.1       cgd 		}
    177   1.1       cgd 		*cp2 = '\0';
    178   1.1       cgd 		argv[argn++] = savestr(linebuf);
    179   1.1       cgd 	}
    180  1.12       wiz 	argv[argn] = NULL;
    181   1.1       cgd 	return argn;
    182   1.1       cgd }
    183   1.1       cgd 
    184   1.1       cgd /*
    185  1.18  christos  * Mark all messages that the user wanted from the command
    186  1.18  christos  * line in the message structure.  Return 0 on success, -1
    187  1.18  christos  * on error.
    188   1.1       cgd  */
    189   1.1       cgd 
    190  1.18  christos /*
    191  1.18  christos  * Bit values for colon modifiers.
    192  1.18  christos  */
    193  1.18  christos #define	CMBOX		0x001		/* Unread messages */
    194  1.18  christos #define	CMDELETED	0x002		/* Deleted messages */
    195  1.18  christos #define	CMMODIFY	0x004		/* Unread messages */
    196  1.18  christos #define	CMNEW		0x008		/* New messages */
    197  1.18  christos #define	CMOLD		0x010		/* Old messages */
    198  1.18  christos #define	CMPRESERVE	0x020		/* Unread messages */
    199  1.18  christos #define	CMREAD		0x040		/* Read messages */
    200  1.18  christos #define	CMSAVED		0x080		/* Saved messages */
    201  1.18  christos #define	CMTAGGED	0x100		/* Tagged messages */
    202  1.18  christos #define	CMUNREAD	0x200		/* Unread messages */
    203  1.18  christos #define CMNEGATE	0x400		/* Negate the match */
    204  1.18  christos #define CMMASK		0x7ff		/* Mask the valid bits */
    205   1.1       cgd 
    206  1.18  christos /*
    207  1.18  christos  * The following table describes the letters which can follow
    208  1.18  christos  * the colon and gives the corresponding modifier bit.
    209  1.18  christos  */
    210  1.18  christos 
    211  1.18  christos static const struct coltab {
    212  1.18  christos 	char	co_char;		/* What to find past : */
    213  1.18  christos 	int	co_bit;			/* Associated modifier bit */
    214  1.18  christos 	int	co_mask;		/* m_status bits to mask */
    215  1.18  christos 	int	co_equal;		/* ... must equal this */
    216  1.18  christos } coltab[] = {
    217  1.18  christos 	{ '!',		CMNEGATE,	0,		0 },
    218  1.18  christos 	{ 'd',		CMDELETED,	MDELETED,	MDELETED },
    219  1.18  christos 	{ 'e',		CMMODIFY,	MMODIFY,	MMODIFY },
    220  1.18  christos 	{ 'm',		CMBOX,		MBOX,		MBOX },
    221  1.18  christos 	{ 'n',		CMNEW,		MNEW,		MNEW },
    222  1.18  christos 	{ 'o',		CMOLD,		MNEW,		0 },
    223  1.18  christos 	{ 'p',		CMPRESERVE,	MPRESERVE,	MPRESERVE },
    224  1.18  christos 	{ 'r',		CMREAD,		MREAD,		MREAD },
    225  1.18  christos 	{ 's',		CMSAVED,	MSAVED,		MSAVED },
    226  1.18  christos 	{ 't',		CMTAGGED,	MTAGGED,	MTAGGED },
    227  1.18  christos 	{ 'u',		CMUNREAD,	MREAD|MNEW,	0 },
    228  1.18  christos 	{ 0,		0,		0,		0 }
    229  1.18  christos };
    230  1.18  christos 
    231  1.18  christos static	int	lastcolmod;
    232  1.18  christos 
    233  1.18  christos static int
    234  1.18  christos ignore_message(int m_flag, int colmod)
    235  1.18  christos {
    236  1.18  christos 	int ignore_msg;
    237  1.18  christos 	const struct coltab *colp;
    238  1.18  christos 
    239  1.18  christos 	ignore_msg = !(colmod & CMNEGATE);
    240  1.18  christos 	colmod &= (~CMNEGATE & CMMASK);
    241  1.18  christos 
    242  1.18  christos 	for (colp = &coltab[0]; colp->co_char; colp++)
    243  1.18  christos 		if (colp->co_bit & colmod &&
    244  1.18  christos 		    (m_flag & colp->co_mask) == colp->co_equal)
    245  1.18  christos 				return !ignore_msg;
    246  1.18  christos 	return ignore_msg;
    247  1.18  christos }
    248  1.18  christos 
    249  1.18  christos /*
    250  1.18  christos  * Turn the character after a colon modifier into a bit
    251  1.18  christos  * value.
    252  1.18  christos  */
    253  1.18  christos static int
    254  1.18  christos evalcol(int col)
    255  1.18  christos {
    256  1.18  christos 	const struct coltab *colp;
    257  1.18  christos 
    258  1.18  christos 	if (col == 0)
    259  1.18  christos 		return lastcolmod;
    260  1.18  christos 	for (colp = &coltab[0]; colp->co_char; colp++)
    261  1.18  christos 		if (colp->co_char == col)
    262  1.18  christos 			return colp->co_bit;
    263  1.18  christos 	return 0;
    264  1.18  christos }
    265  1.18  christos 
    266  1.18  christos static int
    267  1.18  christos get_colmod(int colmod, char *cp)
    268  1.18  christos {
    269  1.18  christos 	if ((cp[0] == '\0') ||
    270  1.18  christos 	    (cp[0] == '!' && cp[1] == '\0'))
    271  1.18  christos 		colmod |= lastcolmod;
    272  1.21  christos 
    273  1.18  christos 	for (/*EMPTY*/; *cp; cp++) {
    274  1.18  christos 		int colresult;
    275  1.18  christos 		if ((colresult = evalcol(*cp)) == 0) {
    276  1.18  christos 			(void)printf("Unknown colon modifier \"%s\"\n", lexstring);
    277  1.18  christos 			return -1;
    278  1.18  christos 		}
    279  1.18  christos 		if (colresult == CMNEGATE)
    280  1.18  christos 			colmod ^= CMNEGATE;
    281  1.18  christos 		else
    282  1.18  christos 			colmod |= colresult;
    283  1.18  christos 	}
    284  1.18  christos 	return colmod;
    285  1.18  christos }
    286  1.18  christos 
    287  1.18  christos static int
    288  1.18  christos syntax_error(const char *msg)
    289  1.18  christos {
    290  1.18  christos 	(void)printf("Syntax error: %s\n", msg);
    291  1.18  christos 	return -1;
    292  1.18  christos }
    293  1.18  christos 
    294  1.18  christos /*
    295  1.18  christos  * scan out a single lexical item and return its token number,
    296  1.18  christos  * updating the string pointer passed **p.  Also, store the value
    297  1.18  christos  * of the number or string scanned in lexnumber or lexstring as
    298  1.18  christos  * appropriate.  In any event, store the scanned `thing' in lexstring.
    299  1.18  christos  */
    300  1.18  christos static enum token_e
    301  1.18  christos scan(char **sp)
    302  1.18  christos {
    303  1.18  christos 	static const struct lex {
    304  1.18  christos 		char	l_char;
    305  1.18  christos 		enum token_e l_token;
    306  1.18  christos 	} singles[] = {
    307  1.18  christos 		{ '$',	TDOLLAR },
    308  1.18  christos 		{ '.',	TDOT },
    309  1.18  christos 		{ '^',	TUP },
    310  1.18  christos 		{ '*',	TSTAR },
    311  1.18  christos 		{ '-',	TDASH },
    312  1.18  christos 		{ '+',	TPLUS },
    313  1.18  christos 		{ '(',	TOPEN },
    314  1.18  christos 		{ ')',	TCLOSE },
    315  1.18  christos 		{ '&',	TAND },
    316  1.18  christos 		{ '|',	TOR },
    317  1.18  christos 		{ '!',	TNOT },
    318  1.18  christos 		{ 0,	0 }
    319  1.18  christos 	};
    320  1.18  christos 	const struct lex *lp;
    321  1.18  christos 	char *cp, *cp2;
    322  1.18  christos 	int c;
    323  1.18  christos 	int quotec;
    324   1.1       cgd 
    325   1.1       cgd 	if (regretp >= 0) {
    326  1.15  christos 		(void)strcpy(lexstring, string_stack[regretp]);
    327   1.1       cgd 		lexnumber = numberstack[regretp];
    328  1.18  christos 		return regretstack[regretp--];
    329   1.1       cgd 	}
    330   1.1       cgd 	cp = *sp;
    331   1.1       cgd 	cp2 = lexstring;
    332  1.18  christos 	lexstring[0] = '\0';
    333   1.1       cgd 
    334   1.1       cgd 	/*
    335   1.1       cgd 	 * strip away leading white space.
    336   1.1       cgd 	 */
    337  1.21  christos 	cp = skip_WSP(cp);
    338   1.1       cgd 
    339   1.1       cgd 	/*
    340   1.1       cgd 	 * If no characters remain, we are at end of line,
    341   1.1       cgd 	 * so report that.
    342   1.1       cgd 	 */
    343  1.21  christos 	if (*cp == '\0') {
    344  1.21  christos 		*sp = cp;
    345  1.18  christos 		return TEOL;
    346   1.1       cgd 	}
    347   1.1       cgd 
    348   1.1       cgd 	/*
    349   1.1       cgd 	 * If the leading character is a digit, scan
    350   1.1       cgd 	 * the number and convert it on the fly.
    351   1.1       cgd 	 * Return TNUMBER when done.
    352   1.1       cgd 	 */
    353  1.21  christos 	c = (unsigned char)*cp++;
    354   1.1       cgd 	if (isdigit(c)) {
    355   1.1       cgd 		lexnumber = 0;
    356   1.1       cgd 		while (isdigit(c)) {
    357  1.18  christos 			lexnumber = lexnumber * 10 + c - '0';
    358   1.1       cgd 			*cp2++ = c;
    359  1.21  christos 			c = (unsigned char)*cp++;
    360   1.1       cgd 		}
    361   1.1       cgd 		*cp2 = '\0';
    362   1.1       cgd 		*sp = --cp;
    363  1.18  christos 		return TNUMBER;
    364   1.1       cgd 	}
    365   1.1       cgd 
    366   1.1       cgd 	/*
    367   1.1       cgd 	 * Check for single character tokens; return such
    368   1.1       cgd 	 * if found.
    369   1.1       cgd 	 */
    370   1.1       cgd 	for (lp = &singles[0]; lp->l_char != 0; lp++)
    371   1.1       cgd 		if (c == lp->l_char) {
    372   1.1       cgd 			lexstring[0] = c;
    373   1.1       cgd 			lexstring[1] = '\0';
    374   1.1       cgd 			*sp = cp;
    375  1.18  christos 			return lp->l_token;
    376   1.1       cgd 		}
    377   1.1       cgd 
    378   1.1       cgd 	/*
    379   1.1       cgd 	 * We've got a string!  Copy all the characters
    380   1.1       cgd 	 * of the string into lexstring, until we see
    381   1.1       cgd 	 * a null, space, or tab.
    382  1.18  christos 	 * Respect quoting and quoted pairs.
    383   1.1       cgd 	 */
    384   1.1       cgd 	quotec = 0;
    385   1.1       cgd 	while (c != '\0') {
    386   1.1       cgd 		if (c == quotec) {
    387  1.18  christos 			quotec = 0;
    388  1.18  christos 			c = *cp++;
    389  1.18  christos 			continue;
    390  1.18  christos 		}
    391  1.18  christos 		if (quotec) {
    392  1.18  christos 			if (c == '\\' && (*cp == quotec || *cp == '\\'))
    393  1.18  christos 				c = *cp++;
    394  1.18  christos 		}
    395  1.18  christos 		else {
    396  1.18  christos 			switch (c) {
    397  1.18  christos 			case '\'':
    398  1.18  christos 			case '"':
    399  1.18  christos 				quotec = c;
    400  1.18  christos 				c = *cp++;
    401  1.18  christos 				continue;
    402  1.18  christos 			case ' ':
    403  1.18  christos 			case '\t':
    404  1.18  christos 				c = '\0';	/* end of token! */
    405  1.18  christos 				continue;
    406  1.18  christos 			default:
    407  1.18  christos 				break;
    408  1.18  christos 			}
    409   1.1       cgd 		}
    410  1.16  christos 		if (cp2 - lexstring < STRINGLEN - 1)
    411   1.1       cgd 			*cp2++ = c;
    412   1.1       cgd 		c = *cp++;
    413   1.1       cgd 	}
    414   1.1       cgd 	if (quotec && c == 0) {
    415  1.15  christos 		(void)fprintf(stderr, "Missing %c\n", quotec);
    416   1.1       cgd 		return TERROR;
    417   1.1       cgd 	}
    418   1.1       cgd 	*sp = --cp;
    419   1.1       cgd 	*cp2 = '\0';
    420  1.18  christos 	return TSTRING;
    421   1.1       cgd }
    422   1.1       cgd 
    423   1.1       cgd /*
    424   1.1       cgd  * Unscan the named token by pushing it onto the regret stack.
    425   1.1       cgd  */
    426  1.18  christos static void
    427  1.11       wiz regret(int token)
    428   1.1       cgd {
    429   1.1       cgd 	if (++regretp >= REGDEP)
    430  1.26  christos 		errx(EXIT_FAILURE, "Too many regrets");
    431   1.1       cgd 	regretstack[regretp] = token;
    432  1.18  christos 	lexstring[sizeof(lexstring) - 1] = '\0';
    433   1.1       cgd 	string_stack[regretp] = savestr(lexstring);
    434   1.1       cgd 	numberstack[regretp] = lexnumber;
    435   1.1       cgd }
    436   1.1       cgd 
    437   1.1       cgd /*
    438   1.1       cgd  * Reset all the scanner global variables.
    439   1.1       cgd  */
    440  1.18  christos static void
    441  1.11       wiz scaninit(void)
    442   1.1       cgd {
    443   1.1       cgd 	regretp = -1;
    444   1.1       cgd }
    445   1.1       cgd 
    446  1.18  christos #define DELIM " \t,"  /* list of string delimiters */
    447  1.18  christos static int
    448  1.18  christos is_substr(const char *big, const char *little)
    449  1.18  christos {
    450  1.18  christos 	const char *cp;
    451  1.18  christos 	if ((cp = strstr(big, little)) == NULL)
    452  1.18  christos 		return 0;
    453  1.18  christos 
    454  1.18  christos 	return strchr(DELIM, cp[strlen(little)]) != 0 &&
    455  1.18  christos 	    (cp == big || strchr(DELIM, cp[-1]) != 0);
    456  1.18  christos }
    457  1.18  christos #undef DELIM
    458  1.18  christos 
    459  1.19  christos 
    460  1.19  christos /*
    461  1.19  christos  * Look for (compiled regex) pattern in a line.
    462  1.19  christos  * Returns:
    463  1.19  christos  *	1 if match found.
    464  1.19  christos  *	0 if no match found.
    465  1.19  christos  *	-1 on error
    466  1.19  christos  */
    467  1.19  christos static int
    468  1.19  christos regexcmp(void *pattern, char *line, size_t len)
    469  1.19  christos {
    470  1.19  christos 	regmatch_t pmatch[1];
    471  1.19  christos 	regmatch_t *pmp;
    472  1.19  christos 	int eflags;
    473  1.19  christos 	int rval;
    474  1.19  christos 	regex_t *preg;
    475  1.19  christos 
    476  1.19  christos 	preg = pattern;
    477  1.19  christos 
    478  1.19  christos 	if (line == NULL)
    479  1.19  christos 		return 0;
    480  1.19  christos 
    481  1.19  christos 	if (len == 0) {
    482  1.19  christos 		pmp = NULL;
    483  1.19  christos 		eflags = 0;
    484  1.19  christos 	}
    485  1.19  christos 	else {
    486  1.19  christos 		pmatch[0].rm_so = 0;
    487  1.19  christos 		pmatch[0].rm_eo = line[len - 1] == '\n' ? len - 1 : len;
    488  1.19  christos 		pmp = pmatch;
    489  1.19  christos 		eflags = REG_STARTEND;
    490  1.19  christos 	}
    491  1.19  christos 
    492  1.19  christos 	switch ((rval = regexec(preg, line, 0, pmp, eflags))) {
    493  1.19  christos 	case 0:
    494  1.19  christos 	case REG_NOMATCH:
    495  1.19  christos 		return rval == 0;
    496  1.19  christos 
    497  1.19  christos 	default: {
    498  1.19  christos 		char errbuf[LINESIZE];
    499  1.19  christos 		(void)regerror(rval, preg, errbuf, sizeof(errbuf));
    500  1.19  christos 		(void)printf("regexec failed: '%s': %s\n", line, errbuf);
    501  1.19  christos 		return -1;
    502  1.19  christos 	}}
    503  1.19  christos }
    504  1.19  christos 
    505   1.1       cgd /*
    506  1.19  christos  * Look for (string) pattern in line.
    507  1.19  christos  * Return 1 if match found.
    508  1.19  christos  */
    509  1.19  christos static int
    510  1.19  christos substrcmp(void *pattern, char *line, size_t len)
    511  1.19  christos {
    512  1.19  christos 	char *substr;
    513  1.19  christos 	substr = pattern;
    514  1.19  christos 
    515  1.19  christos 	if (line == NULL)
    516  1.19  christos 		return 0;
    517  1.19  christos 
    518  1.19  christos 	if (len) {
    519  1.19  christos 		if (line[len - 1] == '\n') {
    520  1.19  christos 			line[len - 1] = '\0';
    521  1.19  christos 		}
    522  1.19  christos 		else {
    523  1.19  christos 			char *cp;
    524  1.19  christos 			cp = salloc(len + 1);
    525  1.19  christos 			(void)strlcpy(cp, line, len + 1);
    526  1.19  christos 			line = cp;
    527  1.19  christos 		}
    528  1.19  christos 	}
    529  1.19  christos 	return strcasestr(line, substr) != NULL;
    530  1.19  christos }
    531  1.19  christos 
    532  1.22  christos /*
    533  1.22  christos  * Look for NULL line.  Used to find non-existent fields.
    534  1.22  christos  * Return 1 if match found.
    535  1.22  christos  */
    536  1.22  christos static int
    537  1.22  christos hasfieldcmp(void *pattern __unused, char *line, size_t len __unused)
    538  1.22  christos {
    539  1.22  christos #ifdef __lint__
    540  1.22  christos 	pattern = pattern;
    541  1.22  christos 	len = len;
    542  1.22  christos #endif
    543  1.22  christos 	return line != NULL;
    544  1.22  christos }
    545  1.22  christos 
    546  1.19  christos static regex_t preg;
    547  1.19  christos /*
    548  1.19  christos  * Determine the compare function and its argument based on the
    549  1.18  christos  * "regex-search" variable.
    550   1.1       cgd  */
    551  1.19  christos static int (*
    552  1.19  christos get_cmpfn(void **pattern, char *str)
    553  1.19  christos )(void *, char *, size_t)
    554   1.1       cgd {
    555  1.19  christos 	char *val;
    556  1.19  christos 	int cflags;
    557  1.18  christos 	int e;
    558  1.19  christos 
    559  1.22  christos 	if (*str == 0) {
    560  1.22  christos 		*pattern = NULL;
    561  1.22  christos 		return hasfieldcmp;
    562  1.22  christos 	}
    563  1.22  christos 
    564  1.18  christos 	if ((val = value(ENAME_REGEX_SEARCH)) != NULL) {
    565  1.18  christos 		cflags = REG_NOSUB;
    566  1.21  christos 		val = skip_WSP(val);
    567  1.18  christos 		if (*val) {
    568  1.18  christos 			if (is_substr(val, "icase"))
    569  1.18  christos 				cflags |= REG_ICASE;
    570  1.18  christos 			if (is_substr(val, "extended"))
    571  1.18  christos 				cflags |= REG_EXTENDED;
    572  1.19  christos 			/*
    573  1.19  christos 			 * NOTE: regcomp() will fail if "nospec" and
    574  1.19  christos 			 * "extended" are used together.
    575  1.19  christos 			 */
    576  1.19  christos 			if (is_substr(val, "nospec"))
    577  1.19  christos 				cflags |= REG_NOSPEC;
    578  1.18  christos 		}
    579  1.19  christos 		if ((e = regcomp(&preg, str, cflags)) != 0) {
    580  1.18  christos 			char errbuf[LINESIZE];
    581  1.19  christos 			(void)regerror(e, &preg, errbuf, sizeof(errbuf));
    582  1.18  christos 			(void)printf("regcomp failed: '%s': %s\n", str, errbuf);
    583  1.19  christos 			return NULL;
    584  1.18  christos 		}
    585  1.19  christos 		*pattern = &preg;
    586  1.19  christos 		return regexcmp;
    587  1.18  christos 	}
    588  1.19  christos 
    589  1.19  christos 	*pattern = str;
    590  1.19  christos 	return substrcmp;
    591  1.19  christos }
    592  1.19  christos 
    593  1.19  christos /*
    594  1.19  christos  * Free any memory allocated by get_cmpfn()
    595  1.19  christos  */
    596  1.19  christos static void
    597  1.19  christos free_cmparg(void *pattern)
    598  1.19  christos {
    599  1.19  christos 	if (pattern == &preg)
    600  1.19  christos 		regfree(&preg);
    601   1.1       cgd }
    602   1.1       cgd 
    603   1.1       cgd /*
    604  1.19  christos  * Check the message body for the pattern.
    605   1.1       cgd  */
    606  1.18  christos static int
    607  1.19  christos matchbody(int (*cmpfn)(void *, char *, size_t),
    608  1.19  christos     void *pattern, struct message *mp, char const *fieldname __unused)
    609   1.1       cgd {
    610  1.19  christos 	FILE *fp;
    611  1.19  christos 	char *line;
    612  1.19  christos 	size_t len;
    613  1.19  christos 	int gotmatch;
    614   1.1       cgd 
    615  1.19  christos #ifdef __lint__
    616  1.19  christos 	fieldname = fieldname;
    617  1.19  christos #endif
    618  1.19  christos 	/*
    619  1.19  christos 	 * Get a temporary file.
    620  1.19  christos 	 */
    621  1.19  christos 	{
    622  1.19  christos 		char *tempname;
    623  1.19  christos 		int fd;
    624  1.19  christos 
    625  1.19  christos 		(void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
    626  1.19  christos 		fp = NULL;
    627  1.19  christos 		if ((fd = mkstemp(tempname)) != -1) {
    628  1.19  christos 			(void)unlink(tempname);
    629  1.28  christos 			if ((fp = Fdopen(fd, "wef+")) == NULL)
    630  1.19  christos 				(void)close(fd);
    631  1.19  christos 		}
    632  1.19  christos 		if (fp == NULL) {
    633  1.19  christos 			warn("%s", tempname);
    634  1.19  christos 			return -1;
    635  1.19  christos 		}
    636  1.19  christos 	}
    637  1.18  christos 
    638  1.19  christos 	/*
    639  1.19  christos 	 * Pump the (decoded) message body into the temp file.
    640  1.19  christos 	 */
    641  1.19  christos 	{
    642  1.19  christos #ifdef MIME_SUPPORT
    643  1.19  christos 		struct mime_info *mip;
    644  1.19  christos 		int retval;
    645  1.19  christos 
    646  1.19  christos 		mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
    647  1.19  christos 		    : NULL;
    648  1.19  christos 
    649  1.19  christos 		retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
    650  1.19  christos 		mime_decode_close(mip);
    651  1.19  christos 		if (retval == -1)
    652  1.19  christos #else
    653  1.19  christos 		if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
    654  1.19  christos #endif
    655  1.19  christos 		{
    656  1.19  christos 			warn("matchbody: mesg=%d", get_msgnum(mp));
    657  1.19  christos 			return -1;
    658  1.19  christos 		}
    659  1.19  christos 	}
    660  1.19  christos 	/*
    661  1.19  christos 	 * XXX - should we read the entire body into a buffer so we
    662  1.19  christos 	 * can search across lines?
    663  1.19  christos 	 */
    664  1.19  christos 	rewind(fp);
    665  1.19  christos 	gotmatch = 0;
    666  1.19  christos 	while ((line = fgetln(fp, &len)) != NULL && len > 0) {
    667  1.19  christos 		gotmatch = cmpfn(pattern, line, len);
    668  1.19  christos 		if (gotmatch)
    669  1.19  christos 			break;
    670  1.19  christos 	}
    671  1.19  christos 	(void)Fclose(fp);
    672  1.18  christos 
    673  1.19  christos 	return gotmatch;
    674   1.1       cgd }
    675   1.1       cgd 
    676   1.1       cgd /*
    677  1.19  christos  * Check the "To:", "Cc:", and "Bcc" fields for the pattern.
    678   1.5       tls  */
    679  1.18  christos static int
    680  1.19  christos matchto(int (*cmpfn)(void *, char *, size_t),
    681  1.19  christos     void *pattern, struct message *mp, char const *fieldname __unused)
    682   1.5       tls {
    683  1.18  christos 	static const char *to_fields[] = { "to", "cc", "bcc", 0 };
    684  1.18  christos 	const char **to;
    685  1.19  christos 	int gotmatch;
    686   1.5       tls 
    687  1.19  christos #ifdef __lint__
    688  1.19  christos 	fieldname = fieldname;
    689  1.19  christos #endif
    690  1.19  christos 	gotmatch = 0;
    691   1.5       tls 	for (to = to_fields; *to; to++) {
    692  1.18  christos 		char *field;
    693  1.18  christos 		field = hfield(*to, mp);
    694  1.19  christos 		gotmatch = cmpfn(pattern, field, 0);
    695  1.19  christos 		if (gotmatch)
    696  1.19  christos 			break;
    697   1.5       tls 	}
    698  1.19  christos 	return gotmatch;
    699   1.5       tls }
    700   1.5       tls 
    701   1.5       tls /*
    702  1.19  christos  * Check a field for the pattern.
    703   1.1       cgd  */
    704  1.18  christos static int
    705  1.19  christos matchfield(int (*cmpfn)(void *, char *, size_t),
    706  1.19  christos     void *pattern, struct message *mp, char const *fieldname)
    707   1.1       cgd {
    708  1.18  christos 	char *field;
    709  1.18  christos 
    710  1.19  christos #ifdef __lint__
    711  1.19  christos 	fieldname = fieldname;
    712  1.19  christos #endif
    713  1.19  christos 	field = hfield(fieldname, mp);
    714  1.19  christos 	return cmpfn(pattern, field, 0);
    715  1.19  christos }
    716  1.19  christos 
    717  1.19  christos /*
    718  1.19  christos  * Check the headline for the pattern.
    719  1.19  christos  */
    720  1.19  christos static int
    721  1.19  christos matchfrom(int (*cmpfn)(void *, char *, size_t),
    722  1.19  christos     void *pattern, struct message *mp, char const *fieldname __unused)
    723  1.19  christos {
    724  1.19  christos 	char headline[LINESIZE];
    725  1.19  christos 	char *field;
    726   1.1       cgd 
    727  1.19  christos #ifdef __lint__
    728  1.19  christos 	fieldname = fieldname;
    729  1.19  christos #endif
    730  1.25  christos 	(void)readline(setinput(mp), headline, (int)sizeof(headline), 0);
    731  1.19  christos 	field = savestr(headline);
    732  1.19  christos 	if (strncmp(field, "From ", 5) != 0)
    733  1.19  christos 		return 1;
    734  1.18  christos 
    735  1.19  christos 	return cmpfn(pattern, field + 5, 0);
    736  1.19  christos }
    737  1.18  christos 
    738  1.19  christos /*
    739  1.19  christos  * Check the sender for the pattern.
    740  1.19  christos  */
    741  1.19  christos static int
    742  1.19  christos matchsender(int (*cmpfn)(void *, char *, size_t),
    743  1.19  christos     void *pattern, struct message *mp, char const *fieldname __unused)
    744  1.19  christos {
    745  1.19  christos 	char *field;
    746  1.19  christos 
    747  1.19  christos #ifdef __lint__
    748  1.19  christos 	fieldname = fieldname;
    749  1.19  christos #endif
    750  1.19  christos 	field = nameof(mp, 0);
    751  1.19  christos 	return cmpfn(pattern, field, 0);
    752  1.19  christos }
    753  1.19  christos 
    754  1.19  christos /*
    755  1.19  christos  * Interpret 'str' and check each message (1 thru 'msgCount') for a match.
    756  1.19  christos  * The 'str' has the format: [/[[x]:]y with the following meanings:
    757  1.19  christos  *
    758  1.19  christos  * y	 pattern 'y' is compared against the senders address.
    759  1.19  christos  * /y	 pattern 'y' is compared with the subject field.  If 'y' is empty,
    760  1.19  christos  *       the last search 'str' is used.
    761  1.19  christos  * /:y	 pattern 'y' is compared with the subject field.
    762  1.19  christos  * /x:y	 pattern 'y' is compared with the specified header field 'x' or
    763  1.19  christos  *       the message body if 'x' == "body".
    764  1.19  christos  *
    765  1.19  christos  * The last two forms require "searchheaders" to be defined.
    766  1.19  christos  */
    767  1.19  christos static int
    768  1.19  christos match_string(int *markarray, char *str, int msgCount)
    769  1.19  christos {
    770  1.19  christos 	int i;
    771  1.19  christos 	int rval;
    772  1.19  christos 	int (*matchfn)(int (*)(void *, char *, size_t),
    773  1.19  christos 	    void *, struct message *, char const *);
    774  1.19  christos 	int (*cmpfn)(void *, char *, size_t);
    775  1.19  christos 	void *cmparg;
    776  1.19  christos 	char const *fieldname;
    777  1.19  christos 
    778  1.19  christos 	if (*str != '/') {
    779  1.19  christos 		matchfn = matchsender;
    780  1.19  christos 		fieldname = NULL;
    781  1.19  christos 	}
    782  1.19  christos 	else {
    783  1.19  christos 		static char lastscan[STRINGLEN];
    784  1.19  christos 		char *cp;
    785   1.1       cgd 
    786  1.19  christos 		str++;
    787  1.19  christos 		if (*str == '\0')
    788  1.19  christos 			str = lastscan;
    789  1.19  christos 		else
    790  1.19  christos 			(void)strlcpy(lastscan, str, sizeof(lastscan));
    791  1.18  christos 
    792  1.19  christos 		if (value(ENAME_SEARCHHEADERS) == NULL ||
    793  1.19  christos 		    (cp = strchr(str, ':')) == NULL) {
    794  1.19  christos 			matchfn = matchfield;
    795  1.19  christos 			fieldname = "subject";
    796  1.19  christos 		/*	str = str; */
    797  1.18  christos 		}
    798  1.18  christos 		else {
    799  1.19  christos 			static const struct matchtbl_s {
    800  1.19  christos 				char const *key;
    801  1.19  christos 				size_t len;
    802  1.19  christos 				char const *fieldname;
    803  1.19  christos 				int (*matchfn)(int (*)(void *, char *, size_t),
    804  1.19  christos 				    void *, struct message *, char const *);
    805  1.19  christos 			} matchtbl[] = {
    806  1.19  christos 				#define	X(a)	a,	sizeof(a) - 1
    807  1.19  christos 				#define	X_NULL	NULL,	0
    808  1.19  christos 				{ X(":"),	"subject",	matchfield },
    809  1.19  christos 				{ X("body:"),	NULL,		matchbody },
    810  1.19  christos 				{ X("from:"),	NULL,		matchfrom },
    811  1.19  christos 				{ X("to:"),	NULL,		matchto },
    812  1.19  christos 				{ X_NULL,	NULL,		matchfield }
    813  1.19  christos 				#undef X_NULL
    814  1.19  christos 				#undef X
    815  1.19  christos 			};
    816  1.19  christos 			const struct matchtbl_s *mtp;
    817  1.19  christos 			size_t len;
    818  1.19  christos 			/*
    819  1.19  christos 			 * Check for special cases!
    820  1.19  christos 			 * These checks are case sensitive so the true fields
    821  1.19  christos 			 * can be grabbed as mentioned in the manpage.
    822  1.19  christos 			 */
    823  1.19  christos 			cp++;
    824  1.19  christos 			len = cp - str;
    825  1.19  christos 			for (mtp = matchtbl; mtp->key; mtp++) {
    826  1.19  christos 				if (len == mtp->len &&
    827  1.19  christos 				    strncmp(str, mtp->key, len) == 0)
    828  1.19  christos 					break;
    829  1.19  christos 			}
    830  1.19  christos 			matchfn = mtp->matchfn;
    831  1.19  christos 			if (mtp->key)
    832  1.19  christos 				fieldname = mtp->fieldname;
    833  1.19  christos 			else {
    834  1.19  christos 				char *p;
    835  1.19  christos 				p = salloc(len);
    836  1.19  christos 				(void)strlcpy(p, str, len);
    837  1.19  christos 				fieldname = p;
    838  1.19  christos 			}
    839  1.19  christos 			str = cp;
    840  1.19  christos 		}
    841   1.3   deraadt 	}
    842  1.19  christos 
    843  1.19  christos 	cmpfn = get_cmpfn(&cmparg, str);
    844  1.19  christos 	if (cmpfn == NULL)
    845  1.18  christos 		return -1;
    846   1.1       cgd 
    847  1.19  christos 	rval = 0;
    848  1.19  christos 	for (i = 1; i <= msgCount; i++) {
    849  1.19  christos 		struct message *mp;
    850  1.19  christos 		mp = get_message(i);
    851  1.19  christos 		rval = matchfn(cmpfn, cmparg, mp, fieldname);
    852  1.19  christos 		if (rval == -1)
    853  1.19  christos 			break;
    854  1.19  christos 		if (rval)
    855  1.19  christos 			markarray[i - 1] = 1;
    856  1.19  christos 		rval = 0;
    857  1.19  christos 	}
    858  1.19  christos 
    859  1.19  christos 	free_cmparg(cmparg);	/* free any memory allocated by get_cmpfn() */
    860  1.19  christos 
    861  1.19  christos 	return rval;
    862   1.1       cgd }
    863   1.1       cgd 
    864  1.19  christos 
    865   1.1       cgd /*
    866   1.1       cgd  * Return the message number corresponding to the passed meta character.
    867   1.1       cgd  */
    868  1.18  christos static int
    869  1.11       wiz metamess(int meta, int f)
    870   1.1       cgd {
    871   1.8     lukem 	int c, m;
    872   1.8     lukem 	struct message *mp;
    873   1.1       cgd 
    874   1.1       cgd 	c = meta;
    875   1.1       cgd 	switch (c) {
    876   1.1       cgd 	case '^':
    877   1.1       cgd 		/*
    878   1.1       cgd 		 * First 'good' message left.
    879   1.1       cgd 		 */
    880  1.18  christos 		for (mp = get_message(1); mp; mp = next_message(mp))
    881   1.1       cgd 			if ((mp->m_flag & MDELETED) == f)
    882  1.18  christos 				return get_msgnum(mp);
    883  1.15  christos 		(void)printf("No applicable messages\n");
    884  1.18  christos 		return -1;
    885   1.1       cgd 
    886   1.1       cgd 	case '$':
    887   1.1       cgd 		/*
    888   1.1       cgd 		 * Last 'good message left.
    889   1.1       cgd 		 */
    890  1.18  christos 		for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
    891   1.1       cgd 			if ((mp->m_flag & MDELETED) == f)
    892  1.18  christos 				return get_msgnum(mp);
    893  1.15  christos 		(void)printf("No applicable messages\n");
    894  1.18  christos 		return -1;
    895   1.1       cgd 
    896   1.1       cgd 	case '.':
    897  1.21  christos 		/*
    898   1.1       cgd 		 * Current message.
    899   1.1       cgd 		 */
    900  1.18  christos 		if (dot == NULL) {
    901  1.18  christos 			(void)printf("No applicable messages\n");
    902  1.18  christos 			return -1;
    903  1.18  christos 		}
    904  1.18  christos 		m = get_msgnum(dot);
    905   1.1       cgd 		if ((dot->m_flag & MDELETED) != f) {
    906  1.15  christos 			(void)printf("%d: Inappropriate message\n", m);
    907  1.18  christos 			return -1;
    908   1.1       cgd 		}
    909  1.18  christos 		return m;
    910   1.1       cgd 
    911   1.1       cgd 	default:
    912  1.15  christos 		(void)printf("Unknown metachar (%c)\n", c);
    913  1.18  christos 		return -1;
    914  1.18  christos 	}
    915  1.18  christos }
    916  1.18  christos 
    917  1.18  christos /*
    918  1.18  christos  * Check the passed message number for legality and proper flags.
    919  1.18  christos  * If f is MDELETED, then either kind will do.  Otherwise, the message
    920  1.18  christos  * has to be undeleted.
    921  1.18  christos  */
    922  1.18  christos static int
    923  1.18  christos check(int mesg, int f)
    924  1.18  christos {
    925  1.18  christos 	struct message *mp;
    926  1.18  christos 
    927  1.18  christos 	if ((mp = get_message(mesg)) == NULL) {
    928  1.18  christos 		(void)printf("%d: Invalid message number\n", mesg);
    929  1.18  christos 		return -1;
    930  1.18  christos 	}
    931  1.18  christos 	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
    932  1.18  christos 		(void)printf("%d: Inappropriate message\n", mesg);
    933  1.18  christos 		return -1;
    934  1.18  christos 	}
    935  1.18  christos 	return 0;
    936  1.18  christos }
    937  1.18  christos 
    938  1.18  christos 
    939  1.18  christos static int
    940  1.18  christos markall_core(int *markarray, char **bufp, int f, int level)
    941  1.18  christos {
    942  1.18  christos 	enum token_e tok;
    943  1.18  christos 	enum logic_op_e {
    944  1.18  christos 		LOP_AND,
    945  1.18  christos 		LOP_OR,
    946  1.18  christos 		LOP_XOR
    947  1.18  christos 	} logic_op;		/* binary logic operation */
    948  1.18  christos 	int logic_invert;	/* invert the result */
    949  1.18  christos 	int *tmparray;	/* temporarly array with result */
    950  1.18  christos 	int msgCount;	/* tmparray length and message count */
    951  1.18  christos 	int beg;	/* first value of a range */
    952  1.18  christos 	int colmod;	/* the colon-modifier for this group */
    953  1.18  christos 	int got_not;	/* for syntax checking of '!' */
    954  1.18  christos 	int got_one;	/* we have a message spec, valid or not */
    955  1.18  christos 	int got_bin;	/* we have a pending binary operation */
    956  1.18  christos 	int i;
    957  1.18  christos 
    958  1.18  christos 	logic_op = LOP_OR;
    959  1.18  christos 	logic_invert = 0;
    960  1.18  christos 	colmod = 0;
    961  1.18  christos 
    962  1.18  christos 	msgCount = get_msgCount();
    963  1.18  christos 	tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
    964  1.18  christos 
    965  1.18  christos 	beg = 0;
    966  1.18  christos 	got_one = 0;
    967  1.18  christos 	got_not = 0;
    968  1.18  christos 	got_bin = 0;
    969  1.18  christos 
    970  1.18  christos 	while ((tok = scan(bufp)) != TEOL) {
    971  1.18  christos 		if (tok == TERROR)
    972  1.18  christos 			return -1;
    973  1.18  christos 
    974  1.18  christos 		/*
    975  1.18  christos 		 * Do some syntax checking.
    976  1.18  christos 		 */
    977  1.18  christos 		switch (tok) {
    978  1.18  christos 		case TDASH:
    979  1.18  christos 		case TPLUS:
    980  1.18  christos 		case TDOLLAR:
    981  1.18  christos 		case TUP:
    982  1.18  christos 		case TDOT:
    983  1.18  christos 		case TNUMBER:
    984  1.18  christos 			break;
    985  1.21  christos 
    986  1.18  christos 		case TAND:
    987  1.18  christos 		case TOR:
    988  1.18  christos 		case TXOR:
    989  1.18  christos 			if (!got_one)
    990  1.18  christos 				return syntax_error("missing left operand");
    991  1.18  christos 			/*FALLTHROUGH*/
    992  1.18  christos 		default:
    993  1.18  christos 			if (beg)
    994  1.18  christos 				return syntax_error("end of range missing");
    995  1.18  christos 			break;
    996  1.18  christos 		}
    997  1.18  christos 
    998  1.18  christos 		/*
    999  1.18  christos 		 * The main tok switch.
   1000  1.18  christos 		 */
   1001  1.18  christos 		switch (tok) {
   1002  1.18  christos 			struct message *mp;
   1003  1.18  christos 
   1004  1.18  christos 		case TERROR:	/* trapped above */
   1005  1.18  christos 		case TEOL:
   1006  1.18  christos 			assert(/*CONSTCOND*/0);
   1007  1.18  christos 			break;
   1008  1.18  christos 
   1009  1.18  christos 		case TUP:
   1010  1.18  christos 			if (got_one) {	/* a possible logical xor */
   1011  1.18  christos 				enum token_e t;
   1012  1.18  christos 				t = scan(bufp); /* peek ahead */
   1013  1.18  christos 				regret(t);
   1014  1.18  christos 				lexstring[0] = '^';  /* restore lexstring */
   1015  1.18  christos 				lexstring[1] = '\0';
   1016  1.18  christos 				if (t != TDASH && t != TEOL && t != TCLOSE) {
   1017  1.18  christos 					/* convert tok to TXOR and put
   1018  1.18  christos 					 * it back on the stack so we
   1019  1.18  christos 					 * can handle it consistently */
   1020  1.18  christos 					tok = TXOR;
   1021  1.18  christos 					regret(tok);
   1022  1.18  christos 					continue;
   1023  1.18  christos 				}
   1024  1.18  christos 			}
   1025  1.18  christos 			/* FALLTHROUGH */
   1026  1.18  christos 		case TDOLLAR:
   1027  1.18  christos 		case TDOT:
   1028  1.18  christos 			lexnumber = metamess(lexstring[0], f);
   1029  1.18  christos 			if (lexnumber == -1)
   1030  1.18  christos 				return -1;
   1031  1.18  christos 			/* FALLTHROUGH */
   1032  1.18  christos 		case TNUMBER:
   1033  1.18  christos 			if (check(lexnumber, f))
   1034  1.18  christos 				return -1;
   1035  1.18  christos 	number:
   1036  1.18  christos 			got_one = 1;
   1037  1.18  christos 			if (beg != 0) {
   1038  1.18  christos 				if (lexnumber < beg) {
   1039  1.18  christos 					(void)printf("invalid range: %d-%d\n", beg, lexnumber);
   1040  1.18  christos 					return -1;
   1041  1.18  christos 				}
   1042  1.18  christos 				for (i = beg; i <= lexnumber; i++)
   1043  1.18  christos 					tmparray[i - 1] = 1;
   1044  1.18  christos 
   1045  1.18  christos 				beg = 0;
   1046  1.18  christos 				break;
   1047  1.18  christos 			}
   1048  1.18  christos 			beg = lexnumber;	/* start of a range */
   1049  1.18  christos 			tok = scan(bufp);
   1050  1.18  christos 			if (tok == TDASH) {
   1051  1.18  christos 				continue;
   1052  1.18  christos 			}
   1053  1.18  christos 			else {
   1054  1.18  christos 				regret(tok);
   1055  1.18  christos 				tmparray[beg - 1] = 1;
   1056  1.18  christos 				beg = 0;
   1057  1.18  christos 			}
   1058  1.18  christos 			break;
   1059  1.18  christos 
   1060  1.18  christos 		case TDASH:
   1061  1.18  christos 			for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
   1062  1.18  christos 				if ((mp->m_flag & MDELETED) == 0)
   1063  1.18  christos 					break;
   1064  1.18  christos 			}
   1065  1.18  christos 			if (mp == NULL) {
   1066  1.18  christos 				(void)printf("Referencing before 1\n");
   1067  1.18  christos 				return -1;
   1068  1.18  christos 			}
   1069  1.18  christos 			lexnumber = get_msgnum(mp);
   1070  1.18  christos 			goto number;
   1071  1.18  christos 
   1072  1.18  christos 		case TPLUS:
   1073  1.18  christos 			for (mp = next_message(dot); mp; mp = next_message(mp)) {
   1074  1.18  christos 				if ((mp->m_flag & MDELETED) == 0)
   1075  1.18  christos 					break;
   1076  1.18  christos 			}
   1077  1.18  christos 			if (mp == NULL) {
   1078  1.18  christos 				(void)printf("Referencing beyond EOF\n");
   1079  1.18  christos 				return -1;
   1080  1.18  christos 			}
   1081  1.18  christos 			lexnumber = get_msgnum(mp);
   1082  1.18  christos 			goto number;
   1083  1.18  christos 
   1084  1.18  christos 		case TSTRING:
   1085  1.18  christos 			if (lexstring[0] == ':') { /* colon modifier! */
   1086  1.18  christos 				colmod = get_colmod(colmod, lexstring + 1);
   1087  1.18  christos 				if (colmod == -1)
   1088  1.21  christos 					return -1;
   1089  1.18  christos 				continue;
   1090  1.18  christos 			}
   1091  1.18  christos 			got_one = 1;
   1092  1.18  christos 			if (match_string(tmparray, lexstring, msgCount) == -1)
   1093  1.18  christos 				return -1;
   1094  1.18  christos 			break;
   1095  1.18  christos 
   1096  1.18  christos 		case TSTAR:
   1097  1.18  christos 			got_one = 1;
   1098  1.18  christos 			for (i = 1; i <= msgCount; i++)
   1099  1.18  christos 				tmparray[i - 1] = 1;
   1100  1.18  christos 			break;
   1101  1.18  christos 
   1102  1.18  christos 
   1103  1.18  christos 			/**************
   1104  1.18  christos 			 * Parentheses.
   1105  1.18  christos 			 */
   1106  1.18  christos 		case TOPEN:
   1107  1.18  christos 			if (markall_core(tmparray, bufp, f, level + 1) == -1)
   1108  1.18  christos 				return -1;
   1109  1.18  christos 			break;
   1110  1.18  christos 
   1111  1.18  christos 		case TCLOSE:
   1112  1.18  christos 			if (level == 0)
   1113  1.18  christos 				return syntax_error("extra ')'");
   1114  1.18  christos 			goto done;
   1115  1.18  christos 
   1116  1.18  christos 
   1117  1.18  christos 			/*********************
   1118  1.18  christos 			 * Logical operations.
   1119  1.18  christos 			 */
   1120  1.18  christos 		case TNOT:
   1121  1.18  christos 			got_not = 1;
   1122  1.18  christos 			logic_invert = ! logic_invert;
   1123  1.18  christos 			continue;
   1124  1.18  christos 
   1125  1.18  christos 			/*
   1126  1.18  christos 			 * Binary operations.
   1127  1.18  christos 			 */
   1128  1.18  christos 		case TAND:
   1129  1.18  christos 			if (got_not)
   1130  1.18  christos 				return syntax_error("'!' precedes '&'");
   1131  1.18  christos 			got_bin = 1;
   1132  1.18  christos 			logic_op = LOP_AND;
   1133  1.18  christos 			continue;
   1134  1.18  christos 
   1135  1.18  christos 		case TOR:
   1136  1.18  christos 			if (got_not)
   1137  1.18  christos 				return syntax_error("'!' precedes '|'");
   1138  1.18  christos 			got_bin = 1;
   1139  1.18  christos 			logic_op = LOP_OR;
   1140  1.18  christos 			continue;
   1141  1.18  christos 
   1142  1.18  christos 		case TXOR:
   1143  1.18  christos 			if (got_not)
   1144  1.18  christos 				return syntax_error("'!' precedes logical '^'");
   1145  1.18  christos 			got_bin = 1;
   1146  1.18  christos 			logic_op = LOP_XOR;
   1147  1.18  christos 			continue;
   1148  1.18  christos 		}
   1149  1.18  christos 
   1150  1.18  christos 		/*
   1151  1.18  christos 		 * Do the logic operations.
   1152  1.18  christos 		 */
   1153  1.18  christos 		if (logic_invert)
   1154  1.18  christos 			for (i = 0; i < msgCount; i++)
   1155  1.18  christos 				tmparray[i] = ! tmparray[i];
   1156  1.18  christos 
   1157  1.18  christos 		switch (logic_op) {
   1158  1.18  christos 		case LOP_AND:
   1159  1.18  christos 			for (i = 0; i < msgCount; i++)
   1160  1.18  christos 				markarray[i] &= tmparray[i];
   1161  1.18  christos 			break;
   1162  1.18  christos 
   1163  1.18  christos 		case LOP_OR:
   1164  1.18  christos 			for (i = 0; i < msgCount; i++)
   1165  1.18  christos 				markarray[i] |= tmparray[i];
   1166  1.18  christos 			break;
   1167  1.18  christos 
   1168  1.18  christos 		case LOP_XOR:
   1169  1.18  christos 			for (i = 0; i < msgCount; i++)
   1170  1.18  christos 				markarray[i] ^= tmparray[i];
   1171  1.18  christos 			break;
   1172  1.18  christos 		}
   1173  1.18  christos 
   1174  1.18  christos 		/*
   1175  1.18  christos 		 * Clear the temporary array and reset the logic
   1176  1.18  christos 		 * operations.
   1177  1.18  christos 		 */
   1178  1.18  christos 		for (i = 0; i < msgCount; i++)
   1179  1.18  christos 			tmparray[i] = 0;
   1180  1.18  christos 
   1181  1.18  christos 		logic_op = LOP_OR;
   1182  1.18  christos 		logic_invert = 0;
   1183  1.18  christos 		got_not = 0;
   1184  1.18  christos 		got_bin = 0;
   1185  1.18  christos 	}
   1186  1.18  christos 
   1187  1.18  christos 	if (beg)
   1188  1.18  christos 		return syntax_error("end of range missing");
   1189  1.18  christos 
   1190  1.18  christos 	if (level)
   1191  1.18  christos 		return syntax_error("missing ')'");
   1192  1.18  christos 
   1193  1.18  christos  done:
   1194  1.18  christos 	if (got_not)
   1195  1.18  christos 		return syntax_error("trailing '!'");
   1196  1.18  christos 
   1197  1.18  christos 	if (got_bin)
   1198  1.18  christos 		return syntax_error("missing right operand");
   1199  1.21  christos 
   1200  1.18  christos 	if (colmod != 0) {
   1201  1.18  christos 		/*
   1202  1.18  christos 		 * If we have colon-modifiers but no messages
   1203  1.18  christos 		 * specifiec, then assume '*' was given.
   1204  1.18  christos 		 */
   1205  1.18  christos 		if (got_one == 0)
   1206  1.18  christos 			for (i = 1; i <= msgCount; i++)
   1207  1.18  christos 				markarray[i - 1] = 1;
   1208  1.18  christos 
   1209  1.18  christos 		for (i = 1; i <= msgCount; i++) {
   1210  1.18  christos 			struct message *mp;
   1211  1.18  christos 			if ((mp = get_message(i)) != NULL &&
   1212  1.18  christos 			    ignore_message(mp->m_flag, colmod))
   1213  1.18  christos 				markarray[i - 1] = 0;
   1214  1.18  christos 		}
   1215  1.18  christos 	}
   1216  1.18  christos 	return 0;
   1217  1.18  christos }
   1218  1.18  christos 
   1219  1.18  christos static int
   1220  1.18  christos markall(char buf[], int f)
   1221  1.18  christos {
   1222  1.18  christos 	int i;
   1223  1.18  christos 	int mc;
   1224  1.18  christos 	int *markarray;
   1225  1.18  christos 	int msgCount;
   1226  1.18  christos 	struct message *mp;
   1227  1.18  christos 
   1228  1.18  christos 	msgCount = get_msgCount();
   1229  1.18  christos 
   1230  1.18  christos 	/*
   1231  1.18  christos 	 * Clear all the previous message marks.
   1232  1.18  christos 	 */
   1233  1.18  christos 	for (i = 1; i <= msgCount; i++)
   1234  1.18  christos 		if ((mp = get_message(i)) != NULL)
   1235  1.18  christos 			mp->m_flag &= ~MMARK;
   1236  1.21  christos 
   1237  1.21  christos 	buf = skip_WSP(buf);
   1238  1.18  christos 	if (*buf == '\0')
   1239  1.18  christos 		return 0;
   1240  1.18  christos 
   1241  1.18  christos 	scaninit();
   1242  1.18  christos 	markarray = csalloc((size_t)msgCount, sizeof(*markarray));
   1243  1.18  christos 	if (markall_core(markarray, &buf, f, 0) == -1)
   1244  1.18  christos 		return -1;
   1245  1.21  christos 
   1246  1.18  christos 	/*
   1247  1.18  christos 	 * Transfer the markarray values to the messages.
   1248  1.18  christos 	 */
   1249  1.18  christos 	mc = 0;
   1250  1.18  christos 	for (i = 1; i <= msgCount; i++) {
   1251  1.18  christos 		if (markarray[i - 1] &&
   1252  1.18  christos 		    (mp = get_message(i)) != NULL &&
   1253  1.18  christos 		    (f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
   1254  1.18  christos 			mp->m_flag |= MMARK;
   1255  1.18  christos 			mc++;
   1256  1.18  christos 		}
   1257  1.18  christos 	}
   1258  1.18  christos 
   1259  1.18  christos 	if (mc == 0) {
   1260  1.18  christos 		(void)printf("No applicable messages.\n");
   1261  1.18  christos 		return -1;
   1262  1.18  christos 	}
   1263  1.18  christos 	return 0;
   1264  1.18  christos }
   1265  1.18  christos 
   1266  1.18  christos /*
   1267  1.18  christos  * Convert the user string of message numbers and
   1268  1.18  christos  * store the numbers into vector.
   1269  1.18  christos  *
   1270  1.18  christos  * Returns the count of messages picked up or -1 on error.
   1271  1.18  christos  */
   1272  1.18  christos PUBLIC int
   1273  1.18  christos getmsglist(char *buf, int *vector, int flags)
   1274  1.18  christos {
   1275  1.18  christos 	int *ip;
   1276  1.18  christos 	struct message *mp;
   1277  1.18  christos 
   1278  1.18  christos 	if (get_msgCount() == 0) {
   1279  1.18  christos 		*vector = 0;
   1280  1.18  christos 		return 0;
   1281   1.1       cgd 	}
   1282  1.18  christos 	if (markall(buf, flags) < 0)
   1283  1.18  christos 		return -1;
   1284  1.18  christos 	ip = vector;
   1285  1.18  christos 	for (mp = get_message(1); mp; mp = next_message(mp))
   1286  1.18  christos 		if (mp->m_flag & MMARK)
   1287  1.18  christos 			*ip++ = get_msgnum(mp);
   1288  1.18  christos 	*ip = 0;
   1289  1.25  christos 	return (int)(ip - vector);
   1290  1.18  christos }
   1291  1.18  christos 
   1292  1.18  christos /*
   1293  1.18  christos  * Find the first message whose flags & m == f  and return
   1294  1.18  christos  * its message number.
   1295  1.18  christos  */
   1296  1.18  christos PUBLIC int
   1297  1.18  christos first(int f, int m)
   1298  1.18  christos {
   1299  1.18  christos 	struct message *mp;
   1300  1.18  christos 
   1301  1.18  christos 	if (get_msgCount() == 0)
   1302  1.18  christos 		return 0;
   1303  1.18  christos 	f &= MDELETED;
   1304  1.18  christos 	m &= MDELETED;
   1305  1.18  christos 	for (mp = dot; mp; mp = next_message(mp))
   1306  1.18  christos 		if ((mp->m_flag & m) == f)
   1307  1.18  christos 			return get_msgnum(mp);
   1308  1.18  christos 	for (mp = prev_message(dot); mp; mp = prev_message(mp))
   1309  1.18  christos 		if ((mp->m_flag & m) == f)
   1310  1.18  christos 			return get_msgnum(mp);
   1311  1.18  christos 	return 0;
   1312  1.18  christos }
   1313  1.18  christos 
   1314  1.18  christos /*
   1315  1.18  christos  * Show all headers without paging.  (-H flag)
   1316  1.18  christos  */
   1317  1.23     perry __dead
   1318  1.18  christos PUBLIC int
   1319  1.18  christos show_headers_and_exit(int flags)
   1320  1.18  christos {
   1321  1.18  christos 	struct message *mp;
   1322  1.18  christos 
   1323  1.25  christos 	/* We are exiting anyway, so use the default signal handler. */
   1324  1.25  christos 	if (signal(SIGINT, SIG_DFL) == SIG_IGN)
   1325  1.25  christos 		(void)signal(SIGINT, SIG_IGN);
   1326  1.25  christos 
   1327  1.18  christos 	flags &= CMMASK;
   1328  1.18  christos 	for (mp = get_message(1); mp; mp = next_message(mp))
   1329  1.18  christos 		if (flags == 0 || !ignore_message(mp->m_flag, flags))
   1330  1.18  christos 			printhead(get_msgnum(mp));
   1331  1.18  christos 
   1332  1.18  christos 	exit(0);
   1333  1.18  christos 	/* NOTREACHED */
   1334  1.18  christos }
   1335  1.18  christos 
   1336  1.18  christos /*
   1337  1.18  christos  * A hack so -H can have an optional modifier as -H[:flags].
   1338  1.18  christos  *
   1339  1.18  christos  * This depends a bit on the internals of getopt().  In particular,
   1340  1.18  christos  * for flags expecting an argument, argv[optind-1] must contain the
   1341  1.18  christos  * optarg and optarg must point to a substring of argv[optind-1] not a
   1342  1.18  christos  * copy of it.
   1343  1.18  christos  */
   1344  1.18  christos PUBLIC int
   1345  1.18  christos get_Hflag(char **argv)
   1346  1.18  christos {
   1347  1.18  christos 	int flags;
   1348  1.18  christos 
   1349  1.18  christos 	flags = ~CMMASK;
   1350  1.18  christos 
   1351  1.18  christos 	if (optarg == NULL)  /* We had an error, just get the flags. */
   1352  1.18  christos 		return flags;
   1353  1.18  christos 
   1354  1.18  christos 	if (*optarg != ':' || optarg == argv[optind - 1]) {
   1355  1.18  christos 		optind--;
   1356  1.18  christos 		optreset = 1;
   1357  1.18  christos 		if (optarg != argv[optind]) {
   1358  1.18  christos 			static char temparg[LINESIZE];
   1359  1.18  christos 			size_t optlen;
   1360  1.18  christos 			size_t arglen;
   1361  1.18  christos 			char *p;
   1362  1.18  christos 
   1363  1.18  christos 			optlen = strlen(optarg);
   1364  1.18  christos 			arglen = strlen(argv[optind]);
   1365  1.18  christos 			p = argv[optind] + arglen - optlen;
   1366  1.18  christos 			optlen = MIN(optlen, sizeof(temparg) - 1);
   1367  1.18  christos 			temparg[0] = '-';
   1368  1.18  christos 			(void)memmove(temparg + 1, p, optlen + 1);
   1369  1.18  christos 			argv[optind] = temparg;
   1370  1.18  christos 		}
   1371  1.18  christos 	}
   1372  1.18  christos 	else {
   1373  1.18  christos 		flags = get_colmod(flags, optarg + 1);
   1374  1.18  christos 	}
   1375  1.18  christos 	return flags;
   1376   1.1       cgd }
   1377