Make WordPress Core

Changeset 38058


Ignore:
Timestamp:
07/13/2016 06:03:52 PM (8 years ago)
Author:
boonebgorges
Message:

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.

Location:
trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/pluggable.php

    r38028 r38058  
    217217
    218218    // Headers
     219    $cc = $bcc = $reply_to = array();
     220
    219221    if ( empty( $headers ) ) {
    220222        $headers = array();
     
    228230        }
    229231        $headers = array();
    230         $cc = array();
    231         $bcc = array();
    232232
    233233        // If it's actually got contents
     
    292292                        $bcc = array_merge( (array) $bcc, explode( ',', $content ) );
    293293                        break;
     294                    case 'reply-to':
     295                        $reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
     296                        break;
    294297                    default:
    295298                        // Add it to our grand headers array
     
    336339     * @param string $from_email Email address to send from.
    337340     */
    338     $phpmailer->From = apply_filters( 'wp_mail_from', $from_email );
     341    $from_email = apply_filters( 'wp_mail_from', $from_email );
    339342
    340343    /**
     
    345348     * @param string $from_name Name associated with the "from" email address.
    346349     */
    347     $phpmailer->FromName = apply_filters( 'wp_mail_from_name', $from_name );
     350    $from_name = apply_filters( 'wp_mail_from_name', $from_name );
     351
     352    $phpmailer->setFrom( $from_email, $from_name );
    348353
    349354    // Set destination addresses
     
    351356        $to = explode( ',', $to );
    352357
    353     foreach ( (array) $to as $recipient ) {
    354         try {
    355             // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
    356             $recipient_name = '';
    357             if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
    358                 if ( count( $matches ) == 3 ) {
    359                     $recipient_name = $matches[1];
    360                     $recipient = $matches[2];
    361                 }
    362             }
    363             $phpmailer->AddAddress( $recipient, $recipient_name);
    364         } catch ( phpmailerException $e ) {
    365             continue;
    366         }
    367     }
    368 
    369358    // Set mail's subject and body
    370359    $phpmailer->Subject = $subject;
    371360    $phpmailer->Body    = $message;
    372361
    373     // Add any CC and BCC recipients
    374     if ( !empty( $cc ) ) {
    375         foreach ( (array) $cc as $recipient ) {
     362    // Use appropriate methods for handling addresses, rather than treating them as generic headers
     363    $address_headers = compact( 'to', 'cc', 'bcc', 'reply_to' );
     364
     365    foreach ( $address_headers as $address_header => $addresses ) {
     366        if ( empty( $addresses ) ) {
     367            continue;
     368        }
     369
     370        foreach ( (array) $addresses as $address ) {
    376371            try {
    377372                // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
    378373                $recipient_name = '';
    379                 if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
     374
     375                if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) {
    380376                    if ( count( $matches ) == 3 ) {
    381377                        $recipient_name = $matches[1];
    382                         $recipient = $matches[2];
     378                        $address        = $matches[2];
    383379                    }
    384380                }
    385                 $phpmailer->AddCc( $recipient, $recipient_name );
    386             } catch ( phpmailerException $e ) {
    387                 continue;
    388             }
    389         }
    390     }
    391 
    392     if ( !empty( $bcc ) ) {
    393         foreach ( (array) $bcc as $recipient) {
    394             try {
    395                 // Break $recipient into name and address parts if in the format "Foo <bar@baz.com>"
    396                 $recipient_name = '';
    397                 if ( preg_match( '/(.*)<(.+)>/', $recipient, $matches ) ) {
    398                     if ( count( $matches ) == 3 ) {
    399                         $recipient_name = $matches[1];
    400                         $recipient = $matches[2];
    401                     }
     381
     382                switch ( $address_header ) {
     383                    case 'to':
     384                        $phpmailer->addAddress( $address, $recipient_name );
     385                        break;
     386                    case 'cc':
     387                        $phpmailer->addCc( $address, $recipient_name );
     388                        break;
     389                    case 'bcc':
     390                        $phpmailer->addBcc( $address, $recipient_name );
     391                        break;
     392                    case 'reply_to':
     393                        $phpmailer->addReplyTo( $address, $recipient_name );
     394                        break;
    402395                }
    403                 $phpmailer->AddBcc( $recipient, $recipient_name );
    404396            } catch ( phpmailerException $e ) {
    405397                continue;
  • trunk/tests/phpunit/includes/mock-mailer.php

    r37358 r38058  
    1818            'cc'      => $this->cc,
    1919            'bcc'     => $this->bcc,
    20             'header'  => $this->MIMEHeader,
     20            'header'  => $this->MIMEHeader . $this->mailHeader,
    2121            'subject' => $this->Subject,
    2222            'body'    => $this->MIMEBody,
  • trunk/tests/phpunit/tests/mail.php

    r37358 r38058  
    308308        $this->assertNotContains( 'quoted-printable', $GLOBALS['phpmailer']->mock_sent[0]['header'] );
    309309    }
     310
     311    /**
     312     * @ticket 21659
     313     */
     314    public function test_wp_mail_addresses_arent_encoded() {
     315        $to      = 'Lukáš To <to@example.org>';
     316        $subject = 'Testing #21659';
     317        $message = 'Only the name should be encoded, not the address.';
     318
     319        $headers = array(
     320            'From'     => 'From: Lukáš From <from@example.org>',
     321            'Cc'       => 'Cc: Lukáš CC <cc@example.org>',
     322            'Bcc'      => 'Bcc: Lukáš BCC <bcc@example.org>',
     323            'Reply-To' => 'Reply-To: Lukáš Reply-To <reply_to@example.org>',
     324        );
     325
     326        $expected = array(
     327            'To'       => 'To: =?UTF-8?B?THVrw6HFoSBUbw==?= <to@example.org>',
     328            'From'     => 'From: =?UTF-8?Q?Luk=C3=A1=C5=A1_From?= <from@example.org>',
     329            'Cc'       => 'Cc: =?UTF-8?B?THVrw6HFoSBDQw==?= <cc@example.org>',
     330            'Bcc'      => 'Bcc: =?UTF-8?B?THVrw6HFoSBCQ0M=?= <bcc@example.org>',
     331            'Reply-To' => 'Reply-To: =?UTF-8?Q?Luk=C3=A1=C5=A1_Reply-To?= <reply_to@example.org>',
     332        );
     333
     334        wp_mail( $to, $subject, $message, array_values( $headers ) );
     335
     336        $mailer        = tests_retrieve_phpmailer_instance();
     337        $sent_headers  = preg_split( "/\r\n|\n|\r/", $mailer->get_sent()->header );
     338        $headers['To'] = "To: $to";
     339
     340        foreach ( $headers as $header => $value ) {
     341            $target_headers = preg_grep( "/^$header:/", $sent_headers );
     342            $this->assertEquals( $expected[ $header ], array_pop( $target_headers ) );
     343        }
     344    }
    310345}
Note: See TracChangeset for help on using the changeset viewer.