list.c revision 1591
1/*
2 * Copyright (c) 1980, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char sccsid[] = "@(#)list.c	8.2 (Berkeley) 4/19/94";
36#endif /* not lint */
37
38#include "rcv.h"
39#include <ctype.h>
40#include "extern.h"
41
42/*
43 * Mail -- a mail program
44 *
45 * Message list handling.
46 */
47
48/*
49 * Convert the user string of message numbers and
50 * store the numbers into vector.
51 *
52 * Returns the count of messages picked up or -1 on error.
53 */
54int
55getmsglist(buf, vector, flags)
56	char *buf;
57	int *vector, flags;
58{
59	register int *ip;
60	register struct message *mp;
61
62	if (msgCount == 0) {
63		*vector = 0;
64		return 0;
65	}
66	if (markall(buf, flags) < 0)
67		return(-1);
68	ip = vector;
69	for (mp = &message[0]; mp < &message[msgCount]; mp++)
70		if (mp->m_flag & MMARK)
71			*ip++ = mp - &message[0] + 1;
72	*ip = 0;
73	return(ip - vector);
74}
75
76/*
77 * Mark all messages that the user wanted from the command
78 * line in the message structure.  Return 0 on success, -1
79 * on error.
80 */
81
82/*
83 * Bit values for colon modifiers.
84 */
85
86#define	CMNEW		01		/* New messages */
87#define	CMOLD		02		/* Old messages */
88#define	CMUNREAD	04		/* Unread messages */
89#define	CMDELETED	010		/* Deleted messages */
90#define	CMREAD		020		/* Read messages */
91
92/*
93 * The following table describes the letters which can follow
94 * the colon and gives the corresponding modifier bit.
95 */
96
97struct coltab {
98	char	co_char;		/* What to find past : */
99	int	co_bit;			/* Associated modifier bit */
100	int	co_mask;		/* m_status bits to mask */
101	int	co_equal;		/* ... must equal this */
102} coltab[] = {
103	'n',		CMNEW,		MNEW,		MNEW,
104	'o',		CMOLD,		MNEW,		0,
105	'u',		CMUNREAD,	MREAD,		0,
106	'd',		CMDELETED,	MDELETED,	MDELETED,
107	'r',		CMREAD,		MREAD,		MREAD,
108	0,		0,		0,		0
109};
110
111static	int	lastcolmod;
112
113int
114markall(buf, f)
115	char buf[];
116	int f;
117{
118	register char **np;
119	register int i;
120	register struct message *mp;
121	char *namelist[NMLSIZE], *bufp;
122	int tok, beg, mc, star, other, valdot, colmod, colresult;
123
124	valdot = dot - &message[0] + 1;
125	colmod = 0;
126	for (i = 1; i <= msgCount; i++)
127		unmark(i);
128	bufp = buf;
129	mc = 0;
130	np = &namelist[0];
131	scaninit();
132	tok = scan(&bufp);
133	star = 0;
134	other = 0;
135	beg = 0;
136	while (tok != TEOL) {
137		switch (tok) {
138		case TNUMBER:
139number:
140			if (star) {
141				printf("No numbers mixed with *\n");
142				return(-1);
143			}
144			mc++;
145			other++;
146			if (beg != 0) {
147				if (check(lexnumber, f))
148					return(-1);
149				for (i = beg; i <= lexnumber; i++)
150					if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
151						mark(i);
152				beg = 0;
153				break;
154			}
155			beg = lexnumber;
156			if (check(beg, f))
157				return(-1);
158			tok = scan(&bufp);
159			regret(tok);
160			if (tok != TDASH) {
161				mark(beg);
162				beg = 0;
163			}
164			break;
165
166		case TPLUS:
167			if (beg != 0) {
168				printf("Non-numeric second argument\n");
169				return(-1);
170			}
171			i = valdot;
172			do {
173				i++;
174				if (i > msgCount) {
175					printf("Referencing beyond EOF\n");
176					return(-1);
177				}
178			} while ((message[i - 1].m_flag & MDELETED) != f);
179			mark(i);
180			break;
181
182		case TDASH:
183			if (beg == 0) {
184				i = valdot;
185				do {
186					i--;
187					if (i <= 0) {
188						printf("Referencing before 1\n");
189						return(-1);
190					}
191				} while ((message[i - 1].m_flag & MDELETED) != f);
192				mark(i);
193			}
194			break;
195
196		case TSTRING:
197			if (beg != 0) {
198				printf("Non-numeric second argument\n");
199				return(-1);
200			}
201			other++;
202			if (lexstring[0] == ':') {
203				colresult = evalcol(lexstring[1]);
204				if (colresult == 0) {
205					printf("Unknown colon modifier \"%s\"\n",
206					    lexstring);
207					return(-1);
208				}
209				colmod |= colresult;
210			}
211			else
212				*np++ = savestr(lexstring);
213			break;
214
215		case TDOLLAR:
216		case TUP:
217		case TDOT:
218			lexnumber = metamess(lexstring[0], f);
219			if (lexnumber == -1)
220				return(-1);
221			goto number;
222
223		case TSTAR:
224			if (other) {
225				printf("Can't mix \"*\" with anything\n");
226				return(-1);
227			}
228			star++;
229			break;
230
231		case TERROR:
232			return -1;
233		}
234		tok = scan(&bufp);
235	}
236	lastcolmod = colmod;
237	*np = NOSTR;
238	mc = 0;
239	if (star) {
240		for (i = 0; i < msgCount; i++)
241			if ((message[i].m_flag & MDELETED) == f) {
242				mark(i+1);
243				mc++;
244			}
245		if (mc == 0) {
246			printf("No applicable messages.\n");
247			return(-1);
248		}
249		return(0);
250	}
251
252	/*
253	 * If no numbers were given, mark all of the messages,
254	 * so that we can unmark any whose sender was not selected
255	 * if any user names were given.
256	 */
257
258	if ((np > namelist || colmod != 0) && mc == 0)
259		for (i = 1; i <= msgCount; i++)
260			if ((message[i-1].m_flag & MDELETED) == f)
261				mark(i);
262
263	/*
264	 * If any names were given, go through and eliminate any
265	 * messages whose senders were not requested.
266	 */
267
268	if (np > namelist) {
269		for (i = 1; i <= msgCount; i++) {
270			for (mc = 0, np = &namelist[0]; *np != NOSTR; np++)
271				if (**np == '/') {
272					if (matchsubj(*np, i)) {
273						mc++;
274						break;
275					}
276				}
277				else {
278					if (matchsender(*np, i)) {
279						mc++;
280						break;
281					}
282				}
283			if (mc == 0)
284				unmark(i);
285		}
286
287		/*
288		 * Make sure we got some decent messages.
289		 */
290
291		mc = 0;
292		for (i = 1; i <= msgCount; i++)
293			if (message[i-1].m_flag & MMARK) {
294				mc++;
295				break;
296			}
297		if (mc == 0) {
298			printf("No applicable messages from {%s",
299				namelist[0]);
300			for (np = &namelist[1]; *np != NOSTR; np++)
301				printf(", %s", *np);
302			printf("}\n");
303			return(-1);
304		}
305	}
306
307	/*
308	 * If any colon modifiers were given, go through and
309	 * unmark any messages which do not satisfy the modifiers.
310	 */
311
312	if (colmod != 0) {
313		for (i = 1; i <= msgCount; i++) {
314			register struct coltab *colp;
315
316			mp = &message[i - 1];
317			for (colp = &coltab[0]; colp->co_char; colp++)
318				if (colp->co_bit & colmod)
319					if ((mp->m_flag & colp->co_mask)
320					    != colp->co_equal)
321						unmark(i);
322
323		}
324		for (mp = &message[0]; mp < &message[msgCount]; mp++)
325			if (mp->m_flag & MMARK)
326				break;
327		if (mp >= &message[msgCount]) {
328			register struct coltab *colp;
329
330			printf("No messages satisfy");
331			for (colp = &coltab[0]; colp->co_char; colp++)
332				if (colp->co_bit & colmod)
333					printf(" :%c", colp->co_char);
334			printf("\n");
335			return(-1);
336		}
337	}
338	return(0);
339}
340
341/*
342 * Turn the character after a colon modifier into a bit
343 * value.
344 */
345int
346evalcol(col)
347	int col;
348{
349	register struct coltab *colp;
350
351	if (col == 0)
352		return(lastcolmod);
353	for (colp = &coltab[0]; colp->co_char; colp++)
354		if (colp->co_char == col)
355			return(colp->co_bit);
356	return(0);
357}
358
359/*
360 * Check the passed message number for legality and proper flags.
361 * If f is MDELETED, then either kind will do.  Otherwise, the message
362 * has to be undeleted.
363 */
364int
365check(mesg, f)
366	int mesg, f;
367{
368	register struct message *mp;
369
370	if (mesg < 1 || mesg > msgCount) {
371		printf("%d: Invalid message number\n", mesg);
372		return(-1);
373	}
374	mp = &message[mesg-1];
375	if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
376		printf("%d: Inappropriate message\n", mesg);
377		return(-1);
378	}
379	return(0);
380}
381
382/*
383 * Scan out the list of string arguments, shell style
384 * for a RAWLIST.
385 */
386int
387getrawlist(line, argv, argc)
388	char line[];
389	char **argv;
390	int  argc;
391{
392	register char c, *cp, *cp2, quotec;
393	int argn;
394	char linebuf[BUFSIZ];
395
396	argn = 0;
397	cp = line;
398	for (;;) {
399		for (; *cp == ' ' || *cp == '\t'; cp++)
400			;
401		if (*cp == '\0')
402			break;
403		if (argn >= argc - 1) {
404			printf(
405			"Too many elements in the list; excess discarded.\n");
406			break;
407		}
408		cp2 = linebuf;
409		quotec = '\0';
410		while ((c = *cp) != '\0') {
411			cp++;
412			if (quotec != '\0') {
413				if (c == quotec)
414					quotec = '\0';
415				else if (c == '\\')
416					switch (c = *cp++) {
417					case '\0':
418						*cp2++ = '\\';
419						cp--;
420						break;
421					case '0': case '1': case '2': case '3':
422					case '4': case '5': case '6': case '7':
423						c -= '0';
424						if (*cp >= '0' && *cp <= '7')
425							c = c * 8 + *cp++ - '0';
426						if (*cp >= '0' && *cp <= '7')
427							c = c * 8 + *cp++ - '0';
428						*cp2++ = c;
429						break;
430					case 'b':
431						*cp2++ = '\b';
432						break;
433					case 'f':
434						*cp2++ = '\f';
435						break;
436					case 'n':
437						*cp2++ = '\n';
438						break;
439					case 'r':
440						*cp2++ = '\r';
441						break;
442					case 't':
443						*cp2++ = '\t';
444						break;
445					case 'v':
446						*cp2++ = '\v';
447						break;
448					default:
449						*cp2++ = c;
450					}
451				else if (c == '^') {
452					c = *cp++;
453					if (c == '?')
454						*cp2++ = '\177';
455					/* null doesn't show up anyway */
456					else if (c >= 'A' && c <= '_' ||
457						 c >= 'a' && c <= 'z')
458						*cp2++ = c & 037;
459					else {
460						*cp2++ = '^';
461						cp--;
462					}
463				} else
464					*cp2++ = c;
465			} else if (c == '"' || c == '\'')
466				quotec = c;
467			else if (c == ' ' || c == '\t')
468				break;
469			else
470				*cp2++ = c;
471		}
472		*cp2 = '\0';
473		argv[argn++] = savestr(linebuf);
474	}
475	argv[argn] = NOSTR;
476	return argn;
477}
478
479/*
480 * scan out a single lexical item and return its token number,
481 * updating the string pointer passed **p.  Also, store the value
482 * of the number or string scanned in lexnumber or lexstring as
483 * appropriate.  In any event, store the scanned `thing' in lexstring.
484 */
485
486struct lex {
487	char	l_char;
488	char	l_token;
489} singles[] = {
490	'$',	TDOLLAR,
491	'.',	TDOT,
492	'^',	TUP,
493	'*',	TSTAR,
494	'-',	TDASH,
495	'+',	TPLUS,
496	'(',	TOPEN,
497	')',	TCLOSE,
498	0,	0
499};
500
501int
502scan(sp)
503	char **sp;
504{
505	register char *cp, *cp2;
506	register int c;
507	register struct lex *lp;
508	int quotec;
509
510	if (regretp >= 0) {
511		strcpy(lexstring, string_stack[regretp]);
512		lexnumber = numberstack[regretp];
513		return(regretstack[regretp--]);
514	}
515	cp = *sp;
516	cp2 = lexstring;
517	c = *cp++;
518
519	/*
520	 * strip away leading white space.
521	 */
522
523	while (c == ' ' || c == '\t')
524		c = *cp++;
525
526	/*
527	 * If no characters remain, we are at end of line,
528	 * so report that.
529	 */
530
531	if (c == '\0') {
532		*sp = --cp;
533		return(TEOL);
534	}
535
536	/*
537	 * If the leading character is a digit, scan
538	 * the number and convert it on the fly.
539	 * Return TNUMBER when done.
540	 */
541
542	if (isdigit(c)) {
543		lexnumber = 0;
544		while (isdigit(c)) {
545			lexnumber = lexnumber*10 + c - '0';
546			*cp2++ = c;
547			c = *cp++;
548		}
549		*cp2 = '\0';
550		*sp = --cp;
551		return(TNUMBER);
552	}
553
554	/*
555	 * Check for single character tokens; return such
556	 * if found.
557	 */
558
559	for (lp = &singles[0]; lp->l_char != 0; lp++)
560		if (c == lp->l_char) {
561			lexstring[0] = c;
562			lexstring[1] = '\0';
563			*sp = cp;
564			return(lp->l_token);
565		}
566
567	/*
568	 * We've got a string!  Copy all the characters
569	 * of the string into lexstring, until we see
570	 * a null, space, or tab.
571	 * If the lead character is a " or ', save it
572	 * and scan until you get another.
573	 */
574
575	quotec = 0;
576	if (c == '\'' || c == '"') {
577		quotec = c;
578		c = *cp++;
579	}
580	while (c != '\0') {
581		if (c == quotec) {
582			cp++;
583			break;
584		}
585		if (quotec == 0 && (c == ' ' || c == '\t'))
586			break;
587		if (cp2 - lexstring < STRINGLEN-1)
588			*cp2++ = c;
589		c = *cp++;
590	}
591	if (quotec && c == 0) {
592		fprintf(stderr, "Missing %c\n", quotec);
593		return TERROR;
594	}
595	*sp = --cp;
596	*cp2 = '\0';
597	return(TSTRING);
598}
599
600/*
601 * Unscan the named token by pushing it onto the regret stack.
602 */
603void
604regret(token)
605	int token;
606{
607	if (++regretp >= REGDEP)
608		panic("Too many regrets");
609	regretstack[regretp] = token;
610	lexstring[STRINGLEN-1] = '\0';
611	string_stack[regretp] = savestr(lexstring);
612	numberstack[regretp] = lexnumber;
613}
614
615/*
616 * Reset all the scanner global variables.
617 */
618void
619scaninit()
620{
621	regretp = -1;
622}
623
624/*
625 * Find the first message whose flags & m == f  and return
626 * its message number.
627 */
628int
629first(f, m)
630	int f, m;
631{
632	register struct message *mp;
633
634	if (msgCount == 0)
635		return 0;
636	f &= MDELETED;
637	m &= MDELETED;
638	for (mp = dot; mp < &message[msgCount]; mp++)
639		if ((mp->m_flag & m) == f)
640			return mp - message + 1;
641	for (mp = dot-1; mp >= &message[0]; mp--)
642		if ((mp->m_flag & m) == f)
643			return mp - message + 1;
644	return 0;
645}
646
647/*
648 * See if the passed name sent the passed message number.  Return true
649 * if so.
650 */
651int
652matchsender(str, mesg)
653	char *str;
654	int mesg;
655{
656	register char *cp, *cp2, *backup;
657
658	if (!*str)	/* null string matches nothing instead of everything */
659		return 0;
660	backup = cp2 = nameof(&message[mesg - 1], 0);
661	cp = str;
662	while (*cp2) {
663		if (*cp == 0)
664			return(1);
665		if (raise(*cp++) != raise(*cp2++)) {
666			cp2 = ++backup;
667			cp = str;
668		}
669	}
670	return(*cp == 0);
671}
672
673/*
674 * See if the given string matches inside the subject field of the
675 * given message.  For the purpose of the scan, we ignore case differences.
676 * If it does, return true.  The string search argument is assumed to
677 * have the form "/search-string."  If it is of the form "/," we use the
678 * previous search string.
679 */
680
681char lastscan[128];
682int
683matchsubj(str, mesg)
684	char *str;
685	int mesg;
686{
687	register struct message *mp;
688	register char *cp, *cp2, *backup;
689
690	str++;
691	if (strlen(str) == 0)
692		str = lastscan;
693	else
694		strcpy(lastscan, str);
695	mp = &message[mesg-1];
696
697	/*
698	 * Now look, ignoring case, for the word in the string.
699	 */
700
701	if (value("searchheaders") && (cp = index(str, ':'))) {
702		*cp++ = '\0';
703		cp2 = hfield(str, mp);
704		cp[-1] = ':';
705		str = cp;
706	} else {
707		cp = str;
708		cp2 = hfield("subject", mp);
709	}
710	if (cp2 == NOSTR)
711		return(0);
712	backup = cp2;
713	while (*cp2) {
714		if (*cp == 0)
715			return(1);
716		if (raise(*cp++) != raise(*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 */
727void
728mark(mesg)
729	int mesg;
730{
731	register 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 */
742void
743unmark(mesg)
744	int mesg;
745{
746	register 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 */
757int
758metamess(meta, f)
759	int meta, 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("No applicable messages\n");
774		return(-1);
775
776	case '$':
777		/*
778		 * Last 'good message left.
779		 */
780		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
781			if ((mp->m_flag & MDELETED) == f)
782				return(mp - &message[0] + 1);
783		printf("No applicable messages\n");
784		return(-1);
785
786	case '.':
787		/*
788		 * Current message.
789		 */
790		m = dot - &message[0] + 1;
791		if ((dot->m_flag & MDELETED) != f) {
792			printf("%d: Inappropriate message\n", m);
793			return(-1);
794		}
795		return(m);
796
797	default:
798		printf("Unknown metachar (%c)\n", c);
799		return(-1);
800	}
801}
802