From 18dccf2fe359926ac1c1bf1c5cc51f68b65cef46 Mon Sep 17 00:00:00 2001 From: David Levine Date: Thu, 25 Dec 2014 11:10:52 -0600 Subject: [PATCH] Added -fixtype switch to mhfixmsg(1). It ensures that each part of a message has the correct MIME type shown in its Content-Type header. --- docs/pending-release-notes | 3 +- man/mhfixmsg.man | 13 ++- test/mhfixmsg/test-mhfixmsg | 40 ++++++++- uip/mhfixmsg.c | 174 ++++++++++++++++++++++++++++++++++-- 4 files changed, 220 insertions(+), 10 deletions(-) diff --git a/docs/pending-release-notes b/docs/pending-release-notes index fe12e698..c621ca1a 100644 --- a/docs/pending-release-notes +++ b/docs/pending-release-notes @@ -25,7 +25,8 @@ NEW FEATURES - When building from source, configure will derive ${prefix} from an existing nmh installation if it finds one in your $PATH. - Added getmymbox and getmyaddr mh-format(5) function escapes. -- The -changecur and -nochangecur switches have been added to mhfixmsg(1). +- New -changecur, -nochangecur, and -fixtype switches have been added + to mhfixmsg(1). - mhfixmsg now removes an extraneous trailing semicolon from header parameter lists. diff --git a/man/mhfixmsg.man b/man/mhfixmsg.man index 3bb8e84e..3afb4ba8 100644 --- a/man/mhfixmsg.man +++ b/man/mhfixmsg.man @@ -1,4 +1,4 @@ -.TH MHFIXMSG %manext1% "November 30, 2014" "%nmhversion%" +.TH MHFIXMSG %manext1% "December 25, 2014" "%nmhversion%" .\" .\" %nmhwarning% .\" @@ -23,6 +23,8 @@ mhfixmsg \- rewrite MIME messages with various transformations .RB [ \-replacetextplain " | " \-noreplacetextplain ] .RB [ \-fixboundary " | " \-nofixboundary ] .RB [ \-fixcte " | " \-nofixcte ] +.RB [ \-fixtype +.IR mimetype ] .RB [ \-outfile .IR outfile ] .RB [ \-rmmproc @@ -149,6 +151,14 @@ and other .B nmh programs that parse MIME messages. .PP +The +.B \-fixtype +switch ensures that each part of the message has the correct MIME type +shown in its Content-Type header. It may be repeated. It is +typically used to replace \*(lqapplication/octet-stream\*(rq with a +more descriptive MIME type. It may not be used for multipart and +message types. +.PP .B mhfixmsg applies one transformation unconditionally: it removes an extraneous trailing semicolon from the parameter lists of MIME header fields. @@ -240,6 +250,7 @@ content type and/or encoding as follows: \-reformat text parts that are not text/plain \-fixboundary outermost multipart part \-fixcte multipart part +\-fixtype all except multipart and message parts .fi .RE .PP diff --git a/test/mhfixmsg/test-mhfixmsg b/test/mhfixmsg/test-mhfixmsg index b6b01081..a7932352 100755 --- a/test/mhfixmsg/test-mhfixmsg +++ b/test/mhfixmsg/test-mhfixmsg @@ -12,7 +12,9 @@ if test -z "${MH_OBJ_DIR}"; then MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR fi -. "${srcdir}/test/post/test-post-common.sh" +. "${MH_OBJ_DIR}/test/common.sh" + +setup_test #### Make sure that html-to-text conversion is what we expect. require_locale en_US.utf-8 en_US.utf8 @@ -52,6 +54,7 @@ Usage: mhfixmsg [+folder] [msgs] [switches] -[no]replacetextplain -[no]fixboundary -[no]fixcte + -fixtype mimetype -file file -outfile file -rmmproc program @@ -1108,6 +1111,41 @@ if test -f '${MH_TEST_DIR}/Mail/inbox/20.backup'; then fi +# check -fixtype +cat >"$expected" <`mhpath new` <"$actual" diff --git a/uip/mhfixmsg.c b/uip/mhfixmsg.c index f80ff9be..af6cd889 100644 --- a/uip/mhfixmsg.c +++ b/uip/mhfixmsg.c @@ -26,6 +26,7 @@ X("nofixboundary", 0, NFIXBOUNDARYSW) \ X("fixcte", 0, FIXCTESW) \ X("nofixcte", 0, NFIXCTESW) \ + X("fixtype mimetype", 0, FIXTYPESW) \ X("file file", 0, FILESW) \ X("outfile file", 0, OUTFILESW) \ X("rmmproc program", 0, RPROCSW) \ @@ -75,6 +76,7 @@ void freects_done (int) NORETURN; typedef struct fix_transformations { int fixboundary; int fixcte; + svector_t fixtypes; int reformat; int replacetextplain; int decodetext; @@ -85,6 +87,8 @@ int mhfixmsgsbr (CT *, const fix_transformations *, char *); static int fix_boundary (CT *, int *); static int get_multipart_boundary (CT, char **); static int replace_boundary (CT, char *, char *); +static int fix_types (CT, svector_t, int *); +static char *replace_substring (char **, const char *, const char *); static int fix_multipart_cte (CT, int *); static int set_ce (CT, int); static int ensure_text_plain (CT *, CT, int *, int); @@ -126,6 +130,7 @@ main (int argc, char **argv) { int status = OK; fix_transformations fx; fx.reformat = fx.fixcte = fx.fixboundary = 1; + fx.fixtypes = NULL; fx.replacetextplain = 0; fx.decodetext = CE_8BIT; fx.textcharset = NULL; @@ -192,6 +197,18 @@ main (int argc, char **argv) { case NFIXCTESW: fx.fixcte = 0; continue; + case FIXTYPESW: + if (! (cp = *argp++) || (*cp == '-' && cp[1])) + adios (NULL, "missing argument to %s", argp[-2]); + if (! strncasecmp (cp, "multipart/", 10) || + ! strncasecmp (cp, "message/", 8)) { + adios (NULL, "-fixtype %s not allowed", cp); + } else if (! strchr (cp, '/')) { + adios (NULL, "-fixtype requires type/subtype"); + } + if (fx.fixtypes == NULL) { fx.fixtypes = svector_create (10); } + svector_push_back (fx.fixtypes, cp); + continue; case REFORMATSW: fx.reformat = 1; continue; @@ -376,6 +393,7 @@ main (int argc, char **argv) { status = 1; } + if (fx.fixtypes != NULL) { svector_free (fx.fixtypes); } free (outfile); free (file); @@ -414,6 +432,9 @@ mhfixmsgsbr (CT *ctp, const fix_transformations *fx, char *outfile) { if (status == OK && fx->fixboundary) { status = fix_boundary (ctp, &message_mods); } + if (status == OK && fx->fixtypes != NULL) { + status = fix_types (*ctp, fx->fixtypes, &message_mods); + } if (status == OK && fx->fixcte) { status = fix_multipart_cte (*ctp, &message_mods); } @@ -727,6 +748,144 @@ replace_boundary (CT ct, char *file, char *boundary) { } +static int +fix_types (CT ct, svector_t fixtypes, 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 = fix_types (part->mp_part, fixtypes, message_mods); + } + break; + } + + case CT_MESSAGE: + if (ct->c_subtype == MESSAGE_EXTERNAL) { + struct exbody *e = (struct exbody *) ct->c_ctparams; + + status = fix_types (e->eb_content, fixtypes, message_mods); + } + break; + + default: { + char **typep, *type; + + if (ct->c_ctinfo.ci_type && ct->c_ctinfo.ci_subtype) { + for (typep = svector_strs (fixtypes); + typep && (type = *typep); + ++typep) { + char *type_subtype = + concat (ct->c_ctinfo.ci_type, "/", ct->c_ctinfo.ci_subtype, + NULL); + + if (! strcasecmp (type, type_subtype) && + decode_part (ct) == OK && + ct->c_cefile.ce_file != NULL) { + char *ct_type_subtype = mime_type (ct->c_cefile.ce_file); + char *cp; + + if ((cp = strchr (ct_type_subtype, ';'))) { + /* Truncate to remove any parameter list from + mime_type () result. */ + *cp = '\0'; + } + + if (strcasecmp (type, ct_type_subtype)) { + char *ct_type, *ct_subtype; + HF hf; + + /* The Content-Type header does not match the + content, so update these struct Content + fields to match: + * c_type, c_subtype + * c_ctinfo.ci_type, c_ctinfo.ci_subtype + * c_ctline + */ + /* Extract type and subtype from type/subtype. */ + ct_type = getcpy (ct_type_subtype); + if ((cp = strchr (ct_type, '/'))) { + *cp = '\0'; + ct_subtype = getcpy (++cp); + } else { + advise (NULL, "missing / in MIME type of %s %s", + ct->c_file, ct->c_partno); + free (ct_type); + return NOTOK; + } + + ct->c_type = ct_str_type (ct_type); + ct->c_subtype = ct_str_subtype (ct->c_type, ct_subtype); + + free (ct->c_ctinfo.ci_type); + ct->c_ctinfo.ci_type = ct_type; + free (ct->c_ctinfo.ci_subtype); + ct->c_ctinfo.ci_subtype = ct_subtype; + if (! replace_substring (&ct->c_ctline, type, + ct_type_subtype)) { + advise (NULL, "did not find %s in %s", + type, ct->c_ctline); + } + + /* Update Content-Type header field. */ + for (hf = ct->c_first_hf; hf; hf = hf->next) { + if (! strcasecmp (TYPE_FIELD, hf->name)) { + if (replace_substring (&hf->value, type, + ct_type_subtype)) { + ++*message_mods; + if (verbosw) { + report (NULL, ct->c_partno, ct->c_file, + "change Content-Type in header " + "from %s to %s", + type, ct_type_subtype); + } + break; + } else { + advise (NULL, "did not find %s in %s", + type, hf->value); + } + } + } + } + free (ct_type_subtype); + } + free (type_subtype); + } + } + }} + + return status; +} + +char * +replace_substring (char **str, const char *old, const char *new) { + char *cp; + + if ((cp = strstr (*str, old))) { + char *remainder = cp + strlen (old); + char *prefix, *new_str; + + if (cp - *str) { + prefix = getcpy (*str); + *(prefix + (cp - *str)) = '\0'; + new_str = concat (prefix, new, remainder, NULL); + free (prefix); + } else { + new_str = concat (new, remainder, NULL); + } + + free (*str); + + return *str = new_str; + } else { + return NULL; + } +} + + static int fix_multipart_cte (CT ct, int *message_mods) { int status = OK; @@ -1062,13 +1221,14 @@ build_text_plain_part (CT encoded_part) { if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, NULL)) == NULL) { advise (NULL, "unable to create temporary file in %s", get_temp_dir()); - } - tmp_plain_file = add (tempfile, NULL); - if (reformat_part (tp_part, tmp_plain_file, - tp_part->c_ctinfo.ci_type, - tp_part->c_ctinfo.ci_subtype, - tp_part->c_type) == OK) { - return tp_part; + } else { + tmp_plain_file = add (tempfile, NULL); + if (reformat_part (tp_part, tmp_plain_file, + tp_part->c_ctinfo.ci_type, + tp_part->c_ctinfo.ci_subtype, + tp_part->c_type) == OK) { + return tp_part; + } } } -- 2.56.0