Added mhmail.in. start of a replacement for compiled mhmail.
[mmh] / uip / mhshowsbr.c
1
2 /*
3  * mhshowsbr.c -- routines to display the contents of MIME messages
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11 #include <fcntl.h>
12 #include <h/signals.h>
13 #include <h/md5.h>
14 #include <errno.h>
15 #include <setjmp.h>
16 #include <signal.h>
17 #include <h/mts.h>
18 #include <h/tws.h>
19 #include <h/mime.h>
20 #include <h/mhparse.h>
21 #include <h/utils.h>
22 #include <sys/wait.h>
23
24 extern int debugsw;
25
26 int pausesw  = 1;
27 int serialsw = 0;
28 int nolist   = 0;
29
30 char *progsw = NULL;
31
32 /* flags for moreproc/header display */
33 int nomore   = 0;
34 char *formsw = NULL;
35
36 pid_t xpid = 0;
37
38 static sigjmp_buf intrenv;
39
40
41 /* mhmisc.c */
42 int part_ok (CT, int);
43 int type_ok (CT, int);
44 void content_error (char *, CT, char *, ...);
45 void flush_errors (void);
46
47 /* mhlistsbr.c */
48 int list_switch (CT, int, int, int, int);
49 int list_content (CT, int, int, int, int);
50
51 /*
52  * prototypes
53  */
54 void show_all_messages (CT *);
55 int show_content_aux (CT, int, int, char *, char *);
56
57 /*
58  * static prototypes
59  */
60 static void show_single_message (CT, char *);
61 static void DisplayMsgHeader (CT, char *);
62 static int show_switch (CT, int, int);
63 static int show_content (CT, int, int);
64 static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, int);
65 static int show_text (CT, int, int);
66 static int show_multi (CT, int, int);
67 static int show_multi_internal (CT, int, int);
68 static int show_multi_aux (CT, int, int, char *);
69 static int show_message_rfc822 (CT, int, int);
70 static int show_partial (CT, int, int);
71 static int show_external (CT, int, int);
72 static void intrser (int);
73
74
75 /*
76  * Top level entry point to show/display a group of messages
77  */
78
79 void
80 show_all_messages (CT *cts)
81 {
82     CT ct, *ctp;
83
84     /*
85      * If form is not specified, then get default form
86      * for showing headers of MIME messages.
87      */
88     if (!formsw)
89         formsw = getcpy (etcpath ("mhl.headers"));
90
91     /*
92      * If form is "mhl.null", suppress display of header.
93      */
94     if (!strcmp (formsw, "mhl.null"))
95         formsw = NULL;
96
97     for (ctp = cts; *ctp; ctp++) {
98         ct = *ctp;
99
100         /* if top-level type is ok, then display message */
101         if (type_ok (ct, 1))
102             show_single_message (ct, formsw);
103     }
104 }
105
106
107 /*
108  * Entry point to show/display a single message
109  */
110
111 static void
112 show_single_message (CT ct, char *form)
113 {
114     sigset_t set, oset;
115
116     int status;
117
118     /* Allow user executable bit so that temporary directories created by
119      * the viewer (e.g., lynx) are going to be accessible */
120     umask (ct->c_umask & ~(0100));
121
122     /*
123      * If you have a format file, then display
124      * the message headers.
125      */
126     if (form)
127         DisplayMsgHeader(ct, form);
128     else
129         xpid = 0;
130
131     /* Show the body of the message */
132     show_switch (ct, 1, 0);
133
134     if (ct->c_fp) {
135         fclose (ct->c_fp);
136         ct->c_fp = NULL;
137     }
138     if (ct->c_ceclosefnx)
139         (*ct->c_ceclosefnx) (ct);
140
141     /* block a few signals */
142     sigemptyset (&set);
143     sigaddset (&set, SIGHUP);
144     sigaddset (&set, SIGINT);
145     sigaddset (&set, SIGQUIT);
146     sigaddset (&set, SIGTERM);
147     sigprocmask (SIG_BLOCK, &set, &oset);
148
149     while (wait (&status) != NOTOK) {
150         pidcheck (status);
151         continue;
152     }
153
154     /* reset the signal mask */
155     sigprocmask (SIG_SETMASK, &oset, &set);
156
157     xpid = 0;
158     flush_errors ();
159 }
160
161
162 /*
163  * Use the mhlproc to show the header fields
164  */
165
166 static void
167 DisplayMsgHeader (CT ct, char *form)
168 {
169     pid_t child_id;
170     int i, vecp;
171     char *vec[8];
172
173     vecp = 0;
174     vec[vecp++] = r1bindex (mhlproc, '/');
175     vec[vecp++] = "-form";
176     vec[vecp++] = form;
177     vec[vecp++] = "-nobody";
178     vec[vecp++] = ct->c_file;
179
180     /*
181      * If we've specified -(no)moreproc,
182      * then just pass that along.
183      */
184     if (nomore) {
185         vec[vecp++] = "-nomoreproc";
186     } else if (progsw) {
187         vec[vecp++] = "-moreproc";
188         vec[vecp++] = progsw;
189     }
190     vec[vecp] = NULL;
191
192     fflush (stdout);
193
194     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
195         sleep (5);
196
197     switch (child_id) {
198     case NOTOK:
199         adios ("fork", "unable to");
200         /* NOTREACHED */
201
202     case OK:
203         execvp (mhlproc, vec);
204         fprintf (stderr, "unable to exec ");
205         perror (mhlproc);
206         _exit (-1);
207         /* NOTREACHED */
208
209     default:
210         xpid = -child_id;
211         break;
212     }
213 }
214
215
216 /*
217  * Switching routine.  Call the correct routine
218  * based on content type.
219  */
220
221 static int
222 show_switch (CT ct, int serial, int alternate)
223 {
224     switch (ct->c_type) {
225         case CT_MULTIPART:
226             return show_multi (ct, serial, alternate);
227             break;
228
229         case CT_MESSAGE:
230             switch (ct->c_subtype) {
231                 case MESSAGE_PARTIAL:
232                     return show_partial (ct, serial, alternate);
233                     break;
234
235                 case MESSAGE_EXTERNAL:
236                     return show_external (ct, serial, alternate);
237                     break;
238
239                 case MESSAGE_RFC822:
240                 default:
241                     return show_message_rfc822 (ct, serial, alternate);
242                     break;
243             }
244             break;
245
246         case CT_TEXT:
247             return show_text (ct, serial, alternate);
248             break;
249
250         case CT_AUDIO:
251         case CT_IMAGE:
252         case CT_VIDEO:
253         case CT_APPLICATION:
254             return show_content (ct, serial, alternate);
255             break;
256
257         default:
258             adios (NULL, "unknown content type %d", ct->c_type);
259             break;
260     }
261
262     return 0;   /* NOT REACHED */
263 }
264
265
266 /*
267  * Generic method for displaying content
268  */
269
270 static int
271 show_content (CT ct, int serial, int alternate)
272 {
273     char *cp, buffer[BUFSIZ];
274     CI ci = &ct->c_ctinfo;
275
276     /* Check for mhn-show-type/subtype */
277     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
278                 invo_name, ci->ci_type, ci->ci_subtype);
279     if ((cp = context_find (buffer)) && *cp != '\0')
280         return show_content_aux (ct, serial, alternate, cp, NULL);
281
282     /* Check for mhn-show-type */
283     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
284     if ((cp = context_find (buffer)) && *cp != '\0')
285         return show_content_aux (ct, serial, alternate, cp, NULL);
286
287     if ((cp = ct->c_showproc))
288         return show_content_aux (ct, serial, alternate, cp, NULL);
289
290     /* complain if we are not a part of a multipart/alternative */
291     if (!alternate)
292         content_error (NULL, ct, "don't know how to display content");
293
294     return NOTOK;
295 }
296
297
298 /*
299  * Parse the display string for displaying generic content
300  */
301
302 int
303 show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked)
304 {
305     int fd, len, buflen, quoted;
306     int xstdin, xlist, xpause, xtty;
307     char *bp, *pp, *file, buffer[BUFSIZ];
308     CI ci = &ct->c_ctinfo;
309
310     if (!ct->c_ceopenfnx) {
311         if (!alternate)
312             content_error (NULL, ct, "don't know how to decode content");
313
314         return NOTOK;
315     }
316
317     file = NULL;
318     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
319         return NOTOK;
320     if (ct->c_showproc && !strcmp (ct->c_showproc, "true"))
321         return (alternate ? DONE : OK);
322     
323     xlist  = 0;
324     xpause = 0;
325     xstdin = 0;
326     xtty   = 0;
327
328     if (cracked) {
329         strncpy (buffer, cp, sizeof(buffer));
330         goto got_command;
331     }
332
333     /* get buffer ready to go */
334     bp = buffer;
335     buflen = sizeof(buffer) - 1;
336     bp[0] = bp[buflen] = '\0';
337     quoted = 0;
338
339     /* Now parse display string */
340     for ( ; *cp && buflen > 0; cp++) {
341         if (*cp == '%') {
342             pp = bp;
343
344             switch (*++cp) {
345             case 'a':
346                 /* insert parameters from Content-Type field */
347             {
348                 char **ap, **ep;
349                 char *s = "";
350
351                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
352                     snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
353                     len = strlen (bp);
354                     bp += len;
355                     buflen -= len;
356                     s = " ";
357                 }
358             }
359             break;
360
361             case 'd':
362                 /* insert content description */
363                 if (ct->c_descr) {
364                     char *s;
365
366                     s = trimcpy (ct->c_descr);
367                     strncpy (bp, s, buflen);
368                     free (s);
369                 }
370                 break;
371
372             case 'e':
373                 /* exclusive execution */
374                 xtty = 1;
375                 break;
376
377             case 'F':
378                 /* %e, %f, and stdin is terminal not content */
379                 xstdin = 1;
380                 xtty = 1;
381                 /* and fall... */
382
383             case 'f':
384                 /* insert filename containing content */
385                 snprintf (bp, buflen, "'%s'", file);
386                 /* since we've quoted the file argument, set things up
387                  * to look past it, to avoid problems with the quoting
388                  * logic below.  (I know, I should figure out what's
389                  * broken with the quoting logic, but..)
390                  */
391                 len = strlen(bp);
392                 buflen -= len;
393                 bp += len;
394                 pp = bp;
395                 break;
396
397             case 'p':
398                 /* %l, and pause prior to displaying content */
399                 xpause = pausesw;
400                 /* and fall... */
401
402             case 'l':
403                 /* display listing prior to displaying content */
404                 xlist = !nolist;
405                 break;
406
407             case 's':
408                 /* insert subtype of content */
409                 strncpy (bp, ci->ci_subtype, buflen);
410                 break;
411
412             case '%':
413                 /* insert character % */
414                 goto raw;
415
416             default:
417                 *bp++ = *--cp;
418                 *bp = '\0';
419                 buflen--;
420                 continue;
421             }
422             len = strlen (bp);
423             bp += len;
424             buflen -= len;
425
426             /* Did we actually insert something? */
427             if (bp != pp) {
428                 /* Insert single quote if not inside quotes already */
429                 if (!quoted && buflen) {
430                     len = strlen (pp);
431                     memmove (pp + 1, pp, len);
432                     *pp++ = '\'';
433                     buflen--;
434                     bp++;
435                 }
436                 /* Escape existing quotes */
437                 while ((pp = strchr (pp, '\'')) && buflen > 3) {
438                     len = strlen (pp++);
439                     memmove (pp + 3, pp, len);
440                     *pp++ = '\\';
441                     *pp++ = '\'';
442                     *pp++ = '\'';
443                     buflen -= 3;
444                     bp += 3;
445                 }
446                 /* If pp is still set, that means we ran out of space. */
447                 if (pp)
448                     buflen = 0;
449                 if (!quoted && buflen) {
450                     *bp++ = '\'';
451                     *bp = '\0';
452                     buflen--;
453                 }
454             }
455         } else {
456 raw:
457             *bp++ = *cp;
458             *bp = '\0';
459             buflen--;
460
461             if (*cp == '\'')
462                 quoted = !quoted;
463         }
464     }
465
466     if (buflen <= 0 ||
467         (ct->c_termproc && (size_t) buflen <= strlen(ct->c_termproc))) {
468         /* content_error would provide a more useful error message
469          * here, except that if we got overrun, it probably would
470          * too.
471          */
472         fprintf(stderr, "Buffer overflow constructing show command!\n");
473         return NOTOK;
474     }
475
476     /* use charset string to modify display method */
477     if (ct->c_termproc) {
478         char term[BUFSIZ];
479
480         strncpy (term, buffer, sizeof(term));
481         snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
482     }
483
484 got_command:
485     return show_content_aux2 (ct, serial, alternate, cracked, buffer,
486                               fd, xlist, xpause, xstdin, xtty);
487 }
488
489
490 /*
491  * Routine to actually display the content
492  */
493
494 static int
495 show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer,
496                    int fd, int xlist, int xpause, int xstdin, int xtty)
497 {
498     pid_t child_id;
499     int i;
500     char *vec[4], exec[BUFSIZ + sizeof "exec "];
501     
502     if (debugsw || cracked) {
503         fflush (stdout);
504
505         fprintf (stderr, "%s msg %s", cracked ? "storing" : "show",
506                  ct->c_file);
507         if (ct->c_partno)
508             fprintf (stderr, " part %s", ct->c_partno);
509         if (cracked)
510             fprintf (stderr, " using command (cd %s; %s)\n", cracked, buffer);
511         else
512             fprintf (stderr, " using command %s\n", buffer);
513     }
514
515     if (xpid < 0 || (xtty && xpid)) {
516         if (xpid < 0)
517             xpid = -xpid;
518         pidcheck(pidwait (xpid, NOTOK));
519         xpid = 0;
520     }
521
522     if (xlist) {
523         char prompt[BUFSIZ];
524
525         if (ct->c_type == CT_MULTIPART)
526             list_content (ct, -1, 1, 0, 0);
527         else
528             list_switch (ct, -1, 1, 0, 0);
529
530         if (xpause && isatty (fileno (stdout))) {
531             int intr;
532             SIGNAL_HANDLER istat;
533
534             if (SOprintf ("Press <return> to show content..."))
535                 printf ("Press <return> to show content...");
536
537             istat = SIGNAL (SIGINT, intrser);
538             if ((intr = sigsetjmp (intrenv, 1)) == OK) {
539                 fflush (stdout);
540                 prompt[0] = 0;
541                 read (fileno (stdout), prompt, sizeof(prompt));
542             }
543             SIGNAL (SIGINT, istat);
544             if (intr != OK || prompt[0] == 'n') {
545                 (*ct->c_ceclosefnx) (ct);
546                 return (alternate ? DONE : NOTOK);
547             }
548             if (prompt[0] == 'q') done(OK);
549         }
550     }
551
552     snprintf (exec, sizeof(exec), "exec %s", buffer);
553
554     vec[0] = "/bin/sh";
555     vec[1] = "-c";
556     vec[2] = exec;
557     vec[3] = NULL;
558
559     fflush (stdout);
560
561     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
562         sleep (5);
563     switch (child_id) {
564         case NOTOK:
565             advise ("fork", "unable to");
566             (*ct->c_ceclosefnx) (ct);
567             return NOTOK;
568
569         case OK:
570             if (cracked)
571                 chdir (cracked);
572             if (!xstdin)
573                 dup2 (fd, 0);
574             close (fd);
575             execvp ("/bin/sh", vec);
576             fprintf (stderr, "unable to exec ");
577             perror ("/bin/sh");
578             _exit (-1);
579             /* NOTREACHED */
580
581         default:
582             if (!serial) {
583                 ct->c_pid = child_id;
584                 if (xtty)
585                     xpid = child_id;
586             } else {
587                 pidcheck (pidXwait (child_id, NULL));
588             }
589
590             if (fd != NOTOK)
591                 (*ct->c_ceclosefnx) (ct);
592             return (alternate ? DONE : OK);
593     }
594 }
595
596
597 /*
598  * show content of type "text"
599  */
600
601 static int
602 show_text (CT ct, int serial, int alternate)
603 {
604     char *cp, buffer[BUFSIZ];
605     CI ci = &ct->c_ctinfo;
606
607     /* Check for mhn-show-type/subtype */
608     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
609                 invo_name, ci->ci_type, ci->ci_subtype);
610     if ((cp = context_find (buffer)) && *cp != '\0')
611         return show_content_aux (ct, serial, alternate, cp, NULL);
612
613     /* Check for mhn-show-type */
614     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
615     if ((cp = context_find (buffer)) && *cp != '\0')
616         return show_content_aux (ct, serial, alternate, cp, NULL);
617
618     /*
619      * Use default method if content is text/plain, or if
620      * if it is not a text part of a multipart/alternative
621      */
622     if (!alternate || ct->c_subtype == TEXT_PLAIN) {
623         snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw :
624                 moreproc && *moreproc ? moreproc : "more");
625         cp = (ct->c_showproc = add (buffer, NULL));
626         return show_content_aux (ct, serial, alternate, cp, NULL);
627     }
628
629     return NOTOK;
630 }
631
632
633 /*
634  * show message body of type "multipart"
635  */
636
637 static int
638 show_multi (CT ct, int serial, int alternate)
639 {
640     char *cp, buffer[BUFSIZ];
641     CI ci = &ct->c_ctinfo;
642
643     /* Check for mhn-show-type/subtype */
644     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
645                 invo_name, ci->ci_type, ci->ci_subtype);
646     if ((cp = context_find (buffer)) && *cp != '\0')
647         return show_multi_aux (ct, serial, alternate, cp);
648
649     /* Check for mhn-show-type */
650     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
651     if ((cp = context_find (buffer)) && *cp != '\0')
652         return show_multi_aux (ct, serial, alternate, cp);
653
654     if ((cp = ct->c_showproc))
655         return show_multi_aux (ct, serial, alternate, cp);
656
657     /*
658      * Use default method to display this multipart content
659      * if it is not a (nested) part of a multipart/alternative,
660      * or if it is one of the known subtypes of multipart.
661      */
662     if (!alternate || ct->c_subtype != MULTI_UNKNOWN)
663         return show_multi_internal (ct, serial, alternate);
664
665     return NOTOK;
666 }
667
668
669 /*
670  * show message body of subtypes of multipart that
671  * we understand directly (mixed, alternate, etc...)
672  */
673
674 static int
675 show_multi_internal (CT ct, int serial, int alternate)
676 {
677     int alternating, nowalternate, nowserial, result;
678     struct multipart *m = (struct multipart *) ct->c_ctparams;
679     struct part *part;
680     CT p;
681     sigset_t set, oset;
682
683     alternating = 0;
684     nowalternate = alternate;
685
686     if (ct->c_subtype == MULTI_PARALLEL) {
687         nowserial = serialsw;
688     } else if (ct->c_subtype == MULTI_ALTERNATE) {
689         nowalternate = 1;
690         alternating  = 1;
691         nowserial = serial;
692     } else {
693         /*
694          * multipart/mixed
695          * mutlipart/digest
696          * unknown subtypes of multipart (treat as mixed per rfc2046)
697          */
698         nowserial = serial;
699     }
700
701     /* block a few signals */
702     if (!nowserial) {
703         sigemptyset (&set);
704         sigaddset (&set, SIGHUP);
705         sigaddset (&set, SIGINT);
706         sigaddset (&set, SIGQUIT);
707         sigaddset (&set, SIGTERM);
708         sigprocmask (SIG_BLOCK, &set, &oset);
709     }
710
711 /*
712  * alternate   -> we are a part inside an multipart/alternative
713  * alternating -> we are a multipart/alternative 
714  */
715
716     result = alternate ? NOTOK : OK;
717
718     for (part = m->mp_parts; part; part = part->mp_next) {
719         p = part->mp_part;
720
721         if (part_ok (p, 1) && type_ok (p, 1)) {
722             int inneresult;
723
724             inneresult = show_switch (p, nowserial, nowalternate);
725             switch (inneresult) {
726                 case NOTOK:
727                     if (alternate && !alternating) {
728                         result = NOTOK;
729                         goto out;
730                     }
731                     continue;
732
733                 case OK:
734                 case DONE:
735                     if (alternating) {
736                         result = DONE;
737                         break;
738                     }
739                     if (alternate) {
740                         alternate = nowalternate = 0;
741                         if (result == NOTOK)
742                             result = inneresult;
743                     }
744                     continue;
745             }
746             break;
747         }
748     }
749
750     if (alternating && !part) {
751         if (!alternate)
752             content_error (NULL, ct, "don't know how to display any of the contents");
753         result = NOTOK;
754         goto out;
755     }
756
757     if (serial && !nowserial) {
758         pid_t pid;
759         int kids;
760         int status;
761
762         kids = 0;
763         for (part = m->mp_parts; part; part = part->mp_next) {
764             p = part->mp_part;
765
766             if (p->c_pid > OK) {
767                 if (kill (p->c_pid, 0) == NOTOK)
768                     p->c_pid = 0;
769                 else
770                     kids++;
771             }
772         }
773
774         while (kids > 0 && (pid = wait (&status)) != NOTOK) {
775             pidcheck (status);
776
777             for (part = m->mp_parts; part; part = part->mp_next) {
778                 p = part->mp_part;
779
780                 if (xpid == pid)
781                     xpid = 0;
782                 if (p->c_pid == pid) {
783                     p->c_pid = 0;
784                     kids--;
785                     break;
786                 }
787             }
788         }
789     }
790
791 out:
792     if (!nowserial) {
793         /* reset the signal mask */
794         sigprocmask (SIG_SETMASK, &oset, &set);
795     }
796
797     return result;
798 }
799
800
801 /*
802  * Parse display string for multipart content
803  * and use external program to display it.
804  */
805
806 static int
807 show_multi_aux (CT ct, int serial, int alternate, char *cp)
808 {
809     int len, buflen, quoted;
810     int xlist, xpause, xtty;
811     char *bp, *pp, *file, buffer[BUFSIZ];
812     struct multipart *m = (struct multipart *) ct->c_ctparams;
813     struct part *part;
814     CI ci = &ct->c_ctinfo;
815     CT p;
816
817     for (part = m->mp_parts; part; part = part->mp_next) {
818         p = part->mp_part;
819
820         if (!p->c_ceopenfnx) {
821             if (!alternate)
822                 content_error (NULL, p, "don't know how to decode content");
823             return NOTOK;
824         }
825
826         if (p->c_storage == NULL) {
827             file = NULL;
828             if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
829                 return NOTOK;
830
831             /* I'm not sure if this is necessary? */
832             p->c_storage = add (file, NULL);
833
834             if (p->c_showproc && !strcmp (p->c_showproc, "true"))
835                 return (alternate ? DONE : OK);
836             (*p->c_ceclosefnx) (p);
837         }
838     }
839
840     xlist     = 0;
841     xpause    = 0;
842     xtty      = 0;
843
844     /* get buffer ready to go */
845     bp = buffer;
846     buflen = sizeof(buffer) - 1;
847     bp[0] = bp[buflen] = '\0';
848     quoted = 0;
849
850     /* Now parse display string */
851     for ( ; *cp && buflen > 0; cp++) {
852         if (*cp == '%') {
853             pp = bp;
854             switch (*++cp) {
855             case 'a':
856                 /* insert parameters from Content-Type field */
857             {
858                 char **ap, **ep;
859                 char *s = "";
860
861                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
862                     snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
863                     len = strlen (bp);
864                     bp += len;
865                     buflen -= len;
866                     s = " ";
867                 }
868             }
869             break;
870
871             case 'd':
872                 /* insert content description */
873                 if (ct->c_descr) {
874                     char *s;
875
876                     s = trimcpy (ct->c_descr);
877                     strncpy (bp, s, buflen);
878                     free (s);
879                 }
880                 break;
881
882             case 'e':
883                 /* exclusive execution */
884                 xtty = 1;
885                 break;
886
887             case 'F':
888                 /* %e and %f */
889                 xtty = 1;
890                 /* and fall... */
891
892             case 'f':
893                 /* insert filename(s) containing content */
894             {
895                 char *s = "";
896                         
897                 for (part = m->mp_parts; part; part = part->mp_next) {
898                     p = part->mp_part;
899
900                     snprintf (bp, buflen, "%s'%s'", s, p->c_storage);
901                     len = strlen (bp);
902                     bp += len;
903                     buflen -= len;
904                     s = " ";
905                 }
906                 /* set our starting pointer back to bp, to avoid
907                  * requoting the filenames we just added
908                  */
909                 pp = bp;
910             }
911             break;
912
913             case 'p':
914                 /* %l, and pause prior to displaying content */
915                 xpause = pausesw;
916                 /* and fall... */
917
918             case 'l':
919                 /* display listing prior to displaying content */
920                 xlist = !nolist;
921                 break;
922
923             case 's':
924                 /* insert subtype of content */
925                 strncpy (bp, ci->ci_subtype, buflen);
926                 break;
927
928             case '%':
929                 /* insert character % */
930                 goto raw;
931
932             default:
933                 *bp++ = *--cp;
934                 *bp = '\0';
935                 buflen--;
936                 continue;
937             }
938             len = strlen (bp);
939             bp += len;
940             buflen -= len;
941
942             /* Did we actually insert something? */
943             if (bp != pp) {
944                 /* Insert single quote if not inside quotes already */
945                 if (!quoted && buflen) {
946                     len = strlen (pp);
947                     memmove (pp + 1, pp, len);
948                     *pp++ = '\'';
949                     buflen--;
950                     bp++;
951                 }
952                 /* Escape existing quotes */
953                 while ((pp = strchr (pp, '\'')) && buflen > 3) {
954                     len = strlen (pp++);
955                     memmove (pp + 3, pp, len);
956                     *pp++ = '\\';
957                     *pp++ = '\'';
958                     *pp++ = '\'';
959                     buflen -= 3;
960                     bp += 3;
961                 }
962                 /* If pp is still set, that means we ran out of space. */
963                 if (pp)
964                     buflen = 0;
965                 if (!quoted && buflen) {
966                     *bp++ = '\'';
967                     *bp = '\0';
968                     buflen--;
969                 }
970             }
971         } else {
972 raw:
973             *bp++ = *cp;
974             *bp = '\0';
975             buflen--;
976
977             if (*cp == '\'')
978                 quoted = !quoted;
979         }
980     }
981
982     if (buflen <= 0 ||
983         (ct->c_termproc && (size_t) buflen <= strlen(ct->c_termproc))) {
984         /* content_error would provide a more useful error message
985          * here, except that if we got overrun, it probably would
986          * too.
987          */
988         fprintf(stderr, "Buffer overflow constructing show command!\n");
989         return NOTOK;
990     }
991
992     /* use charset string to modify display method */
993     if (ct->c_termproc) {
994         char term[BUFSIZ];
995
996         strncpy (term, buffer, sizeof(term));
997         snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
998     }
999
1000     return show_content_aux2 (ct, serial, alternate, NULL, buffer,
1001                               NOTOK, xlist, xpause, 0, xtty);
1002 }
1003
1004
1005 /*
1006  * show content of type "message/rfc822"
1007  */
1008
1009 static int
1010 show_message_rfc822 (CT ct, int serial, int alternate)
1011 {
1012     char *cp, buffer[BUFSIZ];
1013     CI ci = &ct->c_ctinfo;
1014
1015     /* Check for mhn-show-type/subtype */
1016     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
1017                 invo_name, ci->ci_type, ci->ci_subtype);
1018     if ((cp = context_find (buffer)) && *cp != '\0')
1019         return show_content_aux (ct, serial, alternate, cp, NULL);
1020
1021     /* Check for mhn-show-type */
1022     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
1023     if ((cp = context_find (buffer)) && *cp != '\0')
1024         return show_content_aux (ct, serial, alternate, cp, NULL);
1025
1026     if ((cp = ct->c_showproc))
1027         return show_content_aux (ct, serial, alternate, cp, NULL);
1028
1029     /* default method for message/rfc822 */
1030     if (ct->c_subtype == MESSAGE_RFC822) {
1031         cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL));
1032         return show_content_aux (ct, serial, alternate, cp, NULL);
1033     }
1034
1035     /* complain if we are not a part of a multipart/alternative */
1036     if (!alternate)
1037         content_error (NULL, ct, "don't know how to display content");
1038
1039     return NOTOK;
1040 }
1041
1042
1043 /*
1044  * Show content of type "message/partial".
1045  */
1046
1047 static int
1048 show_partial (CT ct, int serial, int alternate)
1049 {
1050     NMH_UNUSED (serial);
1051     NMH_UNUSED (alternate);
1052
1053     content_error (NULL, ct,
1054         "in order to display this message, you must reassemble it");
1055     return NOTOK;
1056 }
1057
1058
1059 /*
1060  * Show content of type "message/external".
1061  *
1062  * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
1063  */
1064
1065 static int
1066 show_external (CT ct, int serial, int alternate)
1067 {
1068     struct exbody *e = (struct exbody *) ct->c_ctparams;
1069     CT p = e->eb_content;
1070
1071     if (!type_ok (p, 0))
1072         return OK;
1073
1074     return show_switch (p, serial, alternate);
1075
1076 #if 0
1077     content_error (NULL, p, "don't know how to display content");
1078     return NOTOK;
1079 #endif
1080 }
1081
1082
1083 static void
1084 intrser (int i)
1085 {
1086     NMH_UNUSED (i);
1087
1088     putchar ('\n');
1089     siglongjmp (intrenv, DONE);
1090 }