]> diplodocus.org Git - nmh/blobdiff - uip/mhparse.c
In test-mhfixmsg, look at configuration to see if iconv is enabled
[nmh] / uip / mhparse.c
index 08eaa9ab33428c9af1b096c8e1f3233b6364fde2..9873e0b184d679f420283f28d1b924939d2b5684 100644 (file)
@@ -11,8 +11,6 @@
 #include <fcntl.h>
 #include <h/signals.h>
 #include <h/md5.h>
-#include <errno.h>
-#include <signal.h>
 #include <h/mts.h>
 #include <h/tws.h>
 #include <h/mime.h>
@@ -30,12 +28,6 @@ extern int wcachesw; /* mhcachesbr.c */
 
 int checksw = 0;       /* check Content-MD5 field */
 
-/*
- * Directory to place temp files.  This must
- * be set before these routines are called.
- */
-char *tmp;
-
 /*
  * These are for mhfixmsg to:
  * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
@@ -88,6 +80,19 @@ struct k2v SubApplication[] = {
     { NULL,           APPLICATION_UNKNOWN }    /* this one must be last! */
 };
 
+/*
+ * Mapping of names of CTE types in mhbuild directives
+ */
+static struct k2v EncodingType[] = {
+    { "8bit",                  CE_8BIT },
+    { "qp",                    CE_QUOTED },
+    { "q-p",                   CE_QUOTED },
+    { "quoted-printable",      CE_QUOTED },
+    { "b64",                   CE_BASE64 },
+    { "base64",                        CE_BASE64 },
+    { NULL,                    0 },
+};
+
 
 /* mhcachesbr.c */
 int find_cache (CT, int, int *, char *, char *, int);
