]> diplodocus.org Git - nmh/blobdiff - uip/mhparse.c
"RFC-2045 to RFC-2049" instead of "thru".
[nmh] / uip / mhparse.c
index 7c1000060dd86ffb4dd11459a0bd382996edcf12..69d2f2c848850d3a654fd577a1a53cbb4b34a2da 100644 (file)
@@ -2,8 +2,6 @@
 /*
  * mhparse.c -- routines to parse 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.
@@ -14,7 +12,6 @@
 #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/mhparse.h>
 #include <h/utils.h>
 
-#ifdef HAVE_SYS_WAIT_H
-# include <sys/wait.h>
-#endif
-
 
 extern int debugsw;
 
-extern int endian;     /* mhmisc.c     */
-
 extern pid_t xpid;     /* mhshowsbr.c  */
 
 /* cache policies */
@@ -45,6 +36,16 @@ int checksw = 0;     /* check Content-MD5 field */
  */
 char *tmp;
 
+/*
+ * These are for mhfixmsg to:
+ * 1) Instruct parser not to detect invalid Content-Transfer-Encoding
+ *    in a multipart.
+ * 2) Suppress the warning about bogus multipart content, and report it.
+ */
+int skip_mp_cte_check;
+int suppress_bogus_mp_content_warning;
+int bogus_mp_content;
+
 /*
  * Structures for TEXT messages
  */
@@ -55,11 +56,7 @@ struct k2v SubText[] = {
     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
 };
 
