diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index e1965df29d..22e4aa63dd 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -162,6 +162,11 @@ public static function decode_mime_string($input, $fallback = null) { $default_charset = $fallback ?: self::get_charset(); + // RFC 6532: detect raw UTF-8 in headers to avoid wrong charset conversion + if ($fallback !== false && mb_check_encoding($input, 'UTF-8') && preg_match('/[\x80-\xFF]/', $input)) { + $default_charset = 'UTF-8'; + } + // rfc: all line breaks or other characters not found // in the Base64 Alphabet must be ignored by decoding software // delete all blanks between MIME-lines, differently we can diff --git a/tests/Framework/MimeTest.php b/tests/Framework/MimeTest.php index 1cf3c63713..348f8f7cf2 100644 --- a/tests/Framework/MimeTest.php +++ b/tests/Framework/MimeTest.php @@ -216,6 +216,23 @@ public function test_header_decode_qp() } } + /** + * Test decoding of raw UTF-8 headers (RFC 6532) + * Uses rcube_mime::decode_mime_string() + */ + public function test_header_decode_raw_utf8() + { + // Raw UTF-8 Korean with wrong fallback charset - should be preserved + $korean = "\xEC\x95\x88\xEB\x85\x95\xED\x95\x98\xEC\x84\xB8\xEC\x9A\x94"; // 안녕하세요 + $res = \rcube_mime::decode_mime_string($korean, 'ISO-8859-1'); + $this->assertSame($korean, $res, 'Raw UTF-8 header with Latin-1 fallback'); + + // Latin-1 high bytes (not valid UTF-8) - should still be converted + $latin1 = "caf\xE9"; // café in ISO-8859-1 + $res = \rcube_mime::decode_mime_string($latin1, 'ISO-8859-1'); + $this->assertSame('café', $res, 'Latin-1 header should be converted'); + } + /** * Test headers parsing */