Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 85 additions & 91 deletions src/wp-includes/pluggable.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,42 +233,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
return $pre_wp_mail;
}

if ( isset( $atts['to'] ) ) {
$to = $atts['to'];
}

if ( ! is_array( $to ) ) {
$to = explode( ',', $to );
}

if ( isset( $atts['subject'] ) ) {
$subject = $atts['subject'];
}

if ( isset( $atts['message'] ) ) {
$message = $atts['message'];
}

if ( isset( $atts['headers'] ) ) {
$headers = $atts['headers'];
}

if ( isset( $atts['attachments'] ) ) {
$attachments = $atts['attachments'];
}

if ( ! is_array( $attachments ) ) {
$attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
}

if ( isset( $atts['embeds'] ) ) {
$embeds = $atts['embeds'];
}

if ( ! is_array( $embeds ) ) {
$embeds = explode( "\n", str_replace( "\r\n", "\n", $embeds ) );
}

global $phpmailer;

// (Re)create it, if it's gone missing.
Expand All @@ -284,10 +248,15 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
};
}

// Headers.
// Process Headers First.
$cc = array();
$bcc = array();
$reply_to = array();
$charset = get_bloginfo( 'charset' );

if ( isset( $atts['headers'] ) ) {
$headers = $atts['headers'];
}

if ( empty( $headers ) ) {
$headers = array();
Expand All @@ -301,11 +270,10 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
} else {
$tempheaders = $headers;
}
$headers = array();
$headers = array();
$parsed_headers = array();

