984d80bdf20bf4581ce47ff78bdc962bd10cac1e
[mmh] / uip / pick.c
1 /*
2 ** pick.c -- search for messages by content
3 **
4 ** This code is Copyright (c) 2002, 2008, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <h/tws.h>
11 #include <h/utils.h>
12 #include <unistd.h>
13 #include <locale.h>
14 #include <sysexits.h>
15
16 #ifdef HAVE_SYS_TIME_H
17 # include <sys/time.h>
18 #endif
19 #include <time.h>
20
21 static struct swit switches[] = {
22 #define ANDSW  0
23         { "and", 0 },
24 #define ORSW  1
25         { "or", 0 },
26 #define NOTSW  2
27         { "not", 0 },
28 #define LBRSW  3
29         { "lbrace", 0 },
30 #define RBRSW  4
31         { "rbrace", 0 },
32 #define CCSW  5
33         { "cc  pattern", 0 },
34 #define DATESW  6
35         { "date  pattern", 0 },
36 #define FROMSW  7
37         { "from  pattern", 0 },
38 #define SRCHSW  8
39         { "search  pattern", 0 },
40 #define SUBJSW  9
41         { "subject  pattern", 0 },
42 #define TOSW  10
43         { "to  pattern", 0 },
44 #define OTHRSW  11
45         { "-othercomponent  pattern", 0 },
46 #define AFTRSW  12
47         { "after date", 0 },
48 #define BEFRSW  13
49         { "before date", 0 },
50 #define DATFDSW  14
51         { "datefield field", 5 },  /* 5 chars required to differ from -date */
52 #define SEQSW  15
53         { "sequence name", 0 },
54 #define PUBLSW  16
55         { "public", 0 },
56 #define NPUBLSW  17
57         { "nopublic", 2 },
58 #define ZEROSW  18
59         { "zero", 0 },
60 #define NZEROSW  19
61         { "nozero", 2 },
62 #define LISTSW  20
63         { "list", 0 },
64 #define NLISTSW  21
65         { "nolist", 2 },
66 #define VERSIONSW  22
67         { "Version", 0 },
68 #define HELPSW  23
69         { "help", 0 },
70         { NULL, 0 }
71 };
72
73 /*
74 ** static prototypes
75 */
76 static int pcompile(char **, char *);
77 static int pmatches(FILE *, int, long, long);
78
79
80 static int listsw = -1;
81
82 void putzero_done();
83
84 int
85 main(int argc, char **argv)
86 {
87         int publicsw = -1, zerosw = 1, vecp = 0;
88         unsigned int seqp = 0;
89         int lo, hi, msgnum;
90         char *maildir, *folder = NULL, buf[100];
91         char *cp, **argp, **arguments;
92         char *seqs[NUMATTRS + 1], *vec[MAXARGS];
93         struct msgs_array msgs = { 0, 0, NULL };
94         struct msgs *mp;
95         FILE *fp;
96
97         if (atexit(putzero_done) != 0) {
98                 adios(EX_OSERR, NULL, "atexit failed");
99         }
100
101         setlocale(LC_ALL, "");
102         invo_name = mhbasename(argv[0]);
103
104         /* read user profile/context */
105         context_read();
106
107         arguments = getarguments(invo_name, argc, argv, 1);
108         argp = arguments;
109
110         while ((cp = *argp++)) {
111                 if (*cp == '-') {
112                         if (*++cp == '-') {
113                                 vec[vecp++] = --cp;
114                                 goto pattern;
115                         }
116                         switch (smatch(cp, switches)) {
117                         case AMBIGSW:
118                                 ambigsw(cp, switches);
119                                 listsw = 0;  /* HACK */
120                                 exit(EX_USAGE);
121                         case UNKWNSW:
122                                 adios(EX_USAGE, NULL, "-%s unknown", cp);
123
124                         case HELPSW:
125                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
126                                 print_help(buf, switches, 1);
127                                 listsw = 0;  /* HACK */
128                                 exit(argc == 2 ? EX_OK : EX_USAGE);
129                         case VERSIONSW:
130                                 print_version(invo_name);
131                                 listsw = 0;  /* HACK */
132                                 exit(argc == 2 ? EX_OK : EX_USAGE);
133
134                         case CCSW:
135                         case DATESW:
136                         case FROMSW:
137                         case SUBJSW:
138                         case TOSW:
139                         case DATFDSW:
140                         case AFTRSW:
141                         case BEFRSW:
142                         case SRCHSW:
143                                 vec[vecp++] = --cp;
144                         pattern:
145                                 if (!(cp = *argp++)) /* allow -xyz arguments */
146                                         adios(EX_USAGE, NULL, "missing argument to %s",
147                                                         argp[-2]);
148                                 vec[vecp++] = cp;
149                                 continue;
150                         case OTHRSW:
151                                 adios(EX_SOFTWARE, NULL, "internal error!");
152
153                         case ANDSW:
154                         case ORSW:
155                         case NOTSW:
156                         case LBRSW:
157                         case RBRSW:
158                                 vec[vecp++] = --cp;
159                                 continue;
160
161                         case SEQSW:
162                                 if (!(cp = *argp++) || *cp == '-')
163                                         adios(EX_USAGE, NULL, "missing argument to %s",
164                                                         argp[-2]);
165
166                                 /* check if too many sequences specified */
167                                 if (seqp >= NUMATTRS)
168                                         adios(EX_USAGE, NULL, "too many sequences (more than %d) specified", NUMATTRS);
169
170                                 if (!seq_nameok(cp))
171                                         exit(EX_USAGE);
172
173                                 seqs[seqp++] = cp;
174                                 continue;
175                         case PUBLSW:
176                                 publicsw = 1;
177                                 continue;
178                         case NPUBLSW:
179                                 publicsw = 0;
180                                 continue;
181                         case ZEROSW:
182                                 zerosw++;
183                                 continue;
184                         case NZEROSW:
185                                 zerosw = 0;
186                                 continue;
187
188                         case LISTSW:
189                                 listsw = 1;
190                                 continue;
191                         case NLISTSW:
192                                 listsw = 0;
193                                 continue;
194                         }
195                 }
196                 if (*cp == '+' || *cp == '@') {
197                         if (folder)
198                                 adios(EX_USAGE, NULL, "only one folder at a time!");
199                         else
200                                 folder = getcpy(expandfol(cp));
201                 } else
202                         app_msgarg(&msgs, cp);
203         }
204         vec[vecp] = NULL;
205
206         /*
207         ** If we didn't specify which messages to search,
208         ** then search the whole folder.
209         */
210         if (!msgs.size)
211                 app_msgarg(&msgs, seq_all);
212
213         if (!folder)
214                 folder = getcurfol();
215         maildir = toabsdir(folder);
216
217         if (chdir(maildir) == NOTOK)
218                 adios(EX_OSERR, maildir, "unable to change directory to");
219
220         /* read folder and create message structure */
221         if (!(mp = folder_read(folder)))
222                 adios(EX_IOERR, NULL, "unable to read folder %s", folder);
223
224         /* check for empty folder */
225         if (mp->nummsg == 0)
226                 adios(EX_DATAERR, NULL, "no messages in %s", folder);
227
228         /* parse all the message ranges/sequences and set SELECTED */
229         for (msgnum = 0; msgnum < msgs.size; msgnum++)
230                 if (!m_convert(mp, msgs.msgs[msgnum]))
231                         exit(EX_USAGE);
232         seq_setprev(mp);  /* set the previous-sequence */
233
234         /*
235         ** If we aren't saving the results to a sequence,
236         ** we default to list the results.
237         */
238         if (listsw == -1)
239                 listsw = !seqp;
240
241         if (publicsw == 1 && is_readonly(mp))
242                 adios(EX_NOPERM, NULL, "folder %s is read-only, so -public not allowed",
243                                 folder);
244
245         if (!pcompile(vec, NULL))
246                 exit(EX_SOFTWARE);
247
248         lo = mp->lowsel;
249         hi = mp->hghsel;
250
251         /*
252         ** If printing message numbers to standard out,
253         ** force line buffering on.
254         */
255         if (listsw)
256                 setvbuf(stdout, NULL, _IOLBF, 0);
257
258         /*
259         ** Scan through all the SELECTED messages and check for a
260         ** match.  If the message does not match, then unselect it.
261         */
262         for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
263                 if (is_selected(mp, msgnum)) {
264                         if ((fp = fopen(cp = m_name(msgnum), "r")) == NULL)
265                                 admonish(cp, "unable to read message");
266                         if (fp && pmatches(fp, msgnum, 0L, 0L)) {
267                                 if (msgnum < lo)
268                                         lo = msgnum;
269                                 if (msgnum > hi)
270                                         hi = msgnum;
271
272                                 if (listsw)
273                                         printf("%s\n", m_name(msgnum));
274                         } else {
275                                 /* if it doesn't match, then unselect it */
276                                 unset_selected(mp, msgnum);
277                                 mp->numsel--;
278                         }
279                         if (fp)
280                                 fclose(fp);
281                 }
282         }
283
284         mp->lowsel = lo;
285         mp->hghsel = hi;
286
287         if (mp->numsel <= 0)
288                 adios(EX_DATAERR, NULL, "no messages match specification");
289
290         seqs[seqp] = NULL;
291
292         /*
293         ** Add the matching messages to sequences
294         */
295         for (seqp = 0; seqs[seqp]; seqp++)
296                 if (!seq_addsel(mp, seqs[seqp], publicsw, zerosw))
297                         exit(EX_USAGE);
298
299         /*
300         ** Print total matched if not printing each matched message number.
301         */
302         if (!listsw) {
303                 printf("%d hit%s\n", mp->numsel, mp->numsel == 1 ? "" : "s");
304         }
305
306         context_replace(curfolder, folder);  /* update current folder */
307         seq_save(mp);  /* synchronize message sequences */
308         context_save();  /* save the context file */
309         folder_free(mp);  /* free folder/message structure */
310         listsw = 0; /* HACK */
311         return 0;
312 }
313
314
315 void
316 putzero_done()
317 {
318         if (listsw && !isatty(fileno(stdout)))
319                 printf("0\n");
320 }
321
322
323 static struct swit parswit[] = {
324 #define PRAND   0
325         { "and", 0 },
326 #define PROR    1
327         { "or", 0 },
328 #define PRNOT   2
329         { "not", 0 },
330 #define PRLBR   3
331         { "lbrace", 0 },
332 #define PRRBR   4
333         { "rbrace", 0 },
334 #define PRCC    5
335         { "cc  pattern", 0 },
336 #define PRDATE  6
337         { "date  pattern", 0 },
338 #define PRFROM  7
339         { "from  pattern", 0 },
340 #define PRSRCH  8
341         { "search  pattern", 0 },
342 #define PRSUBJ  9
343         { "subject  pattern", 0 },
344 #define PRTO   10
345         { "to  pattern", 0 },
346 #define PROTHR 11
347         { "-othercomponent  pattern", 15 },
348 #define PRAFTR 12
349         { "after date", 0 },
350 #define PRBEFR 13
351         { "before date", 0 },
352 #define PRDATF 14
353         { "datefield field", 5 },
354         { NULL, 0 }
355 };
356
357 /* DEFINITIONS FOR PATTERN MATCHING */
358
359 /*
360 ** We really should be using re_comp() and re_exec() here.  Unfortunately,
361 ** pick advertises that lowercase characters matches characters of both
362 ** cases.  Since re_exec() doesn't exhibit this behavior, we are stuck
363 ** with this version.  Furthermore, we need to be able to save and restore
364 ** the state of the pattern matcher in order to do things "efficiently".
365 **
366 ** The matching power of this algorithm isn't as powerful as the re_xxx()
367 ** routines (no \(xxx\) and \n constructs).  Such is life.
368 */
369
370 #define CCHR       2
371 #define CDOT       4
372 #define CCL        6
373 #define NCCL       8
374 #define CDOL      10
375 #define CEOF      11
376
377 #define STAR      01
378
379 #define LBSIZE  1024
380 #define ESIZE   1024
381
382
383 static char linebuf[LBSIZE + 1];
384 static char decoded_linebuf[LBSIZE + 1];
385
386 /* the magic array for case-independence */
387 static char cc[] = {
388         0000,0001,0002,0003,0004,0005,0006,0007,
389         0010,0011,0012,0013,0014,0015,0016,0017,
390         0020,0021,0022,0023,0024,0025,0026,0027,
391         0030,0031,0032,0033,0034,0035,0036,0037,
392         0040,0041,0042,0043,0044,0045,0046,0047,
393         0050,0051,0052,0053,0054,0055,0056,0057,
394         0060,0061,0062,0063,0064,0065,0066,0067,
395         0070,0071,0072,0073,0074,0075,0076,0077,
396         0100,0141,0142,0143,0144,0145,0146,0147,
397         0150,0151,0152,0153,0154,0155,0156,0157,
398         0160,0161,0162,0163,0164,0165,0166,0167,
399         0170,0171,0172,0133,0134,0135,0136,0137,
400         0140,0141,0142,0143,0144,0145,0146,0147,
401         0150,0151,0152,0153,0154,0155,0156,0157,
402         0160,0161,0162,0163,0164,0165,0166,0167,
403         0170,0171,0172,0173,0174,0175,0176,0177,
404
405         0200,0201,0202,0203,0204,0205,0206,0207,
406         0210,0211,0212,0213,0214,0215,0216,0217,
407         0220,0221,0222,0223,0224,0225,0226,0227,
408         0230,0231,0232,0233,0234,0235,0236,0237,
409         0240,0241,0242,0243,0244,0245,0246,0247,
410         0250,0251,0252,0253,0254,0255,0256,0257,
411         0260,0261,0262,0263,0264,0265,0266,0267,
412         0270,0271,0272,0273,0274,0275,0276,0277,
413         0300,0301,0302,0303,0304,0305,0306,0307,
414         0310,0311,0312,0313,0314,0315,0316,0317,
415         0320,0321,0322,0323,0324,0325,0326,0327,
416         0330,0331,0332,0333,0334,0335,0336,0337,
417         0340,0341,0342,0343,0344,0345,0346,0347,
418         0350,0351,0352,0353,0354,0355,0356,0357,
419         0360,0361,0362,0363,0364,0365,0366,0367,
420         0370,0371,0372,0373,0374,0375,0376,0377,
421 };
422
423 /*
424 ** DEFINITIONS FOR NEXUS
425 */
426
427 #define nxtarg() (*argp ? *argp++ : NULL)
428 #define prvarg() argp--
429
430 #define padvise if (!talked++) advise
431
432 struct nexus {
433         int (*n_action)();
434
435         union {
436                 /* for {OR,AND,NOT}action */
437                 struct {
438                         struct nexus *un_L_child;
439                         struct nexus *un_R_child;
440                 } st1;
441
442                 /* for GREPaction */
443                 struct {
444                         int   un_header;
445                         int   un_circf;
446                         char  un_expbuf[ESIZE];
447                         char *un_patbuf;
448                 } st2;
449
450                 /* for TWSaction */
451                 struct {
452                         char *un_datef;
453                         int   un_after;
454                         struct tws un_tws;
455                 } st3;
456         } un;
457 };
458
459 #define n_L_child un.st1.un_L_child
460 #define n_R_child un.st1.un_R_child
461
462 #define n_header un.st2.un_header
463 #define n_circf  un.st2.un_circf
464 #define n_expbuf un.st2.un_expbuf
465 #define n_patbuf un.st2.un_patbuf
466
467 #define n_datef  un.st3.un_datef
468 #define n_after  un.st3.un_after
469 #define n_tws    un.st3.un_tws
470
471 static int talked;
472 static int pdebug = 0;
473
474 static char *datesw;
475 static char **argp;
476
477 static struct nexus *head;
478
479 /*
480 ** prototypes for date routines
481 */
482 static struct tws *tws_parse(char *, int);
483 static struct tws *tws_special(char *);
484
485 /*
486 ** static prototypes
487 */
488 static void PRaction(struct nexus *, int);
489 static int gcompile(struct nexus *, char *);
490 static int advance(char *, char *);
491 static int cclass(unsigned char *, int, int);
492 static int tcompile(char *, struct tws *, int);
493
494 static struct nexus *parse(void);
495 static struct nexus *nexp1(void);
496 static struct nexus *nexp2(void);
497 static struct nexus *nexp3(void);
498 static struct nexus *newnexus(int (*)());
499
500 static int ORaction();
501 static int ANDaction();
502 static int NOTaction();
503 static int GREPaction();
504 static int TWSaction();
505
506
507 static int
508 pcompile(char **vec, char *date)
509 {
510         char *cp;
511
512         if ((cp = getenv("MHPDEBUG")) && *cp)
513                 pdebug++;
514
515         argp = vec;
516         if ((datesw = date) == NULL)
517                 datesw = "date";
518         talked = 0;
519
520         if ((head = parse()) == NULL)
521                 return (talked ? 0 : 1);
522
523         if (*argp) {
524                 padvise(NULL, "%s unexpected", *argp);
525                 return 0;
526         }
527
528         return 1;
529 }
530
531
532 static struct nexus *
533 parse(void)
534 {
535         char  *cp;
536         struct nexus *n, *o;
537
538         if ((n = nexp1()) == NULL || (cp = nxtarg()) == NULL)
539                 return n;
540
541         if (*cp != '-') {
542                 padvise(NULL, "%s unexpected", cp);
543                 return NULL;
544         }
545
546         if (*++cp == '-')
547                 goto header;
548         switch (smatch(cp, parswit)) {
549         case AMBIGSW:
550                 ambigsw(cp, parswit);
551                 talked++;
552                 return NULL;
553         case UNKWNSW:
554                 fprintf(stderr, "-%s unknown\n", cp);
555                 talked++;
556                 return NULL;
557
558         case PROR:
559                 o = newnexus(ORaction);
560                 o->n_L_child = n;
561                 if ((o->n_R_child = parse()))
562                         return o;
563                 padvise(NULL, "missing disjunctive");
564                 return NULL;
565
566 header: ;
567         default:
568                 prvarg();
569                 return n;
570         }
571 }
572
573 static struct nexus *
574 nexp1(void)
575 {
576         char *cp;
577         struct nexus *n, *o;
578
579         if ((n = nexp2()) == NULL || (cp = nxtarg()) == NULL)
580                 return n;
581
582         if (*cp != '-') {
583                 padvise(NULL, "%s unexpected", cp);
584                 return NULL;
585         }
586
587         if (*++cp == '-')
588                 goto header;
589         switch (smatch(cp, parswit)) {
590         case AMBIGSW:
591                 ambigsw(cp, parswit);
592                 talked++;
593                 return NULL;
594         case UNKWNSW:
595                 fprintf(stderr, "-%s unknown\n", cp);
596                 talked++;
597                 return NULL;
598
599         case PRAND:
600                 o = newnexus(ANDaction);
601                 o->n_L_child = n;
602                 if ((o->n_R_child = nexp1()))
603                         return o;
604                 padvise(NULL, "missing conjunctive");
605                 return NULL;
606
607 header: ;
608         default:
609                 prvarg();
610                 return n;
611         }
612 }
613
614
615 static struct nexus *
616 nexp2(void)
617 {
618         char *cp;
619         struct nexus *n;
620
621         if ((cp = nxtarg()) == NULL)
622                 return NULL;
623
624         if (*cp != '-') {
625                 prvarg();
626                 return nexp3();
627         }
628
629         if (*++cp == '-')
630                 goto header;
631         switch (smatch(cp, parswit)) {
632         case AMBIGSW:
633                 ambigsw(cp, parswit);
634                 talked++;
635                 return NULL;
636         case UNKWNSW:
637                 fprintf(stderr, "-%s unknown\n", cp);
638                 talked++;
639                 return NULL;
640
641         case PRNOT:
642                 n = newnexus(NOTaction);
643                 if ((n->n_L_child = nexp3()))
644                         return n;
645                 padvise(NULL, "missing negation");
646                 return NULL;
647
648 header: ;
649         default:
650                 prvarg();
651                 return nexp3();
652         }
653 }
654
655 static struct nexus *
656 nexp3(void)
657 {
658         int i;
659         char *cp, *dp;
660         char buffer[BUFSIZ], temp[64];
661         struct nexus *n;
662
663         if ((cp = nxtarg()) == NULL)
664                 return NULL;
665
666         if (*cp != '-') {
667                 padvise(NULL, "%s unexpected", cp);
668                 return NULL;
669         }
670
671         if (*++cp == '-') {
672                 dp = ++cp;
673                 goto header;
674         }
675         switch (i = smatch(cp, parswit)) {
676         case AMBIGSW:
677                 ambigsw(cp, parswit);
678                 talked++;
679                 return NULL;
680         case UNKWNSW:
681                 fprintf(stderr, "-%s unknown\n", cp);
682                 talked++;
683                 return NULL;
684
685         case PRLBR:
686                 if ((n = parse()) == NULL) {
687                         padvise(NULL, "missing group");
688                         return NULL;
689                 }
690                 if ((cp = nxtarg()) == NULL) {
691                         padvise(NULL, "missing -rbrace");
692                         return NULL;
693                 }
694                 if (*cp++ == '-' && smatch(cp, parswit) == PRRBR)
695                         return n;
696                 padvise(NULL, "%s unexpected", --cp);
697                 return NULL;
698
699         default:
700                 prvarg();
701                 return NULL;
702
703         case PRCC:
704         case PRDATE:
705         case PRFROM:
706         case PRTO:
707         case PRSUBJ:
708                 strncpy(temp, parswit[i].sw, sizeof(temp));
709                 temp[sizeof(temp) - 1] = '\0';
710                 dp = *brkstring(temp, " ", NULL);
711 header: ;
712                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
713                         padvise(NULL, "missing argument to %s", argp[-2]);
714                         return NULL;
715                 }
716                 n = newnexus(GREPaction);
717                 n->n_header = 1;
718                 snprintf(buffer, sizeof(buffer), "^%s[ \t]*:.*%s", dp, cp);
719                 dp = buffer;
720                 goto pattern;
721
722         case PRSRCH:
723                 n = newnexus(GREPaction);
724                 n->n_header = 0;
725                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
726                         padvise(NULL, "missing argument to %s", argp[-2]);
727                         return NULL;
728                 }
729                 dp = cp;
730 pattern: ;
731                 if (!gcompile(n, dp)) {
732                         padvise(NULL, "pattern error in %s %s", argp[-2], cp);
733                         return NULL;
734                 }
735                 n->n_patbuf = getcpy(dp);
736                 return n;
737
738         case PROTHR:
739                 padvise(NULL, "internal error!");
740                 return NULL;
741
742         case PRDATF:
743                 if (!(datesw = nxtarg()) || *datesw == '-') {
744                         padvise(NULL, "missing argument to %s",
745                                         argp[-2]);
746                         return NULL;
747                 }
748                 return nexp3();
749
750         case PRAFTR:
751         case PRBEFR:
752                 if (!(cp = nxtarg())) {  /* allow -xyz arguments */
753                         padvise(NULL, "missing argument to %s", argp[-2]);
754                         return NULL;
755                 }
756                 n = newnexus(TWSaction);
757                 n->n_datef = datesw;
758                 if (!tcompile(cp, &n->n_tws, n->n_after = i == PRAFTR)) {
759                         padvise(NULL, "unable to parse %s %s", argp[-2], cp);
760                         return NULL;
761                 }
762                 return n;
763         }
764 }
765
766
767 static struct nexus *
768 newnexus(int (*action)())
769 {
770         struct nexus *p;
771
772         if ((p = (struct nexus *) mh_xcalloc((size_t) 1, sizeof *p)) == NULL)
773                 adios(EX_OSERR, NULL, "unable to allocate component storage");
774
775         p->n_action = action;
776         return p;
777 }
778
779
780 #define args(a)  a, fp, msgnum, start, stop
781 #define params   args(n)
782 #define plist    \
783         struct nexus  *n; \
784         FILE *fp; \
785         int msgnum; \
786         long start, \
787         stop;
788
789 static int
790 pmatches(FILE *fp, int msgnum, long start, long stop)
791 {
792         if (!head)
793                 return 1;
794
795         if (!talked++ && pdebug)
796                 PRaction(head, 0);
797
798         return (*head->n_action) (args(head));
799 }
800
801
802 static void
803 PRaction(struct nexus *n, int level)
804 {
805         int i;
806
807         for (i = 0; i < level; i++)
808                 fprintf(stderr, "| ");
809
810         if (n->n_action == ORaction) {
811                 fprintf(stderr, "OR\n");
812                 PRaction(n->n_L_child, level + 1);
813                 PRaction(n->n_R_child, level + 1);
814                 return;
815         }
816         if (n->n_action == ANDaction) {
817                 fprintf(stderr, "AND\n");
818                 PRaction(n->n_L_child, level + 1);
819                 PRaction(n->n_R_child, level + 1);
820                 return;
821         }
822         if (n->n_action == NOTaction) {
823                 fprintf(stderr, "NOT\n");
824                 PRaction(n->n_L_child, level + 1);
825                 return;
826         }
827         if (n->n_action == GREPaction) {
828                 fprintf(stderr, "PATTERN(%s) %s\n",
829                                 n->n_header ? "header" : "body", n->n_patbuf);
830                 return;
831         }
832         if (n->n_action == TWSaction) {
833                 fprintf(stderr, "TEMPORAL(%s) %s: %s\n",
834                                 n->n_after ? "after" : "before", n->n_datef,
835                                 dasctime(&n->n_tws));
836                 return;
837         }
838         fprintf(stderr, "UNKNOWN(0x%x)\n",
839                          (unsigned int)(unsigned long) (*n->n_action));
840 }
841
842
843 static int
844 ORaction(params)
845 plist
846 {
847         if ((*n->n_L_child->n_action) (args(n->n_L_child)))
848                 return 1;
849         return (*n->n_R_child->n_action) (args(n->n_R_child));
850 }
851
852
853 static int
854 ANDaction(params)
855 plist
856 {
857         if (!(*n->n_L_child->n_action) (args(n->n_L_child)))
858                 return 0;
859         return (*n->n_R_child->n_action) (args(n->n_R_child));
860 }
861
862
863 static int
864 NOTaction(params)
865 plist
866 {
867         return (!(*n->n_L_child->n_action) (args(n->n_L_child)));
868 }
869
870
871 static int
872 gcompile(struct nexus *n, char *astr)
873 {
874         int c;
875         int cclcnt;
876         unsigned char *ep, *dp, *sp, *lastep = 0;
877
878         dp = (ep = n->n_expbuf) + sizeof n->n_expbuf;
879         sp = astr;
880         if (*sp == '^') {
881                 n->n_circf = 1;
882                 sp++;
883         }
884         else
885                 n->n_circf = 0;
886         for (;;) {
887                 if (ep >= dp)
888                         goto cerror;
889                 if ((c = *sp++) != '*')
890                         lastep = ep;
891                 switch (c) {
892                 case '\0':
893                         *ep++ = CEOF;
894                         return 1;
895
896                 case '.':
897                         *ep++ = CDOT;
898                         continue;
899
900                 case '*':
901                         if (lastep == 0)
902                                 goto defchar;
903                         *lastep |= STAR;
904                         continue;
905
906                 case '$':
907                         if (*sp != '\0')
908                                 goto defchar;
909                         *ep++ = CDOL;
910                         continue;
911
912                 case '[':
913                         *ep++ = CCL;
914                         *ep++ = 0;
915                         cclcnt = 0;
916                         if ((c = *sp++) == '^') {
917                                 c = *sp++;
918                                 ep[-2] = NCCL;
919                         }
920                         if (c == '-') {
921                                 *ep++ = c;
922                                 cclcnt++;
923                                 c = *sp++;
924                         }
925                         do {
926                                 if (c == '-' && *sp != '\0' && *sp != ']') {
927                                         for (c = ep[-1]+1; c < *sp; c++) {
928                                                         *ep++ = c;
929                                                         cclcnt++;
930                                                 if (c == '\0' || ep >= dp)
931                                                         goto cerror;
932                                         }
933                                 } else {
934                                         *ep++ = c;
935                                         cclcnt++;
936                                         if (c == '\0' || ep >= dp)
937                                                 goto cerror;
938                                 }
939                         } while ((c = *sp++) != ']');
940                         if (cclcnt > 255)
941                                 goto cerror;
942                         lastep[1] = cclcnt;
943                         continue;
944
945                         case '\\':
946                                 if ((c = *sp++) == '\0')
947                                 goto cerror;
948 defchar:
949                 default:
950                         *ep++ = CCHR;
951                         *ep++ = c;
952                 }
953         }
954
955 cerror: ;
956         return 0;
957 }
958
959
960 static int
961 GREPaction(params)
962 plist
963 {
964         int c, body, lf;
965         long pos = start;
966         char *p1, *p2, *ebp, *cbp;
967         char ibuf[BUFSIZ];
968
969         fseek(fp, start, SEEK_SET);
970         body = 0;
971         ebp = cbp = ibuf;
972         for (;;) {
973                 if (body && n->n_header)
974                         return 0;
975                 p1 = linebuf;
976                 p2 = cbp;
977                 lf = 0;
978                 for (;;) {
979                         if (p2 >= ebp) {
980                                 if (fgets(ibuf, sizeof ibuf, fp) == NULL
981                                                 || (stop && pos >= stop)) {
982                                         if (lf)
983                                                 break;
984                                         return 0;
985                                 }
986                                 pos += (long) strlen(ibuf);
987                                 p2 = ibuf;
988                                 ebp = ibuf + strlen(ibuf);
989                         }
990                         c = *p2++;
991                         if (lf && c != '\n') {
992                                 if (c != ' ' && c != '\t') {
993                                         --p2;
994                                         break;
995                                 }
996                                 else
997                                         lf = 0;
998                         }
999                         if (c == '\n') {
1000                                 if (body)
1001                                         break;
1002                                 else {
1003                                         if (lf) {
1004                                                 body++;
1005                                                 break;
1006                                         }
1007                                         lf++;
1008                                         c = ' ';
1009                                 }
1010                         }
1011                         if (c && p1 < &linebuf[LBSIZE - 1])
1012                                 *p1++ = c;
1013                 }
1014
1015                 *p1++ = 0;
1016                 cbp = p2;
1017                 p1 = linebuf;
1018                 p2 = n->n_expbuf;
1019
1020                 /*
1021                 ** Attempt to decode as a MIME header.  If it's the
1022                 ** last header, body will be 1 and lf will be at least 1.
1023                 */
1024                 if ((body == 0 || lf > 0) && decode_rfc2047(linebuf,
1025                                 decoded_linebuf, sizeof decoded_linebuf)) {
1026                         p1 = decoded_linebuf;
1027                 }
1028
1029                 if (n->n_circf) {
1030                         if (advance(p1, p2)) 
1031                                 return 1;
1032                         continue;
1033                 }
1034
1035                 if (*p2 == CCHR) {
1036                         c = p2[1];
1037                         do {
1038                                 if (*p1 == c || cc[(unsigned char)*p1] == c)
1039                                         if (advance(p1, p2))
1040                                                 return 1;
1041                         } while (*p1++);
1042                         continue;
1043                 }
1044
1045                 do {
1046                         if (advance(p1, p2))
1047                                 return 1;
1048                 } while (*p1++);
1049         }
1050 }
1051
1052
1053 static int
1054 advance(char *alp, char *aep)
1055 {
1056         unsigned char *lp, *ep, *curlp;
1057
1058         lp = (unsigned char *)alp;
1059         ep = (unsigned char *)aep;
1060         for (;;)
1061                 switch (*ep++) {
1062                 case CCHR:
1063                         if (*ep++ == *lp++ || ep[-1] == cc[lp[-1]])
1064                                 continue;
1065                         return 0;
1066
1067                 case CDOT:
1068                         if (*lp++)
1069                                 continue;
1070                         return 0;
1071
1072                 case CDOL:
1073                         if (*lp == 0)
1074                                 continue;
1075                         return 0;
1076
1077                 case CEOF:
1078                         return 1;
1079
1080                 case CCL:
1081                         if (cclass(ep, *lp++, 1)) {
1082                                 ep += *ep + 1;
1083                                 continue;
1084                         }
1085                         return 0;
1086
1087                 case NCCL:
1088                         if (cclass(ep, *lp++, 0)) {
1089                                 ep += *ep + 1;
1090                                 continue;
1091                         }
1092                         return 0;
1093
1094                 case CDOT | STAR:
1095                         curlp = lp;
1096                         while (*lp++)
1097                                 continue;
1098                         goto star;
1099
1100                 case CCHR | STAR:
1101                         curlp = lp;
1102                         while (*lp++ == *ep || cc[lp[-1]] == *ep)
1103                                 continue;
1104                         ep++;
1105                         goto star;
1106
1107                 case CCL | STAR:
1108                 case NCCL | STAR:
1109                         curlp = lp;
1110                         while (cclass(ep, *lp++, ep[-1] == (CCL | STAR)))
1111                                 continue;
1112                         ep += *ep + 1;
1113                         goto star;
1114
1115 star:
1116                         do {
1117                                 lp--;
1118                                 if (advance(lp, ep))
1119                                         return (1);
1120                         } while (lp > curlp);
1121                         return 0;
1122
1123                 default:
1124                         admonish(NULL, "advance() botch -- you lose big");
1125                         return 0;
1126                 }
1127 }
1128
1129
1130 static int
1131 cclass(unsigned char *aset, int ac, int af)
1132 {
1133         unsigned int n;
1134         unsigned char c, *set;
1135
1136         set = aset;
1137         if ((c = ac) == 0)
1138                 return (0);
1139
1140         n = *set++;
1141         while (n--)
1142                 if (*set++ == c || set[-1] == cc[c])
1143                         return (af);
1144
1145         return (!af);
1146 }
1147
1148
1149 static int
1150 tcompile(char *ap, struct tws *tb, int isafter)
1151 {
1152         struct tws *tw;
1153
1154         if ((tw = tws_parse(ap, isafter)) == NULL)
1155                 return 0;
1156
1157         twscopy(tb, tw);
1158         return 1;
1159 }
1160
1161
1162 static struct tws *
1163 tws_parse(char *ap, int isafter)
1164 {
1165         char buffer[BUFSIZ];
1166         struct tws *tw, *ts;
1167
1168         if ((tw = tws_special(ap)) != NULL) {
1169                 tw->tw_sec = tw->tw_min = isafter ? 59 : 0;
1170                 tw->tw_hour = isafter ? 23 : 0;
1171                 return tw;
1172         }
1173         if ((tw = dparsetime(ap)) != NULL)
1174                 return tw;
1175
1176         if ((ts = dlocaltimenow()) == NULL)
1177                 return NULL;
1178
1179         snprintf(buffer, sizeof(buffer), "%s %s", ap, dtwszone(ts));
1180         if ((tw = dparsetime(buffer)) != NULL)
1181                 return tw;
1182
1183         snprintf(buffer, sizeof(buffer), "%s %02d:%02d:%02d %s", ap,
1184                         ts->tw_hour, ts->tw_min, ts->tw_sec, dtwszone(ts));
1185         if ((tw = dparsetime(buffer)) != NULL)
1186                 return tw;
1187
1188         snprintf(buffer, sizeof(buffer), "%02d %s %04d %s",
1189                         ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year, ap);
1190         if ((tw = dparsetime(buffer)) != NULL)
1191                 return tw;
1192
1193         snprintf(buffer, sizeof(buffer), "%02d %s %04d %s %s",
1194                         ts->tw_mday, tw_moty[ts->tw_mon], ts->tw_year,
1195                         ap, dtwszone(ts));
1196         if ((tw = dparsetime(buffer)) != NULL)
1197                 return tw;
1198
1199         return NULL;
1200 }
1201
1202
1203 static struct tws *
1204 tws_special(char *ap)
1205 {
1206         int i;
1207         time_t clock;
1208         struct tws *tw;
1209
1210         time(&clock);
1211         if (!mh_strcasecmp(ap, "today"))
1212                 return dlocaltime(&clock);
1213         if (!mh_strcasecmp(ap, "yesterday")) {
1214                 clock -= (long) (60 * 60 * 24);
1215                 return dlocaltime(&clock);
1216         }
1217         if (!mh_strcasecmp(ap, "tomorrow")) {
1218                 clock += (long) (60 * 60 * 24);
1219                 return dlocaltime(&clock);
1220         }
1221
1222         for (i = 0; tw_ldotw[i]; i++)
1223                 if (!mh_strcasecmp(ap, tw_ldotw[i]))
1224                         break;
1225         if (tw_ldotw[i]) {
1226                 if ((tw = dlocaltime(&clock)) == NULL)
1227                         return NULL;
1228                 if ((i -= tw->tw_wday) > 0)
1229                         i -= 7;
1230         }
1231         else
1232                 if (*ap != '-')
1233                         return NULL;
1234                 else  /* -ddd days ago */
1235                         i = atoi(ap);  /* we should error check this */
1236
1237         clock += (long) ((60 * 60 * 24) * i);
1238         return dlocaltime(&clock);
1239 }
1240
1241
1242 static int
1243 TWSaction(params)
1244 plist
1245 {
1246         int state;
1247         char *bp;
1248         char buf[BUFSIZ], name[NAMESZ];
1249         struct tws *tw;
1250
1251         fseek(fp, start, SEEK_SET);
1252         for (state = FLD, bp = NULL;;) {
1253                 switch (state = m_getfld(state, name, buf, sizeof buf, fp)) {
1254                 case FLD:
1255                 case FLDPLUS:
1256                         if (bp != NULL) {
1257                                 free(bp);
1258                                 bp = NULL;
1259                         }
1260                         bp = getcpy(buf);
1261                         while (state == FLDPLUS) {
1262                                 state = m_getfld(state, name, buf,
1263                                                 sizeof buf, fp);
1264                                 bp = add(buf, bp);
1265                         }
1266                         if (!mh_strcasecmp(name, n->n_datef))
1267                                 break;
1268                         continue;
1269
1270                 case BODY:
1271                 case FILEEOF:
1272                 case LENERR:
1273                 case FMTERR:
1274                         if (state == LENERR || state == FMTERR)
1275                                 advise(NULL, "format error in message %d", msgnum);
1276                         if (bp != NULL)
1277                                 free(bp);
1278                         return 0;
1279
1280                 default:
1281                         adios(EX_SOFTWARE, NULL, "internal error -- you lose");
1282                 }
1283                 break;
1284         }
1285
1286         if ((tw = dparsetime(bp)) == NULL)
1287                 advise(NULL, "unable to parse %s field in message %d, matching...",
1288                                 n->n_datef, msgnum), state = 1;
1289         else
1290                 state = n->n_after ? (twsort(tw, &n->n_tws) > 0)
1291                         : (twsort(tw, &n->n_tws) < 0);
1292
1293         if (bp != NULL)
1294                 free(bp);
1295         return state;
1296 }