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