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