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