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