]> diplodocus.org Git - nmh/commitdiff
Merge remote-tracking branch 'origin/fix-locking'
authorDavid Levine <levinedl@acm.org>
Thu, 10 Apr 2014 00:19:26 +0000 (19:19 -0500)
committerDavid Levine <levinedl@acm.org>
Thu, 10 Apr 2014 00:19:26 +0000 (19:19 -0500)
16 files changed:
Makefile.am
docs/pending-release-notes
etc/mhshow.marker [new file with mode: 0644]
h/fmt_scan.h
h/mhparse.h
h/prototypes.h
man/mhshow.man
sbr/fmt_compile.c
sbr/m_popen.c [new file with mode: 0644]
test/mhshow/test-charset
test/mhshow/test-textcharset
uip/mhlsbr.c
uip/mhmisc.c
uip/mhn.c
uip/mhshow.c
uip/mhshowsbr.c

index f0670b3..91a915f 100644 (file)
@@ -188,8 +188,9 @@ noinst_HEADERS = h/addrsbr.h h/aliasbr.h h/crawl_folders.h h/dropsbr.h \
 dist_sysconf_DATA = etc/MailAliases etc/components etc/digestcomps \
                    etc/distcomps etc/forwcomps etc/mhl.body etc/mhl.digest \
                    etc/mhl.format etc/mhl.forward etc/mhl.headers \
-                   etc/mhl.reply etc/rcvdistcomps etc/rcvdistcomps.outbox \
-                   etc/replcomps etc/replgroupcomps etc/scan.MMDDYY \
+                   etc/mhl.reply etc/mhshow.marker etc/rcvdistcomps \
+                   etc/rcvdistcomps.outbox etc/replcomps etc/replgroupcomps \
+                   etc/scan.MMDDYY \
                    etc/scan.YYYYMMDD etc/scan.curses etc/scan.default \
                    etc/scan.highlighted \
                    etc/scan.mailx etc/scan.nomime etc/scan.size etc/scan.time \
@@ -575,7 +576,7 @@ sbr_libmh_a_SOURCES = sbr/addrsbr.c sbr/ambigsw.c sbr/atooi.c sbr/arglist.c \
                      sbr/fmt_rfc2047.c sbr/fmt_scan.c sbr/lock_file.c \
                      sbr/m_atoi.c sbr/m_backup.c sbr/m_convert.c \
                      sbr/m_draft.c sbr/m_getfld.c sbr/m_gmprot.c \
-                     sbr/m_maildir.c sbr/m_name.c sbr/m_rand.c \
+                     sbr/m_maildir.c sbr/m_name.c sbr/m_popen.c sbr/m_rand.c \
                      sbr/makedir.c sbr/message_id.c sbr/mime_type.c sbr/mts.c \
                      sbr/norm_charmap.c sbr/path.c \
                      sbr/peekc.c sbr/pidwait.c sbr/pidstatus.c \
index 8c02d84..22385f4 100644 (file)
@@ -81,6 +81,9 @@ NEW FEATURES
 - The MIME parsing and generating routines now support RFC 2231 extended
   parameter information.
 - mh-mime(7) now provides an introduction to nmh's MIME handling.
+- mhshow(1) will now by default display all text content under one pager,
+  and display markers for non-text and non-inline content.  The content
+  markers are changeable via mh-format(5).
 
 -----------------
 OBSOLETE FEATURES
diff --git a/etc/mhshow.marker b/etc/mhshow.marker
new file mode 100644 (file)
index 0000000..cc9ec85
--- /dev/null
@@ -0,0 +1,6 @@
+%;
+%; This is provided as reference only; it shows the default content marker
+%; used by mhshow when it decides to not display content.
+%;
+[ part %{part} - %{content-type} - %<{description}\
+%{description}%?{cdispo-filename}%{cdispo-filename}%|%{ctype-name}%> ]
index 4d768b7..d50dc44 100644 (file)
@@ -267,6 +267,25 @@ int fmt_addcomptext(char *component, char *text);
 
 void fmt_appendcomp(int bucket, char *component, char *text);
 
