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