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