]> diplodocus.org Git - nmh/commitdiff
Added mhfixmsg -[no]decodeheaderfieldbodies switches.
authorDavid Levine <levinedl@acm.org>
Sun, 14 Jan 2018 18:54:19 +0000 (13:54 -0500)
committerDavid Levine <levinedl@acm.org>
Sun, 14 Jan 2018 18:54:19 +0000 (13:54 -0500)
man/mhfixmsg.man
test/mhfixmsg/test-mhfixmsg
uip/mhfixmsg.c

index 5dd12bf18d663238993c3670a1fda63ea814d952..fd9e49f0e7dc3bc2936fe8164651e14d9abe3b1e 100644 (file)
@@ -1,4 +1,4 @@
-.TH MHFIXMSG %manext1% 2016-11-08 "%nmhversion%"
+.TH MHFIXMSG %manext1% 2018-01-14 "%nmhversion%"
 .
 .\" %nmhwarning%
 .
@@ -20,6 +20,9 @@ mhfixmsg \- nmh's MIME-email rewriter with various transformations
 .BR \-nodecodetext ]
 .RB [ \-decodetypes
 .IR "type/[subtype][,...]" ]
+.RB [ \-decodeheaderfieldbodies
+utf-8 |
+.BR \-nodecodeheaderfieldbodies ]
 .RB [ \-crlflinebreaks " | " \-nocrlflinebreaks ]
 .RB [ \-textcharset
 .I charset
@@ -97,6 +100,18 @@ to restrict
 .B \-decodetext
 to just text/plain parts.
 .PP
+The
+.B \-decodeheaderfieldbodies
+switch enables decoding of UTF-8 header field bodies, when supplied
+with its mandatory
+.I utf-8
+argument.  The
+.B \-nodecodeheaderfieldbodies
+inhibits this transformation.  The transformation can produce a message
+that does not conform with RFC 2047, §1, paragraph 6, because the decoded
+header field body could contain unencoded non-ASCII characters.  It is
+therefore not enabled by default.
+.PP
 By default, carriage return characters are preserved or inserted at
 the end of each line of text content.  The
 .B \-crlflinebreaks
@@ -105,7 +120,7 @@ switch selects this behavior and is enabled by default.  The
 switch causes carriage return characters to be stripped from, and not
 inserted in, text content when it is decoded and encoded.  Note that
 its use can cause the generation of MIME messages that do not conform
-to RFC 2046, §4.1.1, paragraph 1.
+with RFC 2046, §4.1.1, paragraph 1.
 .PP
 The
 .B \-textcharset
@@ -294,15 +309,16 @@ content type and/or encoding as follows:
 .PP
 .RS 5
 .nf
-.ta \w'\-crlflinebreaks 'u
-\-decodetext         base64 and quoted-printable encoded text parts
-\-decodetypes        limits parts to which -decodetext applies
-\-crlflinebreaks     text parts
-\-textcharset        text/plain parts
-\-reformat           text parts that are not text/plain
-\-fixboundary        outermost multipart part
-\-fixcte             multipart or message part
-\-fixtype            all except multipart and message parts
+.ta \w'\-decodeheaderfieldbodies 'u
+\-decodetext              base64 and quoted-printable encoded text parts
+\-decodetypes             limits parts to which -decodetext applies
+\-decodeheaderfieldbodies all message parts
+\-crlflinebreaks          text parts
+\-textcharset             text/plain parts
+\-reformat                text parts that are not text/plain
+\-fixboundary             outermost multipart part
+\-fixcte                  multipart or message part
+\-fixtype                 all except multipart and message parts
 .fi
 .RE
 .SS "Backup of Original Message/File"
@@ -529,6 +545,7 @@ is checked.
 .RB ` msgs "' defaults to cur"
 .RB ` "\-decodetext 8bit"'
 .RB ` "\-decodetypes text,application/ics"'
+.RB ` \-nodecodeheaderfieldbodies '
 .RB ` \-crlflinebreaks '
 .RB ` \-notextcharset '
 .RB ` \-reformat '
index e37b56a3d2374cd378fc350ef7be4b9cc7193b61..b927b95e694525d0fa0415a8d1811675f4209f4e 100755 (executable)
@@ -53,6 +53,8 @@ Usage: mhfixmsg [+folder] [msgs] [switches]
   -decodetext 8bit|7bit|binary
   -nodecodetext
   -decodetypes
+  -decodeheaderfieldbodies utf-8
+  -nodecodeheaderfieldbodies
   -[no]crlflinebreaks
   -[no]textcharset
   -[no]reformat
@@ -364,8 +366,8 @@ else
 fi
 
 
-# check -nodecode
-start_test "-nodecode"
+# check -nodecodetext
+start_test "-nodecodetext"
 prepare_space >"$expected" <<EOF
 MIME-Version: 1.0
 From: sender@example.com
@@ -429,7 +431,7 @@ if [ $can_reformat_texthtml -eq 1 ]; then
   printf '%s\n' 'mhfixmsg: 12, insert text/plain part' >"$expected.err"
 
   #### lynx inserts multiple blank lines, so squeeze them.
-  run_prog mhfixmsg last -nodecode -outfile - -verbose 2>"$actual.err" | \
+  run_prog mhfixmsg last -nodecodetext -outfile - -verbose 2>"$actual.err" | \
     squeeze_lines >"$actual"
   check "$expected" "$actual" 'ignore space'
   check "$expected.err" "$actual.err"
@@ -1790,6 +1792,67 @@ run_prog mhfixmsg -file - -outfile - <`mhpath last` >"$actual" 2>/dev/null
 check "$expected" "$actual"
 
 
+start_test "-nodecodeheaderfieldbodies"
+cat >"`mhpath new`" <<EOF
+To: recipient@example.com
+From: sender@example.com
+Date: Wed, 28 Sep 2016 11:24:28 -0400
+Subject: =?utf-8?B?dGhpcyBTdWJqZWN0IHdhcyBVVEYtOCBlbmNvZGVk?=
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary=001a114dd3e8fe9c56053d92f414
+Content-Transfer-Encoding: 8bit
+
+--001a114dd3e8fe9c56053d92f414
+Content-Type: text/plain; charset=UTF-8
+
+This is a test.
+
+--001a114dd3e8fe9c56053d92f414
+Content-Type: text/plain; charset=UTF-8; name="test.txt"
+Content-Disposition: attachment; filename="test.txt"
+Content-Transfer-Encoding: 8bit
+
+This is the first text/plain part, in a subpart.  The file name
+is test.txt.
+
+--001a114dd3e8fe9c56053d92f414--
+EOF
+run_prog mhfixmsg -file - -outfile - -decodeheaderfieldbodies utf-8 \
+     -nodecodeheaderfieldbodies <`mhpath last` >"$actual" 2>/dev/null
+check `mhpath last` "$actual" 'keep first'
+
+
+start_test "test decoding of UTF-8 header value"
+cat >"$expected" <<EOF
+To: recipient@example.com
+From: sender@example.com
+Date: Wed, 28 Sep 2016 11:24:28 -0400
+Subject: this Subject was UTF-8 encoded
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary=001a114dd3e8fe9c56053d92f414
+Content-Transfer-Encoding: 8bit
+
+--001a114dd3e8fe9c56053d92f414
+Content-Type: text/plain; charset=UTF-8
+
+This is a test.
+
+--001a114dd3e8fe9c56053d92f414
+Content-Type: text/plain; charset=UTF-8; name="test.txt"
+Content-Disposition: attachment; filename="test.txt"
+Content-Transfer-Encoding: 8bit
+
+This is the first text/plain part, in a subpart.  The file name
+is test.txt.
+
+--001a114dd3e8fe9c56053d92f414--
+EOF
+
+run_prog mhfixmsg -file - -outfile - -decodeheaderfieldbodies utf-8 \
+     <`mhpath last` >"$actual" 2>/dev/null
+check "$expected" "$actual"
+
+
 # make sure there are no tmp files left over
 find "$MH_TEST_DIR/Mail" -name '*mhfix*' -print \
   >"$actual"
@@ -1798,5 +1861,6 @@ EOF
 
 check "$expected" "$actual"
 
+
 finish_test
 exit $failed
index eedee6cc6514217ff91867d893a2651ec1f7c162..df0306552f755a817208a8fcdbcb02b4d9a3d3ac 100644 (file)
@@ -50,6 +50,8 @@
     X("decodetext 8bit|7bit|binary", 0, DECODETEXTSW) \
     X("nodecodetext", 0, NDECODETEXTSW) \
     X("decodetypes", 0, DECODETYPESW) \
+    X("decodeheaderfieldbodies utf-8", 0, DECODEHEADERFIELDBODIESSW) \
+    X("nodecodeheaderfieldbodies", 0, NDECODEHEADERFIELDBODIESSW) \
     X("crlflinebreaks", 0, CRLFLINEBREAKSSW) \
     X("nocrlflinebreaks", 0, NCRLFLINEBREAKSSW) \
     X("textcharset", 0, TEXTCHARSETSW) \
@@ -99,6 +101,7 @@ typedef struct fix_transformations {
     int replacetextplain;
     int decodetext;
     char *decodetypes;
+    char *decodeheaderfieldbodies; /* Either NULL or "utf-8". */
     /* Whether to use CRLF linebreaks, per RFC 2046 Sec. 4.1.1, par.1. */
     int lf_line_endings;
     char *textcharset;
@@ -137,6 +140,7 @@ static int least_restrictive_encoding (CT) PURE;
 static int less_restrictive (int, int);
 static int convert_charsets (CT, char *, int *);
 static int fix_always (CT, int *);
+static int decode_header_field_bodies (CT, int *);
 static int fix_filename_param (char *, char *, PM *, PM *);
 static int fix_filename_encoding (CT);
 static int write_content (CT, const char *, char *, FILE *, int, int);
@@ -167,6 +171,7 @@ main (int argc, char **argv)
     fx.replacetextplain = 0;
     fx.decodetext = CE_8BIT;
     fx.decodetypes = "text,application/ics";  /* Default, per man page. */
+    fx.decodeheaderfieldbodies = NULL;
     fx.lf_line_endings = 0;
     fx.textcharset = NULL;
 
@@ -219,6 +224,21 @@ main (int argc, char **argv)
                 }
                 fx.decodetypes = cp;
                 continue;
+            case DECODEHEADERFIELDBODIESSW:
+                if (! (cp = *argp++)  ||  *cp == '-') {
+                    die("missing argument to %s", argp[-2]);
+                }
+                fx.decodeheaderfieldbodies = cp;
+                if (strcasecmp (cp, "utf-8")  && strcasecmp (cp, "utf8")) {
+                    /* Because UTF-8 strings can't have embedded nulls.  Other
+                       encodings support that, too, but we won't bother to
+                       enumerate them. */
+                    die("-decodeheaderfieldbodies only supports utf-8");
+                }
+                continue;
+            case NDECODEHEADERFIELDBODIESSW:
+                fx.decodeheaderfieldbodies = NULL;
+                continue;
             case CRLFLINEBREAKSSW:
                 fx.lf_line_endings = 0;
                 continue;
@@ -598,6 +618,9 @@ mhfixmsgsbr (CT *ctp, char *maildir, const fix_transformations *fx,
                                     &message_mods);
         update_cte (*ctp);
     }
+    if (status == OK  &&  fx->decodeheaderfieldbodies) {
+        status = decode_header_field_bodies(*ctp, &message_mods);
+    }
     if (status == OK  &&  fx->textcharset != NULL) {
         status = convert_charsets (*ctp, fx->textcharset, &message_mods);
     }
@@ -2628,6 +2651,66 @@ fix_always (CT ct, int *message_mods)
 }
 
 
+/*
+ * Decodes UTF-8 encoded header values.  Similar to fix_filename_param(), but
+ * does not modify any MIME parameter values.
+ */
+static int
+decode_header_field_bodies (CT ct, int *message_mods)
+{
+    int status = OK;
+
+    switch (ct->c_type) {
+    case CT_MULTIPART: {
+        struct multipart *m = (struct multipart *) ct->c_ctparams;
+        struct part *part;
+
+        for (part = m->mp_parts; status == OK  &&  part; part = part->mp_next) {
+            status = decode_header_field_bodies (part->mp_part, message_mods);
+        }
+        break;
+    }
+
+    case CT_MESSAGE:
+        if (ct->c_subtype == MESSAGE_EXTERNAL) {
+            struct exbody *e = (struct exbody *) ct->c_ctparams;
+
+            status = decode_header_field_bodies (e->eb_content, message_mods);
+        }
+        break;
+    }
+
+    HF hf;
+
+    for (hf = ct->c_first_hf; hf; hf = hf->next) {
+        /* Only decode UTF-8 values. */
+        if (hf->value  &&  has_suffix(hf->value, "?=\n")  &&
+            (! strncasecmp (hf->value, " =?utf8?", 8)  ||
+             ! strncasecmp (hf->value, " =?utf-8?", 9))) {
+            /* Looks like an RFC 2047 encoded parameter. */
+            char decoded[PATH_MAX + 1];
+
+            if (decode_rfc2047 (hf->value, decoded, sizeof decoded)) {
+                const size_t len = strlen(decoded);
+
+                /* decode_rfc2047() could truncate if the buffer fills up.
+                   Detect and discard if that happened. */
+                if (len < sizeof(decoded) - 1  &&  strcmp(hf->value, decoded)) {
+                    hf->value = mh_xrealloc (hf->value, len + 1);
+                    strncpy (hf->value, decoded, len + 1);
+                    ++*message_mods;
+                }
+            } else {
+                inform("failed to decode %s parameter %s", hf->name, hf->value);
+                status = NOTOK;
+            }
+        }
+    }
+
+    return status;
+}
+
+
 /*
  * Factor out common code for loops in fix_filename_encoding().
  */