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