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