@@ -202,23 +207,23 @@ parse_mime (char *file)
     if ((is_stdin = !(strcmp (file, "-")))) {
         char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
         if (tfile == NULL) {
-            advise("mhparse", "unable to create temporary file");
+            advise("mhparse", "unable to create temporary file in %s",
+                  get_temp_dir());
             return NULL;
         }
        file = add (tfile, NULL);
-       chmod (file, 0600);
 
        while (fgets (buffer, sizeof(buffer), stdin))
            fputs (buffer, fp);
        fflush (fp);
 
        if (ferror (stdin)) {
-           unlink (file);
+           (void) m_unlink (file);
            advise ("stdin", "error reading");
            return NULL;
        }
        if (ferror (fp)) {
-           unlink (file);
+           (void) m_unlink (file);
            advise (file, "error writing");
            return NULL;
        }
@@ -230,7 +235,7 @@ parse_mime (char *file)
 
     if (!(ct = get_content (fp, file, 1))) {
        if (is_stdin)
-           unlink (file);
+           (void) m_unlink (file);
        advise (NULL, "unable to decode %s", file);
        return NULL;
     }
@@ -346,7 +351,7 @@ get_content (FILE *in, char *file, int toplevel)
     hp = ct->c_first_hf;       /* start at first header field */
     while (hp) {
        /* Get MIME-Version field */
-       if (!mh_strcasecmp (hp->name, VRSN_FIELD)) {
+       if (!strcasecmp (hp->name, VRSN_FIELD)) {
            int ucmp;
            char c, *cp, *dp;
 
@@ -379,14 +384,14 @@ get_content (FILE *in, char *file, int toplevel)
                continue;
            c = *dp;
            *dp = '\0';
-           ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
+           ucmp = !strcasecmp (cp, VRSN_VALUE);
            *dp = c;
            if (!ucmp) {
                admonish (NULL, "message %s has unknown value for %s: field (%s)",
                ct->c_file, VRSN_FIELD, cp);
            }
        }
-       else if (!mh_strcasecmp (hp->name, TYPE_FIELD)) {
+       else if (!strcasecmp (hp->name, TYPE_FIELD)) {
        /* Get Content-Type field */
            struct str2init *s2i;
            CI ci = &ct->c_ctinfo;
@@ -407,14 +412,14 @@ get_content (FILE *in, char *file, int toplevel)
             * flag for this content type.
             */
            for (s2i = str2cts; s2i->si_key; s2i++)
-               if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
+               if (!strcasecmp (ci->ci_type, s2i->si_key))
                    break;
            if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
                s2i++;
            ct->c_type = s2i->si_val;
            ct->c_ctinitfnx = s2i->si_init;
        }
-       else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) {
+       else if (!strcasecmp (hp->name, ENCODING_FIELD)) {
        /* Get Content-Transfer-Encoding field */
            char c, *cp, *dp;
            struct str2init *s2i;
@@ -444,7 +449,7 @@ get_content (FILE *in, char *file, int toplevel)
             * for this transfer encoding.
             */
            for (s2i = str2ces; s2i->si_key; s2i++)
-               if (!mh_strcasecmp (cp, s2i->si_key))
+               if (!strcasecmp (cp, s2i->si_key))
                    break;
            if (!s2i->si_key && !uprf (cp, "X-"))
                s2i++;
@@ -455,7 +460,7 @@ get_content (FILE *in, char *file, int toplevel)
            if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
                goto out;
        }
-       else if (!mh_strcasecmp (hp->name, MD5_FIELD)) {
+       else if (!strcasecmp (hp->name, MD5_FIELD)) {
        /* Get Content-MD5 field */
            char *cp, *dp, *ep;
 
@@ -495,15 +500,15 @@ get_content (FILE *in, char *file, int toplevel)
            free (ep);
            ct->c_digested++;
        }
-       else if (!mh_strcasecmp (hp->name, ID_FIELD)) {
+       else if (!strcasecmp (hp->name, ID_FIELD)) {
        /* Get Content-ID field */
            ct->c_id = add (hp->value, ct->c_id);
        }
-       else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) {
+       else if (!strcasecmp (hp->name, DESCR_FIELD)) {
        /* Get Content-Description field */
            ct->c_descr = add (hp->value, ct->c_descr);
        }
-       else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) {
+       else if (!strcasecmp (hp->name, DISPO_FIELD)) {
        /* Get Content-Disposition field */
            ct->c_dispo = add (hp->value, ct->c_dispo);
        }
@@ -846,6 +851,47 @@ magic_skip:
            cp++;
     }
 
+    /*
+     * Get any extension directives (right now just the content transfer
+     * encoding, but maybe others) that we care about.
+     */
+
+    if (magic && *cp == '*') {
+       /*
+        * See if it's a CTE we match on
+        */
+       struct k2v *kv;
+
+       dp = ++cp;
+       while (*cp != '\0' && ! isspace((unsigned char) *cp))
+           cp++;
+
+       if (dp == cp) {
+           advise (NULL, "invalid null transfer encoding specification");
+           return NOTOK;
+       }
+
+       if (*cp != '\0')
+           *cp++ = '\0';
+
+       ct->c_reqencoding = CE_UNKNOWN;
+
+       for (kv = EncodingType; kv->kv_key; kv++) {
+           if (strcasecmp(kv->kv_key, dp) == 0) {
+               ct->c_reqencoding = kv->kv_value;
+               break;
+           }
+       }
+
+       if (ct->c_reqencoding == CE_UNKNOWN) {
+           advise (NULL, "invalid CTE specification: \"%s\"", dp);
+           return NOTOK;
+       }
+
+       while (isspace ((unsigned char) *cp))
+           cp++;
+    }
+
     /*
      * Check if anything is left over
      */
@@ -972,7 +1018,7 @@ InitText (CT ct)
 
     /* match subtype */
     for (kv = SubText; kv->kv_key; kv++)
-       if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
+       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
            break;
     ct->c_subtype = kv->kv_value;
 
@@ -983,7 +1029,7 @@ InitText (CT ct)
 
     /* scan for charset parameter */
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
-       if (!mh_strcasecmp (*ap, "charset"))
+       if (!strcasecmp (*ap, "charset"))
            break;
 
     /* check if content specified a character set */
@@ -1056,7 +1102,7 @@ InitMultiPart (CT ct)
 
     /* match subtype */
     for (kv = SubMultiPart; kv->kv_key; kv++)
-       if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
+       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
            break;
     ct->c_subtype = kv->kv_value;
 
@@ -1066,7 +1112,7 @@ InitMultiPart (CT ct)
      */
     bp = 0;
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-       if (!mh_strcasecmp (*ap, "boundary")) {
+       if (!strcasecmp (*ap, "boundary")) {
            bp = *ep;
            break;
        }
@@ -1268,7 +1314,7 @@ InitMessage (CT ct)
 
     /* match subtype */
     for (kv = SubMessage; kv->kv_key; kv++)
-       if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
+       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
            break;
     ct->c_subtype = kv->kv_value;
 
@@ -1287,11 +1333,11 @@ InitMessage (CT ct)
 
                /* scan for parameters "id", "number", and "total" */
                for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-                   if (!mh_strcasecmp (*ap, "id")) {
+                   if (!strcasecmp (*ap, "id")) {
                        p->pm_partid = add (*ep, NULL);
                        continue;
                    }
-                   if (!mh_strcasecmp (*ap, "number")) {
+                   if (!strcasecmp (*ap, "number")) {
                        if (sscanf (*ep, "%d", &p->pm_partno) != 1
                                || p->pm_partno < 1) {
 invalid_param:
@@ -1303,7 +1349,7 @@ invalid_param:
                        }
                        continue;
                    }
-                   if (!mh_strcasecmp (*ap, "total")) {
+                   if (!strcasecmp (*ap, "total")) {
                        if (sscanf (*ep, "%d", &p->pm_maxno) != 1
                                || p->pm_maxno < 1)
                            goto invalid_param;
@@ -1425,12 +1471,12 @@ params_external (CT ct, int composing)
 
     ct->c_ceopenfnx = NULL;
     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
-       if (!mh_strcasecmp (*ap, "access-type")) {
+       if (!strcasecmp (*ap, "access-type")) {
            struct str2init *s2i;
            CT p = e->eb_content;
 
            for (s2i = str2methods; s2i->si_key; s2i++)
-               if (!mh_strcasecmp (*ep, s2i->si_key))
+               if (!strcasecmp (*ep, s2i->si_key))
                    break;
            if (!s2i->si_key) {
                e->eb_access = *ep;
@@ -1447,39 +1493,39 @@ params_external (CT ct, int composing)
                return NOTOK;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "name")) {
+       if (!strcasecmp (*ap, "name")) {
            e->eb_name = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "permission")) {
+       if (!strcasecmp (*ap, "permission")) {
            e->eb_permission = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "site")) {
+       if (!strcasecmp (*ap, "site")) {
            e->eb_site = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "directory")) {
+       if (!strcasecmp (*ap, "directory")) {
            e->eb_dir = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "mode")) {
+       if (!strcasecmp (*ap, "mode")) {
            e->eb_mode = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "size")) {
+       if (!strcasecmp (*ap, "size")) {
            sscanf (*ep, "%lu", &e->eb_size);
            continue;
        }
-       if (!mh_strcasecmp (*ap, "server")) {
+       if (!strcasecmp (*ap, "server")) {
            e->eb_server = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "subject")) {
+       if (!strcasecmp (*ap, "subject")) {
            e->eb_subject = *ep;
            continue;
        }
-       if (!mh_strcasecmp (*ap, "url")) {
+       if (!strcasecmp (*ap, "url")) {
            /*
             * According to RFC 2017, we have to remove all whitespace from
             * the URL
@@ -1496,7 +1542,7 @@ params_external (CT ct, int composing)
            *u = '\0';
            continue;
        }
-       if (composing && !mh_strcasecmp (*ap, "body")) {
+       if (composing && !strcasecmp (*ap, "body")) {
            e->eb_body = getcpy (*ep);
            continue;
        }
@@ -1525,7 +1571,7 @@ InitApplication (CT ct)
 
     /* match subtype */
     for (kv = SubApplication; kv->kv_key; kv++)
-       if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
+       if (!strcasecmp (ci->ci_subtype, kv->kv_key))
            break;
     ct->c_subtype = kv->kv_value;
 
@@ -1631,7 +1677,7 @@ static int
 openBase64 (CT ct, char **file)
 {
     int        bitno, cc, digested;
-    int fd, len, skip, own_ct_fp = 0;
+    int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT;
     uint32_t bits;
     unsigned char value, b;
     char *cp, *ep, buffer[BUFSIZ];
@@ -1654,7 +1700,6 @@ openBase64 (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -1672,19 +1717,22 @@ openBase64 (CT ct, char **file)
         cp = context_find (buffer);
     }
     if (cp != NULL && *cp != '\0') {
-        if (ce->ce_unlink) {
-            /* Temporary file already exists, so we rename to
-               version with extension. */
-            char *file_org = strdup(ce->ce_file);
-            ce->ce_file = add (cp, ce->ce_file);
-            if (rename(file_org, ce->ce_file)) {
-                adios (ce->ce_file, "unable to rename %s to ", file_org);
-            }
-            free(file_org);
-
-        } else {
-            ce->ce_file = add (cp, ce->ce_file);
-        }
+       if (ce->ce_unlink) {
+           /* Create temporary file with filename extension. */
+           if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
+               adios(NULL, "unable to create temporary file in %s",
+                     get_temp_dir());
+           }
+       } else {
+           ce->ce_file = add (cp, ce->ce_file);
+       }
+    } else if (*file == NULL) {
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
     }
 
     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
@@ -1748,17 +1796,20 @@ openBase64 (CT ct, char **file)
 test_end:
                    if ((bitno -= 6) < 0) {
                        b = (bits >> 16) & 0xff;
-                       putc ((char) b, ce->ce_fp);
+                       if (!text || b != '\r')
+                           putc ((char) b, ce->ce_fp);
                        if (digested)
                            MD5Update (&mdContext, &b, 1);
                        if (skip < 2) {
                            b = (bits >> 8) & 0xff;
-                           putc ((char) b, ce->ce_fp);
+                           if (! text || b != '\r')
+                               putc ((char) b, ce->ce_fp);
                            if (digested)
                                MD5Update (&mdContext, &b, 1);
                            if (skip < 1) {
                                b = bits & 0xff;
-                               putc ((char) b, ce->ce_fp);
+                               if (! text || b != '\r')
+                                   putc ((char) b, ce->ce_fp);
                                if (digested)
                                    MD5Update (&mdContext, &b, 1);
                            }
@@ -1888,7 +1939,6 @@ openQuoted (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -1906,19 +1956,22 @@ openQuoted (CT ct, char **file)
         cp = context_find (buffer);
     }
     if (cp != NULL && *cp != '\0') {
-        if (ce->ce_unlink) {
-            /* Temporary file already exists, so we rename to
-               version with extension. */
-            char *file_org = strdup(ce->ce_file);
-            ce->ce_file = add (cp, ce->ce_file);
-            if (rename(file_org, ce->ce_file)) {
-                adios (ce->ce_file, "unable to rename %s to ", file_org);
-            }
-            free(file_org);
-
-        } else {
-            ce->ce_file = add (cp, ce->ce_file);
-        }
+       if (ce->ce_unlink) {
+           /* Create temporary file with filename extension. */
+           if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
+               adios(NULL, "unable to create temporary file in %s",
+                     get_temp_dir());
+           }
+       } else {
+           ce->ce_file = add (cp, ce->ce_file);
+       }
+    } else if (*file == NULL) {
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
     }
 
     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
@@ -2112,7 +2165,6 @@ open7Bit (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -2130,19 +2182,22 @@ open7Bit (CT ct, char **file)
         cp = context_find (buffer);
     }
     if (cp != NULL && *cp != '\0') {
-        if (ce->ce_unlink) {
-            /* Temporary file already exists, so we rename to
-               version with extension. */
-            char *file_org = strdup(ce->ce_file);
-            ce->ce_file = add (cp, ce->ce_file);
-            if (rename(file_org, ce->ce_file)) {
-                adios (ce->ce_file, "unable to rename %s to ", file_org);
-            }
-            free(file_org);
-
-        } else {
-            ce->ce_file = add (cp, ce->ce_file);
-        }
+       if (ce->ce_unlink) {
+           /* Create temporary file with filename extension. */
+           if ((ce->ce_file = m_mktemps(invo_name, cp, NULL, NULL)) == NULL) {
+               adios(NULL, "unable to create temporary file in %s",
+                     get_temp_dir());
+           }
+       } else {
+           ce->ce_file = add (cp, ce->ce_file);
+       }
+    } else if (*file == NULL) {
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
     }
 
     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
@@ -2342,7 +2397,7 @@ openFile (CT ct, char **file)
        return NOTOK;
     }
 
-    if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
+    if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
            && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
                cachefile, sizeof(cachefile)) != NOTOK) {
        int mask;
@@ -2363,12 +2418,12 @@ openFile (CT ct, char **file)
 
            if (ferror (gp)) {
                admonish (ce->ce_file, "error reading");
-               unlink (cachefile);
+               (void) m_unlink (cachefile);
            }
            else
                if (ferror (fp)) {
                    admonish (cachefile, "error writing");
-                   unlink (cachefile);
+                   (void) m_unlink (cachefile);
                }
            fclose (fp);
        }
@@ -2488,7 +2543,7 @@ openFTP (CT ct, char **file)
     ce->ce_unlink = (*file == NULL);
     caching = 0;
     cachefile[0] = '\0';
-    if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
+    if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write"))
            && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
                cachefile, sizeof(cachefile)) != NOTOK) {
        if (*file == NULL) {
@@ -2501,8 +2556,14 @@ openFTP (CT ct, char **file)
        ce->ce_file = add (*file, NULL);
     else if (caching)
        ce->ce_file = add (cachefile, NULL);
-    else
-       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
+    else {
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
+    }
 
     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
        content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
@@ -2521,7 +2582,7 @@ openFTP (CT ct, char **file)
        vec[vecp++] = e->eb_dir;
        vec[vecp++] = e->eb_name;
        vec[vecp++] = ce->ce_file,
-       vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
+       vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii")
                        ? "ascii" : "binary";
        vec[vecp] = NULL;
 
@@ -2573,12 +2634,12 @@ openFTP (CT ct, char **file)
 
                if (ferror (gp)) {
                    admonish (ce->ce_file, "error reading");
-                   unlink (cachefile);
+                   (void) m_unlink (cachefile);
                }
                else
                    if (ferror (fp)) {
                        admonish (cachefile, "error writing");
-                       unlink (cachefile);
+                       (void) m_unlink (cachefile);
                    }
                fclose (fp);
            }
@@ -2690,7 +2751,12 @@ openMail (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -2783,8 +2849,14 @@ openURL (CT ct, char **file)
        ce->ce_file = add(*file, NULL);
     else if (caching)
        ce->ce_file = add(cachefile, NULL);
-    else
-       ce->ce_file = add(m_mktemp(tmp, NULL, NULL), NULL);
+    else {
+       char *tempfile;
+       if ((tempfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
+           adios(NULL, "unable to create temporary file in %s",
+                 get_temp_dir());
+       }
+       ce->ce_file = add (tempfile, NULL);
+    }
 
     if ((ce->ce_fp = fopen(ce->ce_file, "w+")) == NULL) {
        content_error(ce->ce_file, ct, "unable to fopen for read/writing");
@@ -2837,7 +2909,7 @@ openURL (CT ct, char **file)
 
                if (ferror(gp)) {
                    admonish(ce->ce_file, "error reading");
-                   unlink(cachefile);
+                   (void) m_unlink (cachefile);
                }
            }
            umask(mask);
@@ -3129,7 +3201,7 @@ ce_str (int encoding) {
     case CE_BASE64:
         return "base64";
     case CE_QUOTED:
-        return "quoted";
+        return "quoted-printable";
     case CE_8BIT:
         return "8bit";
     case CE_7BIT:
@@ -3279,3 +3351,64 @@ bad_quote:
     *header_attrp = cp;
     return OK;
 }
+
+
+char *
+content_charset (CT ct) {
+    const char *const charset = "charset";
+    char *default_charset = NULL;
+    CI ctinfo = &ct->c_ctinfo;
+    char **ap, **vp;
+    char **src_charset = NULL;
+
+    for (ap = ctinfo->ci_attrs, vp = ctinfo->ci_values; *ap; ++ap, ++vp) {
+        if (! strcasecmp (*ap, charset)) {
+            src_charset = vp;
+            break;
+        }
+    }
+
+    /* RFC 2045, Sec. 5.2:  default to us-ascii. */
+    if (src_charset == NULL) src_charset = &default_charset;
+    if (*src_charset == NULL) *src_charset = "US-ASCII";
+
+    return *src_charset;
+}
+
+
+/* Change the value of a name=value pair in a header field body.
+   If the name isn't there, append them.  In any case, a new
+   string will be allocated and must be free'd by the caller.
+   Trims any trailing newlines. */
+char *
+update_attr (char *body, const char *name, const char *value) {
+    char *bp = nmh_strcasestr (body, name);
+    char *new_body;
+
+    if (bp) {
+        char *other_attrs = strchr (bp, ';');
+
+        *(bp + strlen (name)) = '\0';
+        new_body = concat (body, "\"", value, "\"", NULL);
+
+        if (other_attrs) {
+            char *cp;
+
+            /* Trim any trailing newlines. */
+            for (cp = &other_attrs[strlen (other_attrs) - 1];
+                 cp > other_attrs  &&  *cp == '\n';
+                 *cp-- = '\0') continue;
+            new_body = add (other_attrs, new_body);
+        }
+    } else {
+        char *cp;
+
+        /* Append name/value pair, after first removing a final newline
+           and (extraneous) semicolon. */
+        if (*(cp = &body[strlen (body) - 1]) == '\n') *cp = '\0';
+        if (*(cp = &body[strlen (body) - 1]) == ';') *cp = '\0';
+        new_body = concat (body, "; ", name, "\"", value, "\"", NULL);
+    }
+
+    return new_body;
+}