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