]> diplodocus.org Git - nmh/blobdiff - uip/mhstoresbr.c
Alter HasSuffixC()'s char * to be const.
[nmh] / uip / mhstoresbr.c
index bf5088e7ee4567b420a4385a84db6f88c608861b..9c38a588d2ffbec9fe84e3a3704411830db9fcf5 100644 (file)
@@ -2,8 +2,6 @@
 /*
  * mhstoresbr.c -- routines to save/store the contents of MIME messages
  *
- * $Id$
- *
  * 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 <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/utils.h>
 
+enum clobber_policy_t {
+  NMH_CLOBBER_ALWAYS = 0,
+  NMH_CLOBBER_AUTO,
+  NMH_CLOBBER_SUFFIX,
+  NMH_CLOBBER_ASK,
+  NMH_CLOBBER_NEVER
+};
+
+static enum clobber_policy_t clobber_policy (const char *);
+
+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 */
+
+    /* 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 */
+};
+
+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;
+}
 
-/*
- * The list of top-level contents to display
- */
-extern CT *cts;
-
-int autosw = 0;
+void
+mhstoreinfo_free (mhstoreinfo_t info) {
+    free (info->cwd);
+    free (info->dir);
+    free (info);
+}
 
-/*
- * Cache of current directory.  This must be
- * set before these routines are called.
- */
-char *cwd;
+int
+mhstoreinfo_files_not_clobbered (const mhstoreinfo_t info) {
+    return info->files_not_clobbered;
+}
 
-/*
- * The directory in which to store the contents.
- */
-static char *dir;
 
 /*
  * Type for a compare function for qsort.  This keeps
@@ -49,37 +77,28 @@ typedef int (*qsort_comp) (const void *, const void *);
 
 
 /* mhmisc.c */
-int part_ok (CT, int);
+int part_ok (CT);
 int type_ok (CT, int);
-int make_intermediates (char *);
 void flush_errors (void);
 
-/* mhshowsbr.c */
-int show_content_aux (CT, int, int, char *, char *);
-
-/*
- * prototypes
- */
-void store_all_messages (CT *);
-
 /*
  * 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 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_partial (CT, mhstoreinfo_t);
+static int store_external (CT, mhstoreinfo_t);
 static int ct_compar (CT *, CT *);
-static int store_content (CT, CT);
+static int store_content (CT, 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 *, mhstoreinfo_t);
 
 /*
  * Main entry point to store content
@@ -87,7 +106,7 @@ static int copy_some_headers (FILE *, CT);
  */
 
 void
-store_all_messages (CT *cts)
+store_all_messages (mhstoreinfo_t info)
 {
     CT ct, *ctp;
     char *cp;
@@ -96,16 +115,14 @@ store_all_messages (CT *cts)
      * Check for the directory in which to
      * store any contents.
      */
-    if (autosw)
-       dir = getcpy (cwd);
-    else if ((cp = context_find (nmhstorage)) && *cp)
-       dir = getcpy (cp);
+    if ((cp = context_find (nmhstorage)) && *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 ();
@@ -118,11 +135,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;
@@ -138,43 +155,34 @@ 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);
-           break;
+           return store_multi (ct, info);
 
        case CT_MESSAGE:
            switch (ct->c_subtype) {
                case MESSAGE_PARTIAL:
-                   return store_partial (ct);
-                   break;
+                   return store_partial (ct, info);
 
                case MESSAGE_EXTERNAL:
-                   return store_external (ct);
+                   return store_external (ct, info);
 
                case MESSAGE_RFC822:
                default:
-                   return store_generic (ct);
-                   break;
+                   return store_generic (ct, info);
            }
-           break;
 
        case CT_APPLICATION:
-           return store_application (ct);
-           break;
+       default:
+           return store_application (ct, info);
 
        case CT_TEXT:
        case CT_AUDIO:
        case CT_IMAGE:
        case CT_VIDEO:
-           return store_generic (ct);
-           break;
-
-       default:
-           adios (NULL, "unknown content type %d", ct->c_type);
-           break;
+           return store_generic (ct, info);
     }
 
     return OK; /* NOT REACHED */
