IMAP BODYSTRUCTURE returns RFC 2047-decoded MIME parameter values, corrupting attachment filenames

Issue Description

Summary

BODYSTRUCTURE returns MIME parameter values after RFC 2047 decoding. RFC 3501 §7.4.2 defines the body parameter list as the parameters as they appear in the message, leaving encoded-word decoding to the client. Because the decoded value no longer carries an =?charset?...?= marker, clients treat it as a raw 8-bit string and convert it from the part’s charset, which double-decodes non-ASCII attachment names into mojibake.

Environment

  • Stalwart v0.16.15 (x86_64-unknown-linux-gnu, RocksDB store), reproduced on a clean install
  • Also present in v0.15.5

Steps to reproduce

APPEND a message whose attachment name is encoded per RFC 2047 while the message charset is not UTF-8 (this is exactly what Microsoft Exchange emits):

Content-Type: multipart/mixed; boundary="BOUND1"

--BOUND1
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: quoted-printable

test body
--BOUND1
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document;
	name="=?koi8-r?B?+sHQ0s/TIObp8yD0+i5kb2N4?="
Content-Disposition: attachment;
	filename="=?koi8-r?B?+sHQ0s/TIObp8yD0+i5kb2N4?="; size=10
Content-Transfer-Encoding: base64

dGVzdA==
--BOUND1--

The encoded-word decodes to Запрос ФИС ТЗ.docx.

Then FETCH 1 (BODYSTRUCTURE).

Actual result

("name" "Запрос ФИС ТЗ.docx")   <- UTF-8 bytes d097 d0b0 d0bf d180 d0be d181 ...
... ("attachment" ("filename" "Запрос ФИС ТЗ.docx" "size" "10"))

The parameter is returned decoded, in UTF-8, with no indication of that having happened.

Expected result

The value as it appears in the message, i.e. "=?koi8-r?B?+sHQ0s/TIObp8yD0+i5kb2N4?=", so that the client performs the decoding.

Impact

A client that receives an unencoded parameter value has to fall back to the charset of the MIME part, as prescribed for raw 8-bit parameters. Roundcube does this in rcube_imap::set_part_filename()rcube_mime::decode_mime_string($filename, $charset). With charset=koi8-r the already-UTF-8 bytes get converted a second time and the user sees garbled text.

Messages whose charset is utf-8 are unaffected, so the corruption looks intermittent to users. In our production mailbox every message from an Exchange sender using koi8-r or windows-1251 is affected, while UTF-8 senders are fine.

Cause

mail-parser decodes encoded-words inside parameter values (src/parsers/fields/content_type.rs, decode_rfc2047() in the AttributeValue state), and crates/imap/src/op/fetch.rs forwards the parsed values verbatim:

ct.attributes
    .iter()
    .map(|k| (k.name.as_ref().into(), k.value.as_ref().into()))
    .collect::<Vec<_>>()

The same applies to Content-Disposition parameters and to Content-Description.

Suggested fix

Since the raw value is no longer available at that point, re-encode non-ASCII values into an RFC 2047 encoded-word before serializing them (this also matches what the original message carried). Patch that fixes it, verified on v0.16.15:

/// MIME parameter values stored in the message metadata have already been
/// RFC 2047-decoded by the parser. RFC 3501 (section 7.4.2) requires
/// BODYSTRUCTURE to expose parameters as they appear in the message, leaving
/// the decoding to the client, so non-ASCII values are encoded back into an
/// RFC 2047 encoded-word before being sent.
fn encode_param_value(value: &str) -> Cow<'_, str> {
    if value.is_ascii() {
        Cow::Borrowed(value)
    } else {
        Cow::Owned(format!(
            "=?utf-8?B?{}?=",
            base64::engine::general_purpose::STANDARD.encode(value.as_bytes())
        ))
    }
}

applied to the Content-Type parameters, the Content-Disposition parameters and Content-Description in crates/imap/src/op/fetch.rs.

RFC 2231 (filename*=utf-8''...) would be the formally cleaner encoding, but encoded-words are what the affected clients already handle in this position, and it keeps the filename key intact for clients that only look for it.

Verification

Built v0.16.15 with the patch and re-ran the same FETCH against the same mailbox:

("name" "=?utf-8?B?0JfQsNC/0YDQvtGBINCk0JjQoSDQotCXLmRvY3g=?=")
... ("attachment" ("filename" "=?utf-8?B?0JfQsNC/0YDQvtGBINCk0JjQoSDQotCXLmRvY3g=?=" "size" "10"))

which the client decodes back to Запрос ФИС ТЗ.docx. ASCII parameters (boundary, charset) are untouched, and Roundcube renders the name correctly.

Side note: raw 8-bit parameter values are lost

While testing the patch I checked the other case - a filename sent as raw 8-bit bytes without an encoded-word (filename="<cp1251 bytes>" with charset=windows-1251). Stock v0.16.15 returns every non-ASCII byte already replaced with U+FFFD by the time the value reaches fetch.rs, so the original bytes cannot be recovered by any client. This is independent of the patch above (the patched build behaves identically here) and looks like a separate issue in how non-UTF-8 parameter values are stored in the message metadata - happy to file it separately if useful.

I’m happy to open a PR with this change if the approach looks right.

(Originally filed as GitHub issue stalwartlabs/stalwart#3247, auto-closed by the bot pointing here.)

Stalwart Version

v0.16.x

Installation Method

Binary (Linux)

Database Backend

RocksDB

Blob Storage

RocksDB

Search Engine

Internal

Directory Backend

Internal

I have reviewed the documentation and FAQ and confirm that my issue is NOT addressed there.

on

I have searched this support forum (open and closed topics) and confirm this is not a duplicate.

on

I understand that topics in this category are triaged by a bot first but a human reply will follow up. If I’d prefer a human-only reply, I’ll add the no-ai tag to my topic.

on

This is a well-documented, code-level bug report with a proposed patch, not a documentation question, so I’ll acknowledge it plainly rather than speculate on a fix timeline.

Thanks for the detailed writeup and the reproduction case. This is a legitimate BODYSTRUCTURE spec-compliance issue (RFC 3501 §7.4.2 does call for unencoded parameter values, leaving RFC 2047 decoding to the client), and your analysis of where mail-parser/fetch.rs decode the value before serialization lines up with the described behavior. A staff member will need to confirm the intended fix (encoded-word re-encoding vs. RFC 2231 filename*= extended parameters) and whether the related raw 8-bit/U+FFFD data-loss issue should be tracked separately.

This isn’t something I can resolve from the docs, a human maintainer will need to review the patch and follow up here.

This is an automated reply from the Stalwart Help Bot. Other community members may follow up if this answer is incomplete or wrong.

Fixed in v0.16.17 (yet to be released).