-struct k2v Charset[] = {
-    { "us-ascii",   CHARSET_USASCII },
-    { "iso-8859-1", CHARSET_LATIN },
-    { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
-};
+/* Charset[] removed -- yozo.  Mon Oct  8 01:03:41 JST 2012 */
 
 /*
  * Structures for MULTIPART messages
@@ -92,27 +89,22 @@ struct k2v SubApplication[] = {
 };
 
 
-/* ftpsbr.c */
-int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
-
 /* mhcachesbr.c */
 int find_cache (CT, int, int *, char *, char *, int);
 
 /* mhmisc.c */
 int part_ok (CT, int);
 int type_ok (CT, int);
-int make_intermediates (char *);
 void content_error (char *, CT, char *, ...);
 
 /* mhfree.c */
-void free_content (CT);
 void free_encoding (CT, int);
 
 /*
  * static prototypes
  */
 static CT get_content (FILE *, char *, int);
-static int get_comment (CT, unsigned char **, int);
+static int get_comment (CT, char **, int);
 
 static int InitGeneric (CT);
 static int InitText (CT);
@@ -135,6 +127,7 @@ static int openFTP (CT, char **);
 static int InitMail (CT);
 static int openMail (CT, char **);
 static int readDigest (CT, char *);
+static int get_leftover_mp_content (CT, int);
 
 struct str2init str2cts[] = {
     { "application", CT_APPLICATION, InitApplication },
@@ -204,12 +197,14 @@ parse_mime (char *file)
      * Check if file is actually standard input
      */
     if ((is_stdin = !(strcmp (file, "-")))) {
-       file = add (m_tmpfil (invo_name), NULL);
-       if ((fp = fopen (file, "w+")) == NULL) {
-           advise (file, "unable to fopen for writing and reading");
-           return NULL;
-       }
+        char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
+        if (tfile == NULL) {
+            advise("mhparse", "unable to create temporary file");
+            return NULL;
+        }
+       file = add (tfile, NULL);
        chmod (file, 0600);
+
        while (fgets (buffer, sizeof(buffer), stdin))
            fputs (buffer, fp);
        fflush (fp);
@@ -277,6 +272,7 @@ get_content (FILE *in, char *file, int toplevel)
     char *np, *vp;
     CT ct;
     HF hp;
+    m_getfld_state_t gstate = 0;
 
     /* allocate the content structure */
     if (!(ct = (CT) calloc (1, sizeof(*ct))))
@@ -290,11 +286,12 @@ get_content (FILE *in, char *file, int toplevel)
      * Parse the header fields for this
      * content into a linked list.
      */
-    for (compnum = 1, state = FLD;;) {
-       switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
+    m_getfld_track_filepos (&gstate, in);
+    for (compnum = 1;;) {
+       int bufsz = sizeof buf;
+       switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
        case FLD:
        case FLDPLUS:
-       case FLDEOF:
            compnum++;
 
            /* get copies of the buffers */
@@ -303,22 +300,19 @@ get_content (FILE *in, char *file, int toplevel)
 
            /* if necessary, get rest of field */
            while (state == FLDPLUS) {
-               state = m_getfld (state, name, buf, sizeof(buf), in);
+               bufsz = sizeof buf;
+               state = m_getfld (&gstate, name, buf, &bufsz, in);
                vp = add (buf, vp);     /* add to previous value */
            }
 
            /* Now add the header data to the list */
            add_header (ct, np, vp);
 
-           /* continue, if this isn't the last header field */
-           if (state != FLDEOF) {
-               ct->c_begin = ftell (in) + 1;
-               continue;
-           }
-           /* else fall... */
+           /* continue, to see if this isn't the last header field */
+           ct->c_begin = ftell (in) + 1;
+           continue;
 
        case BODY:
-       case BODYEOF:
            ct->c_begin = ftell (in) - strlen (buf);
            break;
 
@@ -337,6 +331,7 @@ get_content (FILE *in, char *file, int toplevel)
        /* break out of the loop */
        break;
     }
+    m_getfld_state_destroy (&gstate);
 
     /*
      * Read the content headers.  We will parse the
@@ -350,8 +345,7 @@ get_content (FILE *in, char *file, int toplevel)
        /* Get MIME-Version field */
        if (!mh_strcasecmp (hp->name, VRSN_FIELD)) {
            int ucmp;
-           char c;
-           unsigned char *cp, *dp;
+           char c, *cp, *dp;
 
            if (ct->c_vrsn) {
                advise (NULL, "message %s has multiple %s: fields",
@@ -363,12 +357,12 @@ get_content (FILE *in, char *file, int toplevel)
            /* Now, cleanup this field */
            cp = ct->c_vrsn;
 
-           while (isspace (*cp))
+           while (isspace ((unsigned char) *cp))
                cp++;
            for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
                *dp++ = ' ';
            for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
-               if (!isspace (*dp))
+               if (!isspace ((unsigned char) *dp))
                    break;
            *++dp = '\0';
            if (debugsw)
@@ -418,8 +412,7 @@ get_content (FILE *in, char *file, int toplevel)
        }
        else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) {
        /* Get Content-Transfer-Encoding field */
-           char c;
-           unsigned char *cp, *dp;
+           char c, *cp, *dp;
            struct str2init *s2i;
 
            /*
@@ -435,7 +428,7 @@ get_content (FILE *in, char *file, int toplevel)
            /* get copy of this field */
            ct->c_celine = cp = add (hp->value, NULL);
 
-           while (isspace (*cp))
+           while (isspace ((unsigned char) *cp))
                cp++;
            for (dp = cp; istoken (*dp); dp++)
                continue;
@@ -460,8 +453,7 @@ get_content (FILE *in, char *file, int toplevel)
        }
        else if (!mh_strcasecmp (hp->name, MD5_FIELD)) {
        /* Get Content-MD5 field */
-           unsigned char *cp, *dp;
-           char *ep;
+           char *cp, *dp, *ep;
 
            if (!checksw)
                goto next_header;
@@ -474,12 +466,12 @@ get_content (FILE *in, char *file, int toplevel)
 
            ep = cp = add (hp->value, NULL);    /* get a copy */
 
-           while (isspace (*cp))
+           while (isspace ((unsigned char) *cp))
                cp++;
            for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
                *dp++ = ' ';
            for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
-               if (!isspace (*dp))
+               if (!isspace ((unsigned char) *dp))
                    break;
            *++dp = '\0';
            if (debugsw)
@@ -490,7 +482,7 @@ get_content (FILE *in, char *file, int toplevel)
                goto out;
            }
 
-           for (dp = cp; *dp && !isspace (*dp); dp++)
+           for (dp = cp; *dp && !isspace ((unsigned char) *dp); dp++)
                continue;
            *dp = '\0';
 
@@ -593,7 +585,7 @@ add_header (CT ct, char *name, char *value)
    filename="foo".  If it doesn't and value does, use value from
    that. */
 static char *
-incl_name_value (unsigned char *buf, char *name, char *value) {
+incl_name_value (char *buf, char *name, char *value) {
     char *newbuf = buf;
 
     /* Assume that name is non-null. */
@@ -602,12 +594,11 @@ incl_name_value (unsigned char *buf, char *name, char *value) {
 
        if (! strstr (buf, name_plus_equal)) {
            char *insertion;
-           unsigned char *cp;
-           char *prefix, *suffix;
+           char *cp, *prefix, *suffix;
 
            /* Trim trailing space, esp. newline. */
            for (cp = &buf[strlen (buf) - 1];
-                cp >= buf && isspace (*cp);
+                cp >= buf && isspace ((unsigned char) *cp);
                 --cp) {
                *cp = '\0';
            }
@@ -673,11 +664,10 @@ extract_name_value (char *name_suffix, char *value) {
  * directives.  Fills in the information of the CTinfo structure.
  */
 int
-get_ctinfo (unsigned char *cp, CT ct, int magic)
+get_ctinfo (char *cp, CT ct, int magic)
 {
     int        i;
-    unsigned char *dp;
-    char **ap, **ep;
+    char *dp, **ap, **ep;
     char c;
     CI ci;
 
@@ -687,7 +677,7 @@ get_ctinfo (unsigned char *cp, CT ct, int magic)
     /* store copy of Content-Type line */
     cp = ct->c_ctline = add (cp, NULL);
 
-    while (isspace (*cp))      /* trim leading spaces */
+    while (isspace ((unsigned char) *cp))      /* trim leading spaces */
        cp++;
 
     /* change newlines to spaces */
@@ -696,7 +686,7 @@ get_ctinfo (unsigned char *cp, CT ct, int magic)
 
     /* trim trailing spaces */
     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
-       if (!isspace (*dp))
+       if (!isspace ((unsigned char) *dp))
            break;
     *++dp = '\0';
 
@@ -720,10 +710,10 @@ get_ctinfo (unsigned char *cp, CT ct, int magic)
 
     /* down case the content type string */
     for (dp = ci->ci_type; *dp; dp++)
-       if (isalpha(*dp) && isupper (*dp))
-           *dp = tolower (*dp);
+       if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
+           *dp = tolower ((unsigned char) *dp);
 
-    while (isspace (*cp))
+    while (isspace ((unsigned char) *cp))
        cp++;
 
     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
@@ -736,7 +726,7 @@ get_ctinfo (unsigned char *cp, CT ct, int magic)
     }
 
     cp++;
-    while (isspace (*cp))
+    while (isspace ((unsigned char) *cp))
        cp++;
 
     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
@@ -757,11 +747,11 @@ get_ctinfo (unsigned char *cp, CT ct, int magic)
 
     /* down case the content subtype string */
     for (dp = ci->ci_subtype; *dp; dp++)
-       if (isalpha(*dp) && isupper (*dp))
-           *dp = tolower (*dp);
+       if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
+           *dp = tolower ((unsigned char) *dp);
 
 magic_skip:
-    while (isspace (*cp))
+    while (isspace ((unsigned char) *cp))
        cp++;
 
     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
@@ -772,8 +762,7 @@ magic_skip:
      */
     ep = (ap = ci->ci_attrs) + NPARMS;
     while (*cp == ';') {
-       char *vp;
-       unsigned char *up;
+       char *vp, *up;
 
        if (ap >= ep) {
            advise (NULL,
@@ -783,7 +772,7 @@ magic_skip:
        }
 
        cp++;
-       while (isspace (*cp))
+       while (isspace ((unsigned char) *cp))
            cp++;
 
        if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
@@ -797,11 +786,11 @@ magic_skip:
        }
 
        /* down case the attribute name */
-       for (dp = cp; istoken (*dp); dp++)
-           if (isalpha(*dp) && isupper (*dp))
-               *dp = tolower (*dp);
+       for (dp = cp; istoken ((unsigned char) *dp); dp++)
+           if (isalpha((unsigned char) *dp) && isupper ((unsigned char) *dp))
+               *dp = tolower ((unsigned char) *dp);
 
-       for (up = dp; isspace (*dp);)
+       for (up = dp; isspace ((unsigned char) *dp);)
            dp++;
        if (dp == cp || *dp != '=') {
            advise (NULL,
@@ -812,7 +801,7 @@ magic_skip:
 
        vp = (*ap = add (cp, NULL)) + (up - cp);
        *vp = '\0';
-       for (dp++; isspace (*dp);)
+       for (dp++; isspace ((unsigned char) *dp);)
            dp++;
 
        /* now add the attribute value */
@@ -857,7 +846,7 @@ bad_quote:
        }
        ap++;
 
-       while (isspace (*cp))
+       while (isspace ((unsigned char) *cp))
            cp++;
 
        if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
@@ -885,7 +874,7 @@ bad_quote:
        *dp++ = c;
        cp = dp;
 
-       while (isspace (*cp))
+       while (isspace ((unsigned char) *cp))
            cp++;
     }
 
@@ -912,7 +901,7 @@ bad_quote:
        *dp++ = c;
        cp = dp;
 
-       while (isspace (*cp))
+       while (isspace ((unsigned char) *cp))
            cp++;
     }
 
@@ -939,7 +928,7 @@ bad_quote:
        *dp++ = c;
        cp = dp;
 
-       while (isspace (*cp))
+       while (isspace ((unsigned char) *cp))
            cp++;
     }
 
@@ -974,11 +963,10 @@ bad_quote:
 
 
 static int
-get_comment (CT ct, unsigned char **ap, int istype)
+get_comment (CT ct, char **ap, int istype)
 {
     int i;
-    char *bp;
-    unsigned char *cp;
+    char *bp, *cp;
     char c, buffer[BUFSIZ], *dp;
     CI ci;
 
@@ -1028,7 +1016,7 @@ invalid:
        }
     }
 
-    while (isspace (*cp))
+    while (isspace ((unsigned char) *cp))
        cp++;
 
     *ap = cp;
@@ -1046,6 +1034,8 @@ invalid:
 static int
 InitGeneric (CT ct)
 {
+    NMH_UNUSED (ct);
+
     return OK;         /* not much to do here */
 }
 
@@ -1086,14 +1076,8 @@ InitText (CT ct)
 
     /* check if content specified a character set */
     if (*ap) {
-       /* match character set or set to CHARSET_UNKNOWN */
-       for (kv = Charset; kv->kv_key; kv++) {
-           if (!mh_strcasecmp (*ep, kv->kv_key)) {
-               chset = *ep;
-               break;
-           }
-       }
-       t->tx_charset = kv->kv_value;
+       chset = *ep;
+       t->tx_charset = CHARSET_SPECIFIED;
     } else {
        t->tx_charset = CHARSET_UNSPECIFIED;
     }
@@ -1124,8 +1108,7 @@ InitMultiPart (CT ct)
 {
     int        inout;
     long last, pos;
-    unsigned char *cp, *dp;
-    char **ap, **ep;
+    char *cp, *dp, **ap, **ep;
     char *bp, buffer[BUFSIZ];
     struct multipart *m;
     struct k2v *kv;
@@ -1138,11 +1121,24 @@ InitMultiPart (CT ct)
      * The encoding for multipart messages must be either
      * 7bit, 8bit, or binary (per RFC2045).
      */
-    if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
-       && ct->c_encoding != CE_BINARY) {
+    if (! skip_mp_cte_check  &&  ct->c_encoding != CE_7BIT  &&
+        ct->c_encoding != CE_8BIT  &&  ct->c_encoding != CE_BINARY) {
+       /* Copy the Content-Transfer-Encoding header field body so we can
+          remove any trailing whitespace and leading blanks from it. */
+       char *cte = add (ct->c_celine ? ct->c_celine : "(null)", NULL);
+
+       bp = cte + strlen (cte) - 1;
+       while (bp >= cte && isspace ((unsigned char) *bp)) *bp-- = '\0';
+       for (bp = cte; *bp && isblank ((unsigned char) *bp); ++bp) continue;
+
        admonish (NULL,
-                 "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
-                 ci->ci_type, ci->ci_subtype, ct->c_file);
+                 "\"%s/%s\" type in message %s must be encoded in\n"
+                 "7bit, 8bit, or binary, per RFC 2045 (6.4).  One workaround "
+                 "is to\nmanually edit the file and change the \"%s\"\n"
+                 "Content-Transfer-Encoding to one of those.  For now",
+                 ci->ci_type, ci->ci_subtype, ct->c_file, bp);
+       free (cte);
+
        return NOTOK;
     }
 
@@ -1178,7 +1174,7 @@ InitMultiPart (CT ct)
     ct->c_ctparams = (void *) m;
 
     /* check if boundary parameter contains only whitespace characters */
-    for (cp = bp; isspace (*cp); cp++)
+    for (cp = bp; isspace ((unsigned char) *cp); cp++)
        continue;
     if (!*cp) {
        advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
@@ -1188,7 +1184,7 @@ InitMultiPart (CT ct)
 
     /* remove trailing whitespace from boundary parameter */
     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
-       if (!isspace (*dp))
+       if (!isspace ((unsigned char) *dp))
            break;
     *++dp = '\0';
 
@@ -1251,7 +1247,11 @@ end_part:
        }
     }
 
-    advise (NULL, "bogus multipart content in message %s", ct->c_file);
+    if (! suppress_bogus_mp_content_warning) {
+        advise (NULL, "bogus multipart content in message %s", ct->c_file);
+    }
+    bogus_mp_content = 1;
+
     if (!inout && part) {
        p = part->mp_part;
        p->c_end = ct->c_end;
@@ -1303,6 +1303,9 @@ last_part:
        }
     }
 
+    get_leftover_mp_content (ct, 1);
+    get_leftover_mp_content (ct, 0);
+
     fclose (ct->c_fp);
     ct->c_fp = NULL;
     return OK;
@@ -1734,21 +1737,15 @@ static int
 openBase64 (CT ct, char **file)
 {
     int        bitno, cc, digested;
-    int fd, len, skip;
-    unsigned long bits;
-    unsigned char value, *b, *b1, *b2, *b3;
-    unsigned char *cp, *ep;
-    char buffer[BUFSIZ];
+    int fd, len, skip, own_ct_fp = 0;
+    uint32_t bits;
+    unsigned char value, b;
+    char *cp, *ep, buffer[BUFSIZ];
     /* sbeck -- handle suffixes */
     CI ci;
     CE ce;
     MD5_CTX mdContext;
 
-    b  = (unsigned char *) &bits;
-    b1 = &b[endian > 0 ? 1 : 2];
-    b2 = &b[endian > 0 ? 2 : 1];
-    b3 = &b[endian > 0 ? 3 : 0];
-
     ce = ct->c_cefile;
     if (ce->ce_fp) {
        fseek (ce->ce_fp, 0L, SEEK_SET);
@@ -1764,7 +1761,7 @@ openBase64 (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_scratch ("", tmp), NULL);
+       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -1781,8 +1778,21 @@ openBase64 (CT ct, char **file)
                   ci->ci_type);
         cp = context_find (buffer);
     }
-    if (cp != NULL && *cp != '\0')
-        ce->ce_file = add (cp, ce->ce_file);
+    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_fp = fopen (ce->ce_file, "w+")) == NULL) {
        content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
@@ -1792,9 +1802,12 @@ openBase64 (CT ct, char **file)
     if ((len = ct->c_end - ct->c_begin) < 0)
        adios (NULL, "internal error(1)");
 
-    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
-       content_error (ct->c_file, ct, "unable to open for reading");
-       return NOTOK;
+    if (! ct->c_fp) {
+       if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
+           content_error (ct->c_file, ct, "unable to open for reading");
+           return NOTOK;
+       }
+       own_ct_fp = 1;
     }
     
     if ((digested = ct->c_digested))
@@ -1823,13 +1836,13 @@ openBase64 (CT ct, char **file)
            for (ep = (cp = buffer) + cc; cp < ep; cp++) {
                switch (*cp) {
                default:
-                   if (isspace (*cp))
+                   if (isspace ((unsigned char) *cp))
                        break;
-                   if (skip || (*cp & 0x80)
-                       || (value = b642nib[*cp & 0x7f]) > 0x3f) {
+                   if (skip || (((unsigned char) *cp) & 0x80)
+                       || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
                        if (debugsw) {
                            fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
-                               *cp,
+                               (unsigned char) *cp,
                                (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
                                skip);
                        }
@@ -1841,17 +1854,20 @@ openBase64 (CT ct, char **file)
                    bits |= value << bitno;
 test_end:
                    if ((bitno -= 6) < 0) {
-                       putc ((char) *b1, ce->ce_fp);
+                       b = (bits >> 16) & 0xff;
+                       putc ((char) b, ce->ce_fp);
                        if (digested)
-                           MD5Update (&mdContext, b1, 1);
+                           MD5Update (&mdContext, &b, 1);
                        if (skip < 2) {
-                           putc ((char) *b2, ce->ce_fp);
+                           b = (bits >> 8) & 0xff;
+                           putc ((char) b, ce->ce_fp);
                            if (digested)
-                               MD5Update (&mdContext, b2, 1);
+                               MD5Update (&mdContext, &b, 1);
                            if (skip < 1) {
-                               putc ((char) *b3, ce->ce_fp);
+                               b = bits & 0xff;
+                               putc ((char) b, ce->ce_fp);
                                if (digested)
-                                   MD5Update (&mdContext, b3, 1);
+                                   MD5Update (&mdContext, &b, 1);
                            }
                        }
 
@@ -1906,9 +1922,17 @@ self_delimiting:
 
 ready_to_go:
     *file = ce->ce_file;
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     return fileno (ce->ce_fp);
 
 clean_up:
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     free_encoding (ct, 0);
     return NOTOK;
 }
@@ -1948,8 +1972,8 @@ InitQuoted (CT ct)
 static int
 openQuoted (CT ct, char **file)
 {
-    int        cc, digested, len, quoted;
-    unsigned char *cp, *ep;
+    int        cc, digested, len, quoted, own_ct_fp = 0;
+    char *cp, *ep;
     char buffer[BUFSIZ];
     unsigned char mask;
     CE ce;
@@ -1972,7 +1996,7 @@ openQuoted (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_scratch ("", tmp), NULL);
+       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -1989,12 +2013,20 @@ openQuoted (CT ct, char **file)
                   ci->ci_type);
         cp = context_find (buffer);
     }
-    if (cp != NULL && *cp != '\0')
-        ce->ce_file = add (cp, ce->ce_file);
-
-    if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
-       content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
-       return NOTOK;
+    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_fp = fopen (ce->ce_file, "w+")) == NULL) {
@@ -2005,9 +2037,12 @@ openQuoted (CT ct, char **file)
     if ((len = ct->c_end - ct->c_begin) < 0)
        adios (NULL, "internal error(2)");
 
-    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
-       content_error (ct->c_file, ct, "unable to open for reading");
-       return NOTOK;
+    if (! ct->c_fp) {
+       if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
+           content_error (ct->c_file, ct, "unable to open for reading");
+           return NOTOK;
+       }
+       own_ct_fp = 1;
     }
 
     if ((digested = ct->c_digested))
@@ -2030,7 +2065,7 @@ openQuoted (CT ct, char **file)
        len -= cc;
 
        for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
-           if (!isspace (*ep))
+           if (!isspace ((unsigned char) *ep))
                break;
        *++ep = '\n', ep++;
 
@@ -2039,13 +2074,13 @@ openQuoted (CT ct, char **file)
                /* in an escape sequence */
                if (quoted == 1) {
                    /* at byte 1 of an escape sequence */
-                   mask = hex2nib[*cp & 0x7f];
+                   mask = hex2nib[((unsigned char) *cp) & 0x7f];
                    /* next is byte 2 */
                    quoted = 2;
                } else {
                    /* at byte 2 of an escape sequence */
                    mask <<= 4;
-                   mask |= hex2nib[*cp & 0x7f];
+                   mask |= hex2nib[((unsigned char) *cp) & 0x7f];
                    putc (mask, ce->ce_fp);
                    if (digested)
                        MD5Update (&mdContext, &mask, 1);
@@ -2072,7 +2107,8 @@ openQuoted (CT ct, char **file)
                if (cp + 1 >= ep || cp + 2 >= ep) {
                    /* We don't have 2 bytes left, so this is an invalid
                     * escape sequence; just show the raw bytes (below). */
-               } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
+               } else if (isxdigit ((unsigned char) cp[1]) &&
+                                       isxdigit ((unsigned char) cp[2])) {
                    /* Next 2 bytes are hex digits, making this a valid escape
                     * sequence; let's decode it (above). */
                    quoted = 1;
@@ -2129,10 +2165,18 @@ openQuoted (CT ct, char **file)
 
 ready_to_go:
     *file = ce->ce_file;
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     return fileno (ce->ce_fp);
 
 clean_up:
     free_encoding (ct, 0);
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     return NOTOK;
 }
 
@@ -2155,7 +2199,7 @@ Init7Bit (CT ct)
 int
 open7Bit (CT ct, char **file)
 {
-    int        cc, fd, len;
+    int        cc, fd, len, own_ct_fp = 0;
     char buffer[BUFSIZ];
     /* sbeck -- handle suffixes */
     char *cp;
@@ -2177,7 +2221,7 @@ open7Bit (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_scratch ("", tmp), NULL);
+       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -2194,8 +2238,21 @@ open7Bit (CT ct, char **file)
                   ci->ci_type);
         cp = context_find (buffer);
     }
-    if (cp != NULL && *cp != '\0')
-        ce->ce_file = add (cp, ce->ce_file);
+    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_fp = fopen (ce->ce_file, "w+")) == NULL) {
        content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
@@ -2252,9 +2309,12 @@ open7Bit (CT ct, char **file)
     if ((len = ct->c_end - ct->c_begin) < 0)
        adios (NULL, "internal error(3)");
 
-    if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
-       content_error (ct->c_file, ct, "unable to open for reading");
-       return NOTOK;
+    if (! ct->c_fp) {
+       if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
+           content_error (ct->c_file, ct, "unable to open for reading");
+           return NOTOK;
+       }
+       own_ct_fp = 1;
     }
 
     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
@@ -2291,10 +2351,18 @@ open7Bit (CT ct, char **file)
 
 ready_to_go:
     *file = ce->ce_file;
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     return fileno (ce->ce_fp);
 
 clean_up:
     free_encoding (ct, 0);
+    if (own_ct_fp) {
+      fclose (ct->c_fp);
+      ct->c_fp = NULL;
+    }
     return NOTOK;
 }
 
@@ -2450,10 +2518,8 @@ openFTP (CT ct, char **file)
     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
        ftp = NULL;
 
-#ifndef BUILTIN_FTP
     if (!ftp)
        return NOTOK;
-#endif
 
     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
        case NOTOK:
@@ -2520,7 +2586,8 @@ openFTP (CT ct, char **file)
 
     if (e->eb_flags) {
        user = "anonymous";
-       snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
+       snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
+                 LocalName (1));
        pass = buffer;
     } else {
        ruserpass (e->eb_site, &username, &password);
@@ -2545,16 +2612,13 @@ openFTP (CT ct, char **file)
     else if (caching)
        ce->ce_file = add (cachefile, NULL);
     else
-       ce->ce_file = add (m_scratch ("", tmp), NULL);
+       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
 
     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
        content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
        return NOTOK;
     }
 