@@ -183,21 +191,21 @@ store_switch (CT ct)
 
 /*
  * Generic routine to store a MIME content.
- * (audio, video, image, text, message/rfc922)
+ * (audio, video, image, text, message/rfc822)
  */
 
 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, NULL, info);
 }
 
 
@@ -206,13 +214,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);
 
     /*
@@ -222,28 +229,23 @@ store_application (CT ct)
      */
     if (!ct->c_storeproc && ct->c_subtype == APPLICATION_OCTETS) {
        int tarP = 0, zP = 0, gzP = 0;
+       char *cp;
 
-       for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-           /* check for "type=tar" attribute */
-           if (!mh_strcasecmp (*ap, "type")) {
-               if (mh_strcasecmp (*ep, "tar"))
-                   break;
-
+       if ((cp = get_param(ci->ci_first_pm, "type", ' ', 1))) {
+           if (strcasecmp (cp, "tar") == 0)
                tarP = 1;
-               continue;
-           }
+       }
 
-           /* check for "conversions=compress" attribute */
-           if ((!mh_strcasecmp (*ap, "conversions") || !mh_strcasecmp (*ap, "x-conversions"))
-               && (!mh_strcasecmp (*ep, "compress") || !mh_strcasecmp (*ep, "x-compress"))) {
+       /* 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 = 1;
-               continue;
            }
-           /* check for "conversions=gzip" attribute */
-           if ((!mh_strcasecmp (*ap, "conversions") || !mh_strcasecmp (*ap, "x-conversions"))
-               && (!mh_strcasecmp (*ep, "gzip") || !mh_strcasecmp (*ep, "x-gzip"))) {
+           if (strcasecmp (cp, "gzip") == 0 ||
+                   strcasecmp (cp, "x-gzip") == 0) {
                gzP = 1;
-               continue;
            }
        }
 
@@ -252,7 +254,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);
@@ -266,7 +268,7 @@ store_application (CT ct)
        }
     }
 
-    return store_content (ct, NULL);
+    return store_content (ct, NULL, info);
 }
 
 
@@ -275,7 +277,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;
@@ -285,8 +287,15 @@ 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)) {
-           result = store_switch (p);
+       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 = add (ct->c_storage, NULL);
+           }
+           result = store_switch (p, info);
+
            if (result == OK && ct->c_subtype == MULTI_ALTERNATE)
                break;
        }
@@ -302,7 +311,7 @@ store_multi (CT ct)
  */
 
 static int
-store_partial (CT ct)
+store_partial (CT ct, mhstoreinfo_t info)
 {
     int        cur, hi, i;
     CT p, *ctp, *ctq;
@@ -314,7 +323,7 @@ store_partial (CT ct)
        return OK;
 
     hi = i = 0;
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        p = *ctp;
        if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
            pm = (struct partial *) p->c_ctparams;
@@ -336,11 +345,9 @@ store_partial (CT ct)
        return NOTOK;
     }
 
-    if ((base = (CT *) calloc ((size_t) (i + 1), sizeof(*base))) == NULL)
-       adios (NULL, "out of memory");
-
+    base = mh_xcalloc(i + 1, sizeof *base);
     ctq = base;
