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