// If it's actually got contents.
if ( ! empty( $tempheaders ) ) {
// Iterate through the raw headers.
foreach ( (array) $tempheaders as $header ) {
if ( ! str_contains( $header, ':' ) ) {
if ( false !== stripos( $header, 'boundary=' ) ) {
Expand All @@ -318,64 +286,98 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
list( $name, $content ) = explode( ':', trim( $header ), 2 );

// Cleanup crew.
$name = trim( $name );
$name = strtolower( trim( $name ) );
$content = trim( $content );

switch ( strtolower( $name ) ) {
// Mainly for legacy -- process a "From:" header if it's there.
case 'from':
$bracket_pos = strpos( $content, '<' );
if ( false !== $bracket_pos ) {
// Text before the bracketed email is the "From" name.
if ( $bracket_pos > 0 ) {
$from_name = substr( $content, 0, $bracket_pos );
$from_name = str_replace( '"', '', $from_name );
$from_name = trim( $from_name );
}
$parsed_headers[ $name ] = $content;
}

$from_email = substr( $content, $bracket_pos + 1 );
$from_email = str_replace( '>', '', $from_email );
$from_email = trim( $from_email );
/**
* Set the charset from Content-Type, if present.
*/
if ( isset( $parsed_headers['content-type'] ) ) {
$content = $parsed_headers['content-type'];
if ( str_contains( $content, ';' ) ) {
list( $type, $charset_content ) = explode( ';', $content );
$content_type = trim( $type );
if ( false !== stripos( $charset_content, 'charset=' ) ) {
$charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
$boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
}

// Avoid setting an empty $from_email.
} elseif ( '' !== trim( $content ) ) {
$from_email = trim( $content );
}
break;
case 'content-type':
if ( str_contains( $content, ';' ) ) {
list( $type, $charset_content ) = explode( ';', $content );
$content_type = trim( $type );
if ( false !== stripos( $charset_content, 'charset=' ) ) {
$charset = trim( str_replace( array( 'charset=', '"' ), '', $charset_content ) );
} elseif ( false !== stripos( $charset_content, 'boundary=' ) ) {
$boundary = trim( str_replace( array( 'BOUNDARY=', 'boundary=', '"' ), '', $charset_content ) );
$charset = '';
}
// Avoid setting an empty $content_type.
} elseif ( '' !== trim( $content ) ) {
$content_type = trim( $content );
}
}

// Avoid setting an empty $content_type.
} elseif ( '' !== trim( $content ) ) {
$content_type = trim( $content );
foreach ( $parsed_headers as $name => $content ) {
switch ( $name ) {
case 'from':
if ( ! empty( $content ) ) {
$addresses = $phpmailer->parseAddresses( $content, null, $charset );
if ( ! empty( $addresses[0]['name'] ) ) {
$from_name = $addresses[0]['name'];
}
if ( ! empty( $addresses[0]['address'] ) ) {
$from_email = $addresses[0]['address'];
}
}
break;
case 'cc':
$cc = array_merge( (array) $cc, explode( ',', $content ) );
$cc = array_merge( (array) $cc, $phpmailer->parseAddresses( $content, null, $charset ) );
break;
case 'bcc':
$bcc = array_merge( (array) $bcc, explode( ',', $content ) );
$bcc = array_merge( (array) $bcc, $phpmailer->parseAddresses( $content, null, $charset ) );
break;
case 'reply-to':
$reply_to = array_merge( (array) $reply_to, explode( ',', $content ) );
$reply_to = array_merge( (array) $reply_to, $phpmailer->parseAddresses( $content, null, $charset ) );
break;
default:
// Add it to our grand headers array.
$headers[ trim( $name ) ] = trim( $content );
break;
}
}
$parsed_headers = array();
}
}

if ( isset( $atts['to'] ) ) {
$to = $atts['to'];
}

$raw_to = array();
if ( ! is_array( $to ) ) {
$raw_to = explode( ',', $to );
$to = $phpmailer->parseAddresses( $to, null, $charset );
}

if ( isset( $atts['subject'] ) ) {
$subject = $atts['subject'];
}

if ( isset( $atts['message'] ) ) {
$message = $atts['message'];
}

if ( isset( $atts['attachments'] ) ) {
$attachments = $atts['attachments'];
}

if ( ! is_array( $attachments ) ) {
$attachments = explode( "\n", str_replace( "\r\n", "\n", $attachments ) );
}

if ( isset( $atts['embeds'] ) ) {
$embeds = $atts['embeds'];
}

if ( ! is_array( $embeds ) ) {
$embeds = explode( "\n", str_replace( "\r\n", "\n", $embeds ) );
}

// Empty out the values that may be set.
$phpmailer->clearAllRecipients();
$phpmailer->clearAttachments();
Expand Down Expand Up @@ -457,14 +459,8 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
foreach ( (array) $addresses as $address ) {
try {
// Break $recipient into name and address parts if in the format "Foo <bar@baz.com>".
$recipient_name = '';

if ( preg_match( '/(.*)<(.+)>/', $address, $matches ) ) {
if ( count( $matches ) === 3 ) {
$recipient_name = $matches[1];
$address = $matches[2];
}
}
$recipient_name = $address['name'] ?? '';
$address = $address['address'];

switch ( $address_header ) {
case 'to':
Expand All @@ -489,7 +485,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
// Set to use PHP's mail().
$phpmailer->isMail();

// Set Content-Type and charset.
// Set Content-Type and Charset.

// If we don't have a Content-Type from the input headers.
if ( ! isset( $content_type ) ) {
Expand All @@ -512,11 +508,6 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
$phpmailer->isHTML( true );
}

// If we don't have a charset from the input headers.
if ( ! isset( $charset ) ) {
$charset = get_bloginfo( 'charset' );
}

/**
* Filters the default wp_mail() charset.
*
Expand Down Expand Up @@ -609,7 +600,10 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array()
*/
do_action_ref_array( 'phpmailer_init', array( &$phpmailer ) );

$mail_data = compact( 'to', 'subject', 'message', 'headers', 'attachments' );
$mail_data = array_merge(
array( 'to' => $raw_to ),
compact( 'subject', 'message', 'headers', 'attachments' )
);

// Send!
try {
Expand Down
124 changes: 124 additions & 0 deletions tests/phpunit/tests/pluggable/wpMail.php
Original file line number Diff line number Diff line change
Expand Up @@ -625,4 +625,128 @@ 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.' );
}
}

public function address_provider() {
return array(
'single encoded name' => array(
'content' => '=?UTF-8?B?Sm9obg==?= <john@example.com>',
'expected' => array(
array( 'john@example.com', 'John' ),
),
),
'multiple names with one encoded name' => array(
'content' => '=?UTF-8?B?Sm9obg==?= <john@example.com>, Jane <jane@example.com>',
'expected' => array(
array( 'john@example.com', 'John' ),
array( 'jane@example.com', 'Jane' ),
),
),
'multiple encoded names' => array(
'content' => '=?UTF-8?B?Sm9obg==?= <john@example.com>, =?UTF-8?B?SmFuZQ==?= <jane@example.com>',
'expected' => array(
array( 'john@example.com', 'John' ),
array( 'jane@example.com', 'Jane' ),
),
),
);
}

/**
* @ticket 62940
* @dataProvider address_provider
*/
public function test_wp_mail_single_line_utf8_header( $content, $expected ) {
$headers = array( 'Cc', 'Bcc', 'Reply-To' );

foreach ( $headers as $header ) {
$mail_header = sprintf( '%s: %s', $header, $content );
wp_mail( 'test@example.com', 'subject', 'message', $mail_header );
$mailer = tests_retrieve_phpmailer_instance();

switch ( $header ) {
case 'Cc':
$this->assertSame( $expected, $mailer->getCcAddresses() );
break;

case 'Bcc':
$this->assertSame( $expected, $mailer->getBccAddresses() );
break;

case 'Reply-To':
// Reply-To returns associative array, so modify expected data accordingly.
$expected_reply_to = array();
foreach ( $expected as $addr ) {
$expected_reply_to[ $addr[0] ] = $addr;
}
$this->assertSame( $expected_reply_to, $mailer->getReplyToAddresses() );
break;

default:
$this->fail( "Unknown header type: {$header}" );
break;
}
reset_phpmailer_instance();
}
}

/**
* @ticket 62940
* @dataProvider address_provider
*/
public function test_wp_mail_single_line_utf8_header_multiple_to( $content, $expected ) {
wp_mail( $content, 'subject', 'message' );
$mailer = tests_retrieve_phpmailer_instance();
$this->assertSame( $expected, $mailer->getToAddresses() );
}

/**
* @ticket 62940
*/
public function test_wp_mail_encoded_from() {
$header = 'From: =?UTF-8?B?VGVzdA==?= <test@example.com>';
$expected = array( 'test@example.com', 'Test' );
wp_mail( 'john@example.com', 'subject', 'message', $header );
$mailer = tests_retrieve_phpmailer_instance();

// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
$this->assertSame( $expected, array( $mailer->From, $mailer->FromName ) );
}

public function addr_list_provider() {
return array(
'comma in quoted name' => array(
'type' => 'From',
'header' => 'From: "John, Doe" <johndoe@example.com>',
'expected' => array( 'johndoe@example.com', 'John, Doe' ),
),
'angled bracket in quoted name' => array(
'type' => 'From',
'header' => 'From: "John<Doe" <johndoe@example.com>',
'expected' => array( 'johndoe@example.com', 'John<Doe' ),
),
);
}


/**
* @ticket 62940
* @dataProvider addr_list_provider
* @requires extension imap
*/
public function test_wp_mail_headers_with_imap_extension( $type, $header, $expected ) {
wp_mail( 'test@example.com', 'Subject', 'Message', $header );
$mailer = tests_retrieve_phpmailer_instance();

// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
switch ( $type ) {
case 'From':
$this->assertSame( $expected, array( $mailer->From, $mailer->FromName ) );
break;

default:
$this->fail( "Unknown header type: {$type}" );
break;
}
// phpcs:enable
}
}
Loading