]> diplodocus.org Git - nmh/blobdiff - uip/mhstoresbr.c
Include proper header without READLINE_SUPPORT.
[nmh] / uip / mhstoresbr.c
index 3b68970297ffaa69739a2221abf95bf684d6162f..131650be59d9128d463fe730f878d310c17bd787 100644 (file)
-
-/*
- * mhstoresbr.c -- routines to save/store the contents of MIME messages
+/* mhstoresbr.c -- routines to save/store 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
  * complete copyright information.
  */
 
-#include <h/mh.h>
+#include "h/mh.h"
+#include "sbr/read_switch_multiword.h"
+#include "sbr/concat.h"
+#include "sbr/smatch.h"
+#include "sbr/r1bindex.h"
+#include "sbr/uprf.h"
+#include "sbr/getcpy.h"
+#include "sbr/getfolder.h"
+#include "sbr/folder_read.h"
+#include "sbr/folder_free.h"
+#include "sbr/folder_addmsg.h"
+#include "sbr/context_find.h"
+#include "sbr/path.h"
+#include "sbr/error.h"
 #include <fcntl.h>
-#include <h/signals.h>
-#include <h/md5.h>
-#include <h/mts.h>
-#include <h/tws.h>
-#include <h/mime.h>
-#include <h/mhparse.h>
-#include <h/utils.h>
+#include "h/mts.h"
+#include "h/tws.h"
+#include "h/fmt_scan.h"
+#include "h/mime.h"
+#include "h/mhparse.h"
+#include "h/utils.h"
+#include "mhmisc.h"
+#include "mhshowsbr.h"
+#include "sbr/m_maildir.h"
+#include "sbr/m_mktemp.h"
 
+enum clobber_policy_t {
+  NMH_CLOBBER_ALWAYS = 0,
+  NMH_CLOBBER_AUTO,
+  NMH_CLOBBER_SUFFIX,
+  NMH_CLOBBER_ASK,
+  NMH_CLOBBER_NEVER
+};
 
-/*
- * The list of top-level contents to display
- */
-extern CT *cts;
+static enum clobber_policy_t clobber_policy (const char *) PURE;
 
-int autosw = 0;
+struct mhstoreinfo {
+    CT *cts;                 /* Top-level list of contents to store. */
+    char *cwd;               /* cached current directory */
+    int autosw;              /* -auto enabled */
+    int verbosw;             /* -verbose enabled */
+    int files_not_clobbered; /* output flag indicating that store failed
+                                in order to not clobber an existing file */
 
-/*
- * Cache of current directory.  This must be
- * set before these routines are called.
- */
-char *cwd;
+    /* The following must never be touched by a caller:  they are for
+       internal use by the mhstoresbr functions. */
+    char *dir;               /* directory in which to store contents */
   enum clobber_policy_t clobber_policy;  /* -clobber selection */
+};
 
-/*
- * The directory in which to store the contents.
- */
-static char *dir;
+static bool use_param_as_filename(const char *p);
 
-/*
- * Type for a compare function for qsort.  This keeps
- * the compiler happy.
- */
-typedef int (*qsort_comp) (const void *, const void *);
+mhstoreinfo_t
+mhstoreinfo_create (CT *ct, char *pwd, const char *csw, int asw, int vsw)
+{
+    mhstoreinfo_t info;
+
+    NEW(info);
+    info->cts = ct;
+    info->cwd = pwd;
+    info->autosw = asw;
+    info->verbosw = vsw;
+    info->files_not_clobbered = 0;
+    info->dir = NULL;
+    info->clobber_policy = clobber_policy (csw);
+
+    return info;
+}
 
+void
+mhstoreinfo_free (mhstoreinfo_t info)
+{
+    free (info->cwd);
+    free (info->dir);
+    free (info);
+}
 
-/* mhmisc.c */
-int part_ok (CT, int);
-int type_ok (CT, int);
-void flush_errors (void);
+int
+mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info)
+{
+    return info->files_not_clobbered;
+}
 
-/* mhshowsbr.c */
-int show_content_aux (CT, int, int, char *, char *);
 
 /*
- * prototypes
+ * Type for a compare function for qsort.  This keeps
+ * the compiler happy.
  */
-void store_all_messages (CT *);
+typedef int (*qsort_comp) (const void *, const void *);
+
 
 /*
  * static prototypes
  */
-static void store_single_message (CT);
-static int store_switch (CT);
-static int store_generic (CT);
-static int store_application (CT);
-static int store_multi (CT);
-static int store_partial (CT);
-static int store_external (CT);
-static int ct_compar (CT *, CT *);
-static int store_content (CT, CT);
+static void store_single_message (CT, mhstoreinfo_t);
+static int store_switch (CT, mhstoreinfo_t);
+static int store_generic (CT, mhstoreinfo_t);
+static int store_application (CT, mhstoreinfo_t);
+static int store_multi (CT, mhstoreinfo_t);
+static int store_external (CT, mhstoreinfo_t);
+static int store_content (CT, mhstoreinfo_t);
 static int output_content_file (CT, int);
 static int output_content_folder (char *, char *);
 static int parse_format_string (CT, char *, char *, int, char *);
 static void get_storeproc (CT);
