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