From af068cdbe0089ccd70d3d8975f21893388b9232c Mon Sep 17 00:00:00 2001 From: Boone Gorges Date: Wed, 13 Jul 2016 18:03:52 +0000 Subject: [PATCH] Mail: Improve handling of UTF-8 address headers. Previously, `wp_mail()` implemented Reply-To as a generic header, using PHPMailer's `addCustomHeader()`. As such, the email address portion of the header was being incorrectly encoded when the name portion contained UTF-8 characters. Switching to PHPMailer's more specific `addReplyTo()` method fixes the issue. For greater readability, the handling of all address-related headers (To, CC, BCC, Reply-To) has been standardized. Props szepe.viktor, iandunn, bpetty, stephenharris. Fixes #21659. git-svn-id: https://develop.svn.wordpress.org/trunk@38058 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/pluggable.php | 78 ++++++++++++-------------- tests/phpunit/includes/mock-mailer.php | 2 +- tests/phpunit/tests/mail.php | 35 ++++++++++++ 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index a9d350489b..511e6acac6 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -216,6 +216,8 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() } // Headers + $cc = $bcc = $reply_to = array(); + if ( empty( $headers ) ) { $headers = array(); } else { @@ -227,8 +229,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() $tempheaders = $headers; } $headers = array(); - $cc = array(); - $bcc = array(); // If it's actually got contents if ( !empty( $tempheaders ) ) { @@ -291,6 +291,9 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() case 'bcc': $bcc = array_merge( (array) $bcc, explode( ',', $content ) ); break; + case 'reply-to': + $reply_to = array_merge( (array) $reply_to, explode( ',', $content ) ); + break; default: // Add it to our grand headers array $headers[trim( $name )] = trim( $content ); @@ -335,7 +338,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() * * @param string $from_email Email address to send from. */ - $phpmailer->From = apply_filters( 'wp_mail_from', $from_email ); + $from_email = apply_filters( 'wp_mail_from', $from_email ); /** * Filters the name to associate with the "from" email address. @@ -344,63 +347,52 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() * * @param string $from_name Name associated with the "from" email address. */ - $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name ); + $from_name = apply_filters( 'wp_mail_from_name', $from_name ); + + $phpmailer->setFrom( $from_email, $from_name ); // Set destination addresses if ( !is_array( $to ) ) $to = explode( ',', $to ); - foreach ( (array) $to as $recipient ) { - try { - // Break $recipient into name and address parts if in the format "Foo " - $recipient_name = ''; - if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) { - if ( count( $matches ) == 3 ) { - $recipient_name = $matches[1]; - $recipient = $matches[2]; - } - } - $phpmailer->AddAddress( $recipient, $recipient_name); - } catch ( phpmailerException $e ) { - continue; - } - } - // Set mail's subject and body $phpmailer->Subject = $subject; $phpmailer->Body = $message; - // Add any CC and BCC recipients - if ( !empty( $cc ) ) { - foreach ( (array) $cc as $recipient ) { - try { - // Break $recipient into name and address parts if in the format "Foo " - $recipient_name = ''; - if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) { - if ( count( $matches ) == 3 ) { - $recipient_name = $matches[1]; - $recipient = $matches[2]; - } - } - $phpmailer->AddCc( $recipient, $recipient_name ); - } catch ( phpmailerException $e ) { - continue; - } - } - } + // Use appropriate methods for handling addresses, rather than treating them as generic headers + $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' ); - if ( !empty( $bcc ) ) { - foreach ( (array) $bcc as $recipient) { + foreach ( $address_headers as $address_header => $addresses ) { + if ( empty( $addresses ) ) { + continue; + } + + foreach ( (array) $addresses as $address ) { try { // Break $recipient into name and address parts if in the format "Foo " $recipient_name = ''; - if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) { + + if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) { if ( count( $matches ) == 3 ) { $recipient_name = $matches[1]; - $recipient = $matches[2]; + $address = $matches[2]; } } - $phpmailer->AddBcc( $recipient, $recipient_name ); + + switch ( $address_header ) { + case 'to': + $phpmailer->addAddress( $address, $recipient_name ); + break; + case 'cc': + $phpmailer->addCc( $address, $recipient_name ); + break; + case 'bcc': + $phpmailer->addBcc( $address, $recipient_name ); + break; + case 'reply_to': + $phpmailer->addReplyTo( $address, $recipient_name ); + break; + } } catch ( phpmailerException $e ) { continue; } diff --git a/tests/phpunit/includes/mock-mailer.php b/tests/phpunit/includes/mock-mailer.php index c91a67aa19..7acfd579ba 100644 --- a/tests/phpunit/includes/mock-mailer.php +++ b/tests/phpunit/includes/mock-mailer.php @@ -17,7 +17,7 @@ class MockPHPMailer extends PHPMailer { 'to' => $this->to, 'cc' => $this->cc, 'bcc' => $this->bcc, - 'header' => $this->MIMEHeader, + 'header' => $this->MIMEHeader . $this->mailHeader, 'subject' => $this->Subject, 'body' => $this->MIMEBody, ); diff --git a/tests/phpunit/tests/mail.php b/tests/phpunit/tests/mail.php index 5142c72d92..1df6ea6b8a 100644 --- a/tests/phpunit/tests/mail.php +++ b/tests/phpunit/tests/mail.php @@ -307,4 +307,39 @@ class Tests_Mail extends WP_UnitTestCase { $this->assertNotContains( 'quoted-printable', $GLOBALS['phpmailer']->mock_sent[0]['header'] ); } + + /** + * @ticket 21659 + */ + public function test_wp_mail_addresses_arent_encoded() { + $to = 'Lukáš To '; + $subject = 'Testing #21659'; + $message = 'Only the name should be encoded, not the address.'; + + $headers = array( + 'From' => 'From: Lukáš From ', + 'Cc' => 'Cc: Lukáš CC ', + 'Bcc' => 'Bcc: Lukáš BCC ', + 'Reply-To' => 'Reply-To: Lukáš Reply-To ', + ); + + $expected = array( + 'To' => 'To: =?UTF-8?B?THVrw6HFoSBUbw==?= ', + 'From' => 'From: =?UTF-8?Q?Luk=C3=A1=C5=A1_From?= ', + 'Cc' => 'Cc: =?UTF-8?B?THVrw6HFoSBDQw==?= ', + 'Bcc' => 'Bcc: =?UTF-8?B?THVrw6HFoSBCQ0M=?= ', + 'Reply-To' => 'Reply-To: =?UTF-8?Q?Luk=C3=A1=C5=A1_Reply-To?= ', + ); + + wp_mail( $to, $subject, $message, array_values( $headers ) ); + + $mailer = tests_retrieve_phpmailer_instance(); + $sent_headers = preg_split( "/\r\n|\n|\r/", $mailer->get_sent()->header ); + $headers['To'] = "To: $to"; + + foreach ( $headers as $header => $value ) { + $target_headers = preg_grep( "/^$header:/", $sent_headers ); + $this->assertEquals( $expected[ $header ], array_pop( $target_headers ) ); + } + } }