-    for (ctp = cts; *ctp; ctp++) {
+    for (ctp = info->cts; *ctp; ctp++) {
        p = *ctp;
        if (p->c_type == CT_MESSAGE && p->c_subtype == ct->c_subtype) {
            pm = (struct partial *) p->c_ctparams;
@@ -386,19 +393,19 @@ missing_part:
 
     ctq = base;
     ct = *ctq++;
-    if (store_content (ct, NULL) == NOTOK) {
+    if (store_content (ct, NULL, info) == NOTOK) {
 losing:
-       free ((char *) base);
+       free(base);
        return NOTOK;
     }
 
     for (; *ctq; ctq++) {
        p = *ctq;
-       if (store_content (p, ct) == NOTOK)
+       if (store_content (p, ct, info) == NOTOK)
            goto losing;
     }
 
-    free ((char *) base);
+    free(base);
     return OK;
 }
 
@@ -408,7 +415,7 @@ losing:
  */
 
 static int
-store_external (CT ct)
+store_external (CT ct, mhstoreinfo_t info)
 {
     int        result = NOTOK;
     struct exbody *e = (struct exbody *) ct->c_ctparams;
@@ -421,7 +428,7 @@ 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)
@@ -446,7 +453,12 @@ store_external (CT ct)
     p->c_partno = ct->c_partno;
 
     /* we probably need to check if content is really there */
-    result = store_switch (p);
+    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 = add (ct->c_storage, NULL);
+    }
+    result = store_switch (p, info);
 
     p->c_partno = NULL;
     return result;
@@ -480,9 +492,9 @@ ct_compar (CT *a, CT *b)
  */
 
 static int
-store_content (CT ct, CT p)
+store_content (CT ct, CT p, mhstoreinfo_t info)
 {
-    int appending = 0, msgnum;
+    int appending = 0, msgnum = 0;
     int is_partial = 0, first_partial = 0;
     int last_partial = 0;
     char *cp, buffer[BUFSIZ];
@@ -520,11 +532,13 @@ store_content (CT ct, CT p)
         */
        if (p) {
            appending = 1;
-           ct->c_storage = add (p->c_storage, NULL);
+            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);
+               /* record the folder name */
+               if (p->c_folder) {
+                   ct->c_folder = add (p->c_folder, NULL);
+               }
            }
            goto got_filename;
        }
@@ -542,60 +556,70 @@ 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";
        }
     }
 
-    /*
-     * Check the beginning of storage formatting string
-     * to see if we are saving content to a folder.
-     */
-    if (*cp == '+' || *cp == '@') {
-       char *tmpfilenam, *folder;
+    if (! ct->c_storage) {
+       /*
+        * Check the beginning of storage formatting string
+        * to see if we are saving content to a folder.
+        */
+       if (*cp == '+' || *cp == '@') {
+           char *tmpfilenam, *folder;
 
-       /* Store content in temporary file for now */
-       tmpfilenam = m_scratch ("", invo_name);
-       ct->c_storage = add (tmpfilenam, NULL);
+           /* 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",
+                     get_temp_dir());
+           }
+           ct->c_storage = add (tmpfilenam, NULL);
 
-       /* Get the folder name */
-       if (cp[1])
-           folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
-       else
-           folder = getfolder (1);
+           /* Get the folder name */
+           if (cp[1])
+               folder = pluspath (cp);
+           else
+               folder = getfolder (1);
 
-       /* Check if folder exists */
-       create_folder(folder, 0, exit);
+           /* Check if folder exists */
+           create_folder(m_mailpath(folder), 0, exit);
 
-       /* Record the folder name */
-       ct->c_folder = add (folder, NULL);
+           /* Record the folder name */
+           ct->c_folder = add (folder, NULL);
 
-       if (cp[1])
-           free (folder);
+           if (cp[1])
+               free (folder);
 
-       goto got_filename;
-    }
+           goto got_filename;
+       }
 
-    /*
-     * Parse and expand the storage formatting string
-     * in `cp' into `buffer'.
-     */
-    parse_format_string (ct, cp, buffer, sizeof(buffer), dir);
+       /*
+        * Parse and expand the storage formatting string
+        * in `cp' into `buffer'.
+        */
+       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);
+       /*
+        * 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, 0, buffer + 1, info->dir, NULL);
 
-    /* record the filename */
-    ct->c_storage = add (buffer, NULL);
+        /* record the filename */
+       if ((ct->c_storage = clobber_check (add (buffer, NULL), info)) ==
+           NULL) {
+           return NOTOK;
+       }
+    } else {
+       /* The output filename was explicitly specified, so use it. */
+       if ((ct->c_storage = clobber_check (ct->c_storage, info)) ==
+           NULL) {
+           return NOTOK;
+       }
+    }
 
 got_filename:
     /* flush the output stream */