+/*
+ * Iterate over the complete hash table of component structures.
+ *
+ * Arguments are:
+ *
+ * comp                - Pointer to the current component structure.  The next
+ *               component in the hash table after this component.  To
+ *               start (or restart) the iteration of the hash table
+ *               this argument should be NULL.
+ * bucket      - Pointer to hash bucket.  Will be managed by this function,
+ *               the caller should not modify this value.
+ *
+ * Returns the next component in the hash table.  This value should be
+ * passed into the next call to fmt_nextcomp().  Returns NULL at the end
+ * of the hash table.
+ */
+
+struct comp *fmt_nextcomp(struct comp *comp, unsigned int *bucket);
+
 /*
  * The implementation of the %(formataddr) function.  This is available for
  * programs to provide their own local implementation if they wish to do
index 700c0cc..d165a63 100644 (file)
@@ -343,11 +343,21 @@ const struct str2init *get_ce_method (const char *);
 char *content_charset (CT);
 int convert_charset (CT, char *, int *);
 
+/*
+ * Given a content structure, return true if the content has a disposition
+ * of "inline".
+ *
+ * Arguments are:
+ *
+ * ct          - Content structure to examine
+ */
+int is_inline(CT ct);
+
 /*
  * Given a list of messages, display information about them on standard
  * output.
  *
- * Argumens are:
+ * Arguments are:
  *
  * cts         - An array of CT elements of messages that need to be
  *               displayed.  Array is terminated by a NULL.
@@ -472,4 +482,23 @@ char *get_param(PM first, const char *name, char replace, int fetchonly);
  */
 char *get_param_value(PM pm, char replace);
 
+/*
+ * Display MIME message(s) on standard out.
+ *
+ * Arguments are:
+ *
+ * cts         - NULL terminated array of CT structures for messages
+ *               to display
+ * concat      - If true, concatenate all MIME parts.  If false, show each
+ *               MIME part under a separate pager.
+ * textonly    - If true, only display "text" MIME parts
+ * inlineonly  - If true, only display MIME parts that are marked with
+ *               a disposition of "inline" (includes parts that lack a
+ *               Content-Disposition header).
+ * markerform  - The name of a file containg mh-format(5) code used to
+ *               display markers about non-displayed MIME parts.
+ */
+void show_all_messages(CT *cts, int concat, int textonly, int inlineonly,
+                      char *markerform);
+
 extern int checksw;    /* Add Content-MD5 field */
index 0edcf41..da5231b 100644 (file)
@@ -208,6 +208,23 @@ char *m_mktemp(const char *, int *, FILE **);
 char *m_mktemp2(const char *, const char *, int *, FILE **);
 char *m_mktemps(const char *pfx, const char *suffix, int *, FILE **);
 char *get_temp_dir();
+
+/*
+ * Create a subprocess and redirect our standard output to it.
+ *
+ * Arguments are:
+ *
+ * name                - Name of process to create
+ * savestdout  - If true, will save the current stdout file descriptor and
+ *               m_pclose() will close it at the appropriate time.
+ */
+void m_popen(char *name, int savestdout);
+
+/*
+ * Wait for the last process opened by m_popen().
+ */
+void m_pclose(void);
+
 void m_unknown(m_getfld_state_t *, FILE *);
 int makedir (char *);
 char *message_id (time_t, int);
index b6fe883..fd696e0 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHSHOW %manext1% "March 16, 2014" "%nmhversion%"
+.TH MHSHOW %manext1% "April 9, 2014" "%nmhversion%"
 .\"
 .\" %nmhwarning%
 .\"
@@ -18,8 +18,13 @@ mhshow \- display MIME messages
 .RB [ \-type
 .IR content ]
 \&...
+.RB [ \-concat " | " \-noconcat ]
+.RB [ \-textonly " | " \-notextonly ]
+.RB [ \-inlineonly " | " \-noinlineonly ]
 .RB [ \-form
 .IR formfile ]
+.RB [ \-markform
+.IR formfile ]
 .RB [ \-rcache
 .IR policy ]
 .RB [ \-wcache
@@ -44,16 +49,34 @@ message headers as specified in RFC 2047.
 .PP
 By default
 .B mhshow
-will display all parts of a multipart
-message.  By using the
+will display only text parts of a message that are not marked as attachments.
+This behavior can be changed by the
+.B \-notextonly
+and
+.B \-noinlineonly
+switches.
+In addition, by using the
 .B \-part
 and
 .B \-type
 switches, you may
-limit the scope of
+further limit the scope of
 .B mhshow
 to particular subparts (of a
-multipart content) and/or particular content types.
+multipart content) and/or particular content types.  The inclusion of any
+.B \-part
+or
+.B \-type
+switches will override the default settings of
+.B \-textonly
+and
+.BR \-inlineonly.
+.PP
+By default
+.B mhshow
+will concatenate all content under one pager.  If you which each part to
+displayed separately, you can override the default behavior with
+.B \-noconcat.
 .PP
 The option
 .B \-file
