+/*
+ * 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().
+ */
+static int
+fix_filename_param (char *name, char *value, PM *first_pm, PM *last_pm)
+{
+ bool fixed = false;
+
+ if (has_prefix(value, "=?") && has_suffix(value, "?=")) {
+ /* Looks like an RFC 2047 encoded parameter. */
+ char decoded[PATH_MAX + 1];
+
+ if (decode_rfc2047 (value, decoded, sizeof decoded)) {
+ /* Encode using RFC 2231. */
+ replace_param (first_pm, last_pm, name, decoded, 0);
+ fixed = true;
+ } else {
+ inform("failed to decode %s parameter %s", name, value);
+ }
+ }
+
+ return fixed;
+}
+
+
+/*
+ * Replace RFC 2047 encoding with RFC 2231 encoding of name and
+ * filename parameters in Content-Type and Content-Disposition
+ * headers, respectively.
+ */
+static int
+fix_filename_encoding (CT ct)
+{
+ PM pm;
+ HF hf;
+ int fixed = 0;
+
+ for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
+ if (pm->pm_name && pm->pm_value &&
+ strcasecmp (pm->pm_name, "name") == 0) {
+ fixed = fix_filename_param (pm->pm_name, pm->pm_value,
+ &ct->c_ctinfo.ci_first_pm,
+ &ct->c_ctinfo.ci_last_pm);
+ }
+ }
+
+ for (pm = ct->c_dispo_first; pm; pm = pm->pm_next) {
+ if (pm->pm_name && pm->pm_value &&
+ strcasecmp (pm->pm_name, "filename") == 0) {
+ fixed = fix_filename_param (pm->pm_name, pm->pm_value,
+ &ct->c_dispo_first,
+ &ct->c_dispo_last);
+ }
+ }
+
+ /* Fix hf values to correspond. */
+ for (hf = ct->c_first_hf; fixed && hf; hf = hf->next) {
+ enum { OTHER, TYPE_HEADER, DISPO_HEADER } field = OTHER;
+
+ if (strcasecmp (hf->name, TYPE_FIELD) == 0) {
+ field = TYPE_HEADER;
+ } else if (strcasecmp (hf->name, DISPO_FIELD) == 0) {
+ field = DISPO_HEADER;
+ }
+
+ if (field != OTHER) {
+ const char *const semicolon_loc = strchr (hf->value, ';');
+
+ if (semicolon_loc) {
+ const size_t len =
+ strlen (hf->name) + 1 + semicolon_loc - hf->value;
+ const char *const params =
+ output_params (len,
+ field == TYPE_HEADER
+ ? ct->c_ctinfo.ci_first_pm
+ : ct->c_dispo_first,
+ NULL, 0);
+ const char *const new_params = concat (params, "\n", NULL);
+
+ replace_substring (&hf->value, semicolon_loc, new_params);
+ free((void *)new_params); /* Cast away const. Sigh. */
+ free((void *)params);
+ } else {
+ inform("did not find semicolon in %s:%s\n",
+ hf->name, hf->value);
+ }
+ }
+ }
+
+ return OK;
+}
+
+