@@ -612,47 +636,49 @@ got_filename:
      */
     if (ct->c_folder && (!is_partial || last_partial)) {
        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 {
-       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) {
-       if (ct->c_folder) {
-           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;
-
-           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);
-       }
+    if (info->verbosw) {
+        /*
+         * 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 {
+            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) {
+            if (ct->c_folder) {
+                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 = strlen (info->cwd);
+
+                fprintf (stderr, " as file %s\n",
+                         strncmp (ct->c_storage, info->cwd, cwdlen)
+                         || ct->c_storage[cwdlen] != '/'
+                         ? ct->c_storage : ct->c_storage + cwdlen + 1);
+            }
+        }
     }
 
     return OK;
@@ -747,7 +773,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;
@@ -876,9 +904,9 @@ output_content_folder (char *folder, char *filename)
     struct msgs *mp;
 
     /* Read the folder. */
-    if ((mp = folder_read (folder))) {
+    if ((mp = folder_read (folder, 0))) {
        /* Link file into folder */
-       msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, (char *)0);
+       msgnum = folder_addmsg (&mp, filename, 0, 0, 0, 0, NULL);
     } else {
        advise (NULL, "unable to read folder %s", folder);
        return NOTOK;
@@ -947,12 +975,12 @@ parse_format_string (CT ct, char *cp, char *buffer, int buflen, char *dir)
                        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;
@@ -1024,8 +1052,8 @@ raw:
 static void
 get_storeproc (CT ct)
 {
-    char **ap, **ep, *cp;
-    CI ci = &ct->c_ctinfo;
+    char *cp;
+    CI ci;
 
     /*
      * If the storeproc has already been defined,
@@ -1035,22 +1063,40 @@ get_storeproc (CT ct)
     if (ct->c_storeproc)
        return;
 
+    /*
+     * If there's a Content-Disposition header and it has a filename,
+     * use that (RFC-2183).
+     */
+    if (ct->c_dispo) {
+       if ((cp = get_param(ct->c_dispo_first, "filename", '_', 0))
+               && *cp != '/'
+               && *cp != '.'
+               && *cp != '|'
+               && *cp != '!'
+               && !strchr (cp, '%')) {
+               ct->c_storeproc = add (cp, NULL);
+               free(cp);
+               return;
+       }
+        mh_xfree(cp);
+    }
+
     /*
      * Check the attribute/value pairs, for the attribute "name".
      * If found, do a few sanity checks and copy the value into
      * the storeproc.
      */
-    for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-       if (!mh_strcasecmp (*ap, "name")
-           && *(cp = *ep) != '/'
-           && *cp != '.'
-           && *cp != '|'
-           && *cp != '!'
-           && !strchr (cp, '%')) {
+    ci = &ct->c_ctinfo;
+    if ((cp = get_param(ci->ci_first_pm, "name", '_', 0))
+         && *cp != '/'
+         && *cp != '.'
+         && *cp != '|'
+         && *cp != '!'
+         && !strchr (cp, '%')) {
            ct->c_storeproc = add (cp, NULL);
-           return;
-       }
+
     }
+    mh_xfree(cp);
 }
 
 
@@ -1072,13 +1118,236 @@ copy_some_headers (FILE *out, CT ct)
         * messages are not copied.
         */
        if (!uprf (hp->name, XXX_FIELD_PRF)
-               && mh_strcasecmp (hp->name, VRSN_FIELD)
-               && mh_strcasecmp (hp->name, "Subject")
-               && mh_strcasecmp (hp->name, "Encrypted")
-               && mh_strcasecmp (hp->name, "Message-ID"))
+               && 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;
 }
+
+/******************************************************************************/
+/* -clobber support */
+
+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;
+  }
+
+  adios (NULL, "invalid argument, %s, to clobber", value);
+}
+
+
+static char *
+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;
+  char *buffer = mh_xmalloc (buflen);
+  size_t version;
+
+  char *extension = NULL;
+  if (clobber_policy == NMH_CLOBBER_AUTO  &&
+      ((extension = strrchr (file, '.')) != NULL)) {
+    *extension++ = '\0';
+  }
+
+  for (version = 1; version < max_versions; ++version) {
+    int fd;
+
+    switch (clobber_policy) {
+      case NMH_CLOBBER_AUTO: {
+        snprintf (buffer, buflen, "%s-%ld%s%s", file, (long) version,
+                  extension == NULL  ?  ""  :  ".",
+                  extension == NULL  ?  ""  :  extension);
+        break;
+      }
+
+      case NMH_CLOBBER_SUFFIX:
+        snprintf (buffer, buflen, "%s.%ld", file, (long) version);
+        break;
+
+      default:
+        /* Should never get here. */
+        advise (NULL, "will not overwrite %s, invalid clobber policy", buffer);
+        free (buffer);
+        return NULL;
+    }
+
+    /* Actually (try to) create the file here to avoid a race
+       condition on file naming + creation.  This won't solve the
+       problem with old NFS that doesn't support O_EXCL, though.
+       Let the umask strip off permissions from 0666 as desired.
+       That's what fopen () would do if it was creating the file. */
+    if ((fd = open (buffer, O_CREAT | O_EXCL,
+                    S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
+                    S_IROTH | S_IWOTH)) >= 0) {
+      close (fd);
+      break;
+    }
+  }
+
+  free (file);
+
+  if (version >= max_versions) {
+    advise (NULL, "will not overwrite %s, too many versions", buffer);
+    free (buffer);
+    buffer = NULL;
+  }
+
+  return buffer;
+}
+
+
+static char *
+clobber_check (char *original_file, mhstoreinfo_t info) {
+  /* clobber policy        return value
+   * --------------        ------------
+   *   -always             original_file
+   *   -auto               original_file-<digits>.extension
+   *   -suffix             original_file.<digits>
+   *   -ask                original_file, 0, or another filename/path
+   *   -never              0
+   */
+
+  char *file;
+  char *cwd = NULL;
+  int check_again;
+
+  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);
+    slash = strrchr (cwd, '/');
+
+    if (slash) {
+      *slash = '\0';
+    } else {
+      /* original_file isn't a full path, which should only happen if
+         it is -. */
+      free (cwd);
+      cwd = NULL;
+    }
+  }
+
+  do {
+    struct stat st;
+
+    file = original_file;
+    check_again = 0;
+
+    switch (info->clobber_policy) {
+      case NMH_CLOBBER_ALWAYS:
+        break;
+
+      case NMH_CLOBBER_SUFFIX:
+      case NMH_CLOBBER_AUTO:
+        if (stat (file, &st) == OK) {
+          if ((file = next_version (original_file, info->clobber_policy)) ==
+              NULL) {
+              ++info->files_not_clobbered;
+          }
+        }
+        break;
+
+      case NMH_CLOBBER_ASK:
+        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 } };
+          char **ans;
+
+          if (isatty (fileno (stdin))) {
+            char *prompt =
+              concat ("Overwrite \"", file, "\" [y/n/rename]? ", NULL);
+            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);
+            break;
+          }
+
+          switch ((enum answers) smatch (*ans, answer)) {
+            case NMH_YES:
+              break;
+            case NMH_NO:
+              free (file);
+              file = NULL;
+              ++info->files_not_clobbered;
+              break;
+            case NMH_RENAME: {
+              char buf[PATH_MAX];
+              printf ("Enter filename or full path of the new file: ");
+              if (fgets (buf, sizeof buf, stdin) == NULL  ||
+                  buf[0] == '\0') {
+                file = NULL;
+                ++info->files_not_clobbered;
+              } else {
+                TrimSuffixC(buf, '\n');
+              }
+
+              free (file);
+
+              if (buf[0] == '/') {
+                /* Full path, use it. */
+                file = add (buf, NULL);
+              } else {
+                /* Relative path. */
+                file = cwd  ?  concat (cwd, "/", buf, NULL)  :  add (buf, NULL);
+              }
+
+              check_again = 1;
+              break;
+            }
+          }
+        }
+        break;
+
+      case NMH_CLOBBER_NEVER:
+        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);
+          free (file);
+          file = NULL;
+          ++info->files_not_clobbered;
+        }
+        break;
+    }
+
+    original_file = file;
+  } while (check_again);
+
+  free (cwd);
+
+  return file;
+}
+
+/* -clobber support */
+/******************************************************************************/