X-Git-Url: https://diplodocus.org/git/nmh/blobdiff_plain/be2db0893c180332a66245514a5d72985597f181..ddf3a8574f657dcb8c53fc5908e7bebbde1994b5:/uip/mhfixmsg.c diff --git a/uip/mhfixmsg.c b/uip/mhfixmsg.c index ae871044..f410ecb4 100644 --- a/uip/mhfixmsg.c +++ b/uip/mhfixmsg.c @@ -6,6 +6,26 @@ */ #include "h/mh.h" +#include "sbr/m_name.h" +#include "sbr/m_gmprot.h" +#include "sbr/m_getfld.h" +#include "sbr/getarguments.h" +#include "sbr/concat.h" +#include "sbr/seq_setprev.h" +#include "sbr/seq_setcur.h" +#include "sbr/seq_save.h" +#include "sbr/smatch.h" +#include "sbr/fmt_rfc2047.h" +#include "sbr/cpydata.h" +#include "sbr/trimcpy.h" +#include "sbr/m_convert.h" +#include "sbr/m_backup.h" +#include "sbr/getfolder.h" +#include "sbr/folder_read.h" +#include "sbr/context_save.h" +#include "sbr/context_replace.h" +#include "sbr/context_find.h" +#include "sbr/readconfig.h" #include "sbr/ambigsw.h" #include "sbr/path.h" #include "sbr/print_version.h" @@ -30,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) \ @@ -42,6 +64,8 @@ X("nofixboundary", 0, NFIXBOUNDARYSW) \ X("fixcte", 0, FIXCOMPOSITECTESW) \ X("nofixcte", 0, NFIXCOMPOSITECTESW) \ + X("checkbase64", 0, CHECKBASE64SW) \ + X("nocheckbase64", 0, NCHECKBASE64SW) \ X("fixtype mimetype", 0, FIXTYPESW) \ X("file file", 0, FILESW) \ X("outfile file", 0, OUTFILESW) \ @@ -71,7 +95,7 @@ int debugsw; /* Needed by mhparse.c. */ /* * static prototypes */ -typedef struct fix_transformations { +typedef struct { int fixboundary; int fixcompositecte; svector_t fixtypes; @@ -79,9 +103,11 @@ 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; + bool checkbase64; } fix_transformations; static int mhfixmsgsbr (CT *, char *, const fix_transformations *, @@ -100,11 +126,15 @@ static int find_textplain_sibling (CT, int, int *); static int insert_new_text_plain_part (CT, int, CT); static CT build_text_plain_part (CT); static int insert_into_new_mp_alt (CT *, int *); +static int insert_into_new_mp_mixed (CT *, const char *, int *); static CT divide_part (CT); static void copy_ctinfo (CI, CI); static int decode_part (CT); +static size_t get_valid_base64 (CT, char **); +static size_t find_invalid_base64_pos (const char *); +static int check_base64_encoding (CT *); static int reformat_part (CT, char *, char *, char *, int); -static CT build_multipart_alt (CT, CT, int, int); +static CT build_multipart (CT, CT, int, int); static int boundary_in_content (FILE **, char *, const char *); static void transfer_noncontent_headers (CT, CT); static int set_ct_type (CT, int type, int subtype, int encoding); @@ -116,7 +146,8 @@ static void update_cte (CT); 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 fix_always (CT *, const fix_transformations *, 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); @@ -147,8 +178,10 @@ 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; + fx.checkbase64 = true; if (nmh_init(argv[0], true, false)) { return 1; } @@ -199,6 +232,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; @@ -214,6 +262,12 @@ main (int argc, char **argv) case NTEXTCHARSETSW: fx.textcharset = 0; continue; + case CHECKBASE64SW: + fx.checkbase64 = true; + continue; + case NCHECKBASE64SW: + fx.checkbase64 = false; + continue; case FIXBOUNDARYSW: fx.fixboundary = 1; continue; @@ -559,7 +613,7 @@ mhfixmsgsbr (CT *ctp, char *maildir, const fix_transformations *fx, } /* else *outfp was defined by caller */ reverse_alternative_parts (*ctp); - status = fix_always (*ctp, &message_mods); + status = fix_always (ctp, fx, &message_mods); if (status == OK && fx->fixboundary) { status = fix_boundary (ctp, &message_mods); } @@ -578,6 +632,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); } @@ -864,20 +921,20 @@ replace_boundary (CT ct, char *file, char *boundary) if (strcasecmp (TYPE_FIELD, np)) { fprintf (fpout, "%s:%s", np, vp); } else { - char *new_ctline, *new_params; + char *new_ctline, *new_params; - replace_param(&ct->c_ctinfo.ci_first_pm, - &ct->c_ctinfo.ci_last_pm, "boundary", - boundary, 0); + replace_param(&ct->c_ctinfo.ci_first_pm, + &ct->c_ctinfo.ci_last_pm, "boundary", + boundary, 0); - new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/", - ct->c_ctinfo.ci_subtype, NULL); - new_params = output_params(LEN(TYPE_FIELD) + - strlen(new_ctline) + 1, - ct->c_ctinfo.ci_first_pm, NULL, 0); + new_ctline = concat(" ", ct->c_ctinfo.ci_type, "/", + ct->c_ctinfo.ci_subtype, NULL); + new_params = output_params(LEN(TYPE_FIELD) + + strlen(new_ctline) + 1, + ct->c_ctinfo.ci_first_pm, NULL, 0); fprintf (fpout, "%s:%s%s\n", np, new_ctline, - FENDNULL(new_params)); - free(new_ctline); + FENDNULL(new_params)); + free(new_ctline); free(new_params); } @@ -1141,7 +1198,6 @@ fix_composite_cte (CT ct, int *message_mods) NEW(h); h->name = mh_xstrdup (hf->name); - h->hf_encoding = hf->hf_encoding; h->next = hf->next; hf->next = h; @@ -1451,7 +1507,7 @@ find_textplain_sibling (CT parent, int replacetextplain, /* - * Insert a new text/plain part. + * Insert a new text/plain part in a multipart part. */ static int insert_new_text_plain_part (CT ct, int new_subpart_number, CT parent) @@ -1525,12 +1581,13 @@ build_text_plain_part (CT encoded_part) static int insert_into_new_mp_alt (CT *ct, int *message_mods) { + /* The following will call decode_part(). */ CT tp_part = build_text_plain_part (*ct); int status = OK; if (tp_part) { - CT mp_alt = build_multipart_alt (*ct, tp_part, CT_MULTIPART, - MULTI_ALTERNATE); + CT mp_alt = build_multipart (*ct, tp_part, CT_MULTIPART, + MULTI_ALTERNATE); if (mp_alt) { struct multipart *mp = (struct multipart *) mp_alt->c_ctparams; @@ -1560,6 +1617,79 @@ insert_into_new_mp_alt (CT *ct, int *message_mods) } +/* + * Slip new text/plain part into a new multipart/mixed. + */ +static int +insert_into_new_mp_mixed (CT *ct, const char *content, int *message_mods) +{ + CT main_part = divide_part (*ct); + const char *reason = NULL; + const int encoding = content_encoding (main_part, &reason); + int status = OK; + + if (set_ct_type(main_part, (*ct)->c_type, (*ct)->c_subtype, + main_part->c_encoding) != OK) { + inform("failed to set Content-Type of main part"); + return NOTOK; + } + if (set_ct_type(*ct, (*ct)->c_type, (*ct)->c_subtype, encoding) != OK) { + inform("failed to set Content-Type of new part"); + return NOTOK; + } + + if (main_part) { + /* Load remainder into the new part. */ + CE cefile = &(*ct)->c_cefile; + CT mp_alt; + + cefile->ce_file = + mh_xstrdup(m_mktemp2 (NULL, invo_name, NULL, &cefile->ce_fp)); + if (cefile->ce_file == NULL) { + die("unable to create temporary file in %s", get_temp_dir()); + } + cefile->ce_unlink = 1; + fprintf (cefile->ce_fp, "%s", content); + + /* Put both parts into a new multipart. */ + mp_alt = build_multipart (*ct, main_part, CT_MULTIPART, MULTI_MIXED); + if (mp_alt) { + struct multipart *mp = (struct multipart *) mp_alt->c_ctparams; + + /* So fix_composite_cte doesn't try to overwrite the encoding. If + the content needs to be decoded, c_encoding will be properly + set. */ + mp_alt->c_encoding = encoding; + + if (mp && mp->mp_parts) { + mp->mp_parts->mp_part = main_part; + /* Make the new multipart/alternative the parent. */ + *ct = mp_alt; + + ++*message_mods; + if (verbosw) { + report (NULL, (*ct)->c_partno, (*ct)->c_file, + "insert text/plain part"); + } + } else { + free_content (main_part); + free_content (mp_alt); + status = NOTOK; + } + } else { + inform("failed to build multipart/alternate"); + status = NOTOK; + } + } else { + /* Should never happen. */ + inform("failed to insert new text part into multipart/related"); + status = NOTOK; + } + + return status; +} + + /* * Clone a MIME part. */ @@ -1603,13 +1733,13 @@ copy_ctinfo (CI dest, CI src) dest->ci_subtype = src->ci_subtype ? mh_xstrdup (src->ci_subtype) : NULL; for (s_pm = src->ci_first_pm; s_pm; s_pm = s_pm->pm_next) { - d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name, - s_pm->pm_value, 0); - if (s_pm->pm_charset) { - d_pm->pm_charset = mh_xstrdup(s_pm->pm_charset); + d_pm = add_param(&dest->ci_first_pm, &dest->ci_last_pm, s_pm->pm_name, + s_pm->pm_value, 0); + if (s_pm->pm_charset) { + d_pm->pm_charset = mh_xstrdup(s_pm->pm_charset); } - if (s_pm->pm_lang) { - d_pm->pm_lang = mh_xstrdup(s_pm->pm_lang); + if (s_pm->pm_lang) { + d_pm->pm_lang = mh_xstrdup(s_pm->pm_lang); } } @@ -1624,10 +1754,9 @@ copy_ctinfo (CI dest, CI src) static int decode_part (CT ct) { - char *tmp_decoded; - int status; + char *tempfile, *tmp_decoded; FILE *file; - char *tempfile; + int status; if ((tempfile = m_mktemp2 (NULL, invo_name, NULL, &file)) == NULL) { die("unable to create temporary file in %s", get_temp_dir()); @@ -1647,6 +1776,134 @@ decode_part (CT ct) } +/* + * If base64-encoded content has a text trailer, return the location, relative + * to c->c_begin, where the valid base64 ends. And return the trailer in the + * addresses pointed to by remainderp. The caller is responsible for + * deallocating that. If no text trailer, return ct->c_end - ct->c_begin and + * leave remainderp unchanged. + */ +static size_t +get_valid_base64 (CT ct, char **remainderp) { + const size_t len = ct->c_end - ct->c_begin; + char *buf, format[16]; + size_t pos; + int fd; + + if (! ct->c_fp && ((ct->c_fp = fopen (ct->c_file, "r")) == NULL)) { + advise (ct->c_file, "unable to open for reading"); + return NOTOK; + } + if ((fd = fileno (ct->c_fp)) == -1 || + lseek (fd, ct->c_begin, SEEK_SET) == (off_t) -1) { + advise (ct->c_file, "unable to seek in"); + return NOTOK; + } + buf = mh_xmalloc(len + 1); + snprintf(format, sizeof format, "%%%luc", (unsigned long) len); + if (fscanf(ct->c_fp, format, buf) == EOF) { + advise (ct->c_file, "unable to read"); + return NOTOK; + } + buf[len] = '\0'; + + pos = find_invalid_base64_pos(buf); + + if (ct->c_begin + pos < (size_t) ct->c_end) { + *remainderp = mh_xstrdup(&buf[pos]); + } else { + pos = ct->c_end - ct->c_begin; + } + free(buf); + + return pos; +} + + +/* + * Find position in byte string of invalid base64 code. Skip individual + * invalid characters because RFC 2045 Sec 6.8 says they should be ignored. + * The motivating use case is a text footer that was mistakenly applied to + * base64 content. Therefore, if any of these is found, return the position + * of: + * 1. The byte (or end) after one or two consecutive pad ('=') bytes. + * 2. The first of a pair of invalid base64 bytes. + * + * If the base64 code is valid, return the position of the null terminator. + * + * encoded - the base64-encoded string + */ +static size_t +find_invalid_base64_pos (const char *encoded) { + const char *cp; + size_t pos; + bool found_pad = false; + unsigned int found_invalid = 0; + + for (cp = encoded, pos = 0; + *cp && ! found_pad && found_invalid < 2; + ++cp, ++pos) { + if (isspace ((unsigned char) *cp) || + isalnum ((unsigned char) *cp) || + *cp == '+' || *cp == '/' || *cp == '=') { + /* Valid base64 byte. */ + if (*cp == '=') { + /* "evidence that the end of the data has been reached" + according to RFC 2045 */ + found_pad = true; + } + /* Require consecutive invalid bytes. Let decodeBase64() handle + individual ones. */ + found_invalid = 0; + } else { + ++found_invalid; + } + } + + if (found_pad && *cp && *cp == '=') { + /* Skip over last in pair of ==. */ + ++cp, ++pos; + } else if (found_invalid == 2) { + /* If a pair of consecutive invalid bytes, back up to first one. */ + --cp, --pos; + --cp, --pos; + } + + /* Skip over any trailing whitespace. */ + while (*cp && isspace((unsigned char) *cp)) { + ++cp, ++pos; + } + + return pos; +} + + +/* + * Check for valid base64 encoding, and "fix" if invalid. + */ +static int +check_base64_encoding (CT *ctp) +{ + char *remainder = NULL; + int status = OK; + + /* If there's a footer after base64 content, set c_end to before it, and + store the footer in remainder. */ + (*ctp)->c_end = (*ctp)->c_begin + get_valid_base64(*ctp, &remainder); + + if (remainder != NULL) { + /* Move ct to a subpart of a new multipart/related, and add the + remainder as a new text/plain subpart of it. */ + int ignore_message_mods = 0; + + status = insert_into_new_mp_mixed(ctp, remainder, &ignore_message_mods); + free(remainder); + } + + return status; +} + + /* * Reformat content as plain text. * Some of the arguments aren't really needed now, but maybe will @@ -1717,13 +1974,15 @@ reformat_part (CT ct, char *file, char *type, char *subtype, int c_type) /* - * Fill in a multipart/alternative part. + * Create and fill in a multipart part. */ static CT -build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) +build_multipart (CT first_part, CT new_part, int type, int subtype) { char *boundary_prefix = "----=_nmh-multipart"; - char *boundary = concat (boundary_prefix, first_alt->c_partno, NULL); + static unsigned int bp_uses = 0; + char bp_uses_buf[16]; + char *boundary; char *boundary_indicator = "; boundary="; char *typename, *subtypename, *name; CT ct; @@ -1733,7 +1992,15 @@ build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) NEW0(ct); - /* Set up the multipart/alternative part. These fields of *ct were + if (bp_uses > 0) { + snprintf(bp_uses_buf, sizeof bp_uses_buf - 1, "-%d", bp_uses++); + } else { + bp_uses_buf[0] = '\0'; + } + boundary = + concat (boundary_prefix, bp_uses_buf, first_part->c_partno, NULL); + + /* Set up the multipart part. These fields of *ct were initialized to 0 by mh_xcalloc(): c_fp, c_unlink, c_begin, c_end, c_vrsn, c_ctline, c_celine, @@ -1746,7 +2013,7 @@ build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) c_showproc, c_termproc, c_storeproc, c_storage, c_folder */ - ct->c_file = mh_xstrdup (first_alt->c_file); + ct->c_file = mh_xstrdup (first_part->c_file); ct->c_type = type; ct->c_subtype = subtype; @@ -1793,7 +2060,7 @@ build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) snprintf (buffer2, sizeof buffer2, "%d", serial); boundary = concat (boundary_prefix, - FENDNULL(first_alt->c_partno), + FENDNULL(first_part->c_partno), "-", buffer2, NULL); } } @@ -1808,18 +2075,18 @@ build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) boundary, "\"", NULL); /* Load c_first_hf and c_last_hf. */ - transfer_noncontent_headers (first_alt, ct); + transfer_noncontent_headers (first_part, ct); add_header (ct, mh_xstrdup (TYPE_FIELD), concat (name, "\n", NULL)); free (name); /* Load c_partno. */ - if (first_alt->c_partno) { - ct->c_partno = mh_xstrdup (first_alt->c_partno); - free (first_alt->c_partno); - first_alt->c_partno = concat (ct->c_partno, ".1", NULL); + if (first_part->c_partno) { + ct->c_partno = mh_xstrdup (first_part->c_partno); + free (first_part->c_partno); + first_part->c_partno = concat (ct->c_partno, ".1", NULL); new_part->c_partno = concat (ct->c_partno, ".2", NULL); } else { - first_alt->c_partno = mh_xstrdup ("1"); + first_part->c_partno = mh_xstrdup ("1"); new_part->c_partno = mh_xstrdup ("2"); } @@ -1834,7 +2101,7 @@ build_multipart_alt (CT first_alt, CT new_part, int type, int subtype) NEW(p); NEW(p->mp_next); p->mp_next->mp_next = NULL; - p->mp_next->mp_part = first_alt; + p->mp_next->mp_part = first_part; NEW0(m); m->mp_start = concat (boundary, "\n", NULL); @@ -2529,37 +2796,37 @@ convert_charsets (CT ct, char *dest_charset, int *message_mods) * headers, respectively. */ static int -fix_always (CT ct, int *message_mods) +fix_always (CT *ctp, const fix_transformations *fx, int *message_mods) { int status = OK; - switch (ct->c_type) { + switch ((*ctp)->c_type) { case CT_MULTIPART: { - struct multipart *m = (struct multipart *) ct->c_ctparams; + struct multipart *m = (struct multipart *) (*ctp)->c_ctparams; struct part *part; for (part = m->mp_parts; status == OK && part; part = part->mp_next) { - status = fix_always (part->mp_part, message_mods); + status = fix_always (&part->mp_part, fx, message_mods); } break; } case CT_MESSAGE: - if (ct->c_subtype == MESSAGE_EXTERNAL) { - struct exbody *e = (struct exbody *) ct->c_ctparams; + if ((*ctp)->c_subtype == MESSAGE_EXTERNAL) { + struct exbody *e = (struct exbody *) (*ctp)->c_ctparams; - status = fix_always (e->eb_content, message_mods); + status = fix_always (&e->eb_content, fx, message_mods); } break; default: { HF hf; - if (ct->c_first_hf) { - fix_filename_encoding (ct); + if ((*ctp)->c_first_hf) { + fix_filename_encoding (*ctp); } - for (hf = ct->c_first_hf; hf; hf = hf->next) { + for (hf = (*ctp)->c_first_hf; hf; hf = hf->next) { size_t len = strlen (hf->value); if (strcasecmp (hf->name, TYPE_FIELD) != 0 && @@ -2583,31 +2850,95 @@ fix_always (CT ct, int *message_mods) hf->value[len - 1] = '\0'; /* Also, if Content-Type parameter, remove trailing ';' - from ct->c_ctline. This probably isn't necessary + from (*ctp)->c_ctline. This probably isn't necessary but can't hurt. */ - if (strcasecmp(hf->name, TYPE_FIELD) == 0 && ct->c_ctline) { - size_t l = strlen(ct->c_ctline) - 1; - while (isspace((unsigned char)(ct->c_ctline[l])) || - ct->c_ctline[l] == ';') { - ct->c_ctline[l--] = '\0'; + if (strcasecmp(hf->name, TYPE_FIELD) == 0 && (*ctp)->c_ctline) { + size_t l = strlen((*ctp)->c_ctline) - 1; + while (isspace((unsigned char)((*ctp)->c_ctline[l])) || + (*ctp)->c_ctline[l] == ';') { + (*ctp)->c_ctline[l--] = '\0'; if (l == 0) { break; } } } ++*message_mods; if (verbosw) { - report (NULL, ct->c_partno, ct->c_file, + report (NULL, (*ctp)->c_partno, (*ctp)->c_file, "remove trailing ; from %s parameter value", hf->name); } } } + + if (fx->checkbase64 && (*ctp)->c_encoding == CE_BASE64) { + status = check_base64_encoding (ctp); + } }} return status; } +/* + * 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(). */