]> diplodocus.org Git - nmh/blobdiff - uip/mhshowsbr.c
lock_file.c: close(2) file descriptor on failure, avoiding leak.
[nmh] / uip / mhshowsbr.c
index efa9ad3c1e72fced6e977b286323e23de7e4e653..ebb295b7c57a38a412e74c894b950271c86fe7b2 100644 (file)
@@ -1,6 +1,4 @@
-
-/*
- * mhshowsbr.c -- routines to display the contents of MIME messages
+/* mhshowsbr.c -- routines to display the contents of MIME messages
  *
  * This code is Copyright (c) 2002, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
 #include <fcntl.h>
 #include <h/signals.h>
 #include <h/md5.h>
-#include <errno.h>
-#include <setjmp.h>
-#include <signal.h>
 #include <h/mts.h>
 #include <h/tws.h>
 #include <h/mime.h>
 #include <h/mhparse.h>
+#include <h/fmt_scan.h>
 #include <h/utils.h>
+#include "mhmisc.h"
+#include "mhshowsbr.h"
+#include "sbr/m_mktemp.h"
+#ifdef HAVE_ICONV
+#   include <iconv.h>
+#endif /* ! HAVE_ICONV */
 
 extern int debugsw;
 
-int pausesw  = 1;
-int serialsw = 0;
 int nolist   = 0;
 
 char *progsw = NULL;
@@ -32,49 +32,57 @@ char *progsw = NULL;
 int nomore   = 0;
 char *formsw = NULL;
 
-pid_t xpid = 0;
-
-static sigjmp_buf intrenv;
-
-
-/* termsbr.c */
-int SOprintf (char *, ...);
-
-/* mhparse.c */
-int pidcheck (int);
-
-/* mhmisc.c */
-int part_ok (CT, int);
-int type_ok (CT, int);
-void content_error (char *, CT, char *, ...);
-void flush_errors (void);
+/* for output markers and headers */
+char *folder = NULL;
+char *markerform;
+char *headerform;
+int headersw = -1;
 
-/* mhlistsbr.c */
-int list_switch (CT, int, int, int, int);
-int list_content (CT, int, int, int, int);
 
 /*
- * prototypes
+ * static prototypes
  */
-void show_all_messages (CT *);
-int show_content_aux (CT, int, int, char *, char *);
+static void show_single_message (CT, char *, int, int, int, struct format *);
+static void DisplayMsgHeader (CT, char *, int);
+static int show_switch (CT, int, int, int, int, struct format *);
+static int show_content (CT, int, int, int, struct format *);
+static int show_content_aux2 (CT, int, char *, char *, int, int, int, struct format *);
+static int show_text (CT, int, int, struct format *);
+static int show_multi (CT, int, int, int, int, struct format *);
+static int show_multi_internal (CT, int, int, int, int, struct format *);
+static int show_multi_aux (CT, int, char *, struct format *);
+static int show_message_rfc822 (CT, int, struct format *);
+static int show_partial (CT, int);
+static int show_external (CT, int, int, int, int, struct format *);
+static int parse_display_string (CT, char *, int *, int *, char *, char *,
+                                size_t, int multipart);
+static int convert_content_charset (CT, char **);
+static struct format *compile_header(char *);
+static struct format *compile_marker(char *);
+static void output_header (CT, struct format *);
+static void output_marker (CT, struct format *, int);
+static void free_markercomps (void);
+static int pidcheck(int);
 
 /*
- * static prototypes
+ * Components (and list of parameters/components) we care about for the
+ * content marker display.
  */
-static void show_single_message (CT, char *);
-static void DisplayMsgHeader (CT, char *);
-static int show_switch (CT, int, int);
-static int show_content (CT, int, int);
-static int show_content_aux2 (CT, int, int, char *, char *, int, int, int, int, int);
-static int show_text (CT, int, int);
-static int show_multi (CT, int, int);
-static int show_multi_internal (CT, int, int);
-static int show_multi_aux (CT, int, int, char *);
-static int show_message_rfc822 (CT, int, int);
-static int show_partial (CT, int, int);
-static int show_external (CT, int, int);
-static RETSIGTYPE intrser (int);
+
+static struct comp *folder_comp = NULL;
+static struct comp *part_comp = NULL;
+static struct comp *ctype_comp = NULL;
+static struct comp *description_comp = NULL;
+static struct comp *dispo_comp = NULL;
+
+struct param_comp_list {
+    char *param;
+    struct comp *comp;
+    struct param_comp_list *next;
+};
+
+static struct param_comp_list *ctype_pc_list = NULL;
+static struct param_comp_list *dispo_pc_list = NULL;
 
 
 /*
@@ -82,9 +90,10 @@ static RETSIGTYPE intrser (int);
  */
 
 void
-show_all_messages (CT *cts)
+show_all_messages(CT *cts, int concat, int textonly, int inlineonly)
 {
     CT ct, *ctp;
+    struct format *hfmt, *mfmt;
 
     /*
      * If form is not specified, then get default form
@@ -93,6 +102,12 @@ show_all_messages (CT *cts)
     if (!formsw)
        formsw = getcpy (etcpath ("mhl.headers"));
 
+    /*
+     * Compile the content marker and header format lines
+     */
+    mfmt = compile_marker(markerform);
+    hfmt = compile_header(headerform);
+
     /*
      * If form is "mhl.null", suppress display of header.
      */
@@ -103,9 +118,17 @@ show_all_messages (CT *cts)
        ct = *ctp;
 
        /* if top-level type is ok, then display message */
-       if (type_ok (ct, 1))
-           show_single_message (ct, formsw);
+       if (type_ok (ct, 1)) {
+           if (headersw) output_header(ct, hfmt);
+
+           show_single_message (ct, formsw, concat, textonly, inlineonly,
+                                mfmt);
+       }
     }
+
+    free_markercomps();
+    fmt_free(hfmt, 1);
+    fmt_free(mfmt, 1);
 }
 
 
@@ -114,11 +137,12 @@ show_all_messages (CT *cts)
  */
 
 static void
