Home | History | Annotate | Line # | Download | only in mail
list.c revision 1.1
      1 /*
      2  * Copyright (c) 1980 Regents of the University of California.
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  * 3. All advertising materials mentioning features or use of this software
     14  *    must display the following acknowledgement:
     15  *	This product includes software developed by the University of
     16  *	California, Berkeley and its contributors.
     17  * 4. Neither the name of the University nor the names of its contributors
     18  *    may be used to endorse or promote products derived from this software
     19  *    without specific prior written permission.
     20  *
     21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     31  * SUCH DAMAGE.
     32  */
     33 
     34 #ifndef lint
     35 static char sccsid[] = "@(#)list.c	5.14 (Berkeley) 6/1/90";
     36 #endif /* not lint */
     37 
     38 #include "rcv.h"
     39 #include <ctype.h>
     40 
     41 /*
     42  * Mail -- a mail program
     43  *
     44  * Message list handling.
     45  */
     46 
     47 /*
     48  * Convert the user string of message numbers and
     49  * store the numbers into vector.
     50  *
     51  * Returns the count of messages picked up or -1 on error.
     52  */
     53 
     54 getmsglist(buf, vector, flags)
     55 	char *buf;
     56 	int *vector;
     57 {
     58 	register int *ip;
     59 	register struct message *mp;
     60 
     61 	if (msgCount == 0) {
     62 		*vector = 0;
     63 		return 0;
     64 	}
     65 	if (markall(buf, flags) < 0)
     66 		return(-1);
     67 	ip = vector;
     68 	for (mp = &message[0]; mp < &message[msgCount]; mp++)
     69 		if (mp->m_flag & MMARK)
     70 			*ip++ = mp - &message[0] + 1;
     71 	*ip = 0;
     72 	return(ip - vector);
     73 }
     74 
     75 /*
     76  * Mark all messages that the user wanted from the command
     77  * line in the message structure.  Return 0 on success, -1
     78  * on error.
     79  */
     80 
     81 /*
     82  * Bit values for colon modifiers.
     83  */
     84 
     85 #define	CMNEW		01		/* New messages */
     86 #define	CMOLD		02		/* Old messages */
     87 #define	CMUNREAD	04		/* Unread messages */
     88 #define	CMDELETED	010		/* Deleted messages */
     89 #define	CMREAD		020		/* Read messages */
     90 
     91 /*
     92  * The following table describes the letters which can follow
     93  * the colon and gives the corresponding modifier bit.
     94  */
     95 
     96 struct coltab {
     97 	char	co_char;		/* What to find past : */
     98 	int	co_bit;			/* Associated modifier bit */
     99 	int	co_mask;		/* m_status bits to mask */
    100 	int	co_equal;		/* ... must equal this */
    101 } coltab[] = {
    102 	'n',		CMNEW,		MNEW,		MNEW,
    103 	'o',		CMOLD,		MNEW,		0,
    104 	'u',		CMUNREAD,	MREAD,		0,
    105 	'd',		CMDELETED,	MDELETED,	MDELETED,
    106 	'r',		CMREAD,		MREAD,		MREAD,
    107 	0,		0,		0,		0
    108 };
    109 
    110 static	int	lastcolmod;
    111 
    112 markall(buf, f)
    113 	char buf[];
    114 {
    115 	register char **np;
    116 	register int i;
    117 	register struct message *mp;
    118 	char *namelist[NMLSIZE], *bufp;
    119 	int tok, beg, mc, star, other, valdot, colmod, colresult;
    120 
    121 	valdot = dot - &message[0] + 1;
    122 	colmod = 0;
    123 	for (i = 1; i <= msgCount; i++)
    124 		unmark(i);
    125 	bufp = buf;
    126 	mc = 0;
    127 	np = &namelist[0];
    128 	scaninit();
    129 	tok = scan(&bufp);
    130 	star = 0;
    131 	other = 0;
    132 	beg = 0;
    133 	while (tok != TEOL) {
    134 		switch (tok) {
    135 		case TNUMBER:
    136 number:
    137 			if (star) {
    138 				printf("No numbers mixed with *\n");
    139 				return(-1);
    140 			}
    141 			mc++;
    142 			other++;
    143 			if (beg != 0) {
    144 				if (check(lexnumber, f))
    145 					return(-1);
    146 				for (i = beg; i <= lexnumber; i++)
    147 					if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
    148 						mark(i);
    149 				beg = 0;
    150 				break;
    151 			}
    152 			beg = lexnumber;
    153 			if (check(beg, f))
    154 				return(-1);
    155 			tok = scan(&bufp);
    156 			regret(tok);
    157 			if (tok != TDASH) {
    158 				mark(beg);
    159 				beg = 0;
    160 			}
    161 			break;
    162 
    163 		case TPLUS:
    164 			if (beg != 0) {
    165 				printf("Non-numeric second argument\n");
    166 				return(-1);
    167 			}
    168 			i = valdot;
    169 			do {
    170 				i++;
    171 				if (i > msgCount) {
    172 					printf("Referencing beyond EOF\n");
    173 					return(-1);
    174 				}
    175 			} while ((message[i - 1].m_flag & MDELETED) != f);
    176 			mark(i);
    177 			break;
    178 
    179 		case TDASH:
    180 			if (beg == 0) {
    181 				i = valdot;
    182 				do {
    183 					i--;
    184 					if (i <= 0) {
    185 						printf("Referencing before 1\n");
    186 						return(-1);
    187 					}
    188 				} while ((message[i - 1].m_flag & MDELETED) != f);
    189 				mark(i);
    190 			}
    191 			break;
    192 
    193 		case TSTRING:
    194 			if (beg != 0) {
    195 				printf("Non-numeric second argument\n");
    196 				return(-1);
    197 			}
    198 			other++;
    199 			if (lexstring[0] == ':') {
    200 				colresult = evalcol(lexstring[1]);
    201 				if (colresult == 0) {
    202 					printf("Unknown colon modifier \"%s\"\n",
    203 					    lexstring);
    204 					return(-1);
    205 				}
    206 				colmod |= colresult;
    207 			}
    208 			else
    209 				*np++ = savestr(lexstring);
    210 			break;
    211 
    212 		case TDOLLAR:
    213 		case TUP:
    214 		case TDOT:
    215 			lexnumber = metamess(lexstring[0], f);
    216 			if (lexnumber == -1)
    217 				return(-1);
    218 			goto number;
    219 
    220 		case TSTAR:
    221 			if (other) {
    222 				printf("Can't mix \"*\" with anything\n");
    223 				return(-1);
    224 			}
    225 			star++;
    226 			break;
    227 
    228 		case TERROR:
    229 			return -1;
    230 		}
    231 		tok = scan(&bufp);
    232 	}
    233 	lastcolmod = colmod;
    234 	*np = NOSTR;
    235 	mc = 0;
    236 	if (star) {
    237 		for (i = 0; i < msgCount; i++)
    238 			if ((message[i].m_flag & MDELETED) == f) {
    239 				mark(i+1);
    240 				mc++;
    241 			}
    242 		if (mc == 0) {
    243 			printf("No applicable messages.\n");
    244 			return(-1);
    245 		}
    246 		return(0);
    247 	}
    248 
    249 	/*
    250 	 * If no numbers were given, mark all of the messages,
    251 	 * so that we can unmark any whose sender was not selected
    252 	 * if any user names were given.
    253 	 */
    254 
    255 	if ((np > namelist || colmod != 0) && mc == 0)
    256 		for (i = 1; i <= msgCount; i++)
    257 			if ((message[i-1].m_flag & MDELETED) == f)
    258 				mark(i);
    259 
    260 	/*
    261 	 * If any names were given, go through and eliminate any
    262 	 * messages whose senders were not requested.
    263 	 */
    264 
    265 	if (np > namelist) {
    266 		for (i = 1; i <= msgCount; i++) {
    267 			for (mc = 0, np = &namelist[0]; *np != NOSTR; np++)
    268 				if (**np == '/') {
    269 					if (matchsubj(*np, i)) {
    270 						mc++;
    271 						break;
    272 					}
    273 				}
    274 				else {
    275 					if (matchsender(*np, i)) {
    276 						mc++;
    277 						break;
    278 					}
    279 				}
    280 			if (mc == 0)
    281 				unmark(i);
    282 		}
    283 
    284 		/*
    285 		 * Make sure we got some decent messages.
    286 		 */
    287 
    288 		mc = 0;
    289 		for (i = 1; i <= msgCount; i++)
    290 			if (message[i-1].m_flag & MMARK) {
    291 				mc++;
    292 				break;
    293 			}
    294 		if (mc == 0) {
    295 			printf("No applicable messages from {%s",
    296 				namelist[0]);
    297 			for (np = &namelist[1]; *np != NOSTR; np++)
    298 				printf(", %s", *np);
    299 			printf("}\n");
    300 			return(-1);
    301 		}
    302 	}
    303 
    304 	/*
    305 	 * If any colon modifiers were given, go through and
    306 	 * unmark any messages which do not satisfy the modifiers.
    307 	 */
    308 
    309 	if (colmod != 0) {
    310 		for (i = 1; i <= msgCount; i++) {
    311 			register struct coltab *colp;
    312 
    313 			mp = &message[i - 1];
    314 			for (colp = &coltab[0]; colp->co_char; colp++)
    315 				if (colp->co_bit & colmod)
    316 					if ((mp->m_flag & colp->co_mask)
    317 					    != colp->co_equal)
    318 						unmark(i);
    319 
    320 		}
    321 		for (mp = &message[0]; mp < &message[msgCount]; mp++)
    322 			if (mp->m_flag & MMARK)
    323 				break;
    324 		if (mp >= &message[msgCount]) {
    325 			register struct coltab *colp;
    326 
    327 			printf("No messages satisfy");
    328 			for (colp = &coltab[0]; colp->co_char; colp++)
    329 				if (colp->co_bit & colmod)
    330 					printf(" :%c", colp->co_char);
    331 			printf("\n");
    332 			return(-1);
    333 		}
    334 	}
    335 	return(0);
    336 }
    337 
    338 /*
    339  * Turn the character after a colon modifier into a bit
    340  * value.
    341  */
    342 evalcol(col)
    343 {
    344 	register struct coltab *colp;
    345 
    346 	if (col == 0)
    347 		return(lastcolmod);
    348 	for (colp = &coltab[0]; colp->co_char; colp++)
    349 		if (colp->co_char == col)
    350 			return(colp->co_bit);
    351 	return(0);
    352 }
    353 
    354 /*
    355  * Check the passed message number for legality and proper flags.
    356  * If f is MDELETED, then either kind will do.  Otherwise, the message
    357  * has to be undeleted.
    358  */
    359 check(mesg, f)
    360 {
    361 	register struct message *mp;
    362 
    363 	if (mesg < 1 || mesg > msgCount) {
    364 		printf("%d: Invalid message number\n", mesg);
    365 		return(-1);
    366 	}
    367 	mp = &message[mesg-1];
    368 	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
    369 		printf("%d: Inappropriate message\n", mesg);
    370 		return(-1);
    371 	}
    372 	return(0);
    373 }
    374 
    375 /*
    376  * Scan out the list of string arguments, shell style
    377  * for a RAWLIST.
    378  */
    379 
    380 getrawlist(line, argv, argc)
    381 	char line[];
    382 	char **argv;
    383 	int  argc;
    384 {
    385 	register char c, *cp, *cp2, quotec;
    386 	int argn;
    387 	char linebuf[BUFSIZ];
    388 
    389 	argn = 0;
    390 	cp = line;
    391 	for (;;) {
    392 		for (; *cp == ' ' || *cp == '\t'; cp++)
    393 			;
    394 		if (*cp == '\0')
    395 			break;
    396 		if (argn >= argc - 1) {
    397 			printf(
    398 			"Too many elements in the list; excess discarded.\n");
    399 			break;
    400 		}
    401 		cp2 = linebuf;
    402 		quotec = '\0';
    403 		while ((c = *cp) != '\0') {
    404 			cp++;
    405 			if (quotec != '\0') {
    406 				if (c == quotec)
    407 					quotec = '\0';
    408 				else if (c == '\\')
    409 					switch (c = *cp++) {
    410 					case '\0':
    411 						*cp2++ = *--cp;
    412 						break;
    413 					case '0': case '1': case '2': case '3':
    414 					case '4': case '5': case '6': case '7':
    415 						c -= '0';
    416 						if (*cp >= '0' && *cp <= '7')
    417 							c = c * 8 + *cp++ - '0';
    418 						if (*cp >= '0' && *cp <= '7')
    419 							c = c * 8 + *cp++ - '0';
    420 						*cp2++ = c;
    421 						break;
    422 					case 'b':
    423 						*cp2++ = '\b';
    424 						break;
    425 					case 'f':
    426 						*cp2++ = '\f';
    427 						break;
    428 					case 'n':
    429 						*cp2++ = '\n';
    430 						break;
    431 					case 'r':
    432 						*cp2++ = '\r';
    433 						break;
    434 					case 't':
    435 						*cp2++ = '\t';
    436 						break;
    437 					case 'v':
    438 						*cp2++ = '\v';
    439 						break;
    440 					}
    441 				else if (c == '^') {
    442 					c = *cp++;
    443 					if (c == '?')
    444 						*cp2++ = '\177';
    445 					/* null doesn't show up anyway */
    446 					else if (c >= 'A' && c <= '_' ||
    447 						 c >= 'a' && c <= 'z')
    448 						*cp2++ &= 037;
    449 					else
    450 						*cp2++ = *--cp;
    451 				} else
    452 					*cp2++ = c;
    453 			} else if (c == '"' || c == '\'')
    454 				quotec = c;
    455 			else if (c == ' ' || c == '\t')
    456 				break;
    457 			else
    458 				*cp2++ = c;
    459 		}
    460 		*cp2 = '\0';
    461 		argv[argn++] = savestr(linebuf);
    462 	}
    463 	argv[argn] = NOSTR;
    464 	return argn;
    465 }
    466 
    467 /*
    468  * scan out a single lexical item and return its token number,
    469  * updating the string pointer passed **p.  Also, store the value
    470  * of the number or string scanned in lexnumber or lexstring as
    471  * appropriate.  In any event, store the scanned `thing' in lexstring.
    472  */
    473 
    474 struct lex {
    475 	char	l_char;
    476 	char	l_token;
    477 } singles[] = {
    478 	'$',	TDOLLAR,
    479 	'.',	TDOT,
    480 	'^',	TUP,
    481 	'*',	TSTAR,
    482 	'-',	TDASH,
    483 	'+',	TPLUS,
    484 	'(',	TOPEN,
    485 	')',	TCLOSE,
    486 	0,	0
    487 };
    488 
    489 scan(sp)
    490 	char **sp;
    491 {
    492 	register char *cp, *cp2;
    493 	register int c;
    494 	register struct lex *lp;
    495 	int quotec;
    496 
    497 	if (regretp >= 0) {
    498 		strcpy(lexstring, string_stack[regretp]);
    499 		lexnumber = numberstack[regretp];
    500 		return(regretstack[regretp--]);
    501 	}
    502 	cp = *sp;
    503 	cp2 = lexstring;
    504 	c = *cp++;
    505 
    506 	/*
    507 	 * strip away leading white space.
    508 	 */
    509 
    510 	while (c == ' ' || c == '\t')
    511 		c = *cp++;
    512 
    513 	/*
    514 	 * If no characters remain, we are at end of line,
    515 	 * so report that.
    516 	 */
    517 
    518 	if (c == '\0') {
    519 		*sp = --cp;
    520 		return(TEOL);
    521 	}
    522 
    523 	/*
    524 	 * If the leading character is a digit, scan
    525 	 * the number and convert it on the fly.
    526 	 * Return TNUMBER when done.
    527 	 */
    528 
    529 	if (isdigit(c)) {
    530 		lexnumber = 0;
    531 		while (isdigit(c)) {
    532 			lexnumber = lexnumber*10 + c - '0';
    533 			*cp2++ = c;
    534 			c = *cp++;
    535 		}
    536 		*cp2 = '\0';
    537 		*sp = --cp;
    538 		return(TNUMBER);
    539 	}
    540 
    541 	/*
    542 	 * Check for single character tokens; return such
    543 	 * if found.
    544 	 */
    545 
    546 	for (lp = &singles[0]; lp->l_char != 0; lp++)
    547 		if (c == lp->l_char) {
    548 			lexstring[0] = c;
    549 			lexstring[1] = '\0';
    550 			*sp = cp;
    551 			return(lp->l_token);
    552 		}
    553 
    554 	/*
    555 	 * We've got a string!  Copy all the characters
    556 	 * of the string into lexstring, until we see
    557 	 * a null, space, or tab.
    558 	 * If the lead character is a " or ', save it
    559 	 * and scan until you get another.
    560 	 */
    561 
    562 	quotec = 0;
    563 	if (c == '\'' || c == '"') {
    564 		quotec = c;
    565 		c = *cp++;
    566 	}
    567 	while (c != '\0') {
    568 		if (c == quotec) {
    569 			cp++;
    570 			break;
    571 		}
    572 		if (quotec == 0 && (c == ' ' || c == '\t'))
    573 			break;
    574 		if (cp2 - lexstring < STRINGLEN-1)
    575 			*cp2++ = c;
    576 		c = *cp++;
    577 	}
    578 	if (quotec && c == 0) {
    579 		fprintf(stderr, "Missing %c\n", quotec);
    580 		return TERROR;
    581 	}
    582 	*sp = --cp;
    583 	*cp2 = '\0';
    584 	return(TSTRING);
    585 }
    586 
    587 /*
    588  * Unscan the named token by pushing it onto the regret stack.
    589  */
    590 
    591 regret(token)
    592 {
    593 	if (++regretp >= REGDEP)
    594 		panic("Too many regrets");
    595 	regretstack[regretp] = token;
    596 	lexstring[STRINGLEN-1] = '\0';
    597 	string_stack[regretp] = savestr(lexstring);
    598 	numberstack[regretp] = lexnumber;
    599 }
    600 
    601 /*
    602  * Reset all the scanner global variables.
    603  */
    604 
    605 scaninit()
    606 {
    607 	regretp = -1;
    608 }
    609 
    610 /*
    611  * Find the first message whose flags & m == f  and return
    612  * its message number.
    613  */
    614 
    615 first(f, m)
    616 {
    617 	register struct message *mp;
    618 
    619 	if (msgCount == 0)
    620 		return 0;
    621 	f &= MDELETED;
    622 	m &= MDELETED;
    623 	for (mp = dot; mp < &message[msgCount]; mp++)
    624 		if ((mp->m_flag & m) == f)
    625 			return mp - message + 1;
    626 	for (mp = dot-1; mp >= &message[0]; mp--)
    627 		if ((mp->m_flag & m) == f)
    628 			return mp - message + 1;
    629 	return 0;
    630 }
    631 
    632 /*
    633  * See if the passed name sent the passed message number.  Return true
    634  * if so.
    635  */
    636 
    637 matchsender(str, mesg)
    638 	char *str;
    639 {
    640 	register char *cp, *cp2, *backup;
    641 
    642 	if (!*str)	/* null string matches nothing instead of everything */
    643 		return 0;
    644 	backup = cp2 = nameof(&message[mesg - 1], 0);
    645 	cp = str;
    646 	while (*cp2) {
    647 		if (*cp == 0)
    648 			return(1);
    649 		if (raise(*cp++) != raise(*cp2++)) {
    650 			cp2 = ++backup;
    651 			cp = str;
    652 		}
    653 	}
    654 	return(*cp == 0);
    655 }
    656 
    657 /*
    658  * See if the given string matches inside the subject field of the
    659  * given message.  For the purpose of the scan, we ignore case differences.
    660  * If it does, return true.  The string search argument is assumed to
    661  * have the form "/search-string."  If it is of the form "/," we use the
    662  * previous search string.
    663  */
    664 
    665 char lastscan[128];
    666 
    667 matchsubj(str, mesg)
    668 	char *str;
    669 {
    670 	register struct message *mp;
    671 	register char *cp, *cp2, *backup;
    672 
    673 	str++;
    674 	if (strlen(str) == 0)
    675 		str = lastscan;
    676 	else
    677 		strcpy(lastscan, str);
    678 	mp = &message[mesg-1];
    679 
    680 	/*
    681 	 * Now look, ignoring case, for the word in the string.
    682 	 */
    683 
    684 	cp = str;
    685 	cp2 = hfield("subject", mp);
    686 	if (cp2 == NOSTR)
    687 		return(0);
    688 	backup = cp2;
    689 	while (*cp2) {
    690 		if (*cp == 0)
    691 			return(1);
    692 		if (raise(*cp++) != raise(*cp2++)) {
    693 			cp2 = ++backup;
    694 			cp = str;
    695 		}
    696 	}
    697 	return(*cp == 0);
    698 }
    699 
    700 /*
    701  * Mark the named message by setting its mark bit.
    702  */
    703 
    704 mark(mesg)
    705 {
    706 	register int i;
    707 
    708 	i = mesg;
    709 	if (i < 1 || i > msgCount)
    710 		panic("Bad message number to mark");
    711 	message[i-1].m_flag |= MMARK;
    712 }
    713 
    714 /*
    715  * Unmark the named message.
    716  */
    717 
    718 unmark(mesg)
    719 {
    720 	register int i;
    721 
    722 	i = mesg;
    723 	if (i < 1 || i > msgCount)
    724 		panic("Bad message number to unmark");
    725 	message[i-1].m_flag &= ~MMARK;
    726 }
    727 
    728 /*
    729  * Return the message number corresponding to the passed meta character.
    730  */
    731 
    732 metamess(meta, f)
    733 {
    734 	register int c, m;
    735 	register struct message *mp;
    736 
    737 	c = meta;
    738 	switch (c) {
    739 	case '^':
    740 		/*
    741 		 * First 'good' message left.
    742 		 */
    743 		for (mp = &message[0]; mp < &message[msgCount]; mp++)
    744 			if ((mp->m_flag & MDELETED) == f)
    745 				return(mp - &message[0] + 1);
    746 		printf("No applicable messages\n");
    747 		return(-1);
    748 
    749 	case '$':
    750 		/*
    751 		 * Last 'good message left.
    752 		 */
    753 		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
    754 			if ((mp->m_flag & MDELETED) == f)
    755 				return(mp - &message[0] + 1);
    756 		printf("No applicable messages\n");
    757 		return(-1);
    758 
    759 	case '.':
    760 		/*
    761 		 * Current message.
    762 		 */
    763 		m = dot - &message[0] + 1;
    764 		if ((dot->m_flag & MDELETED) != f) {
    765 			printf("%d: Inappropriate message\n", m);
    766 			return(-1);
    767 		}
    768 		return(m);
    769 
    770 	default:
    771 		printf("Unknown metachar (%c)\n", c);
    772 		return(-1);
    773 	}
    774 }
    775