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