Skip to content

Commit 024bbfc

Browse files
committed
Mail: Improve multipart message handling in wp_mail().
This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in #9500 Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes #15448. git-svn-id: https://develop.svn.wordpress.org/trunk@61201 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 56ac994 commit 024bbfc

File tree

2 files changed

+91
-6
lines changed

2 files changed

+91
-6
lines changed

src/wp-includes/pluggable.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ function cache_users( $user_ids ) {
174174
* @since 5.5.0 is_email() is used for email validation,
175175
* instead of PHPMailer's default validator.
176176
* @since 6.9.0 Added $embeds parameter.
177+
* @since 6.9.0 Improved Content-Type header handling for multipart messages.
177178
*
178179
* @global PHPMailer\PHPMailer\PHPMailer $phpmailer
179180
*
@@ -351,6 +352,9 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
351352
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
352353
$boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
353354
$charset = '';
355+
if ( preg_match( '~^multipart/(\S+)~', $content_type, $matches ) ) {
356+
$content_type = 'multipart/' . strtolower( $matches[1] ) . '; boundary="' . $boundary . '"';
357+
}
354358
}
355359

356360
// Avoid setting an empty $content_type.
@@ -547,10 +551,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
547551
}
548552
}
549553
}
550-
551-
if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) {
552-
$phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) );
553-
}
554554
}
555555

556556
if ( ! empty( $attachments ) ) {

tests/phpunit/tests/pluggable/wpMail.php

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,11 @@ public function test_wp_mail_custom_boundaries() {
8383

8484
// We need some better assertions here but these catch the failure for now.
8585
$this->assertSameIgnoreEOL( $body, $mailer->get_sent()->body );
86-
$this->assertStringContainsString( 'boundary="----=_Part_4892_25692638.1192452070893"', iconv_mime_decode_headers( ( $mailer->get_sent()->header ) )['Content-Type'][0] );
87-
$this->assertStringContainsString( 'charset=', $mailer->get_sent()->header );
86+
$headers = iconv_mime_decode_headers( $mailer->get_sent()->header );
87+
$this->assertArrayHasKey( 'Content-Type', $headers, 'Expected Content-Type header to be sent.' );
88+
$content_type_headers = (array) $headers['Content-Type'];
89+
$this->assertCount( 1, $content_type_headers, "Expected only one Content-Type header to be sent. Saw:\n" . implode( "\n", $content_type_headers ) );
90+
$this->assertSame( 'multipart/mixed; boundary="----=_Part_4892_25692638.1192452070893"; charset=', $content_type_headers[0], 'Expected Content-Type to match.' );
8891
}
8992

9093
/**
@@ -669,4 +672,86 @@ public function test_wp_mail_encoding_does_not_bleed() {
669672
$mailer = tests_retrieve_phpmailer_instance();
670673
$this->assertEquals( '7bit', $mailer->Encoding );
671674
}
675+
676+
/**
677+
* Test that wp_mail() can send a multipart/alternative email with plain text and html versions.
678+
*
679+
* @ticket 15448
680+
*/
681+
public function test_wp_mail_plain_and_html() {
682+
$headers = 'Content-Type: multipart/alternative; boundary="TestBoundary"';
683+
684+
$subject = 'Test email with plain text and html versions';
685+
$message = <<<EOT
686+
--TestBoundary
687+
Content-Type: text/plain; charset=us-ascii
688+
689+
Here is some plain text.
690+
--TestBoundary
691+
Content-Type: text/html; charset=UTF-8
692+
Content-Transfer-Encoding: 8bit
693+
694+
<html><head></head><body>Here is the HTML with UTF-8 γειά σου Κόσμε;-)<body></html>
695+
--TestBoundary--
696+
EOT;
697+
698+
wp_mail( $to, $subject, $message, $headers );
699+
$mailer = tests_retrieve_phpmailer_instance();
700+
701+
$this->assertSame( 1, preg_match( '/boundary="(.*)"/', $mailer->get_sent()->header, $matches ), 'Expected to match boundary directive in header.' );
702+
$boundary = $matches[1];
703+
$body = '--' . $boundary . "\n";
704+
$body .= 'Content-Type: text/plain; charset=us-ascii' . "\n";
705+
$body .= "\n";
706+
$body .= 'Here is some plain text.' . "\n";
707+
$body .= '--' . $boundary . "\n";
708+
$body .= 'Content-Type: text/html; charset=UTF-8' . "\n";
709+
$body .= 'Content-Transfer-Encoding: 8bit' . "\n";
710+
$body .= "\n";
711+
$body .= '<html><head></head><body>Here is the HTML with UTF-8 γειά σου Κόσμε;-)<body></html>' . "\n";
712+
$body .= '--' . $boundary . '--' . "\n";
713+
714+
$this->assertSameIgnoreEOL( $body, $mailer->get_sent()->body, 'The body is not as expected.' );
715+
$this->assertStringContainsString(
716+
'Content-Type: multipart/alternative;',
717+
$mailer->get_sent()->header,
718+
'The multipart/alternative header is not present.'
719+
);
720+
}
721+
722+
/**
723+
* Check workarounds using phpmailer_init still work around.
724+
*
725+
* @ticket 15448
726+
*/
727+
public function test_wp_mail_plain_and_html_workaround() {
728+
729+
$subject = 'Test email with plain text derived from html version';
730+
$message = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><p>Hello World! γειά σου Κόσμε</p></body></html>';
731+
732+
$set_alt_body = static function ( WP_PHPMailer $mailer ) {
733+
$mailer->AltBody = strip_tags( $mailer->Body );
734+
};
735+
add_action( 'phpmailer_init', $set_alt_body );
736+
wp_mail( $to, $subject, $message );
737+
remove_action( 'phpmailer_init', $set_alt_body );
738+
739+
$mailer = tests_retrieve_phpmailer_instance();
740+
741+
$this->assertStringContainsString(
742+
'Content-Type: multipart/alternative;',
743+
$mailer->get_sent()->header,
744+
'The multipart/alternative header is not present.'
745+
);
746+
$this->assertStringContainsString(
747+
'Content-Type: text/plain; charset=UTF-8',
748+
$mailer->get_sent()->body,
749+
'The text/plain Content-Type header is not present.'
750+
);
751+
$this->assertStringContainsString(
752+
'Content-Type: text/html; charset=UTF-8',
753+
$mailer->get_sent()->body,
754+
'The text/html Content-Type header is not present.'
755+
);
756+
}
672757
}

0 commit comments

Comments
 (0)