Dropped old show(1) and renamed mhshow(1) to show(1).
[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 (!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, 1, 0, 0);
462                 else
463                         list_switch(ct, -1, 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 && !check_charset(ct->c_charset,
523                                 strlen(ct->c_charset))) {
524                         snprintf(buffer, sizeof(buffer), "%%liconv -f '%s'",
525                                         ct->c_charset);
526                 } else {
527                         snprintf(buffer, sizeof(buffer), "%%lcat");
528                 }
529                 cp = (ct->c_showproc = getcpy(buffer));
530                 return show_content_aux(ct, alternate, cp, NULL);
531         }
532
533         return NOTOK;
534 }
535
536
537 /*
538 ** show message body of type "multipart"
539 */
540
541 static int
542 show_multi(CT ct, int alternate)
543 {
544         char *cp, buffer[BUFSIZ];
545         CI ci = &ct->c_ctinfo;
546
547         /* Check for mhshow-show-type/subtype */
548         snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
549                         ci->ci_type, ci->ci_subtype);
550         if ((cp = context_find(buffer)) && *cp != '\0')
551                 return show_multi_aux(ct, alternate, cp);
552
553         /* Check for mhshow-show-type */
554         snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
555         if ((cp = context_find(buffer)) && *cp != '\0')
556                 return show_multi_aux(ct, alternate, cp);
557
558         if ((cp = ct->c_showproc))
559                 return show_multi_aux(ct, alternate, cp);
560
561         /*
562         ** Use default method to display this multipart content
563         ** if it is not a (nested) part of a multipart/alternative,
564         ** or if it is one of the known subtypes of multipart.
565         */
566         if (!alternate || ct->c_subtype != MULTI_UNKNOWN)
567                 return show_multi_internal(ct, alternate);
568
569         return NOTOK;
570 }
571
572
573 /*
574 ** show message body of subtypes of multipart that
575 ** we understand directly (mixed, alternate, etc...)
576 */
577
578 static int
579 show_multi_internal(CT ct, int alternate)
580 {
581         int alternating, nowalternate, result;
582         struct multipart *m = (struct multipart *) ct->c_ctparams;
583         struct part *part;
584         CT p;
585
586         alternating = 0;
587         nowalternate = alternate;
588
589         if (ct->c_subtype == MULTI_ALTERNATE) {
590                 nowalternate = 1;
591                 alternating  = 1;
592         }
593
594         /*
595         ** Other possible multipart types are:
596         ** - multipart/parallel
597         ** - multipart/mixed
598         ** - multipart/digest
599         ** - unknown subtypes of multipart (treat as mixed per rfc2046)
600         */
601
602         /*
603         ** alternate   -> we are a part inside an multipart/alternative
604         ** alternating -> we are a multipart/alternative
605         */
606
607         result = alternate ? NOTOK : OK;
608
609         for (part = m->mp_parts; part; part = part->mp_next) {
610                 p = part->mp_part;
611
612                 if (part_ok(p, 1) && type_ok(p, 1)) {
613                         int inneresult;
614
615                         inneresult = show_switch(p, nowalternate);
616                         switch (inneresult) {
617                         case NOTOK:
618                                 if (alternate && !alternating) {
619                                         result = NOTOK;
620                                         goto out;
621                                 }
622                                 continue;
623
624                         case OK:
625                         case DONE:
626                                 if (alternating) {
627                                         result = DONE;
628                                         break;
629                                 }
630                                 if (alternate) {
631                                         alternate = nowalternate = 0;
632                                         if (result == NOTOK)
633                                                 result = inneresult;
634                                 }
635                                 continue;
636                         }
637                         break;
638                 }
639         }
640
641         if (alternating && !part) {
642                 if (!alternate)
643                         content_error(NULL, ct, "don't know how to display any of the contents");
644                 result = NOTOK;
645                 goto out;
646         }
647 out:
648         return result;
649 }
650
651
652 /*
653 ** Parse display string for multipart content
654 ** and use external program to display it.
655 */
656
657 static int
658 show_multi_aux(CT ct, int alternate, char *cp)
659 {
660         int len, buflen, quoted;
661         int xlist;
662         char *bp, *pp, *file, buffer[BUFSIZ];
663         struct multipart *m = (struct multipart *) ct->c_ctparams;
664         struct part *part;
665         CI ci = &ct->c_ctinfo;
666         CT p;
667
668         for (part = m->mp_parts; part; part = part->mp_next) {
669                 p = part->mp_part;
670
671                 if (!p->c_ceopenfnx) {
672                         if (!alternate)
673                                 content_error(NULL, p, "don't know how to decode content");
674                         return NOTOK;
675                 }
676
677                 if (p->c_storage == NULL) {
678                         file = NULL;
679                         if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
680                                 return NOTOK;
681
682                         /* I'm not sure if this is necessary? */
683                         p->c_storage = getcpy(file);
684
685                         if (p->c_showproc && strcmp(p->c_showproc, "true")==0)
686                                 return (alternate ? DONE : OK);
687                         (*p->c_ceclosefnx) (p);
688                 }
689         }
690
691         xlist = 0;
692
693         /* get buffer ready to go */
694         bp = buffer;
695         buflen = sizeof(buffer) - 1;
696         bp[0] = bp[buflen] = '\0';
697         quoted = 0;
698
699         /* Now parse display string */
700         for ( ; *cp && buflen > 0; cp++) {
701                 if (*cp == '%') {
702                         pp = bp;
703                         switch (*++cp) {
704                         case 'a':
705                                 /* insert parameters from Content-Type field */
706                                 {
707                                 char **ap, **ep;
708                                 char *s = "";
709
710                                 for (ap = ci->ci_attrs, ep = ci->ci_values;
711                                                 *ap; ap++, ep++) {
712                                         snprintf(bp, buflen, "%s%s=\"%s\"",
713                                                         s, *ap, *ep);
714                                         len = strlen(bp);
715                                         bp += len;
716                                         buflen -= len;
717                                         s = " ";
718                                 }
719                                 }
720                                 break;
721
722                         case 'c':
723                                 /* insert charset */
724                                 strncpy(bp, ct->c_charset ? ct->c_charset :
725                                                 "US-ASCII", buflen);
726                                 break;
727
728                         case 'd':
729                                 /* insert content description */
730                                 if (ct->c_descr) {
731                                         char *s;
732
733                                         s = trimcpy(ct->c_descr);
734                                         strncpy(bp, s, buflen);
735                                         free(s);
736                                 }
737                                 break;
738
739                         case 'F':
740                         case 'f':
741                                 /* insert filename(s) containing content */
742                         {
743                                 char *s = "";
744
745                                 for (part = m->mp_parts; part;
746                                                 part = part->mp_next) {
747                                         p = part->mp_part;
748
749                                         snprintf(bp, buflen, "%s'%s'",
750                                                         s, p->c_storage);
751                                         len = strlen(bp);
752                                         bp += len;
753                                         buflen -= len;
754                                         s = " ";
755                                 }
756                                 /*
757                                 ** set our starting pointer back to bp,
758                                 ** to avoid requoting the filenames we
759                                 ** just added
760                                 */
761                                 pp = bp;
762                         }
763                         break;
764
765                         case 'l':
766                                 /*
767                                 ** display listing prior to displaying content
768                                 */
769                                 xlist = !nolist;
770                                 break;
771
772                         case 's':
773                                 /* insert subtype of content */
774                                 strncpy(bp, ci->ci_subtype, buflen);
775                                 break;
776
777                         case '%':
778                                 /* insert character % */
779                                 goto raw;
780
781                         default:
782                                 *bp++ = *--cp;
783                                 *bp = '\0';
784                                 buflen--;
785                                 continue;
786                         }
787                         len = strlen(bp);
788                         bp += len;
789                         buflen -= len;
790
791                         /* Did we actually insert something? */
792                         if (bp != pp) {
793                                 /*
794                                 ** Insert single quote if not inside quotes
795                                 ** already
796                                 */
797                                 if (!quoted && buflen) {
798                                         len = strlen(pp);
799                                         memmove(pp + 1, pp, len);
800                                         *pp++ = '\'';
801                                         buflen--;
802                                         bp++;
803                                 }
804                                 /* Escape existing quotes */
805                                 while ((pp = strchr(pp, '\'')) && buflen > 3) {
806                                         len = strlen(pp++);
807                                         memmove(pp + 3, pp, len);
808                                         *pp++ = '\\';
809                                         *pp++ = '\'';
810                                         *pp++ = '\'';
811                                         buflen -= 3;
812                                         bp += 3;
813                                 }
814                                 /*
815                                 ** If pp is still set, that means we ran
816                                 ** out of space.
817                                 */
818                                 if (pp)
819                                         buflen = 0;
820                                 if (!quoted && buflen) {
821                                         *bp++ = '\'';
822                                         *bp = '\0';
823                                         buflen--;
824                                 }
825                         }
826                 } else {
827 raw:
828                         *bp++ = *cp;
829                         *bp = '\0';
830                         buflen--;
831
832                         if (*cp == '\'')
833                                 quoted = !quoted;
834                 }
835         }
836
837         return show_content_aux2(ct, alternate, NULL, buffer,
838                         NOTOK, xlist, 0);
839 }
840
841
842 /*
843 ** show content of type "message/rfc822"
844 */
845
846 static int
847 show_message_rfc822(CT ct, int alternate)
848 {
849         char *cp, buffer[BUFSIZ];
850         CI ci = &ct->c_ctinfo;
851
852         /* Check for mhshow-show-type/subtype */
853         snprintf(buffer, sizeof(buffer), "mhshow-show-%s/%s",
854                                 ci->ci_type, ci->ci_subtype);
855         if ((cp = context_find(buffer)) && *cp != '\0')
856                 return show_content_aux(ct, alternate, cp, NULL);
857
858         /* Check for mhshow-show-type */
859         snprintf(buffer, sizeof(buffer), "mhshow-show-%s", ci->ci_type);
860         if ((cp = context_find(buffer)) && *cp != '\0')
861                 return show_content_aux(ct, alternate, cp, NULL);
862
863         if ((cp = ct->c_showproc))
864                 return show_content_aux(ct, alternate, cp, NULL);
865
866         /* default method for message/rfc822 */
867         if (ct->c_subtype == MESSAGE_RFC822) {
868                 cp = (ct->c_showproc = getcpy("%lshow -file %F"));
869                 return show_content_aux(ct, alternate, cp, NULL);
870         }
871
872         /* complain if we are not a part of a multipart/alternative */
873         if (!alternate)
874                 content_error(NULL, ct, "don't know how to display content");
875
876         return NOTOK;
877 }
878
879
880 /*
881 ** Show content of type "message/partial".
882 */
883
884 static int
885 show_partial(CT ct, int alternate)
886 {
887         content_error(NULL, ct,
888                 "in order to display this message, you must reassemble it");
889         return NOTOK;
890 }
891
892
893 /*
894 ** Show how to retrieve content of type "message/external".
895 */
896 static int
897 show_external(CT ct, int alternate)
898 {
899         char **ap, **ep;
900         char *msg;
901         FILE *fp;
902         char buf[BUFSIZ];
903
904         msg = add("You need to fetch the contents yourself:", NULL);
905         ap = ct->c_ctinfo.ci_attrs;
906         ep = ct->c_ctinfo.ci_values;
907         for (; *ap; ap++, ep++) {
908                 msg = add(concat("\n\t", *ap, ": ", *ep, NULL), msg);
909         }
910         if (!(fp = fopen(ct->c_file, "r"))) {
911                 adios(ct->c_file, "unable to open");
912         }
913         fseek(fp, ct->c_begin, SEEK_SET);
914         while (!feof(fp) && ftell(fp) < ct->c_end) {
915                 if (!fgets(buf, sizeof buf, fp)) {
916                         adios(ct->c_file, "unable to read");
917                 }
918                 *strchr(buf, '\n') = '\0';
919                 msg = add(concat("\n\t", buf, NULL), msg);
920         }
921         fclose(fp);
922         content_error(NULL, ct, msg);
923         return OK;
924 }