-show_single_message (CT ct, char *form)
+show_single_message (CT ct, char *form, int concatsw, int textonly,
+                    int inlineonly, struct format *fmt)
 {
     sigset_t set, oset;
 
-    int status;
+    int status = OK;
 
     /* Allow user executable bit so that temporary directories created by
      * the viewer (e.g., lynx) are going to be accessible */
@@ -129,12 +153,10 @@ show_single_message (CT ct, char *form)
      * the message headers.
      */
     if (form)
-       DisplayMsgHeader(ct, form);
-    else
-       xpid = 0;
+       DisplayMsgHeader(ct, form, concatsw);
 
     /* Show the body of the message */
-    show_switch (ct, 1, 0);
+    show_switch (ct, 0, concatsw, textonly, inlineonly, fmt);
 
     if (ct->c_fp) {
        fclose (ct->c_fp);
@@ -149,17 +171,16 @@ show_single_message (CT ct, char *form)
     sigaddset (&set, SIGINT);
     sigaddset (&set, SIGQUIT);
     sigaddset (&set, SIGTERM);
-    SIGPROCMASK (SIG_BLOCK, &set, &oset);
+    sigprocmask (SIG_BLOCK, &set, &oset);
 
-    while (wait (&status) != NOTOK) {
+    while (!concatsw && wait (&status) != NOTOK) {
        pidcheck (status);
        continue;
     }
 
     /* reset the signal mask */
-    SIGPROCMASK (SIG_SETMASK, &oset, &set);
+    sigprocmask (SIG_SETMASK, &oset, &set);
 
-    xpid = 0;
     flush_errors ();
 }
 
@@ -169,52 +190,52 @@ show_single_message (CT ct, char *form)
  */
 
 static void
-DisplayMsgHeader (CT ct, char *form)
+DisplayMsgHeader (CT ct, char *form, int concatsw)
 {
     pid_t child_id;
-    int i, vecp;
-    char *vec[8];
+    int vecp;
+    char **vec;
+    char *file;
 
-    vecp = 0;
-    vec[vecp++] = r1bindex (mhlproc, '/');
-    vec[vecp++] = "-form";
-    vec[vecp++] = form;
-    vec[vecp++] = "-nobody";
-    vec[vecp++] = ct->c_file;
+    vec = argsplit(mhlproc, &file, &vecp);
+    vec[vecp++] = mh_xstrdup("-form");
+    vec[vecp++] = mh_xstrdup(form);
+    vec[vecp++] = mh_xstrdup("-nobody");
+    vec[vecp++] = getcpy(ct->c_file);
 
     /*
      * If we've specified -(no)moreproc,
      * then just pass that along.
      */
-    if (nomore) {
-       vec[vecp++] = "-nomoreproc";
+    if (nomore || concatsw) {
+       vec[vecp++] = mh_xstrdup("-nomoreproc");
     } else if (progsw) {
-       vec[vecp++] = "-moreproc";
-       vec[vecp++] = progsw;
+       vec[vecp++] = mh_xstrdup("-moreproc");
+       vec[vecp++] = mh_xstrdup(progsw);
     }
     vec[vecp] = NULL;
 
     fflush (stdout);
 
-    for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
-       sleep (5);
-
+    child_id = fork();
     switch (child_id) {
     case NOTOK:
        adios ("fork", "unable to");
        /* NOTREACHED */
 
     case OK:
-       execvp (mhlproc, vec);
+       execvp (file, vec);
        fprintf (stderr, "unable to exec ");
        perror (mhlproc);
        _exit (-1);
        /* NOTREACHED */
 
     default:
-       xpid = -child_id;
+       pidcheck(pidwait(child_id, NOTOK));
        break;
     }
+
+    arglist_free(file, vec);
 }
 
 
@@ -224,44 +245,44 @@ DisplayMsgHeader (CT ct, char *form)
  */
 
 static int
-show_switch (CT ct, int serial, int alternate)
+show_switch (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
+            struct format *fmt)
 {
     switch (ct->c_type) {
        case CT_MULTIPART:
-           return show_multi (ct, serial, alternate);
-           break;
+           return show_multi (ct, alternate, concatsw, textonly,
+                              inlineonly, fmt);
 
        case CT_MESSAGE:
            switch (ct->c_subtype) {
                case MESSAGE_PARTIAL:
-                   return show_partial (ct, serial, alternate);
-                   break;
+                   return show_partial (ct, alternate);
 
                case MESSAGE_EXTERNAL:
-                   return show_external (ct, serial, alternate);
-                   break;
+                   return show_external (ct, alternate, concatsw, textonly,
+                                         inlineonly, fmt);
 
                case MESSAGE_RFC822:
+                   return show_message_rfc822 (ct, alternate, fmt);
+
+               /*
+                * Treat unknown message types as equivalent to
+                * application/octet-stream for now
+                */
                default:
-                   return show_message_rfc822 (ct, serial, alternate);
-                   break;
+                   return show_content (ct, alternate, textonly,
+                                        inlineonly, fmt);
            }
-           break;
 
        case CT_TEXT:
-           return show_text (ct, serial, alternate);
-           break;
+           return show_text (ct, alternate, concatsw, fmt);
 
        case CT_AUDIO:
        case CT_IMAGE:
        case CT_VIDEO:
        case CT_APPLICATION:
-           return show_content (ct, serial, alternate);
-           break;
-
        default:
-           adios (NULL, "unknown content type %d", ct->c_type);
-           break;
+           return show_content (ct, alternate, textonly, inlineonly, fmt);
     }
 
     return 0;  /* NOT REACHED */
@@ -273,24 +294,28 @@ show_switch (CT ct, int serial, int alternate)
  */
 
 static int
-show_content (CT ct, int serial, int alternate)
+show_content (CT ct, int alternate, int textonly, int inlineonly,
+             struct format *fmt)
 {
-    char *cp, buffer[BUFSIZ];
+    char *cp;
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+    /*
+     * If we're here, we are not a text type.  So we don't need to check
+     * the content-type.
+     */
 
-    /* Check for mhn-show-type */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+    if (textonly || (inlineonly && !is_inline(ct))) {
+       output_marker(ct, fmt, 1);
+       return OK;
+    }
+
+    /* Check for invo_name-show-type[/subtype] */
+    if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
 
     if ((cp = ct->c_showproc))
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
 
     /* complain if we are not a part of a multipart/alternative */
     if (!alternate)
@@ -305,12 +330,11 @@ show_content (CT ct, int serial, int alternate)
  */
 
 int
-show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked)
+show_content_aux (CT ct, int alternate, char *cp, char *cracked, struct format *fmt)
 {
-    int fd, len, buflen, quoted;
-    int        xstdin, xlist, xpause, xtty;
-    char *bp, *pp, *file, buffer[BUFSIZ];
-    CI ci = &ct->c_ctinfo;
+    int fd;
+    int xstdin = 0, xlist = 0;
+    char *file = NULL, buffer[NMH_BUFSIZ];
 
     if (!ct->c_ceopenfnx) {
        if (!alternate)
@@ -319,175 +343,45 @@ show_content_aux (CT ct, int serial, int alternate, char *cp, char *cracked)
        return NOTOK;
     }
 
-    file = NULL;
     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
        return NOTOK;
     if (ct->c_showproc && !strcmp (ct->c_showproc, "true"))
-       return (alternate ? DONE : OK);
-    
-    xlist  = 0;
-    xpause = 0;
-    xstdin = 0;
-    xtty   = 0;
+       return OK;
+
+    if (! strcmp(invo_name, "mhshow")  &&
+        ct->c_type == CT_TEXT  &&  ct->c_subtype == TEXT_PLAIN) {
+        /* This has to be done after calling c_ceopenfnx, so
+           unfortunately the type checks are necessary without
+           some code rearrangement.  And to make this really ugly,
+           only do it in mhshow, not mhfixmsg, mhn, or mhstore. */
+        if (convert_content_charset (ct, &file) == OK) {
+            (*ct->c_ceclosefnx) (ct);
+            if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
+                return NOTOK;
+        } else {
+            char *charset = content_charset (ct);
+            inform("unable to convert character set%s%s from %s, continuing...",
+                      ct->c_partno  ?  " of part "  :  "",
+                      FENDNULL(ct->c_partno),
+                      charset);
+            free (charset);
+        }
+    }
 
     if (cracked) {
        strncpy (buffer, cp, sizeof(buffer));
        goto got_command;
     }
 
-    /* get buffer ready to go */
-    bp = buffer;
-    buflen = sizeof(buffer) - 1;
-    bp[0] = bp[buflen] = '\0';
-    quoted = 0;
-
-    /* Now parse display string */
-    for ( ; *cp && buflen > 0; cp++) {
-       if (*cp == '%') {
-           pp = bp;
-
-           switch (*++cp) {
-           case 'a':
-               /* insert parameters from Content-Type field */
-           {
-               char **ap, **ep;
-               char *s = "";
-
-               for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-                   snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
-                   len = strlen (bp);
-                   bp += len;
-                   buflen -= len;
-                   s = " ";
-               }
-           }
-           break;
-
-           case 'd':
-               /* insert content description */
-               if (ct->c_descr) {
-                   char *s;
-
-                   s = trimcpy (ct->c_descr);
-                   strncpy (bp, s, buflen);
-                   free (s);
-               }
-               break;
-
-           case 'e':
-               /* exclusive execution */
-               xtty = 1;
-               break;
-
-           case 'F':
-               /* %e, %f, and stdin is terminal not content */
-               xstdin = 1;
-               xtty = 1;
-               /* and fall... */
-
-           case 'f':
-               /* insert filename containing content */
-               snprintf (bp, buflen, "'%s'", file);
-               /* since we've quoted the file argument, set things up
-                * to look past it, to avoid problems with the quoting
-                * logic below.  (I know, I should figure out what's
-                * broken with the quoting logic, but..)
-                */
-               len = strlen(bp);
-               buflen -= len;
-               bp += len;
-               pp = bp;
-               break;
-
-           case 'p':
-               /* %l, and pause prior to displaying content */
-               xpause = pausesw;
-               /* and fall... */
-
-           case 'l':
-               /* display listing prior to displaying content */
-               xlist = !nolist;
-               break;
-
-           case 's':
-               /* insert subtype of content */
-               strncpy (bp, ci->ci_subtype, buflen);
-               break;
-
-           case '%':
-               /* insert character % */
-               goto raw;
-
-           default:
-               *bp++ = *--cp;
-               *bp = '\0';
-               buflen--;
-               continue;
-           }
-           len = strlen (bp);
-           bp += len;
-           buflen -= len;
-
-           /* Did we actually insert something? */
-           if (bp != pp) {
-               /* Insert single quote if not inside quotes already */
-               if (!quoted && buflen) {
-                   len = strlen (pp);
-                   memmove (pp + 1, pp, len);
-                   *pp++ = '\'';
-                   buflen--;
-                   bp++;
-               }
-               /* Escape existing quotes */
-               while ((pp = strchr (pp, '\'')) && buflen > 3) {
-                   len = strlen (pp++);
-                   memmove (pp + 3, pp, len);
-                   *pp++ = '\\';
-                   *pp++ = '\'';
-                   *pp++ = '\'';
-                   buflen -= 3;
-                   bp += 3;
-               }
-               /* If pp is still set, that means we ran out of space. */
-               if (pp)
-                   buflen = 0;
-               if (!quoted && buflen) {
-                   *bp++ = '\'';
-                   *bp = '\0';
-                   buflen--;
-               }
-           }
-       } else {
-raw:
-           *bp++ = *cp;
-           *bp = '\0';
-           buflen--;
-
-           if (*cp == '\'')
-               quoted = !quoted;
-       }
-    }
-
-    if (buflen <= 0 || (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
-       /* content_error would provide a more useful error message
-        * here, except that if we got overrun, it probably would
-        * too.
-        */
-       fprintf(stderr, "Buffer overflow constructing show command!\n");
+    if (parse_display_string (ct, cp, &xstdin, &xlist, file, buffer,
+                             sizeof(buffer) - 1, 0)) {
+       inform("Buffer overflow constructing show command, continuing...");
        return NOTOK;
     }
 
-    /* use charset string to modify display method */
-    if (ct->c_termproc) {
-       char term[BUFSIZ];
-
-       strncpy (term, buffer, sizeof(term));
-       snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
-    }
-
 got_command:
-    return show_content_aux2 (ct, serial, alternate, cracked, buffer,
-                             fd, xlist, xpause, xstdin, xtty);
+    return show_content_aux2 (ct, alternate, cracked, buffer,
+                             fd, xlist, xstdin, fmt);
 }
 
 
@@ -496,13 +390,13 @@ got_command:
  */
 
 static int
-show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer,
-                   int fd, int xlist, int xpause, int xstdin, int xtty)
+show_content_aux2 (CT ct, int alternate, char *cracked, char *buffer,
+                   int fd, int xlist, int xstdin, struct format *fmt)
 {
     pid_t child_id;
-    int i;
-    char *vec[4], exec[BUFSIZ + sizeof "exec "];
-    
+    int vecp;
+    char **vec, *file;
+
     if (debugsw || cracked) {
        fflush (stdout);
 
@@ -516,54 +410,61 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
            fprintf (stderr, " using command %s\n", buffer);
     }
 
-    if (xpid < 0 || (xtty && xpid)) {
-       if (xpid < 0)
-           xpid = -xpid;
-       pidcheck(pidwait (xpid, NOTOK));
-       xpid = 0;
+    if (xlist && fmt) {
+       output_marker(ct, fmt, 0);
     }
 
-    if (xlist) {
-       char prompt[BUFSIZ];
-
-       if (ct->c_type == CT_MULTIPART)
-           list_content (ct, -1, 1, 0, 0);
-       else
-           list_switch (ct, -1, 1, 0, 0);
+    /*
+     * If the command is a zero-length string, just write the output on
+     * stdout.
+     */
 
-       if (xpause && SOprintf ("Press <return> to show content..."))
-           printf ("Press <return> to show content...");
+    if (buffer[0] == '\0') {
+       char readbuf[BUFSIZ];
+       ssize_t cc;
+       char lastchar = '\n';
 
-       if (xpause) {
-           int intr;
-           SIGNAL_HANDLER istat;
+       if (fd == NOTOK) {
+           inform("Cannot use NULL command to display content-type "
+                  "%s/%s", ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype);
+           return NOTOK;
+       }
 
-           istat = SIGNAL (SIGINT, intrser);
-           if ((intr = sigsetjmp (intrenv, 1)) == OK) {
-               fflush (stdout);
-               prompt[0] = 0;
-               read (fileno (stdout), prompt, sizeof(prompt));
-           }
-           SIGNAL (SIGINT, istat);
-           if (intr != OK || prompt[0] == 'n') {
-               (*ct->c_ceclosefnx) (ct);
-               return (alternate ? DONE : NOTOK);
+       while ((cc = read(fd, readbuf, sizeof(readbuf))) > 0) {
+           if ((ssize_t) fwrite(readbuf, sizeof(char), cc, stdout) < cc) {
+               advise ("putline", "fwrite");
            }
-           if (prompt[0] == 'q') done(OK);
+           lastchar = readbuf[cc - 1];
+       }
+
+       if (cc < 0) {
+           advise("read", "while reading text content");
+           return NOTOK;
+       }
+
+       /*
+        * The MIME standards allow content to not have a trailing newline.
+        * But because we are (presumably) sending this to stdout, include
+        * a newline for text content if the final character was not a
+        * newline.  Only do this for mhshow.
+        */
+
+       if (strcmp(invo_name, "mhshow") == 0 && ct->c_type == CT_TEXT &&
+           ct->c_subtype == TEXT_PLAIN && lastchar != '\n') {
+           putchar('\n');
        }
-    }
 
-    snprintf (exec, sizeof(exec), "exec %s", buffer);
+       fflush(stdout);
 
-    vec[0] = "/bin/sh";
-    vec[1] = "-c";
-    vec[2] = exec;
-    vec[3] = NULL;
+       return OK;
+    }
+
+    vec = argsplit(buffer, &file, &vecp);
+    vec[vecp++] = NULL;
 
     fflush (stdout);
 
-    for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
-       sleep (5);
+    child_id = fork();
     switch (child_id) {
        case NOTOK:
            advise ("fork", "unable to");
@@ -571,29 +472,35 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
            return NOTOK;
 
        case OK:
-           if (cracked)
-               chdir (cracked);
+           if (cracked) {
+               if (chdir (cracked) < 0) {
+                   advise (cracked, "chdir");
+               }
+           }
            if (!xstdin)
                dup2 (fd, 0);
            close (fd);
-           execvp ("/bin/sh", vec);
+           execvp (file, vec);
            fprintf (stderr, "unable to exec ");
-           perror ("/bin/sh");
+           perror (buffer);
            _exit (-1);
            /* NOTREACHED */
 
-       default:
-           if (!serial) {
-               ct->c_pid = child_id;
-               if (xtty)
-                   xpid = child_id;
-           } else {
-               pidcheck (pidXwait (child_id, NULL));
-           }
+       default: {
+           int status;
+            char *display_prog = vecp > 2  &&  vec[2][0] != '\0'
+                /* Copy the real display program name.  This relies on the
+                   specific construction of vec[] by argsplit(). */
+                ?  vec[2]
+                :  NULL;
 
+            pidcheck ((status = pidXwait (child_id, display_prog)));
+
+            arglist_free(file, vec);
            if (fd != NOTOK)
                (*ct->c_ceclosefnx) (ct);
-           return (alternate ? DONE : OK);
+           return (alternate ? OK : status);
+        }
     }
 }
 
@@ -603,31 +510,30 @@ show_content_aux2 (CT ct, int serial, int alternate, char *cracked, char *buffer
  */
 
 static int
-show_text (CT ct, int serial, int alternate)
+show_text (CT ct, int alternate, int concatsw, struct format *fmt)
 {
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
-
-    /* Check for mhn-show-type */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+    /* Check for invo_name-show-type[/subtype] */
+    if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
 
     /*
      * Use default method if content is text/plain, or if
      * if it is not a text part of a multipart/alternative
      */
     if (!alternate || ct->c_subtype == TEXT_PLAIN) {
-       snprintf (buffer, sizeof(buffer), "%%p%s '%%F'", progsw ? progsw :
-               moreproc && *moreproc ? moreproc : "more");
-       cp = (ct->c_showproc = add (buffer, NULL));
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+       if (concatsw) {
+           if (ct->c_termproc)
+               snprintf(buffer, sizeof(buffer), "%%lcat");
+           else
+               snprintf(buffer, sizeof(buffer), "%%l");
+       } else
+           snprintf (buffer, sizeof(buffer), "%%l%s %%F", progsw ? progsw :
+                     moreproc && *moreproc ? moreproc : DEFAULT_PAGER);
+       cp = (ct->c_showproc = mh_xstrdup(buffer));
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
     }
 
     return NOTOK;
@@ -639,34 +545,27 @@ show_text (CT ct, int serial, int alternate)
  */
 
 static int
-show_multi (CT ct, int serial, int alternate)
+show_multi (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
+           struct format *fmt)
 {
-    char *cp, buffer[BUFSIZ];
+    char *cp;
     CI ci = &ct->c_ctinfo;
 
-    /* Check for mhn-show-type/subtype */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_multi_aux (ct, serial, alternate, cp);
+    /* Check for invo_name-show-type[/subtype] */
+    if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
+       return show_multi_aux (ct, alternate, cp, fmt);
 
-    /* Check for mhn-show-type */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_multi_aux (ct, serial, alternate, cp);
-
-    if ((cp = ct->c_showproc))
-       return show_multi_aux (ct, serial, alternate, cp);
+    if ((cp = ct->c_showproc)) {
+       return show_multi_aux (ct, alternate, cp, fmt);
+    }
 
     /*
-     * Use default method to display this multipart content
-     * if it is not a (nested) part of a multipart/alternative,
-     * or if it is one of the known subtypes of multipart.
+     * Use default method to display this multipart content.  Even
+     * unknown types are displayable, since they're treated as mixed
+     * per RFC 2046.
      */
-    if (!alternate || ct->c_subtype != MULTI_UNKNOWN)
-       return show_multi_internal (ct, serial, alternate);
-
-    return NOTOK;
+    return show_multi_internal (ct, alternate, concatsw, textonly,
+                               inlineonly, fmt);
 }
 
 
@@ -676,74 +575,78 @@ show_multi (CT ct, int serial, int alternate)
  */
 
 static int
-show_multi_internal (CT ct, int serial, int alternate)
+show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
+                    int inlineonly, struct format *fmt)
 {
-    int        alternating, nowalternate, nowserial, result;
+    int        alternating, nowalternate, result;
     struct multipart *m = (struct multipart *) ct->c_ctparams;
     struct part *part;
+    int request_matched;
+    int display_success;
+    int mult_alt_done;
+    int ret;
     CT p;
-    sigset_t set, oset;
 
     alternating = 0;
     nowalternate = alternate;
 
-    if (ct->c_subtype == MULTI_PARALLEL) {
-       nowserial = serialsw;
-    } else if (ct->c_subtype == MULTI_ALTERNATE) {
+    if (ct->c_subtype == MULTI_ALTERNATE) {
        nowalternate = 1;
        alternating  = 1;
-       nowserial = serial;
-    } else {
-       /*
-        * multipart/mixed
-        * mutlipart/digest
-        * unknown subtypes of multipart (treat as mixed per rfc2046)
-        */
-       nowserial = serial;
     }
 
-    /* block a few signals */
-    if (!nowserial) {
-       sigemptyset (&set);
-       sigaddset (&set, SIGHUP);
-       sigaddset (&set, SIGINT);
-       sigaddset (&set, SIGQUIT);
-       sigaddset (&set, SIGTERM);
-       SIGPROCMASK (SIG_BLOCK, &set, &oset);
-    }
-
-/*
- * alternate   -> we are a part inside an multipart/alternative
- * alternating -> we are a multipart/alternative 
- */
+    /*
+     * alternate   -> we are a part inside a multipart/alternative
+     * alternating -> we are a multipart/alternative
+     */
 
-    result = alternate ? NOTOK : OK;
+    result = NOTOK;
+    request_matched = 0;
+    display_success = 0;
+    mult_alt_done = 0;
 
     for (part = m->mp_parts; part; part = part->mp_next) {
        p = part->mp_part;
 
-       if (part_ok (p, 1) && type_ok (p, 1)) {
+       /* while looking for the right displayable alternative, we
+        * use a looser search criterion than we do after finding it.
+        * specifically, while still looking, part_ok() will match
+        * "parent" parts (e.g.  "-part 2" where 2 is a high-level
+        * multipart).  after finding it, we use part_exact() to only
+        * choose a part that was requested explicitly.
+        */
+       if ((part_exact(p) && type_ok(p, 1)) ||
+               (!mult_alt_done && part_ok (p) && type_ok (p, 1))) {
+
            int inneresult;
 
-           inneresult = show_switch (p, nowserial, nowalternate);
+           inneresult = show_switch (p, nowalternate, concatsw, textonly,
+                                     inlineonly, fmt);
            switch (inneresult) {
-               case NOTOK:
+               case NOTOK:  /* hard display error */
+                   request_matched = 1;
                    if (alternate && !alternating) {
                        result = NOTOK;
                        goto out;
                    }
                    continue;
 
-               case OK:
-               case DONE:
+               case DONE:  /* found no match on content type */
+                   continue;
+
+               case OK:  /* display successful */
+                   request_matched = 1;
+                   display_success = 1;
+                   result = OK;
+
+                   /* if we got success on a sub-part of
+                    * multipart/alternative, we're done, unless
+                    * there's a chance an explicit part should be
+                    * matched later in the alternatives.  */
                    if (alternating) {
-                       result = DONE;
-                       break;
-                   }
-                   if (alternate) {
+                       mult_alt_done = 1;
+                   } else if (alternate) {
                        alternate = nowalternate = 0;
-                       if (result == NOTOK)
-                           result = inneresult;
                    }
                    continue;
            }
@@ -751,54 +654,23 @@ show_multi_internal (CT ct, int serial, int alternate)
        }
     }
 
-    if (alternating && !part) {
+    /* we're supposed to be displaying at least something from a
+     * multipart/alternative.  if we've had parts to consider, and
+     * we've had no success, then we should complain.  we shouldn't
+     * complain if none of the parts matched any -part or -type option.
+     */
+    if (alternating && request_matched && !display_success) {
+       /* if we're ourselves an alternate.  don't complain yet. */
        if (!alternate)
            content_error (NULL, ct, "don't know how to display any of the contents");
        result = NOTOK;
-       goto out;
-    }
-
-    if (serial && !nowserial) {
-       pid_t pid;
-       int kids;
-       int status;
-
-       kids = 0;
-       for (part = m->mp_parts; part; part = part->mp_next) {
-           p = part->mp_part;
-
-           if (p->c_pid > OK) {
-               if (kill (p->c_pid, 0) == NOTOK)
-                   p->c_pid = 0;
-               else
-                   kids++;
-           }
-       }
-
-       while (kids > 0 && (pid = wait (&status)) != NOTOK) {
-           pidcheck (status);
-
-           for (part = m->mp_parts; part; part = part->mp_next) {
-               p = part->mp_part;
-
-               if (xpid == pid)
-                   xpid = 0;
-               if (p->c_pid == pid) {
-                   p->c_pid = 0;
-                   kids--;
-                   break;
-               }
-           }
-       }
     }
 
 out:
-    if (!nowserial) {
-       /* reset the signal mask */
-       SIGPROCMASK (SIG_SETMASK, &oset, &set);
-    }
-
-    return result;
+    /* if no parts matched what was requested, there can't have been
+     * any display errors.  we report DONE rather than OK. */
+    ret = request_matched ? result : DONE;
+    return ret;
 }
 
 
@@ -808,14 +680,14 @@ out:
  */
 
 static int
-show_multi_aux (CT ct, int serial, int alternate, char *cp)
+show_multi_aux (CT ct, int alternate, char *cp, struct format *fmt)
 {
-    int len, buflen, quoted;
-    int xlist, xpause, xtty;
-    char *bp, *pp, *file, buffer[BUFSIZ];
+    /* xstdin is only used in the call to parse_display_string():
+       its value is ignored in the function. */
+    int xstdin = 0, xlist = 0;
+    char *file = NULL, buffer[BUFSIZ];
     struct multipart *m = (struct multipart *) ct->c_ctparams;
     struct part *part;
-    CI ci = &ct->c_ctinfo;
     CT p;
 
     for (part = m->mp_parts; part; part = part->mp_next) {
@@ -828,42 +700,117 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
        }
 
        if (p->c_storage == NULL) {
-           file = NULL;
            if ((*p->c_ceopenfnx) (p, &file) == NOTOK)
                return NOTOK;
 
-           /* I'm not sure if this is necessary? */
-           p->c_storage = add (file, NULL);
+           p->c_storage = mh_xstrdup(FENDNULL(file));
 
            if (p->c_showproc && !strcmp (p->c_showproc, "true"))
-               return (alternate ? DONE : OK);
+               return OK;
            (*p->c_ceclosefnx) (p);
        }
     }
 
-    xlist     = 0;
-    xpause    = 0;
-    xtty      = 0;
+    if (parse_display_string (ct, cp, &xstdin, &xlist, file,
+                             buffer, sizeof(buffer) - 1, 1)) {
+       inform("Buffer overflow constructing show command, continuing...");
+       return NOTOK;
+    }
+
+    return show_content_aux2 (ct, alternate, NULL, buffer, NOTOK, xlist, 0, fmt);
+}
+
+
+/*
+ * show content of type "message/rfc822"
+ */
+
+static int
+show_message_rfc822 (CT ct, int alternate, struct format *fmt)
+{
+    char *cp;
+    CI ci = &ct->c_ctinfo;
+
+    /* Check for invo_name-show-type[/subtype] */
+    if ((cp = context_find_by_type ("show", ci->ci_type, ci->ci_subtype)))
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
+
+    if ((cp = ct->c_showproc))
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
+
+    /* default method for message/rfc822 */
+    if (ct->c_subtype == MESSAGE_RFC822) {
+       cp = (ct->c_showproc = mh_xstrdup("%pshow -file %F"));
+       return show_content_aux (ct, alternate, cp, NULL, fmt);
+    }
+
+    /* complain if we are not a part of a multipart/alternative */
+    if (!alternate)
+       content_error (NULL, ct, "don't know how to display content");
+
+    return NOTOK;
+}
+
+
+/*
+ * Show content of type "message/partial".
+ */
+
+static int
+show_partial (CT ct, int alternate)
+{
+    NMH_UNUSED (alternate);
+
+    content_error (NULL, ct,
+       "in order to display this message, you must reassemble it");
+    return NOTOK;
+}
+
+
+/*
+ * Show content of type "message/external".
+ *
+ * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
+ */
+
+static int
+show_external (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
+              struct format *fmt)
+{
+    struct exbody *e = (struct exbody *) ct->c_ctparams;
+    CT p = e->eb_content;
+
+    if (!type_ok (p, 0))
+       return OK;
+
+    return show_switch (p, alternate, concatsw, textonly, inlineonly, fmt);
+}
+
+
+static int
+parse_display_string (CT ct, char *cp, int *xstdin, int *xlist,
+                      char *file, char *buffer, size_t buflen,
+                      int multipart) {
+    int len, quoted = 0;
+    char *bp = buffer, *pp;
+    CI ci = &ct->c_ctinfo;
 
-    /* get buffer ready to go */
-    bp = buffer;
-    buflen = sizeof(buffer) - 1;
     bp[0] = bp[buflen] = '\0';
-    quoted = 0;
 
-    /* Now parse display string */
     for ( ; *cp && buflen > 0; cp++) {
        if (*cp == '%') {
            pp = bp;
+
            switch (*++cp) {
            case 'a':
                /* insert parameters from Content-Type field */
            {
-               char **ap, **ep;
+               PM pm;
                char *s = "";
 
-               for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-                   snprintf (bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
+               for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
+                   snprintf (bp, buflen, "%s%s=\"%s\"", s, pm->pm_name,
+                             get_param_value(pm, '?'));
                    len = strlen (bp);
                    bp += len;
                    buflen -= len;
@@ -884,44 +831,71 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
                break;
 
            case 'e':
-               /* exclusive execution */
-               xtty = 1;
+               /* no longer implemented */
                break;
 
            case 'F':
-               /* %e and %f */
-               xtty = 1;
-               /* and fall... */
+               /* %f, and stdin is terminal not content */
+               *xstdin = 1;
+               /* FALLTHRU */
 
            case 'f':
-               /* insert filename(s) containing content */
-           {
-               char *s = "";
-                       
-               for (part = m->mp_parts; part; part = part->mp_next) {
-                   p = part->mp_part;
-
-                   snprintf (bp, buflen, "%s'%s'", s, p->c_storage);
-                   len = strlen (bp);
-                   bp += len;
-                   buflen -= len;
-                   s = " ";
+               if (multipart) {
+                   /* insert filename(s) containing content */
+                   struct multipart *m = (struct multipart *) ct->c_ctparams;
+                   struct part *part;
+                   char *s = "";
+                   CT p;
+
+                   for (part = m->mp_parts; part; part = part->mp_next) {
+                       p = part->mp_part;
+
+                       snprintf (bp, buflen, "%s%s", s, p->c_storage);
+                       len = strlen (bp);
+                       bp += len;
+                       buflen -= len;
+                       s = " ";
+                   }
+               } else {
+                   /* insert filename containing content */
+                   snprintf (bp, buflen, "%s", file);
+
+                   /*
+                    * Old comments below are left here for posterity.
+                    * This was/is tricky.
+                    */
+                   /* since we've quoted the file argument, set things up
+                    * to look past it, to avoid problems with the quoting
+                    * logic below.  (I know, I should figure out what's
+                    * broken with the quoting logic, but..)
+                    */
+                   /*
+                    * Here's the email that submitted the patch with
+                    * the comment above:
+                    *   https://www.mail-archive.com/nmh-workers@mhost.com/
+                    *     msg00288.html
+                    * I can't tell from that exactly what was broken,
+                    * beyond misquoting of the filename.  The profile
+                    * had appearances of %F both with and without quotes.
+                    * The unquoted ones should have been quoted by the
+                    * code below.
+                    * The fix was to always quote the filename.  But
+                    * that broke '%F' because it expanded to ''filename''.
+                    */
+                   /*
+                    * Old comments above are left here for posterity.
+                    * The quoting below should work properly now.
+                    */
                }
-               /* set our starting pointer back to bp, to avoid
-                * requoting the filenames we just added
-                */
-               pp = bp;
-           }
-           break;
+               break;
 
            case 'p':
-               /* %l, and pause prior to displaying content */
-               xpause = pausesw;
-               /* and fall... */
+               /* No longer supported */
+               /* FALLTHRU */
 
            case 'l':
                /* display listing prior to displaying content */
-               xlist = !nolist;
+               *xlist = !nolist;
                break;
 
            case 's':
@@ -933,6 +907,41 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
                /* insert character % */
                goto raw;
 
+           case '{' : {
+               const char *closing_brace = strchr(cp, '}');
+
+               if (closing_brace) {
+                   const size_t param_len = closing_brace - cp - 1;
+                   char *param = mh_xmalloc(param_len + 1);
+                   char *value;
+
+                   (void) strncpy(param, cp + 1, param_len);
+                   param[param_len] = '\0';
+                   value = get_param(ci->ci_first_pm, param, '?', 0);
+                   free(param);
+
+                   cp += param_len + 1; /* Skip both braces, too. */
+
+                   if (value) {
+                       /* %{param} is set in the Content-Type header.
+                          After the break below, quote it if necessary. */
+                       (void) strncpy(bp, value, buflen);
+                       free(value);
+                   } else {
+                       /* %{param} not found, so skip it completely.  cp
+                          was advanced above. */
+                       continue;
+                   }
+               } else {
+                   /* This will get confused if there are multiple %{}'s,
+                      but its real purpose is to avoid doing bad things
+                      above if a closing brace wasn't found. */
+                   inform("no closing brace for display string escape %s, continuing...",
+                            cp);
+               }
+               break;
+           }
+
            default:
                *bp++ = *--cp;
                *bp = '\0';
@@ -942,53 +951,93 @@ show_multi_aux (CT ct, int serial, int alternate, char *cp)
            len = strlen (bp);
            bp += len;
            buflen -= len;
+           *bp = '\0';
 
            /* Did we actually insert something? */
            if (bp != pp) {
                /* Insert single quote if not inside quotes already */
                if (!quoted && buflen) {
                    len = strlen (pp);
-                   memmove (pp + 1, pp, len);
+                   memmove (pp + 1, pp, len+1);
                    *pp++ = '\'';
                    buflen--;
                    bp++;
+                   quoted = 1;
                }
                /* Escape existing quotes */
                while ((pp = strchr (pp, '\'')) && buflen > 3) {
                    len = strlen (pp++);
-                   memmove (pp + 3, pp, len);
-                   *pp++ = '\\';
-                   *pp++ = '\'';
-                   *pp++ = '\'';
-                   buflen -= 3;
-                   bp += 3;
+                   if (quoted) {
+                       /* Quoted.  Let this quote close that quoting.
+                          Insert an escaped quote to replace it and
+                          another quote to reopen quoting, which will be
+                          closed below. */
+                       memmove (pp + 2, pp, len);
+                       *pp++ = '\\';
+                       *pp++ = '\'';
+                       buflen -= 2;
+                       bp += 2;
+                       quoted = 0;
+                   } else {
+                       /* Not quoted.  This should not be reached with
+                          the current code, but handle the condition
+                          in case the code changes.  Just escape the
+                          quote. */
+                       memmove (pp, pp-1, len+1);
+                       *(pp++-1) = '\\';
+                       buflen--;
+                       bp++;
+                   }
                }
                /* If pp is still set, that means we ran out of space. */
                if (pp)
                    buflen = 0;
-               if (!quoted && buflen) {
-                   *bp++ = '\'';
-                   *bp = '\0';
-                   buflen--;
+               /* Close quoting. */
+               if (quoted && buflen) {
+                   /* See if we need to close the quote by looking
+                      for an odd number of unescaped close quotes in
+                      the remainder of the display string. */
+                   int found_quote = 0, escaped = 0;
+                   char *c;
+
+                   for (c = cp+1; *c; ++c) {
+                       if (*c == '\\') {
+                           escaped = ! escaped;
+                       } else {
+                           if (escaped) {
+                               escaped = 0;
+                           } else {
+                               if (*c == '\'') {
+                                   found_quote = ! found_quote;
+                               }
+                           }
+                       }
+                   }
+                   if (! found_quote) {
+                       *bp++ = '\'';
+                       buflen--;
+                       quoted = 0;
+                   }
                }
            }
        } else {
 raw:
            *bp++ = *cp;
-           *bp = '\0';
            buflen--;
 
            if (*cp == '\'')
                quoted = !quoted;
        }
+
+       *bp = '\0';
     }
 
-    if (buflen <= 0 || (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
+    if (buflen <= 0 ||
+        (ct->c_termproc && buflen <= strlen(ct->c_termproc))) {
        /* content_error would provide a more useful error message
         * here, except that if we got overrun, it probably would
         * too.
         */
-       fprintf(stderr, "Buffer overflow constructing show command!\n");
        return NOTOK;
     }
 
@@ -997,96 +1046,473 @@ raw:
        char term[BUFSIZ];
 
        strncpy (term, buffer, sizeof(term));
-       snprintf (buffer, sizeof(buffer), ct->c_termproc, term);
+       snprintf (buffer, buflen, ct->c_termproc, term);
     }
 
-    return show_content_aux2 (ct, serial, alternate, NULL, buffer,
-                             NOTOK, xlist, xpause, 0, xtty);
+    return OK;
 }
 
 
-/*
- * show content of type "message/rfc822"
- */
+int
+convert_charset (CT ct, char *dest_charset, int *message_mods) {
+    char *src_charset = content_charset (ct);
+    int status = OK;
+
+    if (strcasecmp (src_charset, dest_charset)) {
+#ifdef HAVE_ICONV
+        iconv_t conv_desc = NULL;
+        char *dest;
+        int fd = -1;
+        char **file = NULL;
+        FILE **fp = NULL;
+        size_t begin;
+        size_t end;
+        int opened_input_file = 0;
+        char src_buffer[BUFSIZ];
+       size_t dest_buffer_size = BUFSIZ;
+       char *dest_buffer = mh_xmalloc(dest_buffer_size);
+        HF hf;
+        char *tempfile;
+       int fromutf8 = !strcasecmp(src_charset, "UTF-8");
+
+        if ((conv_desc = iconv_open (dest_charset, src_charset)) ==
+            (iconv_t) -1) {
+            inform("Can't convert %s to %s", src_charset, dest_charset);
+            free (src_charset);
+            return NOTOK;
+        }
+
+        if ((tempfile = m_mktemp2 (NULL, invo_name, &fd, NULL)) == NULL) {
+            adios (NULL, "unable to create temporary file in %s",
+                   get_temp_dir());
+        }
+        dest = mh_xstrdup(tempfile);
+
+        if (ct->c_cefile.ce_file) {
+            file = &ct->c_cefile.ce_file;
+            fp = &ct->c_cefile.ce_fp;
+            begin = end = 0;
+        } else if (ct->c_file) {
+            file = &ct->c_file;
+            fp = &ct->c_fp;
+            begin = (size_t) ct->c_begin;
+            end = (size_t) ct->c_end;
+        } /* else no input file: shouldn't happen */
+
+        if (file  &&  *file  &&  fp) {
+            if (! *fp) {
+                if ((*fp = fopen (*file, "r")) == NULL) {
+                    advise (*file, "unable to open for reading");
+                    status = NOTOK;
+                } else {
+                    opened_input_file = 1;
+                }
+            }
+        }
+
+        if (fp  &&  *fp) {
+            size_t inbytes;
+            size_t bytes_to_read =
+                end > 0 && end > begin  ?  end - begin  :  sizeof src_buffer;
+
+            fseeko (*fp, begin, SEEK_SET);
+            while ((inbytes = fread (src_buffer, 1,
+                                     min (bytes_to_read, sizeof src_buffer),
+                                     *fp)) > 0) {
+                ICONV_CONST char *ib = src_buffer;
+                char *ob = dest_buffer;
+                size_t outbytes = dest_buffer_size;
+                size_t outbytes_before = outbytes;
+
+                if (end > 0) bytes_to_read -= inbytes;
+
+iconv_start:
+                if (iconv (conv_desc, &ib, &inbytes, &ob, &outbytes) ==
+                    (size_t) -1) {
+                   if (errno == E2BIG) {
+                       /*
+                        * Bump up the buffer by at least a factor of 2
+                        * over what we need.
+                        */
+                       size_t bumpup = inbytes * 2, ob_off = ob - dest_buffer;
+                       dest_buffer_size += bumpup;
+                       dest_buffer = mh_xrealloc(dest_buffer,
+                                                 dest_buffer_size);
+                       ob = dest_buffer + ob_off;
+                       outbytes += bumpup;
+                       outbytes_before += bumpup;
+                       goto iconv_start;
+                   }
+                   if (errno == EINVAL) {
+                       /* middle of multi-byte sequence */
+                       if (write (fd, dest_buffer, outbytes_before - outbytes) < 0) {
+                           advise (dest, "write");
+                       }
+                       fseeko (*fp, -inbytes, SEEK_CUR);
+                       if (end > 0) bytes_to_read += inbytes;
+                       /* inform("convert_charset: EINVAL"); */
+                       continue;
+                   }
+                   if (errno == EILSEQ) {
+                       /* invalid multi-byte sequence */
+                       if (fromutf8) {
+                           for (++ib, --inbytes;
+                                inbytes > 0 &&
+                                       (((unsigned char) *ib) & 0xc0) == 0x80;
+                                ++ib, --inbytes)
+                               continue;
+                       } else {
+                           ib++; inbytes--; /* skip it */
+                       }
+                       (*ob++) = '?'; outbytes --;
+                       /* inform("convert_charset: EILSEQ"); */
+                       goto iconv_start;
+                   }
+                   inform("convert_charset: errno = %d", errno);
+                    status = NOTOK;
+                    break;
+                }
+
+                if (write (fd, dest_buffer, outbytes_before - outbytes) < 0) {
+                    advise (dest, "write");
+                }
+            }
+
+            if (opened_input_file) {
+                fclose (*fp);
+                *fp = NULL;
+            }
+        }
+
+        iconv_close (conv_desc);
+        close (fd);
+
+        if (status == OK) {
+            /* Replace the decoded file with the converted one. */
+            if (ct->c_cefile.ce_file) {
+                if (ct->c_cefile.ce_unlink) {
+                    (void) m_unlink (ct->c_cefile.ce_file);
+                }
+                free (ct->c_cefile.ce_file);
+            }
+            ct->c_cefile.ce_file = dest;
+            ct->c_cefile.ce_unlink = 1;
+
+            ++*message_mods;
+
+            /* Update ct->c_ctline. */
+            if (ct->c_ctline) {
+                char *ctline = concat(" ", ct->c_ctinfo.ci_type, "/",
+                                     ct->c_ctinfo.ci_subtype, NULL);
+               char *outline;
+
+               replace_param(&ct->c_ctinfo.ci_first_pm,
+                             &ct->c_ctinfo.ci_last_pm, "charset",
+                             dest_charset, 0);
+               outline = output_params(LEN(TYPE_FIELD) + 1 + strlen(ctline),
+                                       ct->c_ctinfo.ci_first_pm, NULL, 0);
+               if (outline) {
+                   ctline = add(outline, ctline);
+                   free(outline);
+               }
+
+                free (ct->c_ctline);
+                ct->c_ctline = ctline;
+            } /* else no CT line, which is odd */
+
+            /* Update Content-Type header field. */
+            for (hf = ct->c_first_hf; hf; hf = hf->next) {
+                if (! strcasecmp (TYPE_FIELD, hf->name)) {
+                    char *ctline = concat (ct->c_ctline, "\n", NULL);
+
+                    free (hf->value);
+                    hf->value = ctline;
+                    break;
+                }
+            }
+        } else {
+            (void) m_unlink (dest);
+        }
+       free(dest_buffer);
+#else  /* ! HAVE_ICONV */
+        NMH_UNUSED (message_mods);
+
+        inform("Can't convert %s to %s without iconv", src_charset,
+                dest_charset);
+        errno = ENOSYS;
+        status = NOTOK;
+#endif /* ! HAVE_ICONV */
+    }
+
+    free (src_charset);
+    return status;
+}
+
 
 static int
-show_message_rfc822 (CT ct, int serial, int alternate)
-{
-    char *cp, buffer[BUFSIZ];
-    CI ci = &ct->c_ctinfo;
+convert_content_charset (CT ct, char **file) {
+    int status = OK;
 
-    /* Check for mhn-show-type/subtype */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+#ifdef HAVE_ICONV
+    /* Using current locale, see if the content needs to be converted. */
 
-    /* Check for mhn-show-type */
-    snprintf (buffer, sizeof(buffer), "%s-show-%s", invo_name, ci->ci_type);
-    if ((cp = context_find (buffer)) && *cp != '\0')
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+    /* content_charset() cannot return NULL. */
+    char *src_charset = content_charset (ct);
 
-    if ((cp = ct->c_showproc))
-       return show_content_aux (ct, serial, alternate, cp, NULL);
+    if (! check_charset (src_charset, strlen (src_charset))) {
+        int unused = 0;
 
-    /* default method for message/rfc822 */
-    if (ct->c_subtype == MESSAGE_RFC822) {
-       cp = (ct->c_showproc = add ("%pshow -file '%F'", NULL));
-       return show_content_aux (ct, serial, alternate, cp, NULL);
-    }
+        char *dest_charset = getcpy (get_charset ());
 
-    /* complain if we are not a part of a multipart/alternative */
-    if (!alternate)
-       content_error (NULL, ct, "don't know how to display content");
+        if (convert_charset (ct, dest_charset, &unused) == 0) {
+            *file = ct->c_cefile.ce_file;
+        } else {
+            status = NOTOK;
+        }
 
-    return NOTOK;
-}
+        free (dest_charset);
+    }
+    free (src_charset);
+#else  /* ! HAVE_ICONV */
+    NMH_UNUSED (ct);
+    NMH_UNUSED (file);
+#endif /* ! HAVE_ICONV */
 
+    return status;
+}
 
 /*
- * Show content of type "message/partial".
+ * Compile our format string and save any parameters we care about.
  */
 
-static int
-show_partial (CT ct, int serial, int alternate)
+#define DEFAULT_HEADER "[ Message %{folder}%<{folder}:%>%(msg) ]"
+#define DEFAULT_MARKER "[ part %{part} - %{content-type} - " \
+                      "%<{description}%{description}" \
+                        "%?{cdispo-filename}%{cdispo-filename}" \
+                        "%|%{ctype-name}%>  " \
+                      "%(kilo(size))B %<(unseen)\\(suppressed\\)%> ]"
+
+static struct format *
+compile_header(char *form)
 {
-    content_error (NULL, ct,
-       "in order to display this message, you must reassemble it");
-    return NOTOK;
+    struct format *fmt;
+    char *fmtstring;
+    struct comp *comp = NULL;
+    unsigned int bucket;
+
+    fmtstring = new_fs(form, NULL, DEFAULT_HEADER);
+
+    (void) fmt_compile(fmtstring, &fmt, 1);
+    free_fs();
+
+    while ((comp = fmt_nextcomp(comp, &bucket)) != NULL) {
+       if (strcasecmp(comp->c_name, "folder") == 0) {
+           folder_comp = comp;
+       }
+    }
+
+    return fmt;
 }
 
+static struct format *
+compile_marker(char *form)
+{
+    struct format *fmt;
+    char *fmtstring;
+    struct comp *comp = NULL;
+    unsigned int bucket;
+    struct param_comp_list *pc_entry;
+
+    fmtstring = new_fs(form, NULL, DEFAULT_MARKER);
+
+    (void) fmt_compile(fmtstring, &fmt, 1);
+    free_fs();
+
+    /*
+     * Things we care about:
+     *
+     * part            - Part name (e.g., 1.1)
+     * content-type    - Content-Type
+     * description     - Content-Description
+     * disposition     - Content-Disposition (inline, attachment)
+     * ctype-<param>   - Content-Type parameter
+     * cdispo-<param>  - Content-Disposition parameter
+     */
+
+    while ((comp = fmt_nextcomp(comp, &bucket)) != NULL) {
+       if (strcasecmp(comp->c_name, "part") == 0) {
+           part_comp = comp;
+       } else if (strcasecmp(comp->c_name, "content-type") == 0) {
+           ctype_comp = comp;
+       } else if (strcasecmp(comp->c_name, "description") == 0) {
+           description_comp = comp;
+       } else if (strcasecmp(comp->c_name, "disposition") == 0) {
+           dispo_comp = comp;
+       } else if (strncasecmp(comp->c_name, "ctype-", 6) == 0 &&
+                  strlen(comp->c_name) > 6) {
+           NEW(pc_entry);
+           pc_entry->param = mh_xstrdup(comp->c_name + 6);
+           pc_entry->comp = comp;
+           pc_entry->next = ctype_pc_list;
+           ctype_pc_list = pc_entry;
+       } else if (strncasecmp(comp->c_name, "cdispo-", 7) == 0 &&
+                  strlen(comp->c_name) > 7) {
+           NEW(pc_entry);
+           pc_entry->param = mh_xstrdup(comp->c_name + 7);
+           pc_entry->comp = comp;
+           pc_entry->next = dispo_pc_list;
+           dispo_pc_list = pc_entry;
+       }
+    }
+
+    return fmt;
+}
 
 /*
- * Show content of type "message/external".
- *
- * THE ERROR CHECKING IN THIS ONE IS NOT DONE YET.
+ * Output on stdout an appropriate marker for this content, using mh-format
  */
 
-static int
-show_external (CT ct, int serial, int alternate)
+static void
+output_header(CT ct, struct format *fmt)
 {
-    struct exbody *e = (struct exbody *) ct->c_ctparams;
-    CT p = e->eb_content;
+    charstring_t outbuf = charstring_create (BUFSIZ);
+    int dat[5] = { 0 };
+    char *endp;
+    int message = 0;
+
+    if (folder_comp)
+       folder_comp->c_text = getcpy(folder);
+
+    if (ct->c_file && *ct->c_file) {
+       message = strtol(ct->c_file, &endp, 10);
+       if (*endp) message = 0;
+       dat[0] = message;
+    }
 
-    if (!type_ok (p, 0))
-       return OK;
+    /* it would be nice to populate dat[2], for %(size) here,
+     * but it's not available.  it might also be nice to know
+     * if the message originally had any mime parts or not -- but
+     * there's also no record of that.  (except for MIME-version:)
+     */
 
-    return show_switch (p, serial, alternate);
+    fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
 
-#if 0
-    content_error (NULL, p, "don't know how to display content");
-    return NOTOK;
-#endif
+    fputs(charstring_buffer (outbuf), stdout);
+    charstring_free (outbuf);
+
+    fmt_freecomptext();
+}
+
+static void
+output_marker(CT ct, struct format *fmt, int hidden)
+{
+    charstring_t outbuf = charstring_create (BUFSIZ);
+    struct param_comp_list *pcentry;
+    int partsize;
+    int message = 0;
+    char *endp;
+    int dat[5] = { 0 };
+
+    /*
+     * Grab any items we care about.
+     */
+
+    if (ctype_comp && ct->c_ctinfo.ci_type) {
+       ctype_comp->c_text = concat(ct->c_ctinfo.ci_type, "/",
+                                   ct->c_ctinfo.ci_subtype, NULL);
+    }
+
+    if (part_comp && ct->c_partno) {
+       part_comp->c_text = mh_xstrdup(ct->c_partno);
+    }
+
+    if (description_comp && ct->c_descr) {
+       description_comp->c_text = mh_xstrdup(ct->c_descr);
+    }
+
+    if (dispo_comp && ct->c_dispo_type) {
+       dispo_comp->c_text = mh_xstrdup(ct->c_dispo_type);
+    }
+
+    for (pcentry = ctype_pc_list; pcentry != NULL; pcentry = pcentry->next) {
+       pcentry->comp->c_text = get_param(ct->c_ctinfo.ci_first_pm,
+                                         pcentry->param, '?', 0);
+    }
+
+    for (pcentry = dispo_pc_list; pcentry != NULL; pcentry = pcentry->next) {
+       pcentry->comp->c_text = get_param(ct->c_dispo_first,
+                                         pcentry->param, '?', 0);
+    }
+
+    if (ct->c_cesizefnx)
+       partsize = (*ct->c_cesizefnx) (ct);
+    else
+       partsize = ct->c_end - ct->c_begin;
+
+    if (ct->c_file && *ct->c_file) {
+       message = strtol(ct->c_file, &endp, 10);
+       if (*endp) message = 0;
+       dat[0] = message;
+    }
+    dat[2] = partsize;
+
+    /* make the part's hidden aspect available by overloading the
+     * %(unseen) function.  make the part's size available via %(size).
+     * see comments in h/fmt_scan.h.
+     */
+    dat[4] = hidden;
+
+    fmt_scan(fmt, outbuf, BUFSIZ, dat, NULL);
+
+    fputs(charstring_buffer (outbuf), stdout);
+    charstring_free (outbuf);
+
+    fmt_freecomptext();
 }
 
+/*
+ * Reset (and free) any of the saved marker text
+ */
 
-static RETSIGTYPE
-intrser (int i)
+static void
+free_markercomps(void)
+{
+    struct param_comp_list *pc_entry, *pc2;
+
+    folder_comp = NULL;
+    part_comp = NULL;
+    ctype_comp = NULL;
+    description_comp = NULL;
+    dispo_comp = NULL;
+
+    for (pc_entry = ctype_pc_list; pc_entry != NULL; ) {
+       free(pc_entry->param);
+       pc2 = pc_entry->next;
+       free(pc_entry);
+       pc_entry = pc2;
+    }
+
+    for (pc_entry = dispo_pc_list; pc_entry != NULL; ) {
+       free(pc_entry->param);
+       pc2 = pc_entry->next;
+       free(pc_entry);
+       pc_entry = pc2;
+    }
+}
+
+/*
+ * Exit if the display process returned with a nonzero exit code, or terminated
+ * with a SIGQUIT signal.
+ */
+
+static int
+pidcheck (int status)
 {
-#ifndef RELIABLE_SIGNALS
-    SIGNAL (SIGINT, intrser);
-#endif
+    if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
+       return status;
 
-    putchar ('\n');
-    siglongjmp (intrenv, DONE);
+    fflush (stdout);
+    fflush (stderr);
+    done (1);
+    return 1;
 }