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