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