Fixed trivial compile warnings.
[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 /* 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 RETSIGTYPE 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 = 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 || (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
473         /* content_error would provide a more useful error message
474          * here, except that if we got overrun, it probably would
475          * too.
476          */
477         fprintf(stderr, "Buffer overflow constructing show command!\n");
478         return NOTOK;
479     }
480
481     /* use charset string to modify display method */
482     if (ct->c_termproc) {
483         char term[BUFSIZ];
484
485         strncpy (term, buffer, sizeof(term));
486         snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
487     }
488
489 got_command:
490     return show_content_aux2 (ct, serial, alternate, cracked, buffer,
491                               fd, xlist, xpause, xstdin, xtty);
492 }
493
494
495 /*
496  * Routine to actually display the content
497  */
498
499 static int
500 show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer,
501                    int fd, int xlist, int xpause, int xstdin, int xtty)
502 {
503     pid_t child_id;
504     int i;
505     char *vec[4], exec[BUFSIZ + sizeof "exec "];
506     
507     if (debugsw || cracked) {
508         fflush (stdout);
509
510         fprintf (stderr, "%s msg %s", cracked ? "storing" : "show",
511                  ct->c_file);
512         if (ct->c_partno)
513             fprintf (stderr, " part %s", ct->c_partno);
514         if (cracked)
515             fprintf (stderr, " using command (cd %s; %s)\n", cracked, buffer);
516         else
517             fprintf (stderr, " using command %s\n", buffer);
518     }
519
520     if (xpid < 0 || (xtty && xpid)) {
521         if (xpid < 0)
522             xpid = -xpid;
523         pidcheck(pidwait (xpid, NOTOK));
524         xpid = 0;
525     }
526
527     if (xlist) {
528         char prompt[BUFSIZ];
529
530         if (ct->c_type == CT_MULTIPART)
531             list_content (ct, -1, 1, 0, 0);
532         else
533             list_switch (ct, -1, 1, 0, 0);
534
535         if (xpause && SOprintf ("Press <return> to show content..."))
536             printf ("Press <return> to show content...");
537
538         if (xpause) {
539             int intr;
540             SIGNAL_HANDLER istat;
541
542             istat = SIGNAL (SIGINT, intrser);
543             if ((intr = sigsetjmp (intrenv, 1)) == OK) {
544                 fflush (stdout);
545                 prompt[0] = 0;
546                 read (fileno (stdout), prompt, sizeof(prompt));
547             }
548             SIGNAL (SIGINT, istat);
549             if (intr != OK || prompt[0] == 'n') {
550                 (*ct->c_ceclosefnx) (ct);
551                 return (alternate ? DONE : NOTOK);
552             }
553             if (prompt[0] == 'q') done(OK);
554         }
555     }
556
557     snprintf (exec, sizeof(exec), "exec %s", buffer);
558
559     vec[0] = "/bin/sh";
560     vec[1] = "-c";
561     vec[2] = exec;
562     vec[3] = NULL;
563
564     fflush (stdout);
565
566     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
567         sleep (5);
568     switch (child_id) {
569         case NOTOK:
570             advise ("fork", "unable to");
571             (*ct->c_ceclosefnx) (ct);
572             return NOTOK;
573
574         case OK:
575             if (cracked)
576                 chdir (cracked);
577             if (!xstdin)
578                 dup2 (fd, 0);
579             close (fd);
580             execvp ("/bin/sh", vec);
581             fprintf (stderr, "unable to exec ");
582             perror ("/bin/sh");
583             _exit (-1);
584             /* NOTREACHED */
585
586         default:
587             if (!serial) {
588                 ct->c_pid = child_id;
589                 if (xtty)
590                     xpid = child_id;
591             } else {
592                 pidcheck (pidXwait (child_id, NULL));
593             }
594
595             if (fd != NOTOK)
596                 (*ct->c_ceclosefnx) (ct);
597             return (alternate ? DONE : OK);
598     }
599 }
600
601
602 /*
603  * show content of type "text"
604  */
605
606 static int
607 show_text (CT ct, int serial, int alternate)
608 {
609     char *cp, buffer[BUFSIZ];
610     CI ci = &ct->c_ctinfo;
611
612     /* Check for mhn-show-type/subtype */
613     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
614                 invo_name, ci->ci_type, ci->ci_subtype);
615     if ((cp = context_find (buffer)) && *cp != '\0')
616         return show_content_aux (ct, serial, alternate, cp, NULL);
617
618     /* Check for mhn-show-type */
619     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
620     if ((cp = context_find (buffer)) && *cp != '\0')
621         return show_content_aux (ct, serial, alternate, cp, NULL);
622
623     /*
624      * Use default method if content is text/plain, or if
625      * if it is not a text part of a multipart/alternative
626      */
627     if (!alternate || ct->c_subtype == TEXT_PLAIN) {
628         snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw :
629                 moreproc && *moreproc ? moreproc : "more");
630         cp = (ct->c_showproc = add (buffer, NULL));
631         return show_content_aux (ct, serial, alternate, cp, NULL);
632     }
633
634     return NOTOK;
635 }
636
637
638 /*
639  * show message body of type "multipart"
640  */
641
642 static int
643 show_multi (CT ct, int serial, int alternate)
644 {
645     char *cp, buffer[BUFSIZ];
646     CI ci = &ct->c_ctinfo;
647
648     /* Check for mhn-show-type/subtype */
649     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
650                 invo_name, ci->ci_type, ci->ci_subtype);
651     if ((cp = context_find (buffer)) && *cp != '\0')
652         return show_multi_aux (ct, serial, alternate, cp);
653
654     /* Check for mhn-show-type */
655     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
656     if ((cp = context_find (buffer)) && *cp != '\0')
657         return show_multi_aux (ct, serial, alternate, cp);
658
659     if ((cp = ct->c_showproc))
660         return show_multi_aux (ct, serial, alternate, cp);
661
662     /*
663      * Use default method to display this multipart content
664      * if it is not a (nested) part of a multipart/alternative,
665      * or if it is one of the known subtypes of multipart.
666      */
667     if (!alternate || ct->c_subtype != MULTI_UNKNOWN)
668         return show_multi_internal (ct, serial, alternate);
669
670     return NOTOK;
671 }
672
673
674 /*
675  * show message body of subtypes of multipart that
676  * we understand directly (mixed, alternate, etc...)
677  */
678
679 static int
680 show_multi_internal (CT ct, int serial, int alternate)
681 {
682     int alternating, nowalternate, nowserial, result;
683     struct multipart *m = (struct multipart *) ct->c_ctparams;
684     struct part *part;
685     CT p;
686     sigset_t set, oset;
687
688     alternating = 0;
689     nowalternate = alternate;
690
691     if (ct->c_subtype == MULTI_PARALLEL) {
692         nowserial = serialsw;
693     } else if (ct->c_subtype == MULTI_ALTERNATE) {
694         nowalternate = 1;
695         alternating  = 1;
696         nowserial = serial;
697     } else {
698         /*
699          * multipart/mixed
700          * mutlipart/digest
701          * unknown subtypes of multipart (treat as mixed per rfc2046)
702          */
703         nowserial = serial;
704     }
705
706     /* block a few signals */
707     if (!nowserial) {
708         sigemptyset (&set);
709         sigaddset (&set, SIGHUP);
710         sigaddset (&set, SIGINT);
711         sigaddset (&set, SIGQUIT);
712         sigaddset (&set, SIGTERM);
713         SIGPROCMASK (SIG_BLOCK, &set, &oset);
714     }
715
716 /*
717  * alternate   -> we are a part inside an multipart/alternative
718  * alternating -> we are a multipart/alternative 
719  */
720
721     result = alternate ? NOTOK : OK;
722
723     for (part = m->mp_parts; part; part = part->mp_next) {
724         p = part->mp_part;
725
726         if (part_ok (p, 1) && type_ok (p, 1)) {
727             int inneresult;
728
729             inneresult = show_switch (p, nowserial, nowalternate);
730             switch (inneresult) {
731                 case NOTOK:
732                     if (alternate && !alternating) {
733                         result = NOTOK;
734                         goto out;
735                     }
736                     continue;
737
738                 case OK:
739                 case DONE:
740                     if (alternating) {
741                         result = DONE;
742                         break;
743                     }
744                     if (alternate) {
745                         alternate = nowalternate = 0;
746                         if (result == NOTOK)
747                             result = inneresult;
748                     }
749                     continue;
750             }
751             break;
752         }
753     }
754
755     if (alternating && !part) {
756         if (!alternate)
757             content_error (NULL, ct, "don't know how to display any of the contents");
758         result = NOTOK;
759         goto out;
760     }
761
762     if (serial && !nowserial) {
763         pid_t pid;
764         int kids;
765         int status;
766
767         kids = 0;
768         for (part = m->mp_parts; part; part = part->mp_next) {
769             p = part->mp_part;
770
771             if (p->c_pid > OK) {
772                 if (kill (p->c_pid, 0) == NOTOK)
773                     p->c_pid = 0;
774                 else
775                     kids++;
776             }
777         }
778
779         while (kids > 0 && (pid = wait (&status)) != NOTOK) {
780             pidcheck (status);
781
782             for (part = m->mp_parts; part; part = part->mp_next) {
783                 p = part->mp_part;
784
785                 if (xpid == pid)
786                     xpid = 0;
787                 if (p->c_pid == pid) {
788                     p->c_pid = 0;
789                     kids--;
790                     break;
791                 }
792             }
793         }
794     }
795
796 out:
797     if (!nowserial) {
798         /* reset the signal mask */
799         SIGPROCMASK (SIG_SETMASK, &oset, &set);
800     }
801
802     return result;
803 }
804
805
806 /*
807  * Parse display string for multipart content
808  * and use external program to display it.
809  */
810
811 static int
812 show_multi_aux (CT ct, int serial, int alternate, char *cp)
813 {
814     int len, buflen, quoted;
815     int xlist, xpause, xtty;
816     char *bp, *pp, *file, buffer[BUFSIZ];
817     struct multipart *m = (struct multipart *) ct->c_ctparams;
818     struct part *part;
819     CI ci = &ct->c_ctinfo;
820     CT p;
821
822     for (part = m->mp_parts; part; part = part->mp_next) {
823         p = part->mp_part;
824
825         if (!p->c_ceopenfnx) {
826             if (!alternate)
827                 content_error (NULL, p, "don't know how to decode content");
828             return NOTOK;
829         }
830
831         if (p->c_storage == NULL) {
832             file = NULL;
833             if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
834                 return NOTOK;
835
836             /* I'm not sure if this is necessary? */
837             p->c_storage = add (file, NULL);
838
839             if (p->c_showproc && !strcmp (p->c_showproc, "true"))
840                 return (alternate ? DONE : OK);
841             (*p->c_ceclosefnx) (p);
842         }
843     }
844
845     xlist     = 0;
846     xpause    = 0;
847     xtty      = 0;
848
849     /* get buffer ready to go */
850     bp = buffer;
851     buflen = sizeof(buffer) - 1;
852     bp[0] = bp[buflen] = '\0';
853     quoted = 0;
854
855     /* Now parse display string */
856     for ( ; *cp && buflen > 0; cp++) {
857         if (*cp == '%') {
858             pp = bp;
859             switch (*++cp) {
860             case 'a':
861                 /* insert parameters from Content-Type field */
862             {
863                 char **ap, **ep;
864                 char *s = "";
865
866                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
867                     snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
868                     len = strlen (bp);
869                     bp += len;
870                     buflen -= len;
871                     s = " ";
872                 }
873             }
874             break;
875
876             case 'd':
877                 /* insert content description */
878                 if (ct->c_descr) {
879                     char *s;
880
881                     s = trimcpy (ct->c_descr);
882                     strncpy (bp, s, buflen);
883                     free (s);
884                 }
885                 break;
886
887             case 'e':
888                 /* exclusive execution */
889                 xtty = 1;
890                 break;
891
892             case 'F':
893                 /* %e and %f */
894                 xtty = 1;
895                 /* and fall... */
896
897             case 'f':
898                 /* insert filename(s) containing content */
899             {
900                 char *s = "";
901                         
902                 for (part = m->mp_parts; part; part = part->mp_next) {
903                     p = part->mp_part;
904
905                     snprintf (bp, buflen, "%s'%s'", s, p->c_storage);
906                     len = strlen (bp);
907                     bp += len;
908                     buflen -= len;
909                     s = " ";
910                 }
911                 /* set our starting pointer back to bp, to avoid
912                  * requoting the filenames we just added
913                  */
914                 pp = bp;
915             }
916             break;
917
918             case 'p':
919                 /* %l, and pause prior to displaying content */
920                 xpause = pausesw;
921                 /* and fall... */
922
923             case 'l':
924                 /* display listing prior to displaying content */
925                 xlist = !nolist;
926                 break;
927
928             case 's':
929                 /* insert subtype of content */
930                 strncpy (bp, ci->ci_subtype, buflen);
931                 break;
932
933             case '%':
934                 /* insert character % */
935                 goto raw;
936
937             default:
938                 *bp++ = *--cp;
939                 *bp = '\0';
940                 buflen--;
941                 continue;
942             }
943             len = strlen (bp);
944             bp += len;
945             buflen -= len;
946
947             /* Did we actually insert something? */
948             if (bp != pp) {
949                 /* Insert single quote if not inside quotes already */
950                 if (!quoted && buflen) {
951                     len = strlen (pp);
952                     memmove (pp + 1, pp, len);
953                     *pp++ = '\'';
954                     buflen--;
955                     bp++;
956                 }
957                 /* Escape existing quotes */
958                 while ((pp = strchr (pp, '\'')) && buflen > 3) {
959                     len = strlen (pp++);
960                     memmove (pp + 3, pp, len);
961                     *pp++ = '\\';
962                     *pp++ = '\'';
963                     *pp++ = '\'';
964                     buflen -= 3;
965                     bp += 3;
966                 }
967                 /* If pp is still set, that means we ran out of space. */
968                 if (pp)
969                     buflen = 0;
970                 if (!quoted && buflen) {
971                     *bp++ = '\'';
972                     *bp = '\0';
973                     buflen--;
974                 }
975             }
976         } else {
977 raw:
978             *bp++ = *cp;
979             *bp = '\0';
980             buflen--;
981
982             if (*cp == '\'')
983                 quoted = !quoted;
984         }
985     }
986
987     if (buflen <= 0 || (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
988         /* content_error would provide a more useful error message
989          * here, except that if we got overrun, it probably would
990          * too.
991          */
992         fprintf(stderr, "Buffer overflow constructing show command!\n");
993         return NOTOK;
994     }
995
996     /* use charset string to modify display method */
997     if (ct->c_termproc) {
998         char term[BUFSIZ];
999
1000         strncpy (term, buffer, sizeof(term));
1001         snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
1002     }
1003
1004     return show_content_aux2 (ct, serial, alternate, NULL, buffer,
1005                               NOTOK, xlist, xpause, 0, xtty);
1006 }
1007
1008
1009 /*
1010  * show content of type "message/rfc822"
1011  */
1012
1013 static int
1014 show_message_rfc822 (CT ct, int serial, int alternate)
1015 {
1016     char *cp, buffer[BUFSIZ];
1017     CI ci = &ct->c_ctinfo;
1018
1019     /* Check for mhn-show-type/subtype */
1020     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
1021                 invo_name, ci->ci_type, ci->ci_subtype);
1022     if ((cp = context_find (buffer)) && *cp != '\0')
1023         return show_content_aux (ct, serial, alternate, cp, NULL);
1024
1025     /* Check for mhn-show-type */
1026     snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
1027     if ((cp = context_find (buffer)) && *cp != '\0')
1028         return show_content_aux (ct, serial, alternate, cp, NULL);
1029
1030     if ((cp = ct->c_showproc))
1031         return show_content_aux (ct, serial, alternate, cp, NULL);
1032
1033     /* default method for message/rfc822 */
1034     if (ct->c_subtype == MESSAGE_RFC822) {
1035         cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL));
1036         return show_content_aux (ct, serial, alternate, cp, NULL);
1037     }
1038
1039     /* complain if we are not a part of a multipart/alternative */
1040     if (!alternate)
1041         content_error (NULL, ct, "don't know how to display content");
1042
1043     return NOTOK;
1044 }
1045
1046
1047 /*
1048  * Show content of type "message/partial".
1049  */
1050
1051 static int
1052 show_partial (CT ct, int serial, int alternate)
1053 {
1054     content_error (NULL, ct,
1055         "in order to display this message, you must reassemble it");
1056     return NOTOK;
1057 }
1058
1059
1060 /*
1061  * Show content of type "message/external".
1062  *
1063  * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
1064  */
1065
1066 static int
1067 show_external (CT ct, int serial, int alternate)
1068 {
1069     struct exbody *e = (struct exbody *) ct->c_ctparams;
1070     CT p = e->eb_content;
1071
1072     if (!type_ok (p, 0))
1073         return OK;
1074
1075     return show_switch (p, serial, alternate);
1076
1077 #if 0
1078     content_error (NULL, p, "don't know how to display content");
1079     return NOTOK;
1080 #endif
1081 }
1082
1083
1084 static RETSIGTYPE
1085 intrser (int i)
1086 {
1087 #ifndef RELIABLE_SIGNALS
1088     SIGNAL (SIGINT, intrser);
1089 #endif
1090
1091     putchar ('\n');
1092     siglongjmp (intrenv, DONE);
1093 }