mhshow: Wait for children synchonously because we run them serialized.
[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                                 if (ct->c_charset) {
316                                         strncpy(bp, ct->c_charset, buflen);
317                                 }
318                                 break;
319
320                         case 'd':
321                                 /* insert content description */
322                                 if (ct->c_descr) {
323                                         char *s;
324
325                                         s = trimcpy(ct->c_descr);
326                                         strncpy(bp, s, buflen);
327                                         free(s);
328                                 }
329                                 break;
330
331                         case 'F':
332                                 /* %f, and stdin is terminal not content */
333                                 xstdin = 1;
334                                 /* and fall... */
335
336                         case 'f':
337                                 /* insert filename containing content */
338                                 snprintf(bp, buflen, "'%s'", file);
339                                 /*
340                                 ** since we've quoted the file argument,
341                                 ** set things up to look past it, to avoid
342                                 ** problems with the quoting logic below.
343                                 ** (I know, I should figure out what's
344                                 ** broken with the quoting logic, but..)
345                                 */
346                                 len = strlen(bp);
347                                 buflen -= len;
348                                 bp += len;
349                                 pp = bp;
350                                 break;
351
352                         case 'l':
353                                 /*
354                                 ** display listing prior to displaying content
355                                 */
356                                 xlist = !nolist;
357                                 break;
358
359                         case 's':
360                                 /* insert subtype of content */
361                                 strncpy(bp, ci->ci_subtype, buflen);
362                                 break;
363
364                         case '%':
365                                 /* insert character % */
366                                 goto raw;
367
368                         default:
369                                 *bp++ = *--cp;
370                                 *bp = '\0';
371                                 buflen--;
372                                 continue;
373                         }
374                         len = strlen(bp);
375                         bp += len;
376                         buflen -= len;
377
378                         /* Did we actually insert something? */
379                         if (bp != pp) {
380                                 /*
381                                 ** Insert single quote if not inside quotes
382                                 ** already
383                                 */
384                                 if (!quoted && buflen) {
385                                         len = strlen(pp);
386                                         memmove(pp + 1, pp, len);
387                                         *pp++ = '\'';
388                                         buflen--;
389                                         bp++;
390                                 }
391                                 /* Escape existing quotes */
392                                 while ((pp = strchr(pp, '\'')) && buflen > 3) {
393                                         len = strlen(pp++);
394                                         memmove(pp + 3, pp, len);
395                                         *pp++ = '\\';
396                                         *pp++ = '\'';
397                                         *pp++ = '\'';
398                                         buflen -= 3;
399                                         bp += 3;
400                                 }
401                                 /*
402                                 ** If pp is still set, that means we ran
403                                 ** out of space.
404                                 */
405                                 if (pp)
406                                         buflen = 0;
407                                 if (!quoted && buflen) {
408                                         *bp++ = '\'';
409                                         *bp = '\0';
410                                         buflen--;
411                                 }
412                         }
413                 } else {
414 raw:
415                         *bp++ = *cp;
416                         *bp = '\0';
417                         buflen--;
418
419                         if (*cp == '\'')
420                                 quoted = !quoted;
421                 }
422         }
423
424 got_command:
425         return show_content_aux2(ct, alternate, cracked, buffer,
426                         fd, xlist, xstdin);
427 }
428
429
430 /*
431 ** Routine to actually display the content
432 */
433 static int
434 show_content_aux2(CT ct, int alternate, char *cracked,
435         char *buffer, int fd, int xlist, int xstdin)
436 {
437         pid_t child_id;
438
439         if (debugsw || cracked) {
440                 fflush(stdout);
441
442                 fprintf(stderr, "%s msg %s", cracked ? "storing" : "show",
443                                  ct->c_file);
444                 if (ct->c_partno)
445                         fprintf(stderr, " part %s", ct->c_partno);
446                 if (cracked)
447                         fprintf(stderr, " using command (cd %s; %s)\n",
448                                         cracked, buffer);
449                 else
450                         fprintf(stderr, " using command %s\n", buffer);
451         }
452
453         if (xlist) {
454                 if (ct->c_type == CT_MULTIPART)
455                         list_content(ct, -1, 1, 0, 0);
456                 else
457                         list_switch(ct, -1, 1, 0, 0);
458         }
459
460         fflush(stdout);
461
462         switch (child_id = fork()) {
463         case NOTOK:
464                 advise("fork", "unable to");
465                 (*ct->c_ceclosefnx) (ct);
466                 return NOTOK;
467
468         case OK:
469                 if (cracked)
470                         chdir(cracked);
471                 if (!xstdin)
472                         dup2(fd, 0);
473                 close(fd);
474                 execlp("/bin/sh", "/bin/sh", "-c", buffer, NULL);
475                 fprintf(stderr, "unable to exec ");
476                 perror("/bin/sh");
477                 _exit(-1);
478                 /* NOTREACHED */
479
480         default:
481                 pidcheck(pidXwait(child_id, NULL));
482
483                 if (fd != NOTOK)
484                         (*ct->c_ceclosefnx) (ct);
485                 return (alternate ? DONE : OK);
486         }
487 }
488
489
490 /*
491 ** show content of type "text"
492 */
493
494 static int
495 show_text(CT ct, int alternate)
496 {
497         char *cp, buffer[BUFSIZ];
498         CI ci = &ct->c_ctinfo;
499
500         /* Check for mhshow-show-type/subtype */
501         snprintf(buffer, sizeof(buffer), "%s-show-%s/%s",
502                         invo_name, ci->ci_type, ci->ci_subtype);
503         if ((cp = context_find(buffer)) && *cp != '\0')
504                 return show_content_aux(ct, alternate, cp, NULL);
505
506         /* Check for mhshow-show-type */
507         snprintf(buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
508         if ((cp = context_find(buffer)) && *cp != '\0')
509                 return show_content_aux(ct, alternate, cp, NULL);
510
511         /*
512         ** Use default method if content is text/plain, or if
513         ** if it is not a text part of a multipart/alternative
514         */
515         if (!alternate || ct->c_subtype == TEXT_PLAIN) {
516                 if (!check_charset(ct->c_charset, 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                                 if (ct->c_charset) {
718                                         strncpy(bp, ct->c_charset, buflen);
719                                 }
720                                 break;
721
722                         case 'd':
723                                 /* insert content description */
724                                 if (ct->c_descr) {
725                                         char *s;
726
727                                         s = trimcpy(ct->c_descr);
728                                         strncpy(bp, s, buflen);
729                                         free(s);
730                                 }
731                                 break;
732
733                         case 'F':
734                         case 'f':
735                                 /* insert filename(s) containing content */
736                         {
737                                 char *s = "";
738
739                                 for (part = m->mp_parts; part;
740                                                 part = part->mp_next) {
741                                         p = part->mp_part;
742
743                                         snprintf(bp, buflen, "%s'%s'",
744                                                         s, p->c_storage);
745                                         len = strlen(bp);
746                                         bp += len;
747                                         buflen -= len;
748                                         s = " ";
749                                 }
750                                 /*
751                                 ** set our starting pointer back to bp,
752                                 ** to avoid requoting the filenames we
753                                 ** just added
754                                 */
755                                 pp = bp;
756                         }
757                         break;
758
759                         case 'l':
760                                 /*
761                                 ** display listing prior to displaying content
762                                 */
763                                 xlist = !nolist;
764                                 break;
765
766                         case 's':
767                                 /* insert subtype of content */
768                                 strncpy(bp, ci->ci_subtype, buflen);
769                                 break;
770
771                         case '%':
772                                 /* insert character % */
773                                 goto raw;
774
775                         default:
776                                 *bp++ = *--cp;
777                                 *bp = '\0';
778                                 buflen--;
779                                 continue;
780                         }
781                         len = strlen(bp);
782                         bp += len;
783                         buflen -= len;
784
785                         /* Did we actually insert something? */
786                         if (bp != pp) {
787                                 /*
788                                 ** Insert single quote if not inside quotes
789                                 ** already
790                                 */
791                                 if (!quoted && buflen) {
792                                         len = strlen(pp);
793                                         memmove(pp + 1, pp, len);
794                                         *pp++ = '\'';
795                                         buflen--;
796                                         bp++;
797                                 }
798                                 /* Escape existing quotes */
799                                 while ((pp = strchr(pp, '\'')) && buflen > 3) {
800                                         len = strlen(pp++);
801                                         memmove(pp + 3, pp, len);
802                                         *pp++ = '\\';
803                                         *pp++ = '\'';
804                                         *pp++ = '\'';
805                                         buflen -= 3;
806                                         bp += 3;
807                                 }
808                                 /*
809                                 ** If pp is still set, that means we ran
810                                 ** out of space.
811                                 */
812                                 if (pp)
813                                         buflen = 0;
814                                 if (!quoted && buflen) {
815                                         *bp++ = '\'';
816                                         *bp = '\0';
817                                         buflen--;
818                                 }
819                         }
820                 } else {
821 raw:
822                         *bp++ = *cp;
823                         *bp = '\0';
824                         buflen--;
825
826                         if (*cp == '\'')
827                                 quoted = !quoted;
828                 }
829         }
830
831         return show_content_aux2(ct, alternate, NULL, buffer,
832                         NOTOK, xlist, 0);
833 }
834
835
836 /*
837 ** show content of type "message/rfc822"
838 */
839
840 static int
841 show_message_rfc822(CT ct, int alternate)
842 {
843         char *cp, buffer[BUFSIZ];
844         CI ci = &ct->c_ctinfo;
845
846         /* Check for mhshow-show-type/subtype */
847         snprintf(buffer, sizeof(buffer), "%s-show-%s/%s",
848                                 invo_name, ci->ci_type, ci->ci_subtype);
849         if ((cp = context_find(buffer)) && *cp != '\0')
850                 return show_content_aux(ct, alternate, cp, NULL);
851
852         /* Check for mhshow-show-type */
853         snprintf(buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
854         if ((cp = context_find(buffer)) && *cp != '\0')
855                 return show_content_aux(ct, alternate, cp, NULL);
856
857         if ((cp = ct->c_showproc))
858                 return show_content_aux(ct, alternate, cp, NULL);
859
860         /* default method for message/rfc822 */
861         if (ct->c_subtype == MESSAGE_RFC822) {
862                 cp = (ct->c_showproc = getcpy("%lshow -file %F"));
863                 return show_content_aux(ct, alternate, cp, NULL);
864         }
865
866         /* complain if we are not a part of a multipart/alternative */
867         if (!alternate)
868                 content_error(NULL, ct, "don't know how to display content");
869
870         return NOTOK;
871 }
872
873
874 /*
875 ** Show content of type "message/partial".
876 */
877
878 static int
879 show_partial(CT ct, int alternate)
880 {
881         content_error(NULL, ct,
882                 "in order to display this message, you must reassemble it");
883         return NOTOK;
884 }
885
886
887 /*
888 ** Show how to retrieve content of type "message/external".
889 */
890 static int
891 show_external(CT ct, int alternate)
892 {
893         char **ap, **ep;
894         char *msg;
895         FILE *fp;
896         char buf[BUFSIZ];
897
898         msg = add("You need to fetch the contents yourself:", NULL);
899         ap = ct->c_ctinfo.ci_attrs;
900         ep = ct->c_ctinfo.ci_values;
901         for (; *ap; ap++, ep++) {
902                 msg = add(concat("\n\t", *ap, ": ", *ep, NULL), msg);
903         }
904         if (!(fp = fopen(ct->c_file, "r"))) {
905                 adios(ct->c_file, "unable to open");
906         }
907         fseek(fp, ct->c_begin, SEEK_SET);
908         while (!feof(fp) && ftell(fp) < ct->c_end) {
909                 if (!fgets(buf, sizeof buf, fp)) {
910                         adios(ct->c_file, "unable to read");
911                 }
912                 *strchr(buf, '\n') = '\0';
913                 msg = add(concat("\n\t", buf, NULL), msg);
914         }
915         fclose(fp);
916         content_error(NULL, ct, msg);
917         return OK;
918 }