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