-#ifdef BUILTIN_FTP
-    if (ftp)
-#endif
     {
        int child_id, i, vecp;
        char *vec[9];
@@ -2573,7 +2637,7 @@ openFTP (CT ct, char **file)
 
        fflush (stdout);
 
-       for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
+       for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
            sleep (5);
        switch (child_id) {
            case NOTOK:
@@ -2590,9 +2654,6 @@ openFTP (CT ct, char **file)
 
            default:
                if (pidXwait (child_id, NULL)) {
-#ifdef BUILTIN_FTP
-losing_ftp:
-#endif
                    username = password = NULL;
                    ce->ce_unlink = 1;
                    return NOTOK;
@@ -2600,14 +2661,6 @@ losing_ftp:
                break;
        }
     }
-#ifdef BUILTIN_FTP
-    else
-       if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
-                    ce->ce_file,
-                    e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
-               == NOTOK)
-           goto losing_ftp;
-#endif
 
     if (cachefile[0]) {
        if (caching)
@@ -2726,7 +2779,7 @@ openMail (CT ct, char **file)
     vec[vecp++] = e->eb_body;
     vec[vecp] = NULL;
 
-    for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
+    for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
        sleep (5);
     switch (child_id) {
        case NOTOK:
@@ -2747,7 +2800,7 @@ openMail (CT ct, char **file)
     }
 
     if (*file == NULL) {
-       ce->ce_file = add (m_scratch ("", tmp), NULL);
+       ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
        ce->ce_unlink = 1;
     } else {
        ce->ce_file = add (*file, NULL);
@@ -2775,15 +2828,10 @@ static int
 readDigest (CT ct, char *cp)
 {
     int        bitno, skip;
-    unsigned long bits;
+    uint32_t bits;
     char *bp = cp;
     unsigned char *dp, value, *ep;
-    unsigned char *b, *b1, *b2, *b3;
 
-    b  = (unsigned char *) &bits,
-    b1 = &b[endian > 0 ? 1 : 2],
-    b2 = &b[endian > 0 ? 2 : 1],
-    b3 = &b[endian > 0 ? 3 : 0];
     bitno = 18;
     bits = 0L;
     skip = 0;
@@ -2805,11 +2853,11 @@ test_end:
                if ((bitno -= 6) < 0) {
                    if (dp + (3 - skip) > ep)
                        goto invalid_digest;
-                   *dp++ = *b1;
+                   *dp++ = (bits >> 16) & 0xff;
                    if (skip < 2) {
-                       *dp++ = *b2;
+                       *dp++ = (bits >> 8) & 0xff;
                        if (skip < 1)
-                           *dp++ = *b3;
+                           *dp++ = bits & 0xff;
                    }
                    bitno = 18;
                    bits = 0L;
@@ -2850,3 +2898,109 @@ invalid_digest:
 
     return OK;
 }
+
+
+/* Multipart parts might have content before the first subpart and/or
+   after the last subpart that hasn't been stored anywhere else, so do
+   that. */
+int
+get_leftover_mp_content (CT ct, int before /* or after */) {
+    struct multipart *m = (struct multipart *) ct->c_ctparams;
+    char *boundary;
+    int found_boundary = 0;
+    char buffer[BUFSIZ];
+    int max = BUFSIZ;
+    int read = 0;
+    char *content = NULL;
+
+    if (! m) return NOTOK;
+
+    if (before) {
+        if (! m->mp_parts  ||  ! m->mp_parts->mp_part) return NOTOK;
+
+        /* Isolate the beginning of this part to the beginning of the
+           first subpart and save any content between them. */
+        fseeko (ct->c_fp, ct->c_begin, SEEK_SET);
+        max = m->mp_parts->mp_part->c_begin - ct->c_begin;
+        boundary = concat ("--", m->mp_start, NULL);
+    } else {
+        struct part *last_subpart = NULL;
+        struct part *subpart;
+
+        /* Go to the last subpart to get its end position. */
+        for (subpart = m->mp_parts; subpart; subpart = subpart->mp_next) {
+            last_subpart = subpart;
+        }
+
+        if (last_subpart == NULL) return NOTOK;
+
+        /* Isolate the end of the last subpart to the end of this part
+           and save any content between them. */
+        fseeko (ct->c_fp, last_subpart->mp_part->c_end, SEEK_SET);
+        max = ct->c_end - last_subpart->mp_part->c_end;
+        boundary = concat ("--", m->mp_stop, NULL);
+    }
+
+    /* Back up by 1 to pick up the newline. */
+    while (fgets (buffer, sizeof(buffer) - 1, ct->c_fp)) {
+        read += strlen (buffer);
+        /* Don't look beyond beginning of first subpart (before) or
+           next part (after). */
+        if (read > max) buffer[read-max] = '\0';
+
+        if (before) {
+            if (! strcmp (buffer, boundary)) {
+                found_boundary = 1;
+            }
+        } else {
+            if (! found_boundary  &&  ! strcmp (buffer, boundary)) {
+                found_boundary = 1;
+                continue;
+            }
+        }
+
+        if ((before && ! found_boundary)  ||  (! before && found_boundary)) {
+            if (content) {
+                char *old_content = content;
+                content = concat (content, buffer, NULL);
+                free (old_content);
+            } else {
+                content = before
+                    ?  concat ("\n", buffer, NULL)
+                    :  concat (buffer, NULL);
+            }
+        }
+
+        if (before) {
+            if (found_boundary  ||  read > max) break;
+        } else {
+            if (read > max) break;
+        }
+    }
+
+    /* Skip the newline if that's all there is. */
+    if (content) {
+        char *cp;
+
+        /* Remove trailing newline, except at EOF. */
+        if ((before || ! feof (ct->c_fp)) &&
+            (cp = content + strlen (content)) > content  &&
+            *--cp == '\n') {
+            *cp = '\0';
+        }
+
+        if (strlen (content) > 1) {
+            if (before) {
+                m->mp_content_before = content;
+            } else {
+                m->mp_content_after = content;
+            }
+        } else {
+            free (content);
+        }
+    }
+
+    free (boundary);
+
+    return OK;
+}