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