list.c revision 1.18 1 /* $NetBSD: list.c,v 1.18 2006/11/28 18:45: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.18 2006/11/28 18:45:32 christos Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include <assert.h>
42 #include <regex.h>
43 #include <util.h>
44
45 #include "rcv.h"
46 #include "extern.h"
47 #include "format.h"
48 #include "thread.h"
49
50 /*
51 * Mail -- a mail program
52 *
53 * Message list handling.
54 */
55
56 /*
57 * Token values returned by the scanner used for argument lists.
58 * Also, sizes of scanner-related things.
59 */
60 enum token_e {
61 TEOL, /* End of the command line */
62 TNUMBER, /* A message number or range of numbers */
63 TDASH, /* A simple dash */
64 TSTRING, /* A string (possibly containing '-') */
65 TDOT, /* A "." */
66 TUP, /* An "^" */
67 TDOLLAR, /* A "$" */
68 TSTAR, /* A "*" */
69 TOPEN, /* An '(' */
70 TCLOSE, /* A ')' */
71 TPLUS, /* A '+' */
72 TAND, /* A '&' */
73 TOR, /* A '|' */
74 TXOR, /* A logical '^' */
75 TNOT, /* A '!' */
76 TERROR /* A lexical error */
77 };
78
79 #define REGDEP 2 /* Maximum regret depth. */
80 #define STRINGLEN 1024 /* Maximum length of string token */
81
82 static int lexnumber; /* Number of TNUMBER from scan() */
83 static char lexstring[STRINGLEN]; /* String from TSTRING, scan() */
84 static int regretp; /* Pointer to TOS of regret tokens */
85 static int regretstack[REGDEP]; /* Stack of regretted tokens */
86 static char *string_stack[REGDEP]; /* Stack of regretted strings */
87 static int numberstack[REGDEP]; /* Stack of regretted numbers */
88
89 /*
90 * Scan out the list of string arguments, shell style
91 * for a RAWLIST.
92 */
93 PUBLIC int
94 getrawlist(const char line[], char **argv, int argc)
95 {
96 char c, *cp2, quotec;
97 const char *cp;
98 int argn;
99 char linebuf[LINESIZE];
100
101 argn = 0;
102 cp = line;
103 for (;;) {
104 for (; *cp == ' ' || *cp == '\t'; cp++)
105 continue;
106 if (*cp == '\0')
107 break;
108 if (argn >= argc - 1) {
109 (void)printf(
110 "Too many elements in the list; excess discarded.\n");
111 break;
112 }
113 cp2 = linebuf;
114 quotec = '\0';
115 while ((c = *cp) != '\0') {
116 cp++;
117 if (quotec != '\0') {
118 if (c == quotec)
119 quotec = '\0';
120 else if (c == '\\')
121 switch (c = *cp++) {
122 case '\0':
123 *cp2++ = '\\';
124 cp--;
125 break;
126 case '0': case '1': case '2': case '3':
127 case '4': case '5': case '6': case '7':
128 c -= '0';
129 if (*cp >= '0' && *cp <= '7')
130 c = c * 8 + *cp++ - '0';
131 if (*cp >= '0' && *cp <= '7')
132 c = c * 8 + *cp++ - '0';
133 *cp2++ = c;
134 break;
135 case 'b':
136 *cp2++ = '\b';
137 break;
138 case 'f':
139 *cp2++ = '\f';
140 break;
141 case 'n':
142 *cp2++ = '\n';
143 break;
144 case 'r':
145 *cp2++ = '\r';
146 break;
147 case 't':
148 *cp2++ = '\t';
149 break;
150 case 'v':
151 *cp2++ = '\v';
152 break;
153 default:
154 *cp2++ = c;
155 }
156 else if (c == '^') {
157 c = *cp++;
158 if (c == '?')
159 *cp2++ = '\177';
160 /* null doesn't show up anyway */
161 else if ((c >= 'A' && c <= '_') ||
162 (c >= 'a' && c <= 'z'))
163 *cp2++ = c & 037;
164 else {
165 *cp2++ = '^';
166 cp--;
167 }
168 } else
169 *cp2++ = c;
170 } else if (c == '"' || c == '\'')
171 quotec = c;
172 else if (c == ' ' || c == '\t')
173 break;
174 else
175 *cp2++ = c;
176 }
177 *cp2 = '\0';
178 argv[argn++] = savestr(linebuf);
179 }
180 argv[argn] = NULL;
181 return argn;
182 }
183
184 /*
185 * Mark all messages that the user wanted from the command
186 * line in the message structure. Return 0 on success, -1
187 * on error.
188 */
189
190 /*
191 * Bit values for colon modifiers.
192 */
193 #define CMBOX 0x001 /* Unread messages */
194 #define CMDELETED 0x002 /* Deleted messages */
195 #define CMMODIFY 0x004 /* Unread messages */
196 #define CMNEW 0x008 /* New messages */
197 #define CMOLD 0x010 /* Old messages */
198 #define CMPRESERVE 0x020 /* Unread messages */
199 #define CMREAD 0x040 /* Read messages */
200 #define CMSAVED 0x080 /* Saved messages */
201 #define CMTAGGED 0x100 /* Tagged messages */
202 #define CMUNREAD 0x200 /* Unread messages */
203 #define CMNEGATE 0x400 /* Negate the match */
204 #define CMMASK 0x7ff /* Mask the valid bits */
205
206 /*
207 * The following table describes the letters which can follow
208 * the colon and gives the corresponding modifier bit.
209 */
210
211 static const struct coltab {
212 char co_char; /* What to find past : */
213 int co_bit; /* Associated modifier bit */
214 int co_mask; /* m_status bits to mask */
215 int co_equal; /* ... must equal this */
216 } coltab[] = {
217 { '!', CMNEGATE, 0, 0 },
218 { 'd', CMDELETED, MDELETED, MDELETED },
219 { 'e', CMMODIFY, MMODIFY, MMODIFY },
220 { 'm', CMBOX, MBOX, MBOX },
221 { 'n', CMNEW, MNEW, MNEW },
222 { 'o', CMOLD, MNEW, 0 },
223 { 'p', CMPRESERVE, MPRESERVE, MPRESERVE },
224 { 'r', CMREAD, MREAD, MREAD },
225 { 's', CMSAVED, MSAVED, MSAVED },
226 { 't', CMTAGGED, MTAGGED, MTAGGED },
227 { 'u', CMUNREAD, MREAD|MNEW, 0 },
228 { 0, 0, 0, 0 }
229 };
230
231 static int lastcolmod;
232
233 static int
234 ignore_message(int m_flag, int colmod)
235 {
236 int ignore_msg;
237 const struct coltab *colp;
238
239 ignore_msg = !(colmod & CMNEGATE);
240 colmod &= (~CMNEGATE & CMMASK);
241
242 for (colp = &coltab[0]; colp->co_char; colp++)
243 if (colp->co_bit & colmod &&
244 (m_flag & colp->co_mask) == colp->co_equal)
245 return !ignore_msg;
246 return ignore_msg;
247 }
248
249 /*
250 * Turn the character after a colon modifier into a bit
251 * value.
252 */
253 static int
254 evalcol(int col)
255 {
256 const struct coltab *colp;
257
258 if (col == 0)
259 return lastcolmod;
260 for (colp = &coltab[0]; colp->co_char; colp++)
261 if (colp->co_char == col)
262 return colp->co_bit;
263 return 0;
264 }
265
266 static int
267 get_colmod(int colmod, char *cp)
268 {
269 if ((cp[0] == '\0') ||
270 (cp[0] == '!' && cp[1] == '\0'))
271 colmod |= lastcolmod;
272
273 for (/*EMPTY*/; *cp; cp++) {
274 int colresult;
275 if ((colresult = evalcol(*cp)) == 0) {
276 (void)printf("Unknown colon modifier \"%s\"\n", lexstring);
277 return -1;
278 }
279 if (colresult == CMNEGATE)
280 colmod ^= CMNEGATE;
281 else
282 colmod |= colresult;
283 }
284 return colmod;
285 }
286
287 static int
288 syntax_error(const char *msg)
289 {
290 (void)printf("Syntax error: %s\n", msg);
291 return -1;
292 }
293
294 /*
295 * scan out a single lexical item and return its token number,
296 * updating the string pointer passed **p. Also, store the value
297 * of the number or string scanned in lexnumber or lexstring as
298 * appropriate. In any event, store the scanned `thing' in lexstring.
299 */
300 static enum token_e
301 scan(char **sp)
302 {
303 static const struct lex {
304 char l_char;
305 enum token_e l_token;
306 } singles[] = {
307 { '$', TDOLLAR },
308 { '.', TDOT },
309 { '^', TUP },
310 { '*', TSTAR },
311 { '-', TDASH },
312 { '+', TPLUS },
313 { '(', TOPEN },
314 { ')', TCLOSE },
315 { '&', TAND },
316 { '|', TOR },
317 { '!', TNOT },
318 { 0, 0 }
319 };
320 const struct lex *lp;
321 char *cp, *cp2;
322 int c;
323 int quotec;
324
325 if (regretp >= 0) {
326 (void)strcpy(lexstring, string_stack[regretp]);
327 lexnumber = numberstack[regretp];
328 return regretstack[regretp--];
329 }
330 cp = *sp;
331 cp2 = lexstring;
332 lexstring[0] = '\0';
333
334 /*
335 * strip away leading white space.
336 */
337 cp = skip_blank(cp);
338 c = *cp++;
339
340 /*
341 * If no characters remain, we are at end of line,
342 * so report that.
343 */
344 if (c == '\0') {
345 *sp = --cp;
346 return TEOL;
347 }
348
349 /*
350 * If the leading character is a digit, scan
351 * the number and convert it on the fly.
352 * Return TNUMBER when done.
353 */
354 if (isdigit(c)) {
355 lexnumber = 0;
356 while (isdigit(c)) {
357 lexnumber = lexnumber * 10 + c - '0';
358 *cp2++ = c;
359 c = *cp++;
360 }
361 *cp2 = '\0';
362 *sp = --cp;
363 return TNUMBER;
364 }
365
366 /*
367 * Check for single character tokens; return such
368 * if found.
369 */
370 for (lp = &singles[0]; lp->l_char != 0; lp++)
371 if (c == lp->l_char) {
372 lexstring[0] = c;
373 lexstring[1] = '\0';
374 *sp = cp;
375 return lp->l_token;
376 }
377
378 /*
379 * We've got a string! Copy all the characters
380 * of the string into lexstring, until we see
381 * a null, space, or tab.
382 * Respect quoting and quoted pairs.
383 */
384 quotec = 0;
385 while (c != '\0') {
386 if (c == quotec) {
387 quotec = 0;
388 c = *cp++;
389 continue;
390 }
391 if (quotec) {
392 if (c == '\\' && (*cp == quotec || *cp == '\\'))
393 c = *cp++;
394 }
395 else {
396 switch (c) {
397 case '\'':
398 case '"':
399 quotec = c;
400 c = *cp++;
401 continue;
402 case ' ':
403 case '\t':
404 c = '\0'; /* end of token! */
405 continue;
406 default:
407 break;
408 }
409 }
410 if (cp2 - lexstring < STRINGLEN - 1)
411 *cp2++ = c;
412 c = *cp++;
413 }
414 if (quotec && c == 0) {
415 (void)fprintf(stderr, "Missing %c\n", quotec);
416 return TERROR;
417 }
418 *sp = --cp;
419 *cp2 = '\0';
420 return TSTRING;
421 }
422
423 /*
424 * Unscan the named token by pushing it onto the regret stack.
425 */
426 static void
427 regret(int token)
428 {
429 if (++regretp >= REGDEP)
430 errx(1, "Too many regrets");
431 regretstack[regretp] = token;
432 lexstring[sizeof(lexstring) - 1] = '\0';
433 string_stack[regretp] = savestr(lexstring);
434 numberstack[regretp] = lexnumber;
435 }
436
437 /*
438 * Reset all the scanner global variables.
439 */
440 static void
441 scaninit(void)
442 {
443 regretp = -1;
444 }
445
446 #define DELIM " \t," /* list of string delimiters */
447 static int
448 is_substr(const char *big, const char *little)
449 {
450 const char *cp;
451 if ((cp = strstr(big, little)) == NULL)
452 return 0;
453
454 return strchr(DELIM, cp[strlen(little)]) != 0 &&
455 (cp == big || strchr(DELIM, cp[-1]) != 0);
456 }
457 #undef DELIM
458
459 /*
460 * Compile a regular expression, taking into account the value of the
461 * "regex-search" variable.
462 */
463 static int
464 compile_regex(regex_t *preg, const char *str)
465 {
466 int e;
467 char *val;
468 if ((val = value(ENAME_REGEX_SEARCH)) != NULL) {
469 int cflags;
470 cflags = REG_NOSUB;
471 val = skip_blank(val);
472 if (*val) {
473 if (is_substr(val, "icase"))
474 cflags |= REG_ICASE;
475 if (is_substr(val, "extended"))
476 cflags |= REG_EXTENDED;
477 }
478 if ((e = regcomp(preg, str, cflags)) != 0) {
479 char errbuf[LINESIZE];
480 (void)regerror(e, preg, errbuf, sizeof(errbuf));
481 (void)printf("regcomp failed: '%s': %s\n", str, errbuf);
482 return -1;
483 }
484 return 1;
485 }
486 return 0;
487 }
488
489 /*
490 * See if the passed name sent the passed message number. Return true
491 * if so.
492 */
493 static int
494 matchsender(char *str, int mesg)
495 {
496 regex_t preg;
497 int doregex;
498 char *field;
499
500 if (*str == '\0') /* null string matches nothing instead of everything */
501 return 0;
502
503 field = nameof(get_message(mesg), 0);
504
505 if ((doregex = compile_regex(&preg, str)) == -1)
506 return -1;
507
508 if (doregex)
509 return regexec(&preg, field, 0, NULL, 0) == 0;
510 else
511 return strcasestr(field, str) != NULL;
512 }
513
514 /*
515 * See if the passed name received the passed message number. Return true
516 * if so.
517 */
518 static int
519 matchto(char *str, int mesg)
520 {
521 static const char *to_fields[] = { "to", "cc", "bcc", 0 };
522 const char **to;
523 regex_t preg;
524 int doregex;
525 struct message *mp;
526
527 if (*str == 0) /* null string matches nothing instead of everything */
528 return 0;
529
530 if ((doregex = compile_regex(&preg, str)) == -1)
531 return -1;
532
533 mp = get_message(mesg);
534
535 for (to = to_fields; *to; to++) {
536 char *field;
537 field = hfield(*to, mp);
538 if (field != NULL) {
539 if (doregex) {
540 if (regexec(&preg, field, 0, NULL, 0) == 0)
541 return 1;
542 }
543 else {
544 if (strcasestr(field, str) != NULL)
545 return 1;
546 }
547 }
548 }
549 return 0;
550 }
551
552 /*
553 * See if the given string matches inside the subject field of the
554 * given message. For the purpose of the scan, we ignore case differences.
555 * If it does, return true. The string search argument is assumed to
556 * have the form "/search-string." If it is of the form "/", we use the
557 * previous search string.
558 */
559 static int
560 matchsubj(char *str, int mesg)
561 {
562 regex_t preg;
563 int doregex;
564 char *field;
565 char *cp;
566
567 static char lastscan[STRINGLEN];
568 struct message *mp;
569
570 if (*str == '\0')
571 str = lastscan;
572 else
573 (void)strlcpy(lastscan, str, sizeof(lastscan));
574
575 mp = get_message(mesg);
576
577 /*
578 * Now look, ignoring case, for the word in the string.
579 */
580
581 if (value(ENAME_SEARCHHEADERS) && (cp = strchr(str, ':')) != NULL) {
582 /*
583 * Check for special cases!
584 * These checks are case sensitive so the true fields
585 * can be grabbed as mentioned in the manpage.
586 */
587 if (strncmp(str, "to:", 3) == 0)
588 return matchto(cp + 1, mesg);
589
590 if (strncmp(str, "from:", 5) == 0) {
591 char headline[LINESIZE];
592 (void)mail_readline(setinput(mp), headline, sizeof(headline));
593 field = savestr(headline);
594 }
595 else {
596 *cp = '\0';
597 field = hfield(*str ? str : "subject", mp);
598 *cp = ':';
599 }
600 str = cp + 1;
601 } else {
602 field = hfield("subject", mp);
603 }
604 if (field == NULL)
605 return 0;
606
607 if ((doregex = compile_regex(&preg, str)) == -1)
608 return -1;
609
610 if (doregex)
611 return regexec(&preg, field, 0, NULL, 0) == 0;
612 else
613 return strcasestr(field, str) != NULL;
614 }
615
616 /*
617 * Return the message number corresponding to the passed meta character.
618 */
619 static int
620 metamess(int meta, int f)
621 {
622 int c, m;
623 struct message *mp;
624
625 c = meta;
626 switch (c) {
627 case '^':
628 /*
629 * First 'good' message left.
630 */
631 for (mp = get_message(1); mp; mp = next_message(mp))
632 if ((mp->m_flag & MDELETED) == f)
633 return get_msgnum(mp);
634 (void)printf("No applicable messages\n");
635 return -1;
636
637 case '$':
638 /*
639 * Last 'good message left.
640 */
641 for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
642 if ((mp->m_flag & MDELETED) == f)
643 return get_msgnum(mp);
644 (void)printf("No applicable messages\n");
645 return -1;
646
647 case '.':
648 /*
649 * Current message.
650 */
651 if (dot == NULL) {
652 (void)printf("No applicable messages\n");
653 return -1;
654 }
655 m = get_msgnum(dot);
656 if ((dot->m_flag & MDELETED) != f) {
657 (void)printf("%d: Inappropriate message\n", m);
658 return -1;
659 }
660 return m;
661
662 default:
663 (void)printf("Unknown metachar (%c)\n", c);
664 return -1;
665 }
666 }
667
668 /*
669 * Check the passed message number for legality and proper flags.
670 * If f is MDELETED, then either kind will do. Otherwise, the message
671 * has to be undeleted.
672 */
673 static int
674 check(int mesg, int f)
675 {
676 struct message *mp;
677
678 if ((mp = get_message(mesg)) == NULL) {
679 (void)printf("%d: Invalid message number\n", mesg);
680 return -1;
681 }
682 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
683 (void)printf("%d: Inappropriate message\n", mesg);
684 return -1;
685 }
686 return 0;
687 }
688
689 static int
690 match_string(int *markarray, char *str, int msgCount)
691 {
692 int i;
693 int rval;
694 int (*matchfn)(char *, int);
695
696 matchfn = *str == '/' ? matchsubj : matchsender;
697 if (*str == '/')
698 str++;
699
700 for (i = 1; i <= msgCount; i++) {
701 rval = matchfn(str, i);
702 if (rval == -1)
703 return -1;
704 if (rval)
705 markarray[i - 1] = 1;
706 }
707 return 0;
708 }
709
710
711 static int
712 markall_core(int *markarray, char **bufp, int f, int level)
713 {
714 enum token_e tok;
715 enum logic_op_e {
716 LOP_AND,
717 LOP_OR,
718 LOP_XOR
719 } logic_op; /* binary logic operation */
720 int logic_invert; /* invert the result */
721 int *tmparray; /* temporarly array with result */
722 int msgCount; /* tmparray length and message count */
723 int beg; /* first value of a range */
724 int colmod; /* the colon-modifier for this group */
725 int got_not; /* for syntax checking of '!' */
726 int got_one; /* we have a message spec, valid or not */
727 int got_bin; /* we have a pending binary operation */
728 int i;
729
730 logic_op = LOP_OR;
731 logic_invert = 0;
732 colmod = 0;
733
734 msgCount = get_msgCount();
735 tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
736
737 beg = 0;
738 got_one = 0;
739 got_not = 0;
740 got_bin = 0;
741
742 while ((tok = scan(bufp)) != TEOL) {
743 if (tok == TERROR)
744 return -1;
745
746 /*
747 * Do some syntax checking.
748 */
749 switch (tok) {
750 case TDASH:
751 case TPLUS:
752 case TDOLLAR:
753 case TUP:
754 case TDOT:
755 case TNUMBER:
756 break;
757
758 case TAND:
759 case TOR:
760 case TXOR:
761 if (!got_one)
762 return syntax_error("missing left operand");
763 /*FALLTHROUGH*/
764 default:
765 if (beg)
766 return syntax_error("end of range missing");
767 break;
768 }
769
770 /*
771 * The main tok switch.
772 */
773 switch (tok) {
774 struct message *mp;
775
776 case TERROR: /* trapped above */
777 case TEOL:
778 assert(/*CONSTCOND*/0);
779 break;
780
781 case TUP:
782 if (got_one) { /* a possible logical xor */
783 enum token_e t;
784 t = scan(bufp); /* peek ahead */
785 regret(t);
786 lexstring[0] = '^'; /* restore lexstring */
787 lexstring[1] = '\0';
788 if (t != TDASH && t != TEOL && t != TCLOSE) {
789 /* convert tok to TXOR and put
790 * it back on the stack so we
791 * can handle it consistently */
792 tok = TXOR;
793 regret(tok);
794 continue;
795 }
796 }
797 /* FALLTHROUGH */
798 case TDOLLAR:
799 case TDOT:
800 lexnumber = metamess(lexstring[0], f);
801 if (lexnumber == -1)
802 return -1;
803 /* FALLTHROUGH */
804 case TNUMBER:
805 if (check(lexnumber, f))
806 return -1;
807 number:
808 got_one = 1;
809 if (beg != 0) {
810 if (lexnumber < beg) {
811 (void)printf("invalid range: %d-%d\n", beg, lexnumber);
812 return -1;
813 }
814 for (i = beg; i <= lexnumber; i++)
815 tmparray[i - 1] = 1;
816
817 beg = 0;
818 break;
819 }
820 beg = lexnumber; /* start of a range */
821 tok = scan(bufp);
822 if (tok == TDASH) {
823 continue;
824 }
825 else {
826 regret(tok);
827 tmparray[beg - 1] = 1;
828 beg = 0;
829 }
830 break;
831
832 case TDASH:
833 for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
834 if ((mp->m_flag & MDELETED) == 0)
835 break;
836 }
837 if (mp == NULL) {
838 (void)printf("Referencing before 1\n");
839 return -1;
840 }
841 lexnumber = get_msgnum(mp);
842 goto number;
843
844 case TPLUS:
845 for (mp = next_message(dot); mp; mp = next_message(mp)) {
846 if ((mp->m_flag & MDELETED) == 0)
847 break;
848 }
849 if (mp == NULL) {
850 (void)printf("Referencing beyond EOF\n");
851 return -1;
852 }
853 lexnumber = get_msgnum(mp);
854 goto number;
855
856 case TSTRING:
857 if (lexstring[0] == ':') { /* colon modifier! */
858 colmod = get_colmod(colmod, lexstring + 1);
859 if (colmod == -1)
860 return -1;
861 continue;
862 }
863 got_one = 1;
864 if (match_string(tmparray, lexstring, msgCount) == -1)
865 return -1;
866 break;
867
868 case TSTAR:
869 got_one = 1;
870 for (i = 1; i <= msgCount; i++)
871 tmparray[i - 1] = 1;
872 break;
873
874
875 /**************
876 * Parentheses.
877 */
878 case TOPEN:
879 if (markall_core(tmparray, bufp, f, level + 1) == -1)
880 return -1;
881 break;
882
883 case TCLOSE:
884 if (level == 0)
885 return syntax_error("extra ')'");
886 goto done;
887
888
889 /*********************
890 * Logical operations.
891 */
892 case TNOT:
893 got_not = 1;
894 logic_invert = ! logic_invert;
895 continue;
896
897 /*
898 * Binary operations.
899 */
900 case TAND:
901 if (got_not)
902 return syntax_error("'!' precedes '&'");
903 got_bin = 1;
904 logic_op = LOP_AND;
905 continue;
906
907 case TOR:
908 if (got_not)
909 return syntax_error("'!' precedes '|'");
910 got_bin = 1;
911 logic_op = LOP_OR;
912 continue;
913
914 case TXOR:
915 if (got_not)
916 return syntax_error("'!' precedes logical '^'");
917 got_bin = 1;
918 logic_op = LOP_XOR;
919 continue;
920 }
921
922 /*
923 * Do the logic operations.
924 */
925 if (logic_invert)
926 for (i = 0; i < msgCount; i++)
927 tmparray[i] = ! tmparray[i];
928
929 switch (logic_op) {
930 case LOP_AND:
931 for (i = 0; i < msgCount; i++)
932 markarray[i] &= tmparray[i];
933 break;
934
935 case LOP_OR:
936 for (i = 0; i < msgCount; i++)
937 markarray[i] |= tmparray[i];
938 break;
939
940 case LOP_XOR:
941 for (i = 0; i < msgCount; i++)
942 markarray[i] ^= tmparray[i];
943 break;
944 }
945
946 /*
947 * Clear the temporary array and reset the logic
948 * operations.
949 */
950 for (i = 0; i < msgCount; i++)
951 tmparray[i] = 0;
952
953 logic_op = LOP_OR;
954 logic_invert = 0;
955 got_not = 0;
956 got_bin = 0;
957 }
958
959 if (beg)
960 return syntax_error("end of range missing");
961
962 if (level)
963 return syntax_error("missing ')'");
964
965 done:
966 if (got_not)
967 return syntax_error("trailing '!'");
968
969 if (got_bin)
970 return syntax_error("missing right operand");
971
972 if (colmod != 0) {
973 /*
974 * If we have colon-modifiers but no messages
975 * specifiec, then assume '*' was given.
976 */
977 if (got_one == 0)
978 for (i = 1; i <= msgCount; i++)
979 markarray[i - 1] = 1;
980
981 for (i = 1; i <= msgCount; i++) {
982 struct message *mp;
983 if ((mp = get_message(i)) != NULL &&
984 ignore_message(mp->m_flag, colmod))
985 markarray[i - 1] = 0;
986 }
987 }
988 return 0;
989 }
990
991 static int
992 markall(char buf[], int f)
993 {
994 int i;
995 int mc;
996 int *markarray;
997 int msgCount;
998 struct message *mp;
999
1000 msgCount = get_msgCount();
1001
1002 /*
1003 * Clear all the previous message marks.
1004 */
1005 for (i = 1; i <= msgCount; i++)
1006 if ((mp = get_message(i)) != NULL)
1007 mp->m_flag &= ~MMARK;
1008
1009 buf = skip_blank(buf);
1010 if (*buf == '\0')
1011 return 0;
1012
1013 scaninit();
1014 markarray = csalloc((size_t)msgCount, sizeof(*markarray));
1015 if (markall_core(markarray, &buf, f, 0) == -1)
1016 return -1;
1017
1018 /*
1019 * Transfer the markarray values to the messages.
1020 */
1021 mc = 0;
1022 for (i = 1; i <= msgCount; i++) {
1023 if (markarray[i - 1] &&
1024 (mp = get_message(i)) != NULL &&
1025 (f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
1026 mp->m_flag |= MMARK;
1027 mc++;
1028 }
1029 }
1030
1031 if (mc == 0) {
1032 (void)printf("No applicable messages.\n");
1033 return -1;
1034 }
1035 return 0;
1036 }
1037
1038 /*
1039 * Convert the user string of message numbers and
1040 * store the numbers into vector.
1041 *
1042 * Returns the count of messages picked up or -1 on error.
1043 */
1044 PUBLIC int
1045 getmsglist(char *buf, int *vector, int flags)
1046 {
1047 int *ip;
1048 struct message *mp;
1049
1050 if (get_msgCount() == 0) {
1051 *vector = 0;
1052 return 0;
1053 }
1054 if (markall(buf, flags) < 0)
1055 return -1;
1056 ip = vector;
1057 for (mp = get_message(1); mp; mp = next_message(mp))
1058 if (mp->m_flag & MMARK)
1059 *ip++ = get_msgnum(mp);
1060 *ip = 0;
1061 return ip - vector;
1062 }
1063
1064 /*
1065 * Find the first message whose flags & m == f and return
1066 * its message number.
1067 */
1068 PUBLIC int
1069 first(int f, int m)
1070 {
1071 struct message *mp;
1072
1073 if (get_msgCount() == 0)
1074 return 0;
1075 f &= MDELETED;
1076 m &= MDELETED;
1077 for (mp = dot; mp; mp = next_message(mp))
1078 if ((mp->m_flag & m) == f)
1079 return get_msgnum(mp);
1080 for (mp = prev_message(dot); mp; mp = prev_message(mp))
1081 if ((mp->m_flag & m) == f)
1082 return get_msgnum(mp);
1083 return 0;
1084 }
1085
1086 /*
1087 * Show all headers without paging. (-H flag)
1088 */
1089 __attribute__((__noreturn__))
1090 PUBLIC int
1091 show_headers_and_exit(int flags)
1092 {
1093 struct message *mp;
1094
1095 flags &= CMMASK;
1096 for (mp = get_message(1); mp; mp = next_message(mp))
1097 if (flags == 0 || !ignore_message(mp->m_flag, flags))
1098 printhead(get_msgnum(mp));
1099
1100 exit(0);
1101 /* NOTREACHED */
1102 }
1103
1104 /*
1105 * A hack so -H can have an optional modifier as -H[:flags].
1106 *
1107 * This depends a bit on the internals of getopt(). In particular,
1108 * for flags expecting an argument, argv[optind-1] must contain the
1109 * optarg and optarg must point to a substring of argv[optind-1] not a
1110 * copy of it.
1111 */
1112 PUBLIC int
1113 get_Hflag(char **argv)
1114 {
1115 int flags;
1116
1117 flags = ~CMMASK;
1118
1119 if (optarg == NULL) /* We had an error, just get the flags. */
1120 return flags;
1121
1122 if (*optarg != ':' || optarg == argv[optind - 1]) {
1123 optind--;
1124 optreset = 1;
1125 if (optarg != argv[optind]) {
1126 static char temparg[LINESIZE];
1127 size_t optlen;
1128 size_t arglen;
1129 char *p;
1130
1131 optlen = strlen(optarg);
1132 arglen = strlen(argv[optind]);
1133 p = argv[optind] + arglen - optlen;
1134 optlen = MIN(optlen, sizeof(temparg) - 1);
1135 temparg[0] = '-';
1136 (void)memmove(temparg + 1, p, optlen + 1);
1137 argv[optind] = temparg;
1138 }
1139 }
1140 else {
1141 flags = get_colmod(flags, optarg + 1);
1142 }
1143 return flags;
1144 }
1145