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