-static int copy_some_headers (FILE *, CT);
-static char *clobber_check (char *);
+static char *clobber_check (char *, mhstoreinfo_t);
 
 /*
  * Main entry point to store content
@@ -81,7 +118,7 @@ static char *clobber_check (char *);
  */
 
 void
-store_all_messages (CT *cts)
+store_all_messages (mhstoreinfo_t info)
 {
     CT ct, *ctp;
     char *cp;
@@ -91,13 +128,13 @@ store_all_messages (CT *cts)
      * store any contents.
      */
     if ((cp = context_find (nmhstorage)) && *cp)
-       dir = getcpy (cp);
+       info->dir = mh_xstrdup(cp);
     else
-       dir = getcpy (cwd);
+       info->dir = getcpy (info->cwd);
 
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        ct = *ctp;
-       store_single_message (ct);
+       store_single_message (ct, info);
     }
 
     flush_errors ();
@@ -110,11 +147,11 @@ store_all_messages (CT *cts)
  */
 
 static void
-store_single_message (CT ct)
+store_single_message (CT ct, mhstoreinfo_t info)
 {
     if (type_ok (ct, 1)) {
        umask (ct->c_umask);
-       store_switch (ct);
+       store_switch (ct, info);
        if (ct->c_fp) {
            fclose (ct->c_fp);
            ct->c_fp = NULL;
@@ -130,36 +167,31 @@ store_single_message (CT ct)
  */
 
 static int
-store_switch (CT ct)
+store_switch (CT ct, mhstoreinfo_t info)
 {
     switch (ct->c_type) {
        case CT_MULTIPART:
-           return store_multi (ct);
+           return store_multi (ct, info);
 
        case CT_MESSAGE:
            switch (ct->c_subtype) {
-               case MESSAGE_PARTIAL:
-                   return store_partial (ct);
-
                case MESSAGE_EXTERNAL:
-                   return store_external (ct);
+                   return store_external (ct, info);
 
                case MESSAGE_RFC822:
                default:
-                   return store_generic (ct);
+                   return store_generic (ct, info);
            }
 
        case CT_APPLICATION:
-           return store_application (ct);
+       default:
+           return store_application (ct, info);
 
        case CT_TEXT:
        case CT_AUDIO:
        case CT_IMAGE:
        case CT_VIDEO:
-           return store_generic (ct);
-
-       default:
-           adios (NULL, "unknown content type %d", ct->c_type);
+           return store_generic (ct, info);
     }
 
     return OK; /* NOT REACHED */
@@ -172,17 +204,17 @@ store_switch (CT ct)
  */
 
 static int
-store_generic (CT ct)
+store_generic (CT ct, mhstoreinfo_t info)
 {
     /*
      * Check if the content specifies a filename.
      * Don't bother with this for type "message"
      * (only "message/rfc822" will use store_generic).
      */
-    if (autosw && ct->c_type != CT_MESSAGE)
+    if (info->autosw && ct->c_type != CT_MESSAGE)
        get_storeproc (ct);
 
-    return store_content (ct, NULL);
+    return store_content (ct, info);
 }
 
 
@@ -191,13 +223,12 @@ store_generic (CT ct)
  */
 
 static int
-store_application (CT ct)
+store_application (CT ct, mhstoreinfo_t info)
 {
-    char **ap, **ep;
     CI ci = &ct->c_ctinfo;
 
     /* Check if the content specifies a filename */
-    if (autosw)
+    if (info->autosw)
        get_storeproc (ct);
 
     /*
@@ -206,29 +237,26 @@ store_application (CT ct)
      * attribute/value pairs which specify if this a tar file.
      */
     if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
-       int tarP = 0, zP = 0, gzP = 0;
-
-       for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-           /* check for "type=tar" attribute */
-           if (!strcasecmp (*ap, "type")) {
-               if (strcasecmp (*ep, "tar"))
-                   break;
+       bool tarP = false;
+        bool zP = false;
+        bool gzP = false;
+       char *cp;
 
-               tarP = 1;
-               continue;
-           }
+       if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) {
+           if (strcasecmp (cp, "tar") == 0)
+               tarP = true;
+       }
 
-           /* check for "conversions=compress" attribute */
-           if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
-               && (!strcasecmp (*ep, "compress") || !strcasecmp (*ep, "x-compress"))) {
-               zP = 1;
-               continue;
+       /* check for "conversions=compress" attribute */
+       if ((cp = get_param(ci->ci_first_pm, "conversions", ' ', 1)) ||
+           (cp = get_param(ci->ci_first_pm, "x-conversions", ' ', 1))) {
+           if (strcasecmp (cp, "compress") == 0 ||
+                   strcasecmp (cp, "x-compress") == 0) {
+               zP = true;
            }
-           /* check for "conversions=gzip" attribute */
-           if ((!strcasecmp (*ap, "conversions") || !strcasecmp (*ap, "x-conversions"))
-               && (!strcasecmp (*ep, "gzip") || !strcasecmp (*ep, "x-gzip"))) {
-               gzP = 1;
-               continue;
+           if (strcasecmp (cp, "gzip") == 0 ||
+                   strcasecmp (cp, "x-gzip") == 0) {
+               gzP = true;
            }
        }
 
@@ -237,7 +265,7 @@ store_application (CT ct)
                                  : (gzP ? "%egzip -dc | tar tvf -"
                                     : "%etar tvf -"), NULL);
            if (!ct->c_storeproc) {
-               if (autosw) {
+               if (info->autosw) {
                    ct->c_storeproc = add (zP ? "| uncompress | tar xvpf -"
                                           : (gzP ? "| gzip -dc | tar xvpf -"
                                              : "| tar xvpf -"), NULL);
@@ -251,7 +279,7 @@ store_application (CT ct)
        }
     }
 
-    return store_content (ct, NULL);
+    return store_content (ct, info);
 }
 
 
@@ -260,7 +288,7 @@ store_application (CT ct)
  */
 
 static int
-store_multi (CT ct)
+store_multi (CT ct, mhstoreinfo_t info)
 {
     int        result;
     struct multipart *m = (struct multipart *) ct->c_ctparams;
@@ -270,15 +298,14 @@ store_multi (CT ct)
     for (part = m->mp_parts; part; part = part->mp_next) {
        CT  p = part->mp_part;
 
-       if (part_ok (p, 1) && type_ok (p, 1)) {
+       if (part_ok (p) && type_ok (p, 1)) {
            if (ct->c_storage) {
                /* Support mhstore -outfile.  The MIME parser doesn't
                   load c_storage, so we know that p->c_storage is
                   NULL here. */
-               p->c_storage = ct->c_storage;
+               p->c_storage = mh_xstrdup(ct->c_storage);
            }
-           result = store_switch (p);
-           p->c_storage = NULL;
+           result = store_switch (p, info);
 
            if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
                break;
@@ -289,119 +316,12 @@ store_multi (CT ct)
 }
 
 
-/*
- * Reassemble and store the contents of a collection
- * of messages of type "message/partial".
- */
-
-static int
-store_partial (CT ct)
-{
-    int        cur, hi, i;
-    CT p, *ctp, *ctq;
-    CT *base;
-    struct partial *pm, *qm;
-
-    qm = (struct partial *) ct->c_ctparams;
-    if (qm->pm_stored)
-       return OK;
-
-    hi = i = 0;
-    for (ctp = cts; *ctp; ctp++) {
-       p = *ctp;
-       if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
-           pm = (struct partial *) p->c_ctparams;
-           if (!pm->pm_stored
-                   && strcmp (qm->pm_partid, pm->pm_partid) == 0) {
-               pm->pm_marked = pm->pm_partno;
-               if (pm->pm_maxno)
-                   hi = pm->pm_maxno;
-               pm->pm_stored = 1;
-               i++;
-           }
-           else
-               pm->pm_marked = 0;
-       }
-    }
-
-    if (hi == 0) {
-       advise (NULL, "missing (at least) last part of multipart message");
-       return NOTOK;
-    }
-
-    if ((base = (CT *) calloc ((size_t) (i + 1), sizeof(*base))) == NULL)
-       adios (NULL, "out of memory");
-
-    ctq = base;
-    for (ctp = cts; *ctp; ctp++) {
-       p = *ctp;
-       if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
-           pm = (struct partial *) p->c_ctparams;
-           if (pm->pm_marked)
-               *ctq++ = p;
-       }
-    }
-    *ctq = NULL;
-
-    if (i > 1)
-       qsort ((char *) base, i, sizeof(*base), (qsort_comp) ct_compar);
-
-    cur = 1;
-    for (ctq = base; *ctq; ctq++) {
-       p = *ctq;
-       pm = (struct partial *) p->c_ctparams;
-       if (pm->pm_marked != cur) {
-           if (pm->pm_marked == cur - 1) {
-               admonish (NULL,
-                         "duplicate part %d of %d part multipart message",
-                         pm->pm_marked, hi);
-               continue;
-           }
-
-missing_part:
-           advise (NULL,
-                   "missing %spart %d of %d part multipart message",
-                   cur != hi ? "(at least) " : "", cur, hi);
-           goto losing;
-       }
-        else
-           cur++;
-    }
-    if (hi != --cur) {
-       cur = hi;
-       goto missing_part;
-    }
-
-    /*
-     * Now cycle through the sorted list of messages of type
-     * "message/partial" and save/append them to a file.
-     */
-
-    ctq = base;
-    ct = *ctq++;
-    if (store_content (ct, NULL) == NOTOK) {
-losing:
-       free ((char *) base);
-       return NOTOK;
-    }
-
-    for (; *ctq; ctq++) {
-       p = *ctq;
-       if (store_content (p, ct) == NOTOK)
-           goto losing;
-    }
-
-    free ((char *) base);
-    return OK;
-}
-
-
 /*
  * Store content from a message of type "message/external".
  */
 
 static int
-store_external (CT ct)
+store_external (CT ct, mhstoreinfo_t info)
 {
     int        result = NOTOK;
     struct exbody *e = (struct exbody *) ct->c_ctparams;
@@ -414,19 +334,14 @@ store_external (CT ct)
      * Check if the parameters for the external body
      * specified a filename.
      */
-    if (autosw) {
+    if (info->autosw) {
        char *cp;
 
-       if ((cp = e->eb_name)
-           && *cp != '/'
-           && *cp != '.'
-           && *cp != '|'
-           && *cp != '!'
-           && !strchr (cp, '%')) {
+       if ((cp = e->eb_name) && use_param_as_filename(cp)) {
            if (!ct->c_storeproc)
-               ct->c_storeproc = add (cp, NULL);
+               ct->c_storeproc = mh_xstrdup(cp);
            if (!p->c_storeproc)
-               p->c_storeproc = add (cp, NULL);
+               p->c_storeproc = mh_xstrdup(cp);
        }
     }
 
@@ -442,95 +357,28 @@ store_external (CT ct)
     if (ct->c_storage) {
        /* Support mhstore -outfile.  The MIME parser doesn't load
           c_storage, so we know that p->c_storage is NULL here. */
-       p->c_storage = ct->c_storage;
+       p->c_storage = mh_xstrdup(ct->c_storage);
     }
-    result = store_switch (p);
-    p->c_storage = NULL;
+    result = store_switch (p, info);
 
     p->c_partno = NULL;
     return result;
 }
 
 
-/*
- * Compare the numbering from two different
- * message/partials (needed for sorting).
- */
-
-static int
-ct_compar (CT *a, CT *b)
-{
-    struct partial *am = (struct partial *) ((*a)->c_ctparams);
-    struct partial *bm = (struct partial *) ((*b)->c_ctparams);
-
-    return (am->pm_marked - bm->pm_marked);
-}
-
-
 /*
  * Store contents of a message or message part to
  * a folder, a file, the standard output, or pass
  * the contents to a command.
- *
- * If the current content to be saved is a followup part
- * to a collection of messages of type "message/partial",
- * then field "p" is a pointer to the Content structure
- * to the first message/partial in the group.
  */
 
 static int
-store_content (CT ct, CT p)
+store_content (CT ct, mhstoreinfo_t info)
 {
-    int appending = 0, msgnum = 0;
-    int is_partial = 0, first_partial = 0;
-    int last_partial = 0;
+    bool appending = false;
+    int msgnum = 0;
     char *cp, buffer[BUFSIZ];
 
-    /*
-     * Do special processing for messages of
-     * type "message/partial".
-     *
-     * We first check if this content is of type
-     * "message/partial".  If it is, then we need to check
-     * whether it is the first and/or last in the group.
-     *
-     * Then if "p" is a valid pointer, it points to the Content
-     * structure of the first partial in the group.  So we copy
-     * the file name and/or folder name from that message.  In
-     * this case, we also note that we will be appending.
-     */
-    if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
-       struct partial *pm = (struct partial *) ct->c_ctparams;
-
-       /* Yep, it's a message/partial */
-       is_partial = 1;
-
-       /* But is it the first and/or last in the collection? */
-       if (pm->pm_partno == 1)
-           first_partial = 1;
-       if (pm->pm_maxno && pm->pm_partno == pm->pm_maxno)
-           last_partial = 1;
-
-       /*
-        * If "p" is a valid pointer, then it points to the
-        * Content structure for the first message in the group.
-        * So we just copy the filename or foldername information
-        * from the previous iteration of this function.
-        */
-       if (p) {
-           appending = 1;
-            if (! ct->c_storage) {
-               ct->c_storage = add (p->c_storage, NULL);
-
-               /* record the folder name */
-               if (p->c_folder) {
-                   ct->c_folder = add (p->c_folder, NULL);
-               }
-           }
-           goto got_filename;
-       }
-    }
-
     /*
      * Get storage formatting string.
      *
@@ -543,13 +391,9 @@ store_content (CT ct, CT p)
     if ((cp = ct->c_storeproc) == NULL || *cp == '\0') {
        CI ci = &ct->c_ctinfo;
 
-       snprintf (buffer, sizeof(buffer), "%s-store-%s/%s",
-               invo_name, ci->ci_type, ci->ci_subtype);
-       if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
-           snprintf (buffer, sizeof(buffer), "%s-store-%s", invo_name, ci->ci_type);
-           if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
-               cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s";
-           }
+       cp = context_find_by_type ("store", ci->ci_type, ci->ci_subtype);
+       if (cp == NULL) {
+           cp = ct->c_type == CT_MESSAGE ? "+" : "%m%P.%s";
        }
     }
 
@@ -563,10 +407,10 @@ store_content (CT ct, CT p)
 
            /* Store content in temporary file for now */
            if ((tmpfilenam = m_mktemp(invo_name, NULL, NULL)) == NULL) {
-               adios(NULL, "unable to create temporary file in %s",
+               die("unable to create temporary file in %s",
                      get_temp_dir());
            }
-           ct->c_storage = add (tmpfilenam, NULL);
+           ct->c_storage = mh_xstrdup(tmpfilenam);
 
            /* Get the folder name */
            if (cp[1])
@@ -578,7 +422,7 @@ store_content (CT ct, CT p)
            create_folder(m_mailpath(folder), 0, exit);
 
            /* Record the folder name */
-           ct->c_folder = add (folder, NULL);
+           ct->c_folder = mh_xstrdup(folder);
 
            if (cp[1])
                free (folder);
@@ -590,23 +434,24 @@ store_content (CT ct, CT p)
         * Parse and expand the storage formatting string
         * in `cp' into `buffer'.
         */
-       parse_format_string (ct, cp, buffer, sizeof(buffer), dir);
+       parse_format_string (ct, cp, buffer, sizeof(buffer), info->dir);
 
        /*
         * If formatting begins with '|' or '!', then pass
         * content to standard input of a command and return.
         */
        if (buffer[0] == '|' || buffer[0] == '!')
-           return show_content_aux (ct, 1, 0, buffer + 1, dir);
+           return show_content_aux (ct, 0, buffer + 1, info->dir, NULL);
 
         /* record the filename */
-       if ((ct->c_storage = clobber_check (add (buffer, NULL))) == NULL) {
+       if ((ct->c_storage = clobber_check (mh_xstrdup(buffer), info)) ==
+           NULL) {
            return NOTOK;
        }
     } else {
-        /* The output filename was explicitly specified, so use it. */
-       if ((ct->c_storage = clobber_check (add (ct->c_storage, NULL))) ==
-            NULL) {
+       /* The output filename was explicitly specified, so use it. */
+       if ((ct->c_storage = clobber_check (ct->c_storage, info)) ==
+           NULL) {
            return NOTOK;
        }
     }
@@ -621,52 +466,41 @@ got_filename:
 
     /*
      * If necessary, link the file into a folder and remove
-     * the temporary file.  If this message is a partial,
-     * then only do this if it is the last one in the group.
+     * the temporary file.
      */
-    if (ct->c_folder && (!is_partial || last_partial)) {
+    if (ct->c_folder) {
        msgnum = output_content_folder (ct->c_folder, ct->c_storage);
-       unlink (ct->c_storage);
+       (void) m_unlink (ct->c_storage);
        if (msgnum == NOTOK)
            return NOTOK;
     }
 
-    /*
-     * Now print out the name/number of the message
-     * that we are storing.
-     */
-    if (is_partial) {
-       if (first_partial)
-           fprintf (stderr, "reassembling partials ");
-       if (last_partial)
-           fprintf (stderr, "%s", ct->c_file);
-       else
-           fprintf (stderr, "%s,", ct->c_file);
-    } else {
+    if (info->verbosw) {
+        /*
+         * Now print out the name/number of the message
+         * that we are storing.
+         */
        fprintf (stderr, "storing message %s", ct->c_file);
        if (ct->c_partno)
            fprintf (stderr, " part %s", ct->c_partno);
-    }
 
-    /*
-     * Unless we are in the "middle" of group of message/partials,
-     * we now print the name of the file, folder, and/or message
-     * to which we are storing the content.
-     */
-    if (!is_partial || last_partial) {
+        /*
+         * We now print the name of the file, folder, and/or message
+         * to which we are storing the content.
+         */
        if (ct->c_folder) {
-           fprintf (stderr, " to folder %s as message %d\n", ct->c_folder, msgnum);
+           fprintf (stderr, " to folder %s as message %d\n", ct->c_folder,
+                   msgnum);
        } else if (!strcmp(ct->c_storage, "-")) {
            fprintf (stderr, " to stdout\n");
        } else {
-           int cwdlen;
+           int cwdlen = strlen (info->cwd);
 
-           cwdlen = strlen (cwd);
            fprintf (stderr, " as file %s\n",
-               strncmp (ct->c_storage, cwd, cwdlen)
-               || ct->c_storage[cwdlen] != '/'
-               ? ct->c_storage : ct->c_storage + cwdlen + 1);
-       }
+                   !has_prefix(ct->c_storage, info->cwd)
+                   || ct->c_storage[cwdlen] != '/'
+                   ? ct->c_storage : ct->c_storage + cwdlen + 1);
+            }
     }
 
     return OK;
@@ -680,7 +514,6 @@ got_filename:
 static int
 output_content_file (CT ct, int appending)
 {
-    int filterstate;
     char *file, buffer[BUFSIZ];
     long pos, last;
     FILE *fp;
@@ -697,7 +530,7 @@ output_content_file (CT ct, int appending)
        int cc, fd;
 
        if (!ct->c_ceopenfnx) {
-           advise (NULL, "don't know how to decode part %s of message %s",
+           inform("don't know how to decode part %s of message %s",
                    ct->c_partno, ct->c_file);
            return NOTOK;
        }
@@ -740,17 +573,6 @@ losing:
            }
        }
 
-       /*
-        * Filter the header fields of the initial enclosing
-        * message/partial into the file.
-        */
-       if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
-           struct partial *pm = (struct partial *) ct->c_ctparams;
-
-           if (pm->pm_partno == 1)
-               copy_some_headers (fp, ct);
-       }
-
        for (;;) {
            switch (cc = read (fd, buffer, sizeof(buffer))) {
                case NOTOK:
@@ -761,7 +583,9 @@ losing:
                    break;
 
                default:
-                   fwrite (buffer, sizeof(*buffer), cc, fp);
+                   if ((int) fwrite (buffer, sizeof(*buffer), cc, fp) < cc) {
+                       advise ("output_content_file", "fwrite");
+                   }
                    continue;
            }
            break;
@@ -774,7 +598,7 @@ losing:
 
        fclose (fp);
 
-       return (cc != NOTOK ? OK : NOTOK);
+        return cc == NOTOK ? NOTOK : OK;
     }
 
     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
@@ -807,21 +631,7 @@ losing:
        }
     }
 
-    /*
-     * Copy a few of the header fields of the initial
-     * enclosing message/partial into the file.
-     */
-    filterstate = 0;
-    if (ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_PARTIAL) {
-       struct partial *pm = (struct partial *) ct->c_ctparams;
-
-       if (pm->pm_partno == 1) {
-           copy_some_headers (fp, ct);
-           filterstate = 1;
-       }
-    }
-
-    while (fgets (buffer, sizeof(buffer) - 1, ct->c_fp)) {
+    while (fgets (buffer, sizeof buffer, ct->c_fp)) {
        if ((pos += strlen (buffer)) > last) {
            int diff;
 
@@ -829,37 +639,6 @@ losing:
            if (diff >= 0)
                buffer[diff] = '\0';
        }
-       /*
-        * If this is the first content of a group of
-        * message/partial contents, then we only copy a few
-        * of the header fields of the enclosed message.
-        */
-       if (filterstate) {
-           switch (buffer[0]) {
-               case ' ':
-               case '\t':
-                   if (filterstate < 0)
-                       buffer[0] = 0;
-                   break;
-
-               case '\n':
-                   filterstate = 0;
-                   break;
-
-               default:
-                   if (!uprf (buffer, XXX_FIELD_PRF)
-                           && !uprf (buffer, VRSN_FIELD)
-                           && !uprf (buffer, "Subject:")
-                           && !uprf (buffer, "Encrypted:")
-                           && !uprf (buffer, "Message-ID:")) {
-                       filterstate = -1;
-                       buffer[0] = 0;
-                       break;
-                   }
-                   filterstate = 1;
-                   break;
-           }
-       }
        fputs (buffer, fp);
        if (pos >= last)
            break;
@@ -890,13 +669,12 @@ output_content_folder (char *folder, char *filename)
     struct msgs *mp;
 
     /* Read the folder. */
-    if ((mp = folder_read (folder, 0))) {
-       /* Link file into folder */
-       msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, (char *)0);
-    } else {
-       advise (NULL, "unable to read folder %s", folder);
+    if (!(mp = folder_read(folder, 0))) {
+       inform("unable to read folder %s", folder);
        return NOTOK;
     }
+    /* Link file into folder */
+    msgnum = folder_addmsg(&mp, filename, 0, 0, 0, 0, NULL);
 
     /* free folder structure */
     folder_free (mp);
@@ -939,7 +717,9 @@ parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
      * appropriate directory.
      */
     if (*cp != '/' && *cp != '|' && *cp != '!') {
-       snprintf (bp, buflen, "%s/", dir[1] ? dir : "");
+        if (!strcmp(dir, "/"))
+            dir = ""; /* Don't start with "//". */
+       snprintf (bp, buflen, "%s/", dir);
        len = strlen (bp);
        bp += len;
        buflen -= len;
@@ -960,13 +740,14 @@ parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
                        *bp = '\0';
                        buflen--;
                        continue;
-                   } else {
-                       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;
@@ -987,7 +768,7 @@ parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
                    break;
 
                case 'p':
-                   /* insert part number withouth leading dot */
+                   /* insert part number without leading dot */
                    if (ct->c_partno)
                        strncpy (bp, ct->c_partno, buflen);
                    break;
@@ -1038,7 +819,7 @@ raw:
 static void
 get_storeproc (CT ct)
 {
-    char **ap, **ep, *cp;
+    char *cp;
     CI ci;
 
     /*
@@ -1054,29 +835,13 @@ get_storeproc (CT ct)
      * use that (RFC-2183).
      */
     if (ct->c_dispo) {
-       char *cp = strchr (ct->c_dispo, ';');
-       CI ci = calloc (1, sizeof *ci);
-       int status;
-       int found_filename = 0;
-
-       if (cp  &&  parse_header_attrs (ct->c_file, strlen (invo_name) + 2, &cp,
-                                        ci, &status) == OK) {
-           for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-               if (! strcasecmp (*ap, "filename")
-                   && *(cp = *ep) != '/'
-                   && *cp != '.'
-                   && *cp != '|'
-                   && *cp != '!'
-                   && !strchr (cp, '%')) {
-                   ct->c_storeproc = add (cp, NULL);
-                   found_filename = 1;
-               }
-               free (*ap);
-           }
+       if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0)) &&
+            use_param_as_filename(cp)) {
+               ct->c_storeproc = mh_xstrdup(cp);
+               free(cp);
+               return;
        }
-
-       free (ci);
-       if (found_filename) return;
+        free(cp);
     }
 
     /*
@@ -1085,86 +850,44 @@ get_storeproc (CT ct)
      * the storeproc.
      */
     ci = &ct->c_ctinfo;
-    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-       if (! strcasecmp (*ap, "name")
-           && *(cp = *ep) != '/'
-           && *cp != '.'
-           && *cp != '|'
-           && *cp != '!'
-           && !strchr (cp, '%')) {
-           ct->c_storeproc = add (cp, NULL);
-           return;
-       }
-    }
-}
-
-
-/*
- * Copy some of the header fields of the initial message/partial
- * message into the header of the reassembled message.
- */
+    if ((cp = get_param(ci->ci_first_pm, "name", '_', 0)) &&
+        use_param_as_filename(cp)) {
+           ct->c_storeproc = mh_xstrdup(cp);
 
-static int
-copy_some_headers (FILE *out, CT ct)
-{
-    HF hp;
-
-    hp = ct->c_first_hf;       /* start at first header field */
-
-    while (hp) {
-       /*
-        * A few of the header fields of the enclosing
-        * messages are not copied.
-        */
-       if (!uprf (hp->name, XXX_FIELD_PRF)
-               && strcasecmp (hp->name, VRSN_FIELD)
-               && strcasecmp (hp->name, "Subject")
-               && strcasecmp (hp->name, "Encrypted")
-               && strcasecmp (hp->name, "Message-ID"))
-           fprintf (out, "%s:%s", hp->name, hp->value);
-       hp = hp->next;  /* next header field */
     }
-
-    return OK;
+    free(cp);
 }
 
+
 /******************************************************************************/
 /* -clobber support */
 
-enum clobber_policy_t {
-  NMH_CLOBBER_ALWAYS,
-  NMH_CLOBBER_AUTO,
-  NMH_CLOBBER_SUFFIX,
-  NMH_CLOBBER_ASK,
-  NMH_CLOBBER_NEVER
-};
-
-static enum clobber_policy_t clobber_policy = NMH_CLOBBER_ALWAYS;
-
-int files_not_clobbered = 0;
-
-int
-save_clobber_policy (const char *value) {
-  if (! strcasecmp (value, "always")) {
-    clobber_policy = NMH_CLOBBER_ALWAYS;
-  } else if (! strcasecmp (value, "auto")) {
-    clobber_policy = NMH_CLOBBER_AUTO;
-  } else if (! strcasecmp (value, "suffix")) {
-    clobber_policy = NMH_CLOBBER_SUFFIX;
-  } else if (! strcasecmp (value, "ask")) {
-    clobber_policy = NMH_CLOBBER_ASK;
-  } else if (! strcasecmp (value, "never")) {
-    clobber_policy = NMH_CLOBBER_NEVER;
-  } else {
-    return 1;
+static enum clobber_policy_t
+clobber_policy (const char *value)
+{
+  if (value == NULL  ||  ! strcasecmp (value, "always")) {
+    return NMH_CLOBBER_ALWAYS;
+  }
+  if (! strcasecmp (value, "auto")) {
+    return NMH_CLOBBER_AUTO;
+  }
+  if (! strcasecmp (value, "suffix")) {
+    return NMH_CLOBBER_SUFFIX;
+  }
+  if (! strcasecmp (value, "ask")) {
+    return NMH_CLOBBER_ASK;
+  }
+  if (! strcasecmp (value, "never")) {
+    return NMH_CLOBBER_NEVER;
   }
 
-  return 0;
+  die("invalid argument, %s, to clobber", value);
 }
 
 
 static char *
-next_version (char *file, enum clobber_policy_t clobber_policy) {
+next_version (char *file, enum clobber_policy_t clobber_policy)
+{
   const size_t max_versions = 1000000;
   /* 8 = log max_versions  +  one for - or .  +  one for null terminator */
   const size_t buflen = strlen (file) + 8;
@@ -1194,9 +917,8 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
 
       default:
         /* Should never get here. */
-        advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
+        inform("will not overwrite %s, invalid clobber policy", buffer);
         free (buffer);
-        ++files_not_clobbered;
         return NULL;
     }
 
@@ -1216,10 +938,9 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
   free (file);
 
   if (version >= max_versions) {
-    advise (NULL, "will not overwrite %s, too many versions", buffer);
+    inform("will not overwrite %s, too many versions", buffer);
     free (buffer);
     buffer = NULL;
-    ++files_not_clobbered;
   }
 
   return buffer;
@@ -1227,7 +948,8 @@ next_version (char *file, enum clobber_policy_t clobber_policy) {
 
 
 static char *
-clobber_check (char *original_file) {
+clobber_check (char *original_file, mhstoreinfo_t info)
+{
   /* clobber policy        return value
    * --------------        ------------
    *   -always             original_file
@@ -1239,19 +961,25 @@ clobber_check (char *original_file) {
 
   char *file;
   char *cwd = NULL;
-  int check_again;
+  bool check_again;
 
-  if (clobber_policy == NMH_CLOBBER_ASK) {
+  if (! strcmp (original_file, "-")) {
+      return original_file;
+  }
+
+  if (info->clobber_policy == NMH_CLOBBER_ASK) {
     /* Save cwd for possible use in loop below. */
     char *slash;
 
-    cwd = add (original_file, NULL);
+    cwd = mh_xstrdup(original_file);
     slash = strrchr (cwd, '/');
 
     if (slash) {
       *slash = '\0';
     } else {
-      /* original_file wasn't a full path, which shouldn't happen. */
+      /* original_file isn't a full path, which should only happen if
+         it is -. */
+      free (cwd);
       cwd = NULL;
     }
   }
@@ -1260,16 +988,19 @@ clobber_check (char *original_file) {
     struct stat st;
 
     file = original_file;
-    check_again = 0;
+    check_again = false;
 
-    switch (clobber_policy) {
+    switch (info->clobber_policy) {
       case NMH_CLOBBER_ALWAYS:
         break;
 
       case NMH_CLOBBER_SUFFIX:
       case NMH_CLOBBER_AUTO:
         if (stat (file, &st) == OK) {
-          file = next_version (original_file, clobber_policy);
+          if ((file = next_version (original_file, info->clobber_policy)) ==
+              NULL) {
+              ++info->files_not_clobbered;
+          }
         }
         break;
 
@@ -1277,17 +1008,20 @@ clobber_check (char *original_file) {
         if (stat (file, &st) == OK) {
           enum answers { NMH_YES, NMH_NO, NMH_RENAME };
           static struct swit answer[4] = {
-            { "yes", 0, NMH_YES }, { "no", 0, NMH_NO }, { "rename", 0, NMH_RENAME }, { NULL, 0, 0 } };
+            { "yes", 0, NMH_YES },
+            { "no", 0, NMH_NO },
+            { "rename", 0, NMH_RENAME },
+            { NULL, 0, 0 } };
           char **ans;
 
           if (isatty (fileno (stdin))) {
             char *prompt =
               concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
-            ans = getans (prompt, answer);
+            ans = read_switch_multiword (prompt, answer);
             free (prompt);
           } else {
             /* Overwrite, that's what nmh used to do.  And warn. */
-            advise (NULL, "-clobber ask but no tty, so overwrite %s", file);
+            inform("-clobber ask but no tty, so overwrite %s", file);
             break;
           }
 
@@ -1297,33 +1031,30 @@ clobber_check (char *original_file) {
             case NMH_NO:
               free (file);
               file = NULL;
-              ++files_not_clobbered;
+              ++info->files_not_clobbered;
               break;
             case NMH_RENAME: {
               char buf[PATH_MAX];
-              printf ("Enter filename or full path of the new file: ");
+              fputs("Enter filename or full path of the new file: ", stdout);
               if (fgets (buf, sizeof buf, stdin) == NULL  ||
                   buf[0] == '\0') {
                 file = NULL;
-                ++files_not_clobbered;
+                ++info->files_not_clobbered;
               } else {
-                char *newline = strchr (buf, '\n');
-                if (newline) {
-                  *newline = '\0';
-                }
+                trim_suffix_c(buf, '\n');
               }
 
               free (file);
 
               if (buf[0] == '/') {
                 /* Full path, use it. */
-                file = add (buf, NULL);
+                file = mh_xstrdup(buf);
               } else {
                 /* Relative path. */
-                file = cwd  ?  concat (cwd, "/", buf, NULL)  :  add (buf, NULL);
+                file = cwd  ?  concat (cwd, "/", buf, NULL)  :  mh_xstrdup(buf);
               }
 
-              check_again = 1;
+              check_again = true;
               break;
             }
           }
@@ -1334,10 +1065,10 @@ clobber_check (char *original_file) {
         if (stat (file, &st) == OK) {
           /* Keep count of files that would have been clobbered,
              and return that as process exit status. */
-          advise (NULL, "will not overwrite %s with -clobber never", file);
+          inform("will not overwrite %s with -clobber never", file);
           free (file);
           file = NULL;
-          ++files_not_clobbered;
+          ++info->files_not_clobbered;
         }
         break;
     }
@@ -1345,12 +1076,18 @@ clobber_check (char *original_file) {
     original_file = file;
   } while (check_again);
 
-  if (cwd) {
-    free (cwd);
-  }
+  free (cwd);
 
   return file;
 }
 
+static bool
+use_param_as_filename(const char *p)
+{
+    /* Preserve result of original test that considered an empty string
+     * OK. */
+    return !*p || (!strchr("/.|!", *p) && !strchr(p, '%'));
+}
+
 /* -clobber support */
 /******************************************************************************/