ef5f86099601f52b3d260b9a844aef2d45c208ab
[mmh] / uip / mhl.c
1 /*
2 ** mhl.c -- the nmh message listing program
3 **
4 ** This code is Copyright (c) 2002, 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/signals.h>
11 #include <h/addrsbr.h>
12 #include <h/fmt_scan.h>
13 #include <h/tws.h>
14 #include <h/utils.h>
15 #include <setjmp.h>
16 #include <signal.h>
17
18 /*
19 ** MAJOR BUG:
20 ** for a component containing addresses, ADDRFMT, if COMPRESS is also
21 ** set, then addresses get split wrong (not at the spaces between commas).
22 ** To fix this correctly, putstr() should know about "atomic" strings that
23 ** must NOT be broken across lines.  That's too difficult for right now
24 ** (it turns out that there are a number of degernate cases), so in
25 ** oneline(), instead of
26 **
27 **       (*onelp == '\n' && !onelp[1])
28 **
29 ** being a terminating condition,
30 **
31 **       (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT)))
32 **
33 ** is used instead.  This cuts the line prematurely, and gives us a much
34 ** better chance of getting things right.
35 */
36
37 #define ONECOMP  0
38 #define TWOCOMP  1
39 #define BODYCOMP 2
40
41 #define QUOTE  '\\'
42
43 static struct swit mhlswitches[] = {
44 #define CLRSW  0
45         { "clear", 0 },
46 #define NCLRSW  1
47         { "noclear", 0 },
48 #define FOLDSW  2
49         { "folder +folder", 0 },
50 #define FORMSW  3
51         { "form formfile", 0 },
52 #define PROGSW  4
53         { "moreproc program", 0 },
54 #define LENSW  5
55         { "length lines", 0 },
56 #define WIDTHSW  6
57         { "width columns", 0 },
58 #define SLEEPSW  7
59         { "sleep seconds",  0 },
60 #define VERSIONSW  8
61         { "version", 0 },
62 #define HELPSW  9
63         { "help", 0 },
64 #define FORW1SW  10
65         { "forward", -7 },
66 #define FORW2SW  11
67         { "forwall", -7 },
68 #define DGSTSW  12
69         { "digest list", -6 },
70 #define VOLUMSW  13
71         { "volume number", -6 },
72 #define ISSUESW  14
73         { "issue number", -5 },
74 #define NBODYSW  15
75         { "nobody", -6 },
76         { NULL, 0 }
77 };
78
79 #define NOCOMPONENT 0x000001  /* don't show component name   */
80 #define UPPERCASE   0x000002  /* display in all upper case   */
81 #define CENTER      0x000004  /* center line                 */
82 #define CLEARTEXT   0x000008  /* cleartext                   */
83 #define EXTRA       0x000010  /* an "extra" component        */
84 #define HDROUTPUT   0x000020  /* already output              */
85 #define CLEARSCR    0x000040  /* clear screen                */
86 #define LEFTADJUST  0x000080  /* left justify multiple lines */
87 #define COMPRESS    0x000100  /* compress text               */
88 #define ADDRFMT     0x000200  /* contains addresses          */
89 #define DATEFMT     0x000400  /* contains dates              */
90 #define FORMAT      0x000800  /* parse address/date/RFC-2047 field */
91 #define INIT        0x001000  /* initialize component        */
92 #define SPLIT       0x002000  /* split headers (don't concatenate) */
93 #define NONEWLINE   0x004000  /* don't write trailing newline */
94 #define LBITS       "\020\01NOCOMPONENT\02UPPERCASE\03CENTER\04CLEARTEXT\05EXTRA\06HDROUTPUT\07CLEARSCR\010LEFTADJUST\011COMPRESS\012ADDRFMT\013DATEFMT\014FORMAT\015INIT\016SPLIT\017NONEWLINE"
95 #define GFLAGS      (NOCOMPONENT | UPPERCASE | CENTER | LEFTADJUST | COMPRESS | SPLIT)
96
97 struct mcomp {
98         char *c_name;  /* component name */
99         char *c_text;  /* component text */
100         char *c_ovtxt; /* text overflow indicator */
101         char *c_nfs;   /* iff FORMAT */
102         struct format *c_fmt;  /*  .. */
103         int c_offset;  /* left margin indentation */
104         int c_ovoff;   /* overflow indentation */
105         int c_width;   /* width of field */
106         int c_cwidth;  /* width of component */
107         int c_length;  /* length in lines */
108         long c_flags;
109         struct mcomp *c_next;
110 };
111
112 static struct mcomp *msghd = NULL;
113 static struct mcomp *msgtl = NULL;
114 static struct mcomp *fmthd = NULL;
115 static struct mcomp *fmttl = NULL;
116
117 static struct mcomp global = {
118         NULL, NULL, "", NULL, NULL, 0, -1, 80, -1, 40, 0, 0
119 };
120
121 static struct mcomp holder = {
122         NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, NOCOMPONENT, 0
123 };
124
125 struct pair {
126         char *p_name;
127         long p_flags;
128 };
129
130 static struct pair pairs[] = {
131         { "Date", DATEFMT },
132         { "From", ADDRFMT },
133         { "Sender", ADDRFMT },
134         { "Reply-To", ADDRFMT },
135         { "To", ADDRFMT },
136         { "Cc", ADDRFMT },
137         { "Bcc", ADDRFMT },
138         { "Resent-Date", DATEFMT },
139         { "Resent-From", ADDRFMT },
140         { "Resent-Sender", ADDRFMT },
141         { "Resent-Reply-To", ADDRFMT },
142         { "Resent-To", ADDRFMT },
143         { "Resent-Cc", ADDRFMT },
144         { "Resent-Bcc", ADDRFMT },
145         { NULL, 0 }
146 };
147
148 struct triple {
149         char *t_name;
150         long t_on;
151         long t_off;
152 };
153
154 static struct triple triples[] = {
155         { "nocomponent",  NOCOMPONENT, 0 },
156         { "uppercase", UPPERCASE, 0 },
157         { "nouppercase", 0, UPPERCASE },
158         { "center", CENTER, 0 },
159         { "nocenter", 0, CENTER },
160         { "clearscreen", CLEARSCR, 0 },
161         { "noclearscreen", 0, CLEARSCR },
162         { "noclear", 0, CLEARSCR },
163         { "leftadjust", LEFTADJUST, 0 },
164         { "noleftadjust", 0, LEFTADJUST },
165         { "compress", COMPRESS, 0 },
166         { "nocompress", 0, COMPRESS },
167         { "split", SPLIT, 0 },
168         { "nosplit", 0, SPLIT },
169         { "addrfield", ADDRFMT, DATEFMT },
170         { "datefield", DATEFMT, ADDRFMT },
171         { "newline", 0, NONEWLINE },
172         { "nonewline", NONEWLINE, 0 },
173         { NULL, 0, 0 }
174 };
175
176
177 static int clearflg  = 0;
178 static int dobody    = 1;
179 static int forwflg   = 0;
180 static int forwall   = 0;
181
182 static int sleepsw = NOTOK;
183
184 static char *digest = NULL;
185 static int volume = 0;
186 static int issue = 0;
187
188 static int exitstat = 0;
189 static int mhldebug = 0;
190
191 #define PITTY  (-1)
192 #define NOTTY  0
193 static int ontty = NOTTY;
194
195 static int row;
196 static int column;
197
198 static int lm;
199 static int llim;
200 static int ovoff;
201 static int term;
202 static int wid;
203
204 static char *ovtxt;
205
206 static unsigned char *onelp;
207
208 static char *parptr;
209
210 static int num_ignores = 0;
211 static char *ignores[MAXARGS];
212
213 static  jmp_buf env;
214 static  jmp_buf mhlenv;
215
216 static char delim3[] =
217 "\n----------------------------------------------------------------------\n\n";
218 static char delim4[] = "\n------------------------------\n\n";
219
220 static FILE *(*mhl_action) () = (FILE *(*) ()) 0;
221
222
223 /*
224 ** Redefine a couple of functions.
225 ** These are undefined later in the code.
226 */
227 #define adios mhladios
228 #define done  mhldone
229
230 /*
231 ** prototypes
232 */
233 static void mhl_format(char *, int, int);
234 static int evalvar(struct mcomp *);
235 static int ptoi(char *, int *);
236 static int ptos(char *, char **);
237 static char *parse(void);
238 static void process(char *, char *, int, int);
239 static void mhlfile(FILE *, char *, int, int);
240 static int mcomp_flags(char *);
241 static char *mcomp_add(long, char *, char *);
242 static void mcomp_format(struct mcomp *, struct mcomp *);
243 static struct mcomp *add_queue(struct mcomp **, struct mcomp **,
244                 char *, char *, int);
245 static void free_queue(struct mcomp **, struct mcomp **);
246 static void putcomp(struct mcomp *, struct mcomp *, int);
247 static char *oneline(char *, long);
248 static void putstr(char *);
249 static void putch(char);
250 static RETSIGTYPE intrser(int);
251 static RETSIGTYPE pipeser(int);
252 static RETSIGTYPE quitser(int);
253 static void mhladios(char *, char *, ...);
254 static void mhldone(int);
255 static void m_popen(char *);
256
257 void m_pclose(void);
258
259 void clear_screen(void);  /* from termsbr.c */
260 int SOprintf(char *, ...);  /* from termsbr.c */
261 int sc_width(void);  /* from termsbr.c */
262 int sc_length(void);  /* from termsbr.c */
263
264
265 int
266 main(int argc, char **argv)
267 {
268         int length = 0;
269         int i, width = 0, vecp = 0;
270         char *cp, *folder = NULL, *form = NULL;
271         char buf[BUFSIZ], *files[MAXARGS];
272         char **argp, **arguments;
273         char *progsw = NULL;
274
275 #ifdef LOCALE
276         setlocale(LC_ALL, "");
277 #endif
278
279         invo_name = mhbasename(argv[0]);
280
281         /* read user profile/context */
282         context_read();
283
284         arguments = getarguments(invo_name, argc, argv, 1);
285         argp = arguments;
286
287         if ((cp = getenv("MHLDEBUG")) && *cp)
288                 mhldebug++;
289
290         while ((cp = *argp++)) {
291                 if (*cp == '-') {
292                         switch (smatch(++cp, mhlswitches)) {
293                         case AMBIGSW:
294                                 ambigsw(cp, mhlswitches);
295                                 done(1);
296                         case UNKWNSW:
297                                 adios(NULL, "-%s unknown\n", cp);
298
299                         case HELPSW:
300                                 snprintf(buf, sizeof(buf), "%s [switches] [files ...]", invo_name);
301                                 print_help(buf, mhlswitches, 1);
302                                 done(1);
303                         case VERSIONSW:
304                                 print_version(invo_name);
305                                 done(1);
306
307                         case CLRSW:
308                                 clearflg = 1;
309                                 continue;
310                         case NCLRSW:
311                                 clearflg = -1;
312                                 continue;
313
314                         case FOLDSW:
315                                 if (!(folder = *argp++) || *folder == '-')
316                                         adios(NULL, "missing argument to %s",
317                                                         argp[-2]);
318                                 continue;
319                         case FORMSW:
320                                 if (!(form = *argp++) || *form == '-')
321                                         adios(NULL, "missing argument to %s",
322                                                         argp[-2]);
323                                 continue;
324
325                         case SLEEPSW:
326                                 if (!(cp = *argp++) || *cp == '-')
327                                         adios(NULL, "missing argument to %s",
328                                                         argp[-2]);
329                                 sleepsw = atoi(cp);  /* ZERO ok! */
330                                 continue;
331
332                         case PROGSW:
333                                 if (!(progsw = *argp++) || *progsw == '-')
334                                         adios(NULL, "missing argument to %s",
335                                                         argp[-2]);
336                                 continue;
337
338                         case LENSW:
339                                 if (!(cp = *argp++) || *cp == '-')
340                                         adios(NULL, "missing argument to %s",
341                                                         argp[-2]);
342                                 if ((length = atoi(cp)) < 1)
343                                         adios(NULL, "bad argument %s %s",
344                                                         argp[-2], cp);
345                                 continue;
346                         case WIDTHSW:
347                                 if (!(cp = *argp++) || *cp == '-')
348                                         adios(NULL, "missing argument to %s",
349                                                         argp[-2]);
350                                 if ((width = atoi(cp)) < 1)
351                                         adios(NULL, "bad argument %s %s",
352                                                         argp[-2], cp);
353                                 continue;
354
355                         case DGSTSW:
356                                 if (!(digest = *argp++) ||
357                                                 *digest == '-')
358                                         adios(NULL, "missing argument to %s",
359                                                         argp[-2]);
360                                 continue;
361                         case ISSUESW:
362                                 if (!(cp = *argp++) || *cp == '-')
363                                         adios(NULL, "missing argument to %s",
364                                                         argp[-2]);
365                                 if ((issue = atoi(cp)) < 1)
366                                         adios(NULL, "bad argument %s %s",
367                                                         argp[-2], cp);
368                                 continue;
369                         case VOLUMSW:
370                                 if (!(cp = *argp++) || *cp == '-')
371                                         adios(NULL, "missing argument to %s",
372                                                         argp[-2]);
373                                 if ((volume = atoi(cp)) < 1)
374                                         adios(NULL, "bad argument %s %s",
375                                                         argp[-2], cp);
376                                 continue;
377
378                         case FORW2SW:
379                                 forwall++;  /* fall */
380                         case FORW1SW:
381                                 forwflg++;
382                                 clearflg = -1;/* XXX */
383                                 continue;
384
385                         case NBODYSW:
386                                 dobody = 0;
387                                 continue;
388                         }
389                 }
390                 files[vecp++] = cp;
391         }
392
393         if (!folder)
394                 folder = getenv("mhfolder");
395
396         if (isatty(fileno(stdout))) {
397                 if (mhl_action) {
398                         SIGNAL(SIGINT, SIG_IGN);
399                         SIGNAL2(SIGQUIT, quitser);
400                 }
401                 SIGNAL2(SIGPIPE, pipeser);
402                 m_popen(progsw ? progsw : moreproc);
403                 ontty = PITTY;
404         } else {
405                 ontty = NOTTY;
406         }
407
408         mhl_format(form ? form : mhlformat, length, width);
409
410         if (vecp == 0) {
411                 process(folder, NULL, 1, vecp = 1);
412         } else {
413                 for (i = 0; i < vecp; i++)
414                         process(folder, files[i], i + 1, vecp);
415         }
416
417         if (forwall) {
418                 if (digest) {
419                         printf("%s", delim4);
420                         if (volume == 0) {
421                                 snprintf(buf, sizeof(buf),
422                                         "End of %s Digest\n", digest);
423                         } else {
424                                 snprintf(buf, sizeof(buf), "End of %s Digest [Volume %d Issue %d]\n", digest, volume, issue);
425                         }
426                         i = strlen(buf);
427                         for (cp = buf + i; i > 1; i--)
428                                 *cp++ = '*';
429                         *cp++ = '\n';
430                         *cp = 0;
431                         printf("%s", buf);
432                 } else
433                         printf("\n------- End of Forwarded Message%s\n\n",
434                                 vecp > 1 ? "s" : "");
435         }
436
437         fflush(stdout);
438         if (ferror(stdout)) {
439                 adios("output", "error writing");
440         }
441
442         if (clearflg > 0 && ontty == NOTTY)
443                 clear_screen();
444
445         if (ontty == PITTY)
446                 m_pclose();
447
448         return exitstat;
449 }
450
451
452 static void
453 mhl_format(char *file, int length, int width)
454 {
455         int i;
456         char *bp, *cp, **ip;
457         char *ap, buffer[BUFSIZ], name[NAMESZ];
458         struct mcomp *c1;
459         struct stat st;
460         FILE *fp;
461         static dev_t dev = 0;
462         static ino_t ino = 0;
463         static time_t mtime = 0;
464
465         if (fmthd != NULL) {
466                 if (stat(etcpath(file), &st) != NOTOK
467                         && mtime == st.st_mtime
468                         && dev == st.st_dev
469                         && ino == st.st_ino)
470                         goto out;
471                 else
472                         free_queue(&fmthd, &fmttl);
473         }
474
475         if ((fp = fopen(etcpath(file), "r")) == NULL)
476                 adios(file, "unable to open format file");
477
478         if (fstat(fileno(fp), &st) != NOTOK) {
479                 mtime = st.st_mtime;
480                 dev = st.st_dev;
481                 ino = st.st_ino;
482         }
483
484         global.c_ovtxt = global.c_nfs = NULL;
485         global.c_fmt = NULL;
486         global.c_offset = 0;
487         global.c_ovoff = -1;
488         if ((i = sc_width()) > 5)
489                 global.c_width = i;
490         global.c_cwidth = -1;
491         if ((i = sc_length()) > 5)
492                 global.c_length = i - 1;
493         global.c_flags = 0;
494         *(ip = ignores) = NULL;
495
496         while (vfgets(fp, &ap) == OK) {
497                 bp = ap;
498                 if (*bp == ';')
499                         continue;
500
501                 if ((cp = strchr(bp, '\n')))
502                         *cp = 0;
503
504                 if (*bp == ':') {
505                         c1 = add_queue(&fmthd, &fmttl, NULL, bp + 1,
506                                         CLEARTEXT);
507                         continue;
508                 }
509
510                 parptr = bp;
511                 strncpy(name, parse(), sizeof(name));
512                 switch(*parptr) {
513                 case '\0':
514                 case ',':
515                 case '=':
516                         /*
517                         ** Split this list of fields to ignore, and copy
518                         ** it to the end of the current "ignores" list.
519                         */
520                         if (!mh_strcasecmp(name, "ignores")) {
521                                 char **tmparray, **p;
522                                 int n = 0;
523
524                                 /* split the fields */
525                                 tmparray = brkstring(getcpy(++parptr), ",",
526                                                 NULL);
527
528                                 /* count number of fields split */
529                                 p = tmparray;
530                                 while (*p++)
531                                         n++;
532
533                                 /*
534                                 ** copy pointers to split fields
535                                 ** to ignores array
536                                 */
537                                 ip = copyip(tmparray, ip,
538                                                 MAXARGS - num_ignores);
539                                 num_ignores += n;
540                                 continue;
541                         }
542                         parptr = bp;
543                         while (*parptr) {
544                                 if (evalvar(&global))
545                                         adios(NULL, "format file syntax error: %s", bp);
546                                 if (*parptr)
547                                         parptr++;
548                         }
549                         continue;
550
551                 case ':':
552                         c1 = add_queue(&fmthd, &fmttl, name, NULL, INIT);
553                         while (*parptr == ':' || *parptr == ',') {
554                                 parptr++;
555                                 if (evalvar(c1))
556                                         adios(NULL, "format file syntax error: %s", bp);
557                         }
558                         if (!c1->c_nfs && global.c_nfs) {
559                                 if ((c1->c_flags & DATEFMT) &&
560                                                 (global.c_flags & DATEFMT)) {
561                                         c1->c_nfs = getcpy(global.c_nfs);
562                                 } else if ((c1->c_flags & ADDRFMT) &&
563                                                 (global.c_flags & ADDRFMT)) {
564                                         c1->c_nfs = getcpy(global.c_nfs);
565                                 }
566                         }
567                         continue;
568
569                 default:
570                         adios(NULL, "format file syntax error: %s", bp);
571                 }
572         }
573         fclose(fp);
574
575         if (mhldebug) {
576                 for (c1 = fmthd; c1; c1 = c1->c_next) {
577                         fprintf(stderr, "c1: name=\"%s\" text=\"%s\" ovtxt=\"%s\"\n", c1->c_name, c1->c_text, c1->c_ovtxt);
578                         fprintf(stderr, "\tnfs=0x%x fmt=0x%x\n", (unsigned int)(unsigned long) c1->c_nfs, (unsigned int)(unsigned long) c1->c_fmt);
579                         fprintf(stderr, "\toffset=%d ovoff=%d width=%d cwidth=%d length=%d\n", c1->c_offset, c1->c_ovoff, c1->c_width, c1->c_cwidth, c1->c_length);
580                         fprintf (stderr, "\tflags=%s\n", snprintb(buffer, sizeof(buffer), (unsigned) c1->c_flags, LBITS));
581                 }
582         }
583
584 out:
585         if (clearflg == 1) {
586                 global.c_flags |= CLEARSCR;
587         } else {
588                 if (clearflg == -1)
589                         global.c_flags &= ~CLEARSCR;
590         }
591
592         if (length)
593                 global.c_length = length;
594         if (width)
595                 global.c_width = width;
596         if (global.c_length < 5)
597                 global.c_length = 10000;
598         if (global.c_width < 5)
599                 global.c_width = 10000;
600 }
601
602
603 static int
604 evalvar(struct mcomp *c1)
605 {
606         char *cp, name[NAMESZ];
607         struct triple *ap;
608
609         if (!*parptr)
610                 return 0;
611         strncpy(name, parse(), sizeof(name));
612
613         if (!mh_strcasecmp(name, "component")) {
614                 if (ptos(name, &c1->c_text))
615                         return 1;
616                 c1->c_flags &= ~NOCOMPONENT;
617                 return 0;
618         }
619
620         if (!mh_strcasecmp(name, "overflowtext"))
621                 return ptos(name, &c1->c_ovtxt);
622
623         if (!mh_strcasecmp(name, "formatfield")) {
624                 char *nfs;
625
626                 if (ptos(name, &cp))
627                         return 1;
628                 cp = concat("=", cp, NULL);
629                 nfs = new_fs(cp, NULL);
630                 free(cp);
631                 c1->c_nfs = getcpy(nfs);
632                 c1->c_flags |= FORMAT;
633                 return 0;
634         }
635
636         if (!mh_strcasecmp(name, "decode")) {
637                 char *nfs;
638
639                 nfs = new_fs("=%(decode{text})", NULL);
640                 c1->c_nfs = getcpy(nfs);
641                 c1->c_flags |= FORMAT;
642                 return 0;
643         }
644
645         if (!mh_strcasecmp(name, "offset"))
646                 return ptoi(name, &c1->c_offset);
647         if (!mh_strcasecmp(name, "overflowoffset"))
648                 return ptoi(name, &c1->c_ovoff);
649         if (!mh_strcasecmp(name, "width"))
650                 return ptoi(name, &c1->c_width);
651         if (!mh_strcasecmp(name, "compwidth"))
652                 return ptoi(name, &c1->c_cwidth);
653         if (!mh_strcasecmp(name, "length"))
654                 return ptoi(name, &c1->c_length);
655
656         for (ap = triples; ap->t_name; ap++)
657                 if (!mh_strcasecmp(ap->t_name, name)) {
658                         c1->c_flags |= ap->t_on;
659                         c1->c_flags &= ~ap->t_off;
660                         return 0;
661                 }
662
663         return 1;
664 }
665
666
667 static int
668 ptoi(char *name, int *i)
669 {
670         char *cp;
671
672         if (*parptr++ != '=' || !*(cp = parse())) {
673                 advise(NULL, "missing argument to variable %s", name);
674                 return 1;
675         }
676
677         *i = atoi(cp);
678         return 0;
679 }
680
681
682 static int
683 ptos(char *name, char **s)
684 {
685         char c, *cp;
686
687         if (*parptr++ != '=') {
688                 advise(NULL, "missing argument to variable %s", name);
689                 return 1;
690         }
691
692         if (*parptr != '"') {
693                 for (cp = parptr; *parptr && *parptr != ':' && *parptr != ',';
694                                 parptr++)
695                         continue;
696         } else {
697                 for (cp = ++parptr; *parptr && *parptr != '"'; parptr++)
698                         if (*parptr == QUOTE)
699                                 if (!*++parptr)
700                                         parptr--;
701         }
702         c = *parptr;
703         *parptr = 0;
704         *s = getcpy(cp);
705         if ((*parptr = c) == '"')
706                 parptr++;
707         return 0;
708 }
709
710
711 static char *
712 parse(void)
713 {
714         int c;
715         char *cp;
716         static char result[NAMESZ];
717
718         for (cp = result; *parptr && (cp - result < NAMESZ); parptr++) {
719                 c = *parptr;
720                 if (isalnum(c)
721                                 || c == '.'
722                                 || c == '-'
723                                 || c == '_'
724                                 || c == '['
725                                 || c == ']')
726                         *cp++ = c;
727                 else
728                         break;
729         }
730         *cp = '\0';
731
732         return result;
733 }
734
735
736 static void
737 process(char *folder, char *fname, int ofilen, int ofilec)
738 {
739         char *cp = NULL;
740         FILE *fp = NULL;
741         struct mcomp *c1;
742
743         switch (setjmp(env)) {
744         case OK:
745                 if (fname) {
746                         fp = mhl_action ? (*mhl_action) (fname) :
747                                         fopen(fname, "r");
748                         if (fp == NULL) {
749                                 advise(fname, "unable to open");
750                                 exitstat++;
751                                 return;
752                         }
753                 } else {
754                         fname = "(stdin)";
755                         fp = stdin;
756                 }
757                 cp = folder ? concat(folder, ":", fname, NULL) : getcpy(fname);
758                 if (ontty != PITTY)
759                         SIGNAL(SIGINT, intrser);
760                 mhlfile(fp, cp, ofilen, ofilec);
761                 /* FALL THROUGH! */
762         default:
763                 if (ontty != PITTY)
764                         SIGNAL(SIGINT, SIG_IGN);
765                 if (mhl_action == NULL && fp != stdin)
766                         fclose(fp);
767                 free(cp);
768                 if (holder.c_text) {
769                         free(holder.c_text);
770                         holder.c_text = NULL;
771                 }
772                 free_queue(&msghd, &msgtl);
773                 for (c1 = fmthd; c1; c1 = c1->c_next)
774                         c1->c_flags &= ~HDROUTPUT;
775                 break;
776         }
777 }
778
779
780 static void
781 mhlfile(FILE *fp, char *mname, int ofilen, int ofilec)
782 {
783         int state;
784         struct mcomp *c1, *c2, *c3;
785         char **ip, name[NAMESZ], buf[BUFSIZ];
786
787         if (forwall) {
788                 if (digest)
789                         printf("%s", ofilen == 1 ? delim3 : delim4);
790                 else {
791                         printf("\n-------");
792                         if (ofilen == 1)
793                                 printf(" Forwarded Message%s",
794                                                 ofilec > 1 ? "s" : "");
795                         else
796                                 printf(" Message %d", ofilen);
797                         printf("\n\n");
798                 }
799         } else {
800                 switch (ontty) {
801                 case PITTY:
802                         if (ofilec > 1) {
803                                 if (ofilen > 1) {
804                                         if ((global.c_flags & CLEARSCR))
805                                                 clear_screen();
806                                         else
807                                                 printf("\n\n\n");
808                                 }
809                                 printf(">>> %s\n\n", mname);
810                         }
811                         break;
812
813                 default:
814                         if (ofilec > 1) {
815                                 if (ofilen > 1) {
816                                         printf("\n\n\n");
817                                         if (clearflg > 0)
818                                                 clear_screen();
819                                 }
820                                 printf(">>> %s\n\n", mname);
821                         }
822                         break;
823                 }
824         }
825
826         for (state = FLD;;) {
827                 switch (state = m_getfld(state, name, buf, sizeof(buf), fp)) {
828                 case FLD:
829                 case FLDPLUS:
830                         for (ip = ignores; *ip; ip++)
831                                 if (!mh_strcasecmp(name, *ip)) {
832                                         while (state == FLDPLUS)
833                                                 state = m_getfld(state, name, buf, sizeof(buf), fp);
834                                         break;
835                                 }
836                         if (*ip)
837                                 continue;
838
839                         for (c2 = fmthd; c2; c2 = c2->c_next)
840                                 if (!mh_strcasecmp(c2->c_name, name))
841                                         break;
842                         c1 = NULL;
843                         if (!((c3 = c2 ? c2 : &global)->c_flags & SPLIT))
844                                 for (c1 = msghd; c1; c1 = c1->c_next)
845                                         if (!mh_strcasecmp(c1->c_name,
846                                                         c3->c_name)) {
847                                                 c1->c_text = mcomp_add(c1->c_flags, buf, c1->c_text);
848                                                 break;
849                                         }
850                         if (c1 == NULL)
851                                 c1 = add_queue(&msghd, &msgtl, name, buf, 0);
852                         while (state == FLDPLUS) {
853                                 state = m_getfld(state, name, buf,
854                                                 sizeof(buf), fp);
855                                 c1->c_text = add(buf, c1->c_text);
856                         }
857                         if (c2 == NULL)
858                                 c1->c_flags |= EXTRA;
859                         continue;
860
861                 case BODY:
862                 case FILEEOF:
863                         row = column = 0;
864                         for (c1 = fmthd; c1; c1 = c1->c_next) {
865                                 if (c1->c_flags & CLEARTEXT) {
866                                         putcomp(c1, c1, ONECOMP);
867                                         continue;
868                                 }
869                                 if (!mh_strcasecmp(c1->c_name, "messagename")) {
870                                         holder.c_text = concat("(Message ",
871                                                         mname, ")\n", NULL);
872                                         putcomp(c1, &holder, ONECOMP);
873                                         free(holder.c_text);
874                                         holder.c_text = NULL;
875                                         continue;
876                                 }
877                                 if (!mh_strcasecmp(c1->c_name, "extras")) {
878                                         for (c2 = msghd; c2; c2 = c2->c_next)
879                                                 if (c2->c_flags & EXTRA)
880                                                         putcomp(c1, c2, TWOCOMP);
881                                         continue;
882                                 }
883                                 if (dobody && !mh_strcasecmp(c1->c_name, "body")) {
884                                         holder.c_text = mh_xmalloc(sizeof(buf));
885                                         strncpy(holder.c_text, buf, sizeof(buf));
886                                         while (state == BODY) {
887                                                 putcomp(c1, &holder, BODYCOMP);
888                                                 state = m_getfld(state, name, holder.c_text, sizeof(buf), fp);
889                                         }
890                                         free(holder.c_text);
891                                         holder.c_text = NULL;
892                                         continue;
893                                 }
894                                 for (c2 = msghd; c2; c2 = c2->c_next)
895                                         if (!mh_strcasecmp(c2->c_name,
896                                                         c1->c_name)) {
897                                                 putcomp(c1, c2, ONECOMP);
898                                                 if (!(c1->c_flags & SPLIT))
899                                                         break;
900                                         }
901                         }
902                         return;
903
904                 case LENERR:
905                 case FMTERR:
906                         advise(NULL, "format error in message %s", mname);
907                         exitstat++;
908                         return;
909
910                 default:
911                         adios(NULL, "getfld() returned %d", state);
912                 }
913         }
914 }
915
916
917 static int
918 mcomp_flags(char *name)
919 {
920         struct pair *ap;
921
922         for (ap = pairs; ap->p_name; ap++)
923                 if (!mh_strcasecmp(ap->p_name, name))
924                         return (ap->p_flags);
925
926         return 0;
927 }
928
929
930 static char *
931 mcomp_add(long flags, char *s1, char *s2)
932 {
933         char *dp;
934
935         if (!(flags & ADDRFMT))
936                 return add(s1, s2);
937
938         if (s2 && *(dp = s2 + strlen(s2) - 1) == '\n')
939                 *dp = 0;
940
941         return add(s1, add(",\n", s2));
942 }
943
944
945 struct pqpair {
946         char *pq_text;
947         char *pq_error;
948         struct pqpair *pq_next;
949 };
950
951
952 static void
953 mcomp_format(struct mcomp *c1, struct mcomp *c2)
954 {
955         int dat[5];
956         char *ap, *cp;
957         char buffer[BUFSIZ], error[BUFSIZ];
958         struct comp *cptr;
959         struct pqpair *p, *q;
960         struct pqpair pq;
961         struct mailname *mp;
962
963         ap = c2->c_text;
964         c2->c_text = NULL;
965         dat[0] = 0;
966         dat[1] = 0;
967         dat[2] = 0;
968         dat[3] = sizeof(buffer) - 1;
969         dat[4] = 0;
970         fmt_compile(c1->c_nfs, &c1->c_fmt);
971
972         if (!(c1->c_flags & ADDRFMT)) {
973                 FINDCOMP(cptr, "text");
974                 if (cptr)
975                         cptr->c_text = ap;
976                 if ((cp = strrchr(ap, '\n')))  /* drop ending newline */
977                         if (!cp[1])
978                                 *cp = 0;
979
980                 fmt_scan(c1->c_fmt, buffer, sizeof(buffer) - 1, dat);
981                 /* Don't need to append a newline, dctime() already did */
982                 c2->c_text = getcpy(buffer);
983
984                 free(ap);
985                 return;
986         }
987
988         (q = &pq)->pq_next = NULL;
989         while ((cp = getname(ap))) {
990                 if ((p = (struct pqpair *)
991                                 calloc((size_t) 1, sizeof(*p))) == NULL)
992                         adios(NULL, "unable to allocate pqpair memory");
993
994                 if ((mp = getm(cp, NULL, 0, AD_NAME, error)) == NULL) {
995                         p->pq_text = getcpy(cp);
996                         p->pq_error = getcpy(error);
997                 } else {
998                         p->pq_text = getcpy(mp->m_text);
999                         mnfree(mp);
1000                 }
1001                 q = (q->pq_next = p);
1002         }
1003
1004         for (p = pq.pq_next; p; p = q) {
1005                 FINDCOMP(cptr, "text");
1006                 if (cptr)
1007                         cptr->c_text = p->pq_text;
1008                 FINDCOMP(cptr, "error");
1009                 if (cptr)
1010                         cptr->c_text = p->pq_error;
1011
1012                 fmt_scan(c1->c_fmt, buffer, sizeof(buffer) - 1, dat);
1013                 if (*buffer) {
1014                         if (c2->c_text)
1015                                 c2->c_text = add(",\n", c2->c_text);
1016                         if (*(cp = buffer + strlen(buffer) - 1) == '\n')
1017                                 *cp = 0;
1018                         c2->c_text = add(buffer, c2->c_text);
1019                 }
1020
1021                 free(p->pq_text);
1022                 if (p->pq_error)
1023                         free(p->pq_error);
1024                 q = p->pq_next;
1025                 free((char *) p);
1026         }
1027
1028         c2->c_text = add("\n", c2->c_text);
1029         free (ap);
1030 }
1031
1032
1033 static struct mcomp *
1034 add_queue(struct mcomp **head, struct mcomp **tail, char *name,
1035                 char *text, int flags)
1036 {
1037         struct mcomp *c1;
1038
1039         if ((c1 = (struct mcomp *) calloc((size_t) 1, sizeof(*c1))) == NULL)
1040                 adios(NULL, "unable to allocate comp memory");
1041
1042         c1->c_flags = flags & ~INIT;
1043         if ((c1->c_name = name ? getcpy(name) : NULL))
1044                 c1->c_flags |= mcomp_flags(c1->c_name);
1045         c1->c_text = text ? getcpy(text) : NULL;
1046         if (flags & INIT) {
1047                 if (global.c_ovtxt)
1048                         c1->c_ovtxt = getcpy(global.c_ovtxt);
1049                 c1->c_offset = global.c_offset;
1050                 c1->c_ovoff = global. c_ovoff;
1051                 c1->c_width = c1->c_length = 0;
1052                 c1->c_cwidth = global.c_cwidth;
1053                 c1->c_flags |= global.c_flags & GFLAGS;
1054         }
1055         if (*head == NULL)
1056                 *head = c1;
1057         if (*tail != NULL)
1058                 (*tail)->c_next = c1;
1059         *tail = c1;
1060
1061         return c1;
1062 }
1063
1064
1065 static void
1066 free_queue(struct mcomp **head, struct mcomp **tail)
1067 {
1068         struct mcomp *c1, *c2;
1069
1070         for (c1 = *head; c1; c1 = c2) {
1071                 c2 = c1->c_next;
1072                 if (c1->c_name)
1073                         free(c1->c_name);
1074                 if (c1->c_text)
1075                         free(c1->c_text);
1076                 if (c1->c_ovtxt)
1077                         free(c1->c_ovtxt);
1078                 if (c1->c_nfs)
1079                         free(c1->c_nfs);
1080                 if (c1->c_fmt)
1081                         free((char *) c1->c_fmt);
1082                 free((char *) c1);
1083         }
1084
1085         *head = *tail = NULL;
1086 }
1087
1088
1089 static void
1090 putcomp(struct mcomp *c1, struct mcomp *c2, int flag)
1091 {
1092         int count, cchdr;
1093         unsigned char *cp;
1094
1095         cchdr = 0;
1096         lm = 0;
1097         llim = c1->c_length ? c1->c_length : -1;
1098         wid = c1->c_width ? c1->c_width : global.c_width;
1099         ovoff = (c1->c_ovoff >= 0 ? c1->c_ovoff : global.c_ovoff)
1100                         + c1->c_offset;
1101         if ((ovtxt = c1->c_ovtxt ? c1->c_ovtxt : global.c_ovtxt) == NULL)
1102                 ovtxt = "";
1103         if (wid < ovoff + strlen(ovtxt) + 5)
1104                 adios(NULL, "component: %s width(%d) too small for overflow(%d)", c1->c_name, wid, ovoff + strlen(ovtxt) + 5);
1105         onelp = NULL;
1106
1107         if (c1->c_flags & CLEARTEXT) {
1108                 putstr(c1->c_text);
1109                 putstr("\n");
1110                 return;
1111         }
1112
1113         if (c1->c_nfs && (c1->c_flags & (ADDRFMT | DATEFMT | FORMAT)))
1114                 mcomp_format(c1, c2);
1115
1116         if (c1->c_flags & CENTER) {
1117                 count = (c1->c_width ? c1->c_width : global.c_width)
1118                                 - c1->c_offset - strlen(c2->c_text);
1119                 if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT))
1120                         count -= strlen(c1->c_text ? c1->c_text : c1->c_name)
1121                                         + 2;
1122                 lm = c1->c_offset + (count / 2);
1123         } else {
1124                 if (c1->c_offset)
1125                         lm = c1->c_offset;
1126         }
1127
1128         if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT)) {
1129                 if (c1->c_flags & UPPERCASE)  /* uppercase component also */
1130                         for (cp = (c1->c_text ? c1->c_text : c1->c_name); *cp; cp++)
1131                                 if (islower(*cp))
1132                                         *cp = toupper(*cp);
1133                 putstr(c1->c_text ? c1->c_text : c1->c_name);
1134                 if (flag != BODYCOMP) {
1135                         putstr(": ");
1136                         if (!(c1->c_flags & SPLIT))
1137                                 c1->c_flags |= HDROUTPUT;
1138
1139                 cchdr++;
1140                 if ((count = c1->c_cwidth -
1141                         strlen(c1->c_text ? c1->c_text : c1->c_name) - 2) > 0)
1142                         while (count--)
1143                                 putstr(" ");
1144                 } else
1145                         c1->c_flags |= HDROUTPUT;  /* for BODYCOMP */
1146         }
1147
1148         if (flag == TWOCOMP && !(c2->c_flags & HDROUTPUT)
1149                 && !(c2->c_flags & NOCOMPONENT)) {
1150                 if (c1->c_flags & UPPERCASE)
1151                         for (cp = c2->c_name; *cp; cp++)
1152                                 if (islower(*cp))
1153                                         *cp = toupper(*cp);
1154                 putstr(c2->c_name);
1155                 putstr(": ");
1156                 if (!(c1->c_flags & SPLIT))
1157                         c2->c_flags |= HDROUTPUT;
1158
1159                 cchdr++;
1160                 if ((count = c1->c_cwidth - strlen(c2->c_name) - 2) > 0)
1161                         while (count--)
1162                                 putstr(" ");
1163         }
1164         if (c1->c_flags & UPPERCASE)
1165                 for (cp = c2->c_text; *cp; cp++)
1166                         if (islower(*cp))
1167                                 *cp = toupper(*cp);
1168
1169         count = 0;
1170         if (cchdr) {
1171                 if (flag == TWOCOMP)
1172                         count = (c1->c_cwidth >= 0) ?
1173                                         c1->c_cwidth : strlen(c2->c_name) + 2;
1174                 else
1175                         count = (c1->c_cwidth >= 0) ?
1176                                         c1->c_cwidth : strlen(c1->c_text ?
1177                                         c1->c_text : c1->c_name) + 2;
1178         }
1179         count += c1->c_offset;
1180
1181         if ((cp = oneline(c2->c_text, c1->c_flags)))
1182            putstr(cp);
1183         if (term == '\n')
1184                 putstr("\n");
1185         while ((cp = oneline(c2->c_text, c1->c_flags))) {
1186                 lm = count;
1187                 if (flag == BODYCOMP && !(c1->c_flags & NOCOMPONENT))
1188                         putstr(c1->c_text ? c1->c_text : c1->c_name);
1189                 if (*cp)
1190                         putstr(cp);
1191                 if (term == '\n')
1192                         putstr("\n");
1193         }
1194         if (flag == BODYCOMP && term == '\n')
1195                 c1->c_flags &= ~HDROUTPUT;  /* Buffer ended on a newline */
1196 }
1197
1198
1199 static char *
1200 oneline(char *stuff, long flags)
1201 {
1202         int spc;
1203         char *cp, *ret;
1204
1205         if (onelp == NULL)
1206                 onelp = stuff;
1207         if (*onelp == 0)
1208                 return (onelp = NULL);
1209
1210         ret = onelp;
1211         term = 0;
1212         if (flags & COMPRESS) {
1213                 for (spc = 1, cp = ret; *onelp; onelp++)
1214                         if (isspace(*onelp)) {
1215                                 if (*onelp == '\n' &&
1216                                                 (!onelp[1] ||
1217                                                 (flags & ADDRFMT))) {
1218                                         term = '\n';
1219                                         *onelp++ = 0;
1220                                         break;
1221                                 } else if (!spc) {
1222                                         *cp++ = ' ';
1223                                         spc++;
1224                                 }
1225                         } else {
1226                                 *cp++ = *onelp;
1227                                 spc = 0;
1228                         }
1229
1230                 *cp = 0;
1231         } else {
1232                 while (*onelp && *onelp != '\n')
1233                         onelp++;
1234                 if (*onelp == '\n') {
1235                         term = '\n';
1236                         *onelp++ = 0;
1237                 }
1238                 if (flags & LEFTADJUST)
1239                         while (*ret == ' ' || *ret == '\t')
1240                                 ret++;
1241         }
1242         if (*onelp == 0 && term == '\n' && (flags & NONEWLINE))
1243                 term = 0;
1244
1245         return ret;
1246 }
1247
1248
1249 static void
1250 putstr(char *string)
1251 {
1252         if (!column && lm > 0) {
1253                 while (lm > 0)
1254                         if (lm >= 8) {
1255                                 putch('\t');
1256                                 lm -= 8;
1257                         } else {
1258                                 putch(' ');
1259                                 lm--;
1260                         }
1261         }
1262         lm = 0;
1263         while (*string)
1264                 putch(*string++);
1265 }
1266
1267
1268 static void
1269 putch(char ch)
1270 {
1271         char buf[BUFSIZ];
1272
1273         if (llim == 0)
1274                 return;
1275
1276         switch (ch) {
1277         case '\n':
1278                 if (llim > 0)
1279                         llim--;
1280                 column = 0;
1281                 row++;
1282                 if (row != global.c_length)
1283                         break;
1284                 fflush(stdout);
1285                 buf[0] = 0;
1286                 read(fileno(stdout), buf, sizeof(buf));
1287                 if (strchr(buf, '\n')) {
1288                         if (global.c_flags & CLEARSCR)
1289                                 clear_screen();
1290                         row = 0;
1291                 } else {
1292                         putchar('\n');
1293                         row = global.c_length / 3;
1294                 }
1295                 return;
1296
1297         case '\t':
1298                 column |= 07;
1299                 column++;
1300                 break;
1301
1302         case '\b':
1303                 column--;
1304                 break;
1305
1306         case '\r':
1307                 column = 0;
1308                 break;
1309
1310         default:
1311                 /*
1312                 ** If we are forwarding this message, and the first
1313                 ** column contains a dash, then add a dash and a space.
1314                 */
1315                 if (column == 0 && forwflg && ch == '-') {
1316                         putchar('-');
1317                         putchar(' ');
1318                 }
1319                 if (ch >= ' ')
1320                         column++;
1321                 break;
1322         }
1323
1324         if (column >= wid) {
1325                 putch('\n');
1326                 if (ovoff > 0)
1327                         lm = ovoff;
1328                 putstr(ovtxt ? ovtxt : "");
1329                 putch(ch);
1330                 return;
1331         }
1332
1333         putchar(ch);
1334 }
1335
1336
1337 static RETSIGTYPE
1338 intrser(int i)
1339 {
1340 #ifndef RELIABLE_SIGNALS
1341         SIGNAL(SIGINT, intrser);
1342 #endif
1343
1344         discard(stdout);
1345         putchar('\n');
1346         longjmp(env, DONE);
1347 }
1348
1349
1350 static RETSIGTYPE
1351 pipeser(int i)
1352 {
1353 #ifndef RELIABLE_SIGNALS
1354         SIGNAL(SIGPIPE, pipeser);
1355 #endif
1356
1357         done(NOTOK);
1358 }
1359
1360
1361 static RETSIGTYPE
1362 quitser(int i)
1363 {
1364 #ifndef RELIABLE_SIGNALS
1365         SIGNAL(SIGQUIT, quitser);
1366 #endif
1367
1368         putchar('\n');
1369         fflush(stdout);
1370         done(NOTOK);
1371 }
1372
1373
1374 #undef adios
1375 #undef done
1376
1377 static void
1378 mhladios(char *what, char *fmt, ...)
1379 {
1380         va_list ap;
1381
1382         va_start(ap, fmt);
1383         advertise(what, NULL, fmt, ap);
1384         va_end(ap);
1385         mhldone(1);
1386 }
1387
1388
1389 static void
1390 mhldone(int status)
1391 {
1392         exitstat = status;
1393         if (mhl_action)
1394                 longjmp(mhlenv, DONE);
1395         else
1396                 done(exitstat);
1397 }
1398
1399
1400 static int m_pid = NOTOK;
1401 static int sd = NOTOK;
1402
1403 static void
1404 m_popen(char *name)
1405 {
1406         int pd[2];
1407
1408         if (mhl_action && (sd = dup(fileno(stdout))) == NOTOK)
1409                 adios("standard output", "unable to dup()");
1410
1411         if (pipe(pd) == NOTOK)
1412                 adios("pipe", "unable to");
1413
1414         switch (m_pid = fork()) {
1415         case NOTOK:
1416                 adios("fork", "unable to");
1417
1418         case OK:
1419                 SIGNAL(SIGINT, SIG_DFL);
1420                 SIGNAL(SIGQUIT, SIG_DFL);
1421
1422                 close(pd[1]);
1423                 if (pd[0] != fileno(stdin)) {
1424                         dup2(pd[0], fileno(stdin));
1425                         close(pd[0]);
1426                 }
1427                 execlp(name, mhbasename(name), NULL);
1428                 fprintf(stderr, "unable to exec ");
1429                 perror(name);
1430                 _exit(-1);
1431
1432         default:
1433                 close(pd[0]);
1434                 if (pd[1] != fileno(stdout)) {
1435                         dup2(pd[1], fileno(stdout));
1436                         close(pd[1]);
1437                 }
1438         }
1439 }
1440
1441
1442 void
1443 m_pclose(void)
1444 {
1445         if (m_pid == NOTOK)
1446                 return;
1447
1448         if (sd != NOTOK) {
1449                 fflush(stdout);
1450                 if (dup2(sd, fileno(stdout)) == NOTOK)
1451                         adios("standard output", "unable to dup2()");
1452
1453                 clearerr(stdout);
1454                 close(sd);
1455                 sd = NOTOK;
1456         }
1457         else
1458                 fclose(stdout);
1459
1460         pidwait(m_pid, OK);
1461         m_pid = NOTOK;
1462 }