@@ -571,6 +594,45 @@ e.g.,
 which is created automatically during
 .B nmh
 installation.
+.PP
+SS Content\-Type Marker
+If
+.B mhshow
+decides to not display a particular part due to the switches of
+.B \-textonly
+or
+.B \-inlineonly
+it will display a marker containing information about the part.  This
+marker is processed via
+.IR mh\-format (5)
+and can be changed by the use of the
+.B \-markform
+switch to specify a file containing the
+.IR mh\-format (5)
+instructions to use when displaying the content marker.  In addition to
+the normal set of
+.IR mh\-format (5)
+instructions, the following
+.I component
+escapes are supported:
+.PP
+.RS 5
+.nf
+.ta \w'cdispo-<PARAM>  'u +\w'Returns   'u
+.I "Escape     Returns Description"
+part   string  MIME part number
+content\-type  string  MIME Content\-Type of part
+description    string  Content\-Description header
+disposition    string  Content disposition (attachment or inline)
+ctype-<PARAM>  string  Value of <PARAM> from Content\-Type header
+cdispo-<PARAM> string  Value of <PARAM> from
+               Content\-Disposition header
+.fi
+.RE
+All MIME parameters and the \*(lqContent-Description\*(rq header will have
+RFC 2231 decoding applied and be converted
+to the local character set.
+.PP
 .SH FILES
 .B mhshow
 looks for all format files and mhn.defaults in multiple locations:
@@ -588,6 +650,7 @@ is checked.
 ^$MHSHOW~^Additional profile entries
 ^%etcdir%/mhn.defaults~^System default MIME profile entries
 ^%etcdir%/mhl.headers~^The headers template
+^%etcdir%/mhshow.marker~^Example content marker
 .fi
 .SH "PROFILE COMPONENTS"
 .fc ^ ~
@@ -619,6 +682,9 @@ is checked.
 .RB ` +folder "' defaults to the current folder"
 .RB ` msgs "' defaults to cur"
 .RB ` \-nocheck '
+.RB ` \-concat '
+.RB ` \-textonly '
+.RB ` \-inlineonly '
 .RB ` \-form\ mhl.headers '
 .RB ` \-rcache\ ask '
 .RB ` \-wcache\ ask '
index 970e71e..3f170f3 100644 (file)
@@ -1087,6 +1087,25 @@ fmt_appendcomp(int bucket, char *component, char *text)
     }
 }
 
+/*
+ * Iterate over our component hash table
+ */
+
+struct comp *
+fmt_nextcomp(struct comp *comp, unsigned int *bucket)
+{
+    if (comp == NULL)
+       *bucket = 0;
+    else
+       comp = comp->c_next;
+
+    while (comp == NULL && *bucket < sizeof(wantcomp)/sizeof(wantcomp[0])) {
+       comp = wantcomp[(*bucket)++];
+    }
+
+    return comp;
+}
+
 /*
  * Free and reset our component hash table
  */
diff --git a/sbr/m_popen.c b/sbr/m_popen.c
new file mode 100644 (file)
index 0000000..8a3bae6
--- /dev/null
@@ -0,0 +1,83 @@
+
+/*
+ * m_popen.c -- Interface for a popen() call that redirects the current
+ *             process standard output to the popen()d process.
+ *
+ * This code is Copyright (c) 2014, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+#include <h/mh.h>
+#include <h/signals.h>
+
+static int m_pid = NOTOK;      /* Process we're waiting for */
+static  int sd = NOTOK;                /* Original standard output */
+
+/*
+ * Fork a process and redirect our standard output to that process
+ */
+
+void
+m_popen (char *name, int savestdout)
+{
+    int pd[2];
+    char *file;
+    char **arglist;
+
+    if (savestdout && (sd = dup (fileno (stdout))) == NOTOK)
+       adios ("standard output", "unable to dup()");
+
+    if (pipe (pd) == NOTOK)
+       adios ("pipe", "unable to");
+
+    switch (m_pid = fork()) {
+       case NOTOK: 
+           adios ("fork", "unable to");
+
+       case OK: 
+           SIGNAL (SIGINT, SIG_DFL);
+           SIGNAL (SIGQUIT, SIG_DFL);
+
+           close (pd[1]);
+           if (pd[0] != fileno (stdin)) {
+               dup2 (pd[0], fileno (stdin));
+               close (pd[0]);
+           }
+           arglist = argsplit(name, &file, NULL);
+           execvp (file, arglist);
+           fprintf (stderr, "unable to exec ");
+           perror (name);
+           _exit (-1);
+
+       default: 
+           close (pd[0]);
+           if (pd[1] != fileno (stdout)) {
+               dup2 (pd[1], fileno (stdout));
+               close (pd[1]);
+           }
+    }
+}
+
+
+void
+m_pclose (void)
+{
+    if (m_pid == NOTOK)
+       return;
+
+    if (sd != NOTOK) {
+       fflush (stdout);
+       if (dup2 (sd, fileno (stdout)) == NOTOK)
+           adios ("standard output", "unable to dup2()");
+
+       clearerr (stdout);
+       close (sd);
+       sd = NOTOK;
+    }
+    else
+       fclose (stdout);
+
+    pidwait (m_pid, OK);
+    m_pid = NOTOK;
+}
index 974442c..efcab90 100755 (executable)
@@ -18,6 +18,8 @@ setup_test
 
 LC_ALL=en_US.UTF-8; export LC_ALL
 
+test "$ICONV_ENABLED" -eq 0  &&  text_size=10  || text_size=11
+
 expected=$MH_TEST_DIR/$$.expected
 actual=$MH_TEST_DIR/$$.actual
 
@@ -46,7 +48,7 @@ Subject: test
 
 MIME-Version: 1.0
 
-part       text/plain                  10
+part       text/plain                  $text_size
 This is a test
 EOF
 
index 9fad5d8..5fc07e6 100755 (executable)
@@ -47,7 +47,7 @@ Subject: test display with charset conversion
 
 MIME-Version: 1.0
 
-part       text/plain                  10
+part       text/plain                  11
 4 รท 2 = 2
 EOF
 
index 3ebfa69..e9d4de8 100644 (file)
@@ -332,7 +332,6 @@ static void pipeser (int);
 static void quitser (int);
 static void mhladios (char *, char *, ...);
 static void mhldone (int);
-static void m_popen (char *);
 static void filterbody (struct mcomp *, char *, int, int, FILE *,
                         m_getfld_state_t);
 static void compile_formatfield(struct mcomp *);
@@ -482,7 +481,7 @@ mhl (int argc, char **argv)
                SIGNAL2 (SIGQUIT, quitser);
            }
            SIGNAL2 (SIGPIPE, pipeser);
-           m_popen (moreproc);
+           m_popen (moreproc, mhl_action != NULL);
            ontty = PITTY;
        } else {
            SIGNAL (SIGINT, SIG_IGN);
@@ -1644,74 +1643,6 @@ mhldone (int status)
 }
 
 
-static int m_pid = NOTOK;
-static  int sd = NOTOK;
-
-static void
-m_popen (char *name)
-{
-    int pd[2];
-    char *file;
-    char **arglist;
-
-    if (mhl_action && (sd = dup (fileno (stdout))) == NOTOK)
-       adios ("standard output", "unable to dup()");
-
-    if (pipe (pd) == NOTOK)
-       adios ("pipe", "unable to");
-
-    switch (m_pid = fork()) {
-       case NOTOK: 
-           adios ("fork", "unable to");
-
-       case OK: 
-           SIGNAL (SIGINT, SIG_DFL);
-           SIGNAL (SIGQUIT, SIG_DFL);
-
-           close (pd[1]);
-           if (pd[0] != fileno (stdin)) {
-               dup2 (pd[0], fileno (stdin));
-               close (pd[0]);
-           }
-           arglist = argsplit(name, &file, NULL);
-           execvp (file, arglist);
-           fprintf (stderr, "unable to exec ");
-           perror (name);
-           _exit (-1);
-
-       default: 
-           close (pd[0]);
-           if (pd[1] != fileno (stdout)) {
-               dup2 (pd[1], fileno (stdout));
-               close (pd[1]);
-           }
-    }
-}
-
-
-void
-m_pclose (void)
-{
-    if (m_pid == NOTOK)
-       return;
-
-    if (sd != NOTOK) {
-       fflush (stdout);
-       if (dup2 (sd, fileno (stdout)) == NOTOK)
-           adios ("standard output", "unable to dup2()");
-
-       clearerr (stdout);
-       close (sd);
-       sd = NOTOK;
-    }
-    else
-       fclose (stdout);
-
-    pidwait (m_pid, OK);
-    m_pid = NOTOK;
-}
-
-
 /*
  * Compile a format string used by the formatfield option and save it
  * for later.
index 7d32023..f4b8916 100644 (file)
@@ -75,6 +75,29 @@ type_ok (CT ct, int sP)
 }
 
 
+/*
+ * Returns true if this content is marked as "inline".
+ *
+ * Technically we should check parent content to see if they have
+ * disposition to use as a default, but we don't right now.  Maybe
+ * later ....
+ */
+
+int
+is_inline(CT ct)
+{
+    /*
+     * If there isn't any disposition at all, it's "inline".  Obviously
+     * if it's "inline", then it's inline.  RFC 2183 says if it's an unknown
+     * disposition, treat it as 'attachment'.
+     */
+
+    if (! ct->c_dispo_type || strcasecmp(ct->c_dispo_type, "inline") == 0)
+       return 1;
+    else
+       return 0;
+}
+
 int
 make_intermediates (char *file)
 {
index 3fd2d39..7363688 100644 (file)
--- a/uip/mhn.c
+++ b/uip/mhn.c
@@ -119,9 +119,6 @@ int part_ok (CT, int);
 int type_ok (CT, int);
 void flush_errors (void);
 
-/* mhshowsbr.c */
-void show_all_messages (CT *);
-
 /* mhstoresbr.c */
 typedef struct mhstoreinfo *mhstoreinfo_t;
 mhstoreinfo_t mhstoreinfo_create(CT *, char *, const char *, int, int);
@@ -563,7 +560,7 @@ do_cache:
      * Show the message content
      */
     if (showsw)
-       show_all_messages (cts);
+       show_all_messages (cts, 0, 0, 0, NULL);
 
     /* Now free all the structures for the content */
     for (ctp = cts; *ctp; ctp++)
index e897f2b..96b99b7 100644 (file)
     X("nocheck", 0, NCHECKSW) \
     X("verbose", 0, VERBSW) \
     X("noverbose", 0, NVERBSW) \
+    X("concat", 0, CONCATSW) \
+    X("noconcat", 0, NCONCATSW) \
+    X("textonly", 0, TEXTONLYSW) \
+    X("notextonly", 0, NTEXTONLYSW) \
+    X("inlineonly", 0, INLINESW) \
+    X("noinlineonly", 0, NINLINESW) \
     X("file file", 0, FILESW) \
     X("form formfile", 0, FORMSW) \
+    X("markform formfile", 0, MARKFORMSW) \
     X("part number", 0, PARTSW) \
     X("type content", 0, TYPESW) \
     X("rcache policy", 0, RCACHESW) \
@@ -83,9 +90,6 @@ int part_ok (CT, int);
 int type_ok (CT, int);
 void flush_errors (void);
 
-/* mhshowsbr.c */
-void show_all_messages (CT *);
-
 /* mhfree.c */
 extern CT *cts;
 void freects_done (int) NORETURN;
@@ -99,8 +103,8 @@ static void pipeser (int);
 int
 main (int argc, char **argv)
 {
-    int msgnum, *icachesw;
-    char *cp, *file = NULL, *folder = NULL;
+    int msgnum, *icachesw, concatsw = -1, textonly = -1, inlineonly = -1;
+    char *cp, *file = NULL, *folder = NULL, *markform = NULL;
     char *maildir, buf[100], **argp;
     char **arguments;
     struct msgs_array msgs = { 0, 0, NULL };
@@ -162,6 +166,25 @@ do_cache:
                checksw = 0;
                continue;
 
+           case CONCATSW:
+               concatsw = 1;
+               continue;
+           case NCONCATSW:
+               concatsw = 0;
+               continue;
+           case TEXTONLYSW:
+               textonly = 1;
+               continue;
+           case NTEXTONLYSW:
+               textonly = 0;
+               continue;
+           case INLINESW:
+               inlineonly = 1;
+               continue;
+           case NINLINESW:
+               inlineonly = 1;
+               continue;
+
            case PARTSW:
                if (!(cp = *argp++) || *cp == '-')
                    adios (NULL, "missing argument to %s", argp[-2]);
@@ -194,6 +217,11 @@ do_cache:
                formsw = getcpy (etcpath (cp));
                continue;
 
+           case MARKFORMSW:
+               if (!(markform = *argp++) || *markform == '-')
+                   adios (NULL, "missing argument to %s", argp[-2]);
+               continue;
+
            /*
             * Switches for moreproc/mhlproc
             */
@@ -235,6 +263,18 @@ do_cache:
     parts[npart] = NULL;
     types[ntype] = NULL;
 
+    /*
+     * If we had any specific parts or types specified, turn off text only
+     * content.
+     */
+
+    if (npart > 0 || ntype > 0) {
+       if (textonly == -1)
+           textonly = 0;
+       if (inlineonly == -1)
+           inlineonly = 0;
+    }
+
     /*
      * Check if we've specified an additional profile
      */
@@ -366,10 +406,13 @@ do_cache:
        context_save ();                  /* save the context file  */
     }
 
+    if (concatsw)
+       m_popen(moreproc, 0);
+
     /*
      * Show the message content
      */
-    show_all_messages (cts);
+    show_all_messages (cts, concatsw, textonly, inlineonly, markform);
 
     /* Now free all the structures for the content */
     for (ctp = cts; *ctp; ctp++)
@@ -378,6 +421,9 @@ do_cache:
     free ((char *) cts);
     cts = NULL;
 
+    if (concatsw)
+       m_pclose();
+
     done (0);
     return 1;
 }
index c53f22f..3421f32 100644 (file)
@@ -16,6 +16,7 @@
 #include <h/tws.h>
 #include <h/mime.h>
 #include <h/mhparse.h>
+#include <h/fmt_scan.h>
 #include <h/utils.h>
 #ifdef HAVE_ICONV
 #   include <iconv.h>
@@ -41,38 +42,61 @@ void flush_errors (void);
 /*
  * prototypes
  */
-void show_all_messages (CT *);
 int show_content_aux (CT, int, char *, char *);
 
 /*
  * static prototypes
  */
-static void show_single_message (CT, char *);
-static void DisplayMsgHeader (CT, char *);
-static int show_switch (CT, int);
-static int show_content (CT, int);
+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 *fmt);
 static int show_content_aux2 (CT, int, char *, char *, int, int, int);
-static int show_text (CT, int);
-static int show_multi (CT, int);
-static int show_multi_internal (CT, int);
+static int show_text (CT, int, int);
+static int show_multi (CT, int, int, int, int, struct format *);
+static int show_multi_internal (CT, int, int, int, int, struct format *fmt);
 static int show_multi_aux (CT, int, char *);
 static int show_message_rfc822 (CT, int);
 static int show_partial (CT, int);
-static int show_external (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_marker(char *);
+static void output_marker (CT, struct format *);
+static void free_markercomps (void);
 static int pidcheck(int);
 
+/*
+ * Components (and list of parameters/components) we care about for the
+ * content marker display.
+ */
+
+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;
+
 
 /*
  * Top level entry point to show/display a group of messages
  */
 
 void
-show_all_messages (CT *cts)
+show_all_messages (CT *cts, int concatsw, int textonly, int inlineonly,
+                  char *markerform)
 {
     CT ct, *ctp;
+    struct format *fmt;
 
     /*
      * If form is not specified, then get default form
@@ -81,6 +105,11 @@ show_all_messages (CT *cts)
     if (!formsw)
        formsw = getcpy (etcpath ("mhl.headers"));
 
+    /*
+     * Compile the content marker format line
+     */
+    fmt = compile_marker(markerform);
+
     /*
      * If form is "mhl.null", suppress display of header.
      */
@@ -92,8 +121,12 @@ show_all_messages (CT *cts)
 
        /* if top-level type is ok, then display message */
        if (type_ok (ct, 1))
-           show_single_message (ct, formsw);
+           show_single_message (ct, formsw, concatsw, textonly, inlineonly,
+                                fmt);
     }
+
+    free_markercomps();
+    fmt_free(fmt, 1);
 }
 
 
@@ -102,7 +135,8 @@ 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;
 
@@ -117,10 +151,10 @@ show_single_message (CT ct, char *form)
      * the message headers.
      */
     if (form)
-       DisplayMsgHeader(ct, form);
+       DisplayMsgHeader(ct, form, concatsw);
 
     /* Show the body of the message */
-    show_switch (ct, 0);
+    show_switch (ct, 0, concatsw, textonly, inlineonly, fmt);
 
     if (ct->c_fp) {
        fclose (ct->c_fp);
@@ -137,7 +171,7 @@ show_single_message (CT ct, char *form)
     sigaddset (&set, SIGTERM);
     sigprocmask (SIG_BLOCK, &set, &oset);
 
-    while (wait (&status) != NOTOK) {
+    while (!concatsw && wait (&status) != NOTOK) {
        pidcheck (status);
        continue;
     }
@@ -154,7 +188,7 @@ 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;
@@ -171,7 +205,7 @@ DisplayMsgHeader (CT ct, char *form)
      * If we've specified -(no)moreproc,
      * then just pass that along.
      */
-    if (nomore) {
+    if (nomore || concatsw) {
        vec[vecp++] = getcpy("-nomoreproc");
     } else if (progsw) {
        vec[vecp++] = getcpy("-moreproc");
@@ -211,11 +245,13 @@ DisplayMsgHeader (CT ct, char *form)
  */
 
 static int
-show_switch (CT ct, 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, alternate);
+           return show_multi (ct, alternate, concatsw, textonly,
+                              inlineonly, fmt);
 
        case CT_MESSAGE:
            switch (ct->c_subtype) {
@@ -223,7 +259,8 @@ show_switch (CT ct, int alternate)
                    return show_partial (ct, alternate);
 
                case MESSAGE_EXTERNAL:
-                   return show_external (ct, alternate);
+                   return show_external (ct, alternate, concatsw, textonly,
+                                         inlineonly, fmt);
 
                case MESSAGE_RFC822:
                default:
@@ -231,13 +268,13 @@ show_switch (CT ct, int alternate)
            }
 
        case CT_TEXT:
-           return show_text (ct, alternate);
+           return show_text (ct, alternate, concatsw);
 
        case CT_AUDIO:
        case CT_IMAGE:
        case CT_VIDEO:
        case CT_APPLICATION:
-           return show_content (ct, alternate);
+           return show_content (ct, alternate, textonly, inlineonly, fmt);
 
        default:
            adios (NULL, "unknown content type %d", ct->c_type);
@@ -252,11 +289,22 @@ show_switch (CT ct, int alternate)
  */
 
 static int
-show_content (CT ct, int alternate)
+show_content (CT ct, int alternate, int textonly, int inlineonly,
+             struct format *fmt)
 {
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
 
+    /*
+     * If we're here, we are not a text type.  So we don't need to check
+     * the content-type.
+     */
+
+    if (textonly || (inlineonly && is_inline(ct))) {
+       output_marker(ct, fmt);
+       return OK;
+    }
+
     /* Check for invo_name-show-type/subtype */
     snprintf (buffer, sizeof(buffer), "%s-show-%s/%s",
                invo_name, ci->ci_type, ci->ci_subtype);
@@ -309,7 +357,11 @@ show_content_aux (CT ct, int alternate, char *cp, char *cracked)
            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) {
+        if (convert_content_charset (ct, &file) == OK) {
+            (*ct->c_ceclosefnx) (ct);
+            if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
+                return NOTOK;
+        } else {
             admonish (NULL, "unable to convert character set%s to %s",
                       ct->c_partno  ?  "of part "  :  "",
                       ct->c_partno  ?  ct->c_partno  :  "",
@@ -366,6 +418,33 @@ show_content_aux2 (CT ct, int alternate, char *cracked, char *buffer,
            list_switch (ct, -1, 1, 0, 0, 0);
     }
 
+    /*
+     * If the command is a zero-length string, just write the output on
+     * stdout.
+     */
+
+    if (buffer[0] == '\0') {
+       char readbuf[BUFSIZ];
+       ssize_t cc;
+
+       if (fd == NOTOK) {
+           advise(NULL, "Cannot use NULL command to display content-type "
+                  "%s/%s", ct->c_ctinfo.ci_type, ct->c_ctinfo.ci_subtype);
+           return NOTOK;
+       }
+
+       while ((cc = read(fd, readbuf, sizeof(readbuf))) > 0) {
+           fwrite(readbuf, sizeof(char), cc, stdout);
+       }
+
+       if (cc < 0) {
+           advise("read", "while reading text content");
+           return NOTOK;
+       }
+
+       return OK;
+    }
+
     vec = argsplit(buffer, &file, &vecp);
     vec[vecp++] = NULL;
 
@@ -408,7 +487,7 @@ show_content_aux2 (CT ct, int alternate, char *cracked, char *buffer,
  */
 
 static int
-show_text (CT ct, int alternate)
+show_text (CT ct, int alternate, int concatsw)
 {
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
@@ -429,8 +508,11 @@ show_text (CT ct, int alternate)
      * if it is not a text part of a multipart/alternative
      */
     if (!alternate || ct->c_subtype == TEXT_PLAIN) {
-       snprintf (buffer, sizeof(buffer), "%%l%s %%F", progsw ? progsw :
-               moreproc && *moreproc ? moreproc : DEFAULT_PAGER);
+       if (concatsw)
+           snprintf(buffer, sizeof(buffer), "%%l");
+       else
+           snprintf (buffer, sizeof(buffer), "%%l%s %%F", progsw ? progsw :
+                     moreproc && *moreproc ? moreproc : DEFAULT_PAGER);
        cp = (ct->c_showproc = add (buffer, NULL));
        return show_content_aux (ct, alternate, cp, NULL);
     }
@@ -444,7 +526,8 @@ show_text (CT ct, int alternate)
  */
 
 static int
-show_multi (CT ct, int alternate)
+show_multi (CT ct, int alternate, int concatsw, int textonly, int inlineonly,
+           struct format *fmt)
 {
     char *cp, buffer[BUFSIZ];
     CI ci = &ct->c_ctinfo;
@@ -468,7 +551,8 @@ show_multi (CT ct, int alternate)
      * unknown types are displayable, since they're treated as mixed
      * per RFC 2046.
      */
-    return show_multi_internal (ct, alternate);
+    return show_multi_internal (ct, alternate, concatsw, textonly,
+                               inlineonly, fmt);
 }
 
 
@@ -478,7 +562,8 @@ show_multi (CT ct, int alternate)
  */
 
 static int
-show_multi_internal (CT ct, int alternate)
+show_multi_internal (CT ct, int alternate, int concatsw, int textonly,
+                    int inlineonly, struct format *fmt)
 {
     int        alternating, nowalternate, result;
     struct multipart *m = (struct multipart *) ct->c_ctparams;
@@ -506,7 +591,8 @@ show_multi_internal (CT ct, int alternate)
        if (part_ok (p, 1) && type_ok (p, 1)) {
            int inneresult;
 
-           inneresult = show_switch (p, nowalternate);
+           inneresult = show_switch (p, nowalternate, concatsw, textonly,
+                                     inlineonly, fmt);
            switch (inneresult) {
                case NOTOK:
                    if (alternate && !alternating) {
@@ -652,7 +738,8 @@ show_partial (CT ct, int alternate)
  */
 
 static int
-show_external (CT ct, int alternate)
+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;
@@ -660,7 +747,7 @@ show_external (CT ct, int alternate)
     if (!type_ok (p, 0))
        return OK;
 
-    return show_switch (p, alternate);
+    return show_switch (p, alternate, concatsw, textonly, inlineonly, fmt);
 }
 
 
@@ -1112,6 +1199,146 @@ convert_content_charset (CT ct, char **file) {
     return OK;
 }
 
+/*
+ * Compile our format string and save any parameters we care about.
+ */
+
+#define DEFAULT_MARKER "[ part %{part} - %{content-type} - %<{description}" \
+                      "%{description}%?{cdispo-filename}%{cdispo-filename}" \
+                      "%|%{ctype-name}%> ]"
+
+static struct format *
+compile_marker(char *markerform)
+{
+    struct format *fmt;
+    char *fmtstring;
+    struct comp *comp = NULL;
+    unsigned int bucket;
+    struct param_comp_list *pc_entry;
+
+    fmtstring = new_fs(markerform, NULL, DEFAULT_MARKER);
+
+    (void) fmt_compile(fmtstring, &fmt, 1);
+    free(fmtstring);
+
+    /*
+     * 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) {
+           pc_entry = mh_xmalloc(sizeof(*pc_entry));
+           pc_entry->param = getcpy(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) {
+           pc_entry = mh_xmalloc(sizeof(*pc_entry));
+           pc_entry->param = getcpy(comp->c_name + 7);
+           pc_entry->comp = comp;
+           pc_entry->next = dispo_pc_list;
+           dispo_pc_list = pc_entry;
+       }
+    }
+
+    return fmt;
+}
+
+/*
+ * Output on stdout an appropriate marker for this content, using mh-format
+ */
+
+static void
+output_marker(CT ct, struct format *fmt)
+{
+    char outbuf[BUFSIZ];
+    struct param_comp_list *pcentry;
+    int dat[5];
+
+    /*
+     * 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 = getcpy(ct->c_partno);
+    }
+
+    if (description_comp && ct->c_descr) {
+       description_comp->c_text = getcpy(ct->c_descr);
+    }
+
+    if (dispo_comp && ct->c_dispo_type) {
+       dispo_comp->c_text = getcpy(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);
+    }
+
+    fmt_scan(fmt, outbuf, sizeof(outbuf), sizeof(outbuf), dat, NULL);
+
+    fputs(outbuf, stdout);
+
+    fmt_freecomptext();
+}
+
+/*
+ * Reset (and free) any of the saved marker text
+ */
+
+static void
+free_markercomps(void)
+{
+    struct param_comp_list *pc_entry, *pc2;
+
+    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.