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