list.c revision 1.19 1 /* $NetBSD: list.c,v 1.19 2006/12/25 18:43:29 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.19 2006/12/25 18:43:29 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 * Support "nospec", but don't document it as
556 * it may not be portable.
557 * NOTE: regcomp() will fail if "nospec" and
558 * "extended" are used together.
559 */
560 if (is_substr(val, "nospec"))
561 cflags |= REG_NOSPEC;
562 }
563 if ((e = regcomp(&preg, str, cflags)) != 0) {
564 char errbuf[LINESIZE];
565 (void)regerror(e, &preg, errbuf, sizeof(errbuf));
566 (void)printf("regcomp failed: '%s': %s\n", str, errbuf);
567 return NULL;
568 }
569 *pattern = &preg;
570 return regexcmp;
571 }
572
573 *pattern = str;
574 return substrcmp;
575 }
576
577 /*
578 * Free any memory allocated by get_cmpfn()
579 */
580 static void
581 free_cmparg(void *pattern)
582 {
583 if (pattern == &preg)
584 regfree(&preg);
585 }
586
587 /*
588 * Check the message body for the pattern.
589 */
590 static int
591 matchbody(int (*cmpfn)(void *, char *, size_t),
592 void *pattern, struct message *mp, char const *fieldname __unused)
593 {
594 FILE *fp;
595 char *line;
596 size_t len;
597 int gotmatch;
598
599 #ifdef __lint__
600 fieldname = fieldname;
601 #endif
602 /*
603 * Get a temporary file.
604 */
605 {
606 char *tempname;
607 int fd;
608
609 (void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir);
610 fp = NULL;
611 if ((fd = mkstemp(tempname)) != -1) {
612 (void)unlink(tempname);
613 if ((fp = Fdopen(fd, "w+")) == NULL)
614 (void)close(fd);
615 }
616 if (fp == NULL) {
617 warn("%s", tempname);
618 return -1;
619 }
620 }
621
622 /*
623 * Pump the (decoded) message body into the temp file.
624 */
625 {
626 #ifdef MIME_SUPPORT
627 struct mime_info *mip;
628 int retval;
629
630 mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp)
631 : NULL;
632
633 retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip);
634 mime_decode_close(mip);
635 if (retval == -1)
636 #else
637 if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1)
638 #endif
639 {
640 warn("matchbody: mesg=%d", get_msgnum(mp));
641 return -1;
642 }
643 }
644 /*
645 * XXX - should we read the entire body into a buffer so we
646 * can search across lines?
647 */
648 rewind(fp);
649 gotmatch = 0;
650 while ((line = fgetln(fp, &len)) != NULL && len > 0) {
651 gotmatch = cmpfn(pattern, line, len);
652 if (gotmatch)
653 break;
654 }
655 (void)Fclose(fp);
656
657 return gotmatch;
658 }
659
660 /*
661 * Check the "To:", "Cc:", and "Bcc" fields for the pattern.
662 */
663 static int
664 matchto(int (*cmpfn)(void *, char *, size_t),
665 void *pattern, struct message *mp, char const *fieldname __unused)
666 {
667 static const char *to_fields[] = { "to", "cc", "bcc", 0 };
668 const char **to;
669 int gotmatch;
670
671 #ifdef __lint__
672 fieldname = fieldname;
673 #endif
674 gotmatch = 0;
675 for (to = to_fields; *to; to++) {
676 char *field;
677 field = hfield(*to, mp);
678 gotmatch = cmpfn(pattern, field, 0);
679 if (gotmatch)
680 break;
681 }
682 return gotmatch;
683 }
684
685 /*
686 * Check a field for the pattern.
687 */
688 static int
689 matchfield(int (*cmpfn)(void *, char *, size_t),
690 void *pattern, struct message *mp, char const *fieldname)
691 {
692 char *field;
693
694 #ifdef __lint__
695 fieldname = fieldname;
696 #endif
697 field = hfield(fieldname, mp);
698 return cmpfn(pattern, field, 0);
699 }
700
701 /*
702 * Check the headline for the pattern.
703 */
704 static int
705 matchfrom(int (*cmpfn)(void *, char *, size_t),
706 void *pattern, struct message *mp, char const *fieldname __unused)
707 {
708 char headline[LINESIZE];
709 char *field;
710
711 #ifdef __lint__
712 fieldname = fieldname;
713 #endif
714 (void)mail_readline(setinput(mp), headline, sizeof(headline));
715 field = savestr(headline);
716 if (strncmp(field, "From ", 5) != 0)
717 return 1;
718
719 return cmpfn(pattern, field + 5, 0);
720 }
721
722 /*
723 * Check the sender for the pattern.
724 */
725 static int
726 matchsender(int (*cmpfn)(void *, char *, size_t),
727 void *pattern, struct message *mp, char const *fieldname __unused)
728 {
729 char *field;
730
731 #ifdef __lint__
732 fieldname = fieldname;
733 #endif
734 field = nameof(mp, 0);
735 return cmpfn(pattern, field, 0);
736 }
737
738 /*
739 * Interpret 'str' and check each message (1 thru 'msgCount') for a match.
740 * The 'str' has the format: [/[[x]:]y with the following meanings:
741 *
742 * y pattern 'y' is compared against the senders address.
743 * /y pattern 'y' is compared with the subject field. If 'y' is empty,
744 * the last search 'str' is used.
745 * /:y pattern 'y' is compared with the subject field.
746 * /x:y pattern 'y' is compared with the specified header field 'x' or
747 * the message body if 'x' == "body".
748 *
749 * The last two forms require "searchheaders" to be defined.
750 */
751 static int
752 match_string(int *markarray, char *str, int msgCount)
753 {
754 int i;
755 int rval;
756 int (*matchfn)(int (*)(void *, char *, size_t),
757 void *, struct message *, char const *);
758 int (*cmpfn)(void *, char *, size_t);
759 void *cmparg;
760 char const *fieldname;
761
762 if (*str != '/') {
763 matchfn = matchsender;
764 fieldname = NULL;
765 }
766 else {
767 static char lastscan[STRINGLEN];
768 char *cp;
769
770 str++;
771 if (*str == '\0')
772 str = lastscan;
773 else
774 (void)strlcpy(lastscan, str, sizeof(lastscan));
775
776 if (value(ENAME_SEARCHHEADERS) == NULL ||
777 (cp = strchr(str, ':')) == NULL) {
778 matchfn = matchfield;
779 fieldname = "subject";
780 /* str = str; */
781 }
782 else {
783 static const struct matchtbl_s {
784 char const *key;
785 size_t len;
786 char const *fieldname;
787 int (*matchfn)(int (*)(void *, char *, size_t),
788 void *, struct message *, char const *);
789 } matchtbl[] = {
790 #define X(a) a, sizeof(a) - 1
791 #define X_NULL NULL, 0
792 { X(":"), "subject", matchfield },
793 { X("body:"), NULL, matchbody },
794 { X("from:"), NULL, matchfrom },
795 { X("to:"), NULL, matchto },
796 { X_NULL, NULL, matchfield }
797 #undef X_NULL
798 #undef X
799 };
800 const struct matchtbl_s *mtp;
801 size_t len;
802 /*
803 * Check for special cases!
804 * These checks are case sensitive so the true fields
805 * can be grabbed as mentioned in the manpage.
806 */
807 cp++;
808 len = cp - str;
809 for (mtp = matchtbl; mtp->key; mtp++) {
810 if (len == mtp->len &&
811 strncmp(str, mtp->key, len) == 0)
812 break;
813 }
814 matchfn = mtp->matchfn;
815 if (mtp->key)
816 fieldname = mtp->fieldname;
817 else {
818 char *p;
819 p = salloc(len);
820 (void)strlcpy(p, str, len);
821 fieldname = p;
822 }
823 str = cp;
824 }
825 }
826
827 if (*str == '\0') /* an empty string matches nothing instead of everything */
828 return 0;
829
830 cmpfn = get_cmpfn(&cmparg, str);
831 if (cmpfn == NULL)
832 return -1;
833
834 rval = 0;
835 for (i = 1; i <= msgCount; i++) {
836 struct message *mp;
837 mp = get_message(i);
838 rval = matchfn(cmpfn, cmparg, mp, fieldname);
839 if (rval == -1)
840 break;
841 if (rval)
842 markarray[i - 1] = 1;
843 rval = 0;
844 }
845
846 free_cmparg(cmparg); /* free any memory allocated by get_cmpfn() */
847
848 return rval;
849 }
850
851
852 /*
853 * Return the message number corresponding to the passed meta character.
854 */
855 static int
856 metamess(int meta, int f)
857 {
858 int c, m;
859 struct message *mp;
860
861 c = meta;
862 switch (c) {
863 case '^':
864 /*
865 * First 'good' message left.
866 */
867 for (mp = get_message(1); mp; mp = next_message(mp))
868 if ((mp->m_flag & MDELETED) == f)
869 return get_msgnum(mp);
870 (void)printf("No applicable messages\n");
871 return -1;
872
873 case '$':
874 /*
875 * Last 'good message left.
876 */
877 for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp))
878 if ((mp->m_flag & MDELETED) == f)
879 return get_msgnum(mp);
880 (void)printf("No applicable messages\n");
881 return -1;
882
883 case '.':
884 /*
885 * Current message.
886 */
887 if (dot == NULL) {
888 (void)printf("No applicable messages\n");
889 return -1;
890 }
891 m = get_msgnum(dot);
892 if ((dot->m_flag & MDELETED) != f) {
893 (void)printf("%d: Inappropriate message\n", m);
894 return -1;
895 }
896 return m;
897
898 default:
899 (void)printf("Unknown metachar (%c)\n", c);
900 return -1;
901 }
902 }
903
904 /*
905 * Check the passed message number for legality and proper flags.
906 * If f is MDELETED, then either kind will do. Otherwise, the message
907 * has to be undeleted.
908 */
909 static int
910 check(int mesg, int f)
911 {
912 struct message *mp;
913
914 if ((mp = get_message(mesg)) == NULL) {
915 (void)printf("%d: Invalid message number\n", mesg);
916 return -1;
917 }
918 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
919 (void)printf("%d: Inappropriate message\n", mesg);
920 return -1;
921 }
922 return 0;
923 }
924
925
926 static int
927 markall_core(int *markarray, char **bufp, int f, int level)
928 {
929 enum token_e tok;
930 enum logic_op_e {
931 LOP_AND,
932 LOP_OR,
933 LOP_XOR
934 } logic_op; /* binary logic operation */
935 int logic_invert; /* invert the result */
936 int *tmparray; /* temporarly array with result */
937 int msgCount; /* tmparray length and message count */
938 int beg; /* first value of a range */
939 int colmod; /* the colon-modifier for this group */
940 int got_not; /* for syntax checking of '!' */
941 int got_one; /* we have a message spec, valid or not */
942 int got_bin; /* we have a pending binary operation */
943 int i;
944
945 logic_op = LOP_OR;
946 logic_invert = 0;
947 colmod = 0;
948
949 msgCount = get_msgCount();
950 tmparray = csalloc((size_t)msgCount, sizeof(*tmparray));
951
952 beg = 0;
953 got_one = 0;
954 got_not = 0;
955 got_bin = 0;
956
957 while ((tok = scan(bufp)) != TEOL) {
958 if (tok == TERROR)
959 return -1;
960
961 /*
962 * Do some syntax checking.
963 */
964 switch (tok) {
965 case TDASH:
966 case TPLUS:
967 case TDOLLAR:
968 case TUP:
969 case TDOT:
970 case TNUMBER:
971 break;
972
973 case TAND:
974 case TOR:
975 case TXOR:
976 if (!got_one)
977 return syntax_error("missing left operand");
978 /*FALLTHROUGH*/
979 default:
980 if (beg)
981 return syntax_error("end of range missing");
982 break;
983 }
984
985 /*
986 * The main tok switch.
987 */
988 switch (tok) {
989 struct message *mp;
990
991 case TERROR: /* trapped above */
992 case TEOL:
993 assert(/*CONSTCOND*/0);
994 break;
995
996 case TUP:
997 if (got_one) { /* a possible logical xor */
998 enum token_e t;
999 t = scan(bufp); /* peek ahead */
1000 regret(t);
1001 lexstring[0] = '^'; /* restore lexstring */
1002 lexstring[1] = '\0';
1003 if (t != TDASH && t != TEOL && t != TCLOSE) {
1004 /* convert tok to TXOR and put
1005 * it back on the stack so we
1006 * can handle it consistently */
1007 tok = TXOR;
1008 regret(tok);
1009 continue;
1010 }
1011 }
1012 /* FALLTHROUGH */
1013 case TDOLLAR:
1014 case TDOT:
1015 lexnumber = metamess(lexstring[0], f);
1016 if (lexnumber == -1)
1017 return -1;
1018 /* FALLTHROUGH */
1019 case TNUMBER:
1020 if (check(lexnumber, f))
1021 return -1;
1022 number:
1023 got_one = 1;
1024 if (beg != 0) {
1025 if (lexnumber < beg) {
1026 (void)printf("invalid range: %d-%d\n", beg, lexnumber);
1027 return -1;
1028 }
1029 for (i = beg; i <= lexnumber; i++)
1030 tmparray[i - 1] = 1;
1031
1032 beg = 0;
1033 break;
1034 }
1035 beg = lexnumber; /* start of a range */
1036 tok = scan(bufp);
1037 if (tok == TDASH) {
1038 continue;
1039 }
1040 else {
1041 regret(tok);
1042 tmparray[beg - 1] = 1;
1043 beg = 0;
1044 }
1045 break;
1046
1047 case TDASH:
1048 for (mp = prev_message(dot); mp; mp = prev_message(mp)) {
1049 if ((mp->m_flag & MDELETED) == 0)
1050 break;
1051 }
1052 if (mp == NULL) {
1053 (void)printf("Referencing before 1\n");
1054 return -1;
1055 }
1056 lexnumber = get_msgnum(mp);
1057 goto number;
1058
1059 case TPLUS:
1060 for (mp = next_message(dot); mp; mp = next_message(mp)) {
1061 if ((mp->m_flag & MDELETED) == 0)
1062 break;
1063 }
1064 if (mp == NULL) {
1065 (void)printf("Referencing beyond EOF\n");
1066 return -1;
1067 }
1068 lexnumber = get_msgnum(mp);
1069 goto number;
1070
1071 case TSTRING:
1072 if (lexstring[0] == ':') { /* colon modifier! */
1073 colmod = get_colmod(colmod, lexstring + 1);
1074 if (colmod == -1)
1075 return -1;
1076 continue;
1077 }
1078 got_one = 1;
1079 if (match_string(tmparray, lexstring, msgCount) == -1)
1080 return -1;
1081 break;
1082
1083 case TSTAR:
1084 got_one = 1;
1085 for (i = 1; i <= msgCount; i++)
1086 tmparray[i - 1] = 1;
1087 break;
1088
1089
1090 /**************
1091 * Parentheses.
1092 */
1093 case TOPEN:
1094 if (markall_core(tmparray, bufp, f, level + 1) == -1)
1095 return -1;
1096 break;
1097
1098 case TCLOSE:
1099 if (level == 0)
1100 return syntax_error("extra ')'");
1101 goto done;
1102
1103
1104 /*********************
1105 * Logical operations.
1106 */
1107 case TNOT:
1108 got_not = 1;
1109 logic_invert = ! logic_invert;
1110 continue;
1111
1112 /*
1113 * Binary operations.
1114 */
1115 case TAND:
1116 if (got_not)
1117 return syntax_error("'!' precedes '&'");
1118 got_bin = 1;
1119 logic_op = LOP_AND;
1120 continue;
1121
1122 case TOR:
1123 if (got_not)
1124 return syntax_error("'!' precedes '|'");
1125 got_bin = 1;
1126 logic_op = LOP_OR;
1127 continue;
1128
1129 case TXOR:
1130 if (got_not)
1131 return syntax_error("'!' precedes logical '^'");
1132 got_bin = 1;
1133 logic_op = LOP_XOR;
1134 continue;
1135 }
1136
1137 /*
1138 * Do the logic operations.
1139 */
1140 if (logic_invert)
1141 for (i = 0; i < msgCount; i++)
1142 tmparray[i] = ! tmparray[i];
1143
1144 switch (logic_op) {
1145 case LOP_AND:
1146 for (i = 0; i < msgCount; i++)
1147 markarray[i] &= tmparray[i];
1148 break;
1149
1150 case LOP_OR:
1151 for (i = 0; i < msgCount; i++)
1152 markarray[i] |= tmparray[i];
1153 break;
1154
1155 case LOP_XOR:
1156 for (i = 0; i < msgCount; i++)
1157 markarray[i] ^= tmparray[i];
1158 break;
1159 }
1160
1161 /*
1162 * Clear the temporary array and reset the logic
1163 * operations.
1164 */
1165 for (i = 0; i < msgCount; i++)
1166 tmparray[i] = 0;
1167
1168 logic_op = LOP_OR;
1169 logic_invert = 0;
1170 got_not = 0;
1171 got_bin = 0;
1172 }
1173
1174 if (beg)
1175 return syntax_error("end of range missing");
1176
1177 if (level)
1178 return syntax_error("missing ')'");
1179
1180 done:
1181 if (got_not)
1182 return syntax_error("trailing '!'");
1183
1184 if (got_bin)
1185 return syntax_error("missing right operand");
1186
1187 if (colmod != 0) {
1188 /*
1189 * If we have colon-modifiers but no messages
1190 * specifiec, then assume '*' was given.
1191 */
1192 if (got_one == 0)
1193 for (i = 1; i <= msgCount; i++)
1194 markarray[i - 1] = 1;
1195
1196 for (i = 1; i <= msgCount; i++) {
1197 struct message *mp;
1198 if ((mp = get_message(i)) != NULL &&
1199 ignore_message(mp->m_flag, colmod))
1200 markarray[i - 1] = 0;
1201 }
1202 }
1203 return 0;
1204 }
1205
1206 static int
1207 markall(char buf[], int f)
1208 {
1209 int i;
1210 int mc;
1211 int *markarray;
1212 int msgCount;
1213 struct message *mp;
1214
1215 msgCount = get_msgCount();
1216
1217 /*
1218 * Clear all the previous message marks.
1219 */
1220 for (i = 1; i <= msgCount; i++)
1221 if ((mp = get_message(i)) != NULL)
1222 mp->m_flag &= ~MMARK;
1223
1224 buf = skip_blank(buf);
1225 if (*buf == '\0')
1226 return 0;
1227
1228 scaninit();
1229 markarray = csalloc((size_t)msgCount, sizeof(*markarray));
1230 if (markall_core(markarray, &buf, f, 0) == -1)
1231 return -1;
1232
1233 /*
1234 * Transfer the markarray values to the messages.
1235 */
1236 mc = 0;
1237 for (i = 1; i <= msgCount; i++) {
1238 if (markarray[i - 1] &&
1239 (mp = get_message(i)) != NULL &&
1240 (f == MDELETED || (mp->m_flag & MDELETED) == 0)) {
1241 mp->m_flag |= MMARK;
1242 mc++;
1243 }
1244 }
1245
1246 if (mc == 0) {
1247 (void)printf("No applicable messages.\n");
1248 return -1;
1249 }
1250 return 0;
1251 }
1252
1253 /*
1254 * Convert the user string of message numbers and
1255 * store the numbers into vector.
1256 *
1257 * Returns the count of messages picked up or -1 on error.
1258 */
1259 PUBLIC int
1260 getmsglist(char *buf, int *vector, int flags)
1261 {
1262 int *ip;
1263 struct message *mp;
1264
1265 if (get_msgCount() == 0) {
1266 *vector = 0;
1267 return 0;
1268 }
1269 if (markall(buf, flags) < 0)
1270 return -1;
1271 ip = vector;
1272 for (mp = get_message(1); mp; mp = next_message(mp))
1273 if (mp->m_flag & MMARK)
1274 *ip++ = get_msgnum(mp);
1275 *ip = 0;
1276 return ip - vector;
1277 }
1278
1279 /*
1280 * Find the first message whose flags & m == f and return
1281 * its message number.
1282 */
1283 PUBLIC int
1284 first(int f, int m)
1285 {
1286 struct message *mp;
1287
1288 if (get_msgCount() == 0)
1289 return 0;
1290 f &= MDELETED;
1291 m &= MDELETED;
1292 for (mp = dot; mp; mp = next_message(mp))
1293 if ((mp->m_flag & m) == f)
1294 return get_msgnum(mp);
1295 for (mp = prev_message(dot); mp; mp = prev_message(mp))
1296 if ((mp->m_flag & m) == f)
1297 return get_msgnum(mp);
1298 return 0;
1299 }
1300
1301 /*
1302 * Show all headers without paging. (-H flag)
1303 */
1304 __attribute__((__noreturn__))
1305 PUBLIC int
1306 show_headers_and_exit(int flags)
1307 {
1308 struct message *mp;
1309
1310 flags &= CMMASK;
1311 for (mp = get_message(1); mp; mp = next_message(mp))
1312 if (flags == 0 || !ignore_message(mp->m_flag, flags))
1313 printhead(get_msgnum(mp));
1314
1315 exit(0);
1316 /* NOTREACHED */
1317 }
1318
1319 /*
1320 * A hack so -H can have an optional modifier as -H[:flags].
1321 *
1322 * This depends a bit on the internals of getopt(). In particular,
1323 * for flags expecting an argument, argv[optind-1] must contain the
1324 * optarg and optarg must point to a substring of argv[optind-1] not a
1325 * copy of it.
1326 */
1327 PUBLIC int
1328 get_Hflag(char **argv)
1329 {
1330 int flags;
1331
1332 flags = ~CMMASK;
1333
1334 if (optarg == NULL) /* We had an error, just get the flags. */
1335 return flags;
1336
1337 if (*optarg != ':' || optarg == argv[optind - 1]) {
1338 optind--;
1339 optreset = 1;
1340 if (optarg != argv[optind]) {
1341 static char temparg[LINESIZE];
1342 size_t optlen;
1343 size_t arglen;
1344 char *p;
1345
1346 optlen = strlen(optarg);
1347 arglen = strlen(argv[optind]);
1348 p = argv[optind] + arglen - optlen;
1349 optlen = MIN(optlen, sizeof(temparg) - 1);
1350 temparg[0] = '-';
1351 (void)memmove(temparg + 1, p, optlen + 1);
1352 argv[optind] = temparg;
1353 }
1354 }
1355 else {
1356 flags = get_colmod(flags, optarg + 1);
1357 }
1358 return flags;
1359 }
1360