Skip to content
Snippets Groups Projects
Unverified Commit ab49a62b authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #84883 by quietone, alexpott, DuaelFr, scor, roderik, TR, pillarsdotnet,...

Issue #84883 by quietone, alexpott, DuaelFr, scor, roderik, TR, pillarsdotnet, jungle, ayushmishra206, greggles, dww, ximo, Hardik_Patel_12, netbjarne, kiamlaluno, longwave, earnie: Unicode::mimeHeaderEncode() doesn't correctly follow RFC 2047
parent 89fcf77b
Branches
Tags
25 merge requests!12227Issue #3181946 by jonmcl, mglaman,!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!1896Issue #2940605: Can only intentionally re-render an entity with references 20 times,!1101Issue #2412669 by claudiu.cristea, Julfabre, sidharrell, catch, daffie,...,!1039Issue #2556069 by claudiu.cristea, bnjmnm, lauriii, pfrenssen, Tim Bozeman,...,!10223132456: Fix issue where views instances are emptied before an ajax request is complete,!1012Issue #3226887: Hreflang on non-canonical content pages,!872Draft: Issue #3221319: Race condition when creating menu links and editing content deletes menu links,!825Issue #3211838 by mondrake, longwave: Convert assertions involving use of...,!795Issue #3212005 by guilhermevp, tedbow, phenaproxima: Add @throws docs to...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10,!748#1091852 Display Bug when using #states (Forms API) with Ajax Request,!731Add a new recipe to Umami demo - Chicken souvlaki and couscous,!730Issue #3211810 by alexpott, xjm, Spokje, Amber Himes Matz, Kristen Pol,...,!594Put each entity type table into a details element on admin/config/regional/content-language,!592Issue #2957953: Editing menus user-experience has regressed,!579Issue #2230909: Simple decimals fail to pass validation,!560Move callback classRemove outside of the loop,!555Issue #3202493,!512Issue #3207771: Menu UI node type form documentation points to non-existent function,!485Sets the autocomplete attribute for username/password input field on login form.,!449Issue #2784233: Allow multiple vocabularies in the taxonomy filter,!231Issue #2671162: summary text wysiwyg patch working fine on 9.2.0-dev,!43Resolve #3173180: Add UI for 'loading' html attribute to images,!30Issue #3182188: Updates composer usage to point at ./vendor/bin/composer
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace Drupal\Component\Utility; namespace Drupal\Component\Utility;
@trigger_error('\Drupal\Component\Utility\Mail is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. See https://www.drupal.org/node/3207439', E_USER_DEPRECATED);
/** /**
* Provides helpers to ensure emails are compliant with RFCs. * Provides helpers to ensure emails are compliant with RFCs.
* *
...@@ -29,8 +31,15 @@ class Mail { ...@@ -29,8 +31,15 @@ class Mail {
* @return string * @return string
* A RFC compliant version of the string, ready to be used as * A RFC compliant version of the string, ready to be used as
* "display-name" in mail originator header fields. * "display-name" in mail originator header fields.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use
* \Symfony\Component\Mime\Header\MailboxHeader instead.
*
* @see https://www.drupal.org/node/3207439
*/ */
public static function formatDisplayName($string) { public static function formatDisplayName($string) {
@trigger_error('\Drupal\Component\Utility\Mail::formatDisplayName() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use \Symfony\Component\Mime\Header\MailboxHeader instead. See https://www.drupal.org/node/3207439', E_USER_DEPRECATED);
// Make sure we don't process html-encoded characters. They may create // Make sure we don't process html-encoded characters. They may create
// unneeded trouble if left encoded, besides they will be correctly // unneeded trouble if left encoded, besides they will be correctly
// processed if decoded. // processed if decoded.
......
...@@ -393,8 +393,14 @@ public static function strcasecmp($str1, $str2) { ...@@ -393,8 +393,14 @@ public static function strcasecmp($str1, $str2) {
* *
* @return string * @return string
* The mime-encoded header. * The mime-encoded header.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use
* \Symfony\Component\Mime\Header\UnstructuredHeader instead.
*
* @see https://www.drupal.org/node/3207439
*/ */
public static function mimeHeaderEncode($string, $shorten = FALSE) { public static function mimeHeaderEncode($string, $shorten = FALSE) {
@trigger_error('\Drupal\Component\Utility\Unicode::mimeHeaderEncode() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use \Symfony\Component\Mime\Header\UnstructuredHeader instead. See https://www.drupal.org/node/3207439', E_USER_DEPRECATED);
if (preg_match('/[^\x20-\x7E]/', $string)) { if (preg_match('/[^\x20-\x7E]/', $string)) {
// floor((75 - strlen("=?UTF-8?B??=")) * 0.75); // floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
$chunk_size = 47; $chunk_size = 47;
...@@ -423,8 +429,14 @@ public static function mimeHeaderEncode($string, $shorten = FALSE) { ...@@ -423,8 +429,14 @@ public static function mimeHeaderEncode($string, $shorten = FALSE) {
* *
* @return string * @return string
* The mime-decoded header. * The mime-decoded header.
*
* @deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use
* iconv_mime_decode() instead.
*
* @see https://www.drupal.org/node/3207439
*/ */
public static function mimeHeaderDecode($header) { public static function mimeHeaderDecode($header) {
@trigger_error('\Drupal\Component\Utility\Unicode::mimeHeaderDecode() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use iconv_mime_decode() instead. See https://www.drupal.org/node/3207439', E_USER_DEPRECATED);
$callback = function ($matches) { $callback = function ($matches) {
$data = (strtolower($matches[2]) == 'b') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3])); $data = (strtolower($matches[2]) == 'b') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
if (strtolower($matches[1]) != 'utf-8') { if (strtolower($matches[1]) != 'utf-8') {
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
use Drupal\Component\Render\MarkupInterface; use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Render\PlainTextOutput; use Drupal\Component\Render\PlainTextOutput;
use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Mail as MailHelper;
use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerTrait; use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\Core\Plugin\DefaultPluginManager;
...@@ -17,6 +16,8 @@ ...@@ -17,6 +16,8 @@
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface; use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Header\MailboxHeader;
/** /**
* Provides a Mail plugin manager. * Provides a Mail plugin manager.
...@@ -255,9 +256,12 @@ public function doMail($module, $key, $to, $langcode, $params = [], $reply = NUL ...@@ -255,9 +256,12 @@ public function doMail($module, $key, $to, $langcode, $params = [], $reply = NUL
// To prevent email from looking like spam, the addresses in the Sender and // To prevent email from looking like spam, the addresses in the Sender and
// Return-Path headers should have a domain authorized to use the // Return-Path headers should have a domain authorized to use the
// originating SMTP server. // originating SMTP server.
$headers['Sender'] = $headers['Return-Path'] = $site_mail; $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $site_mail;
// Make sure the site-name is a RFC-2822 compliant 'display-name'. // Make sure the site-name is a RFC-2822 compliant 'display-name'.
$headers['From'] = MailHelper::formatDisplayName($site_config->get('name')) . ' <' . $site_mail . '>'; if ($site_mail) {
$mailbox = new MailboxHeader('From', new Address($site_mail, $site_config->get('name') ?: ''));
$headers['From'] = $mailbox->getBodyAsString();
}
if ($reply) { if ($reply) {
$headers['Reply-to'] = $reply; $headers['Reply-to'] = $reply;
} }
......
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
namespace Drupal\Core\Mail\Plugin\Mail; namespace Drupal\Core\Mail\Plugin\Mail;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Mail\MailFormatHelper; use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Mail\MailInterface; use Drupal\Core\Mail\MailInterface;
use Drupal\Core\Site\Settings; use Drupal\Core\Site\Settings;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\Header\UnstructuredHeader;
/** /**
* Defines the default Drupal mail backend, using PHP's native mail() function. * Defines the default Drupal mail backend, using PHP's native mail() function.
...@@ -18,6 +19,13 @@ ...@@ -18,6 +19,13 @@
*/ */
class PhpMail implements MailInterface { class PhpMail implements MailInterface {
/**
* A list of headers that can contain multiple email addresses.
*
* @see \Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP
*/
private const MAILBOX_LIST_HEADERS = ['from', 'to', 'reply-to', 'cc', 'bcc'];
/** /**
* The configuration factory. * The configuration factory.
* *
...@@ -75,13 +83,17 @@ public function mail(array $message) { ...@@ -75,13 +83,17 @@ public function mail(array $message) {
unset($message['headers']['Return-Path']); unset($message['headers']['Return-Path']);
} }
} }
$mimeheaders = [];
$headers = new Headers();
foreach ($message['headers'] as $name => $value) { foreach ($message['headers'] as $name => $value) {
$mimeheaders[] = $name . ': ' . Unicode::mimeHeaderEncode($value); if (in_array(strtolower($name), self::MAILBOX_LIST_HEADERS, TRUE)) {
$value = explode(',', $value);
}
$headers->addHeader($name, $value);
} }
$line_endings = Settings::get('mail_line_endings', PHP_EOL); $line_endings = Settings::get('mail_line_endings', PHP_EOL);
// Prepare mail commands. // Prepare mail commands.
$mail_subject = Unicode::mimeHeaderEncode($message['subject']); $mail_subject = (new UnstructuredHeader('subject', $message['subject']))->getBodyAsString();
// Note: email uses CRLF for line-endings. PHP's API requires LF // Note: email uses CRLF for line-endings. PHP's API requires LF
// on Unix and CRLF on Windows. Drupal automatically guesses the // on Unix and CRLF on Windows. Drupal automatically guesses the
// line-ending format appropriate for your system. If you need to // line-ending format appropriate for your system. If you need to
...@@ -89,7 +101,8 @@ public function mail(array $message) { ...@@ -89,7 +101,8 @@ public function mail(array $message) {
$mail_body = preg_replace('@\r?\n@', $line_endings, $message['body']); $mail_body = preg_replace('@\r?\n@', $line_endings, $message['body']);
// For headers, PHP's API suggests that we use CRLF normally, // For headers, PHP's API suggests that we use CRLF normally,
// but some MTAs incorrectly replace LF with CRLF. See #234403. // but some MTAs incorrectly replace LF with CRLF. See #234403.
$mail_headers = implode("\n", $mimeheaders); $mail_headers = str_replace("\r\n", "\n", $headers->toString());
$mail_subject = str_replace("\r\n", "\n", $mail_subject);
$request = \Drupal::request(); $request = \Drupal::request();
......
...@@ -907,7 +907,6 @@ middlewares ...@@ -907,7 +907,6 @@ middlewares
midgardmvc midgardmvc
mikey mikey
milli milli
mimeheaders
mimetypes mimetypes
minifyzombies minifyzombies
minimatch minimatch
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
namespace Drupal\Tests\system\Kernel\Mail; namespace Drupal\Tests\system\Kernel\Mail;
use Drupal\Component\Utility\Random; use Drupal\Component\Utility\Random;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\File\FileSystemInterface; use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Mail\MailFormatHelper; use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Mail\Plugin\Mail\TestMailCollector; use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
...@@ -147,9 +146,9 @@ public function testFromAndReplyToHeader() { ...@@ -147,9 +146,9 @@ public function testFromAndReplyToHeader() {
$captured_emails = \Drupal::state()->get('system.test_mail_collector'); $captured_emails = \Drupal::state()->get('system.test_mail_collector');
$sent_message = end($captured_emails); $sent_message = end($captured_emails);
// From header is correctly encoded. // From header is correctly encoded.
$this->assertEquals('=?UTF-8?B?RHLDqXBhbCB0aGlzIGlzIGEgdmVyeSBsb25nIHRlc3Qgc2VudGVuY2UgdG8gdGU=?= <mailtest@example.com>', $sent_message['headers']['From']); $this->assertEquals('=?utf-8?Q?Dr=C3=A9pal?= this is a very long test sentence to test what happens with very long site names <mailtest@example.com>', $sent_message['headers']['From']);
// From header is correctly encoded. // From header is correctly encoded.
$this->assertEquals('Drépal this is a very long test sentence to te <mailtest@example.com>', Unicode::mimeHeaderDecode($sent_message['headers']['From'])); $this->assertEquals('Drépal this is a very long test sentence to test what happens with very long site names <mailtest@example.com>', iconv_mime_decode($sent_message['headers']['From']));
$this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.'); $this->assertFalse(isset($sent_message['headers']['Reply-to']), 'Message reply-to is not set if not specified.');
// Errors-to header must not be set, it is deprecated. // Errors-to header must not be set, it is deprecated.
$this->assertFalse(isset($sent_message['headers']['Errors-To'])); $this->assertFalse(isset($sent_message['headers']['Errors-To']));
...@@ -179,9 +178,9 @@ public function testFromAndReplyToHeader() { ...@@ -179,9 +178,9 @@ public function testFromAndReplyToHeader() {
$captured_emails = \Drupal::state()->get('system.test_mail_collector'); $captured_emails = \Drupal::state()->get('system.test_mail_collector');
$sent_message = end($captured_emails); $sent_message = end($captured_emails);
// From header is correctly encoded. // From header is correctly encoded.
$this->assertEquals('=?UTF-8?B?RHLDqXBhbCwgInNpXHRlIg==?= <mailtest@example.com>', $sent_message['headers']['From']); $this->assertEquals('=?utf-8?Q?Dr=C3=A9pal=2C_=22si=5Cte=22?= <mailtest@example.com>', $sent_message['headers']['From']);
// From header is correctly encoded. // From header is correctly encoded.
$this->assertEquals($site_name . ' <mailtest@example.com>', Unicode::mimeHeaderDecode($sent_message['headers']['From'])); $this->assertEquals($site_name . ' <mailtest@example.com>', iconv_mime_decode($sent_message['headers']['From']));
} }
/** /**
......
...@@ -4,15 +4,18 @@ ...@@ -4,15 +4,18 @@
use Drupal\Component\Utility\Mail; use Drupal\Component\Utility\Mail;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
/** /**
* Test mail helpers implemented in Mail component. * Test mail helpers implemented in Mail component.
* *
* @group Utility * @group Utility
* @group legacy
* *
* @coversDefaultClass \Drupal\Component\Utility\Mail * @coversDefaultClass \Drupal\Component\Utility\Mail
*/ */
class MailTest extends TestCase { class MailTest extends TestCase {
use ExpectDeprecationTrait;
/** /**
* Tests RFC-2822 'display-name' formatter. * Tests RFC-2822 'display-name' formatter.
...@@ -21,6 +24,7 @@ class MailTest extends TestCase { ...@@ -21,6 +24,7 @@ class MailTest extends TestCase {
* @covers ::formatDisplayName * @covers ::formatDisplayName
*/ */
public function testFormatDisplayName($string, $safe_display_name) { public function testFormatDisplayName($string, $safe_display_name) {
$this->expectDeprecation('\Drupal\Component\Utility\Unicode::mimeHeaderEncode() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use \Symfony\Component\Mime\Header\UnstructuredHeader instead. See https://www.drupal.org/node/3207439');
$this->assertEquals($safe_display_name, Mail::formatDisplayName($string)); $this->assertEquals($safe_display_name, Mail::formatDisplayName($string));
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\Unicode;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
/** /**
* Test unicode handling features implemented in Unicode component. * Test unicode handling features implemented in Unicode component.
...@@ -13,14 +14,17 @@ ...@@ -13,14 +14,17 @@
* @coversDefaultClass \Drupal\Component\Utility\Unicode * @coversDefaultClass \Drupal\Component\Utility\Unicode
*/ */
class UnicodeTest extends TestCase { class UnicodeTest extends TestCase {
use ExpectDeprecationTrait;
/** /**
* Tests multibyte encoding. * Tests multibyte encoding.
* *
* @dataProvider providerTestMimeHeader * @dataProvider providerTestMimeHeader
* @covers ::mimeHeaderEncode * @covers ::mimeHeaderEncode
* @group legacy
*/ */
public function testMimeHeaderEncode($value, $encoded) { public function testMimeHeaderEncode($value, $encoded) {
$this->expectDeprecation('\Drupal\Component\Utility\Unicode::mimeHeaderEncode() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use \Symfony\Component\Mime\Header\UnstructuredHeader instead. See https://www.drupal.org/node/3207439');
$this->assertEquals($encoded, Unicode::mimeHeaderEncode($value)); $this->assertEquals($encoded, Unicode::mimeHeaderEncode($value));
} }
...@@ -44,8 +48,10 @@ public function providerTestMimeHeader() { ...@@ -44,8 +48,10 @@ public function providerTestMimeHeader() {
* *
* @dataProvider providerTestMimeHeaderDecode * @dataProvider providerTestMimeHeaderDecode
* @covers ::mimeHeaderDecode * @covers ::mimeHeaderDecode
* @group legacy
*/ */
public function testMimeHeaderDecode($value, $encoded) { public function testMimeHeaderDecode($value, $encoded) {
$this->expectDeprecation('\Drupal\Component\Utility\Unicode::mimeHeaderDecode() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use iconv_mime_decode() instead. See https://www.drupal.org/node/3207439');
$this->assertEquals($value, Unicode::mimeHeaderDecode($encoded)); $this->assertEquals($value, Unicode::mimeHeaderDecode($encoded));
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment