From f0cea67d9517e1febf1a84e0f55e559c438317a5 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Tue, 22 Jul 2025 18:33:33 +0530 Subject: [PATCH 1/5] Mail: Fix multiline From header parsing in wp_mail() --- src/wp-includes/pluggable.php | 13 +++++++++++++ tests/phpunit/tests/pluggable/wpMail.php | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 5edd0c760cbb2..ce1793291ac58 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -298,6 +298,15 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() * both string headers and an array of headers. */ $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); + + // Line which starts with space is a continuation of previous line, need to keep them as one + for ( $index = 0; $index < count( $tempheaders ); $index++ ) { + if ( $index > 0 && $tempheaders[ $index ] && ' ' === $tempheaders[ $index ][0] ) { + $tempheaders[ $index - 1 ] .= "\n" . $tempheaders[ $index ]; + array_splice( $tempheaders, $index, 1 ); + --$index; + } + } } else { $tempheaders = $headers; } @@ -331,6 +340,10 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() $from_name = substr( $content, 0, $bracket_pos ); $from_name = str_replace( '"', '', $from_name ); $from_name = trim( $from_name ); + // Only decode MIME headers if content contains newlines (multiline headers) + if ( function_exists( 'mb_decode_mimeheader' ) && str_contains( $content, "\n" ) ) { + $from_name = mb_decode_mimeheader( $from_name ); + } } $from_email = substr( $content, $bracket_pos + 1 ); diff --git a/tests/phpunit/tests/pluggable/wpMail.php b/tests/phpunit/tests/pluggable/wpMail.php index 85e3b46f61a5b..0cb5a4764162e 100644 --- a/tests/phpunit/tests/pluggable/wpMail.php +++ b/tests/phpunit/tests/pluggable/wpMail.php @@ -625,4 +625,22 @@ public function test_wp_mail_string_embeds() { $this->assertStringContainsString( 'cid:' . $key, $mailer->get_sent()->body, 'The cid ' . $key . ' is not referenced in the mail body.' ); } } + + /** + * @ticket 28473 + */ + public function test_wp_mail_multiline_header() { + $headers = 'From: =?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg?='; + $headers .= "\n =?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YI=?= "; + wp_mail( 'test@test.com', 'subject', 'message', $headers ); + + $mailer = tests_retrieve_phpmailer_instance(); + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $this->assertSame( 'test@example.com', $mailer->From ); + $this->assertSame( + 'тест тест тест тест тест тест тест', + $mailer->FromName + ); + // phpcs:enable + } } From 953ba026c17b3df94d2f61f5db2ad915de9d3a5f Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Mon, 28 Jul 2025 21:19:57 +0530 Subject: [PATCH 2/5] Mail: Improve comment for header continuation handling in wp_mail() --- src/wp-includes/pluggable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index ce1793291ac58..97cac88451b4a 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -299,7 +299,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() */ $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); - // Line which starts with space is a continuation of previous line, need to keep them as one + // Line which starts with space is a continuation of previous line, need to keep them as one. for ( $index = 0; $index < count( $tempheaders ); $index++ ) { if ( $index > 0 && $tempheaders[ $index ] && ' ' === $tempheaders[ $index ][0] ) { $tempheaders[ $index - 1 ] .= "\n" . $tempheaders[ $index ]; From 18683129ef5c551962591a14e006e07e96fa6252 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Tue, 5 Aug 2025 16:07:11 +0530 Subject: [PATCH 3/5] Mail: Enhance wp_mail() header parsing to handle whitespace and MIME decoding more effectively --- src/wp-includes/pluggable.php | 8 ++++---- tests/phpunit/tests/pluggable/wpMail.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 97cac88451b4a..51871b7d0b24e 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -299,9 +299,9 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() */ $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); - // Line which starts with space is a continuation of previous line, need to keep them as one. + // Line which starts with whitespace (space or tab) is a continuation of previous line, need to keep them as one. for ( $index = 0; $index < count( $tempheaders ); $index++ ) { - if ( $index > 0 && $tempheaders[ $index ] && ' ' === $tempheaders[ $index ][0] ) { + if ( $index > 0 && isset( $tempheaders[ $index ] ) && ( ' ' === $tempheaders[ $index ][0] || "\t" === $tempheaders[ $index ][0] ) ) { $tempheaders[ $index - 1 ] .= "\n" . $tempheaders[ $index ]; array_splice( $tempheaders, $index, 1 ); --$index; @@ -340,8 +340,8 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() $from_name = substr( $content, 0, $bracket_pos ); $from_name = str_replace( '"', '', $from_name ); $from_name = trim( $from_name ); - // Only decode MIME headers if content contains newlines (multiline headers) - if ( function_exists( 'mb_decode_mimeheader' ) && str_contains( $content, "\n" ) ) { + // Decode MIME headers if they contain encoded content or newlines + if ( function_exists( 'mb_decode_mimeheader' ) && ( str_contains( $content, "\n" ) || str_contains( $from_name, '=?' ) ) ) { $from_name = mb_decode_mimeheader( $from_name ); } } diff --git a/tests/phpunit/tests/pluggable/wpMail.php b/tests/phpunit/tests/pluggable/wpMail.php index 0cb5a4764162e..40d1c843ca010 100644 --- a/tests/phpunit/tests/pluggable/wpMail.php +++ b/tests/phpunit/tests/pluggable/wpMail.php @@ -643,4 +643,18 @@ public function test_wp_mail_multiline_header() { ); // phpcs:enable } + + /** + * @ticket 28473 + */ + public function test_wp_mail_single_line_utf8_header() { + $headers = 'From: =?UTF-8?B?VGVzdA==?= '; + wp_mail( 'test@test.com', 'subject', 'message', $headers ); + + $mailer = tests_retrieve_phpmailer_instance(); + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $this->assertSame( 'test@example.com', $mailer->From ); + $this->assertSame( 'Test', $mailer->FromName ); + // phpcs:enable + } } From 234b0695b0e2c40e096993b11f5b19566a1ea4c6 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Tue, 9 Sep 2025 16:44:44 +0530 Subject: [PATCH 4/5] Mail: Fix multiline From header parsing in wp_mail() Properly handle RFC 5322 folded headers and add MIME decoding support. --- src/wp-includes/pluggable.php | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 51871b7d0b24e..92218815e7832 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -294,17 +294,19 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() } else { if ( ! is_array( $headers ) ) { /* - * Explode the headers out, so this function can take - * both string headers and an array of headers. + * Process headers and handle folding in a single pass. + * Lines starting with whitespace (space or tab) are continuations of the previous line. */ - $tempheaders = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); - - // Line which starts with whitespace (space or tab) is a continuation of previous line, need to keep them as one. - for ( $index = 0; $index < count( $tempheaders ); $index++ ) { - if ( $index > 0 && isset( $tempheaders[ $index ] ) && ( ' ' === $tempheaders[ $index ][0] || "\t" === $tempheaders[ $index ][0] ) ) { - $tempheaders[ $index - 1 ] .= "\n" . $tempheaders[ $index ]; - array_splice( $tempheaders, $index, 1 ); - --$index; + $tempheaders = array(); + $normalized_headers = str_replace( "\r\n", "\n", $headers ); + + foreach ( explode( "\n", $normalized_headers ) as $header_line ) { + if ( ! empty( $tempheaders ) && isset( $header_line[0] ) && ( ' ' === $header_line[0] || "\t" === $header_line[0] ) ) { + // Continuation line - append to previous header. + $last_index = count( $tempheaders ) - 1; + $tempheaders[ $last_index ] .= "\n" . $header_line; + } else { + $tempheaders[] = $header_line; } } } else { From a9c8e97eb2b3508b55ce5c738fda28803d7da528 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Wed, 10 Sep 2025 22:21:31 +0530 Subject: [PATCH 5/5] Mail: Remove unnecessary MIME decoding for From header in wp_mail() --- src/wp-includes/pluggable.php | 4 ---- tests/phpunit/tests/pluggable/wpMail.php | 18 +++--------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index 92218815e7832..6141669101494 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -342,10 +342,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() $from_name = substr( $content, 0, $bracket_pos ); $from_name = str_replace( '"', '', $from_name ); $from_name = trim( $from_name ); - // Decode MIME headers if they contain encoded content or newlines - if ( function_exists( 'mb_decode_mimeheader' ) && ( str_contains( $content, "\n" ) || str_contains( $from_name, '=?' ) ) ) { - $from_name = mb_decode_mimeheader( $from_name ); - } } $from_email = substr( $content, $bracket_pos + 1 ); diff --git a/tests/phpunit/tests/pluggable/wpMail.php b/tests/phpunit/tests/pluggable/wpMail.php index 40d1c843ca010..4ed0aeba8fd5a 100644 --- a/tests/phpunit/tests/pluggable/wpMail.php +++ b/tests/phpunit/tests/pluggable/wpMail.php @@ -627,6 +627,8 @@ public function test_wp_mail_string_embeds() { } /** + * Tests that wp_mail() correctly handles multiline From headers by unfolding them. + * * @ticket 28473 */ public function test_wp_mail_multiline_header() { @@ -638,23 +640,9 @@ public function test_wp_mail_multiline_header() { // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase $this->assertSame( 'test@example.com', $mailer->From ); $this->assertSame( - 'тест тест тест тест тест тест тест', + '=?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg?= =?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YI=?=', $mailer->FromName ); // phpcs:enable } - - /** - * @ticket 28473 - */ - public function test_wp_mail_single_line_utf8_header() { - $headers = 'From: =?UTF-8?B?VGVzdA==?= '; - wp_mail( 'test@test.com', 'subject', 'message', $headers ); - - $mailer = tests_retrieve_phpmailer_instance(); - // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $this->assertSame( 'test@example.com', $mailer->From ); - $this->assertSame( 'Test', $mailer->FromName ); - // phpcs:enable - } }