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