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