From f2915d9a4cc03bfd06cdd325c93d31da191bed26 Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Tue, 25 Sep 2018 16:41:49 +1000
Subject: [PATCH] Issue #2704597 by Neograph734, Berdir, izus, alexpott, TR:
Relative URLs in mails should be converted to absolute ones
(cherry picked from commit 530f7ac51526c7d0f2f6013f05ac8bbd37309176)
---
core/lib/Drupal/Core/Mail/MailManager.php | 10 +
.../mail_html_test/mail_html_test.info.yml | 6 +
.../mail_html_test/mail_html_test.module | 17 ++
.../src/Plugin/Mail/TestHtmlMailCollector.php | 33 ++++
.../tests/src/Functional/Mail/MailTest.php | 181 +++++++++++++++++-
5 files changed, 246 insertions(+), 1 deletion(-)
create mode 100644 core/modules/system/tests/modules/mail_html_test/mail_html_test.info.yml
create mode 100644 core/modules/system/tests/modules/mail_html_test/mail_html_test.module
create mode 100644 core/modules/system/tests/modules/mail_html_test/src/Plugin/Mail/TestHtmlMailCollector.php
diff --git a/core/lib/Drupal/Core/Mail/MailManager.php b/core/lib/Drupal/Core/Mail/MailManager.php
index 3a7a93f8b193..bbc0432c6da4 100644
--- a/core/lib/Drupal/Core/Mail/MailManager.php
+++ b/core/lib/Drupal/Core/Mail/MailManager.php
@@ -2,7 +2,9 @@
namespace Drupal\Core\Mail;
+use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerTrait;
@@ -10,6 +12,7 @@
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -277,6 +280,13 @@ public function doMail($module, $key, $to, $langcode, $params = [], $reply = NUL
// Retrieve the responsible implementation for this message.
$system = $this->getInstance(['module' => $module, 'key' => $key]);
+ // Attempt to convert relative URLs to absolute.
+ foreach ($message['body'] as &$body_part) {
+ if ($body_part instanceof MarkupInterface) {
+ $body_part = Markup::create(Html::transformRootRelativeUrlsToAbsolute((string) $body_part, \Drupal::request()->getSchemeAndHttpHost()));
+ }
+ }
+
// Format the message body.
$message = $system->format($message);
diff --git a/core/modules/system/tests/modules/mail_html_test/mail_html_test.info.yml b/core/modules/system/tests/modules/mail_html_test/mail_html_test.info.yml
new file mode 100644
index 000000000000..08ae1e8f0ce3
--- /dev/null
+++ b/core/modules/system/tests/modules/mail_html_test/mail_html_test.info.yml
@@ -0,0 +1,6 @@
+name: 'HTML mail test support'
+description: 'Test if HTML in mails works as expected.'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/mail_html_test/mail_html_test.module b/core/modules/system/tests/modules/mail_html_test/mail_html_test.module
new file mode 100644
index 000000000000..f873c89b50aa
--- /dev/null
+++ b/core/modules/system/tests/modules/mail_html_test/mail_html_test.module
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Helper module for the html mail and url conversion tests.
+ */
+
+/**
+ * Implements hook_mail().
+ */
+function mail_html_test_mail($key, &$message, $params) {
+ switch ($key) {
+ case 'render_from_message_param':
+ $message['body'][] = \Drupal::service('renderer')->renderPlain($params['message']);
+ break;
+ }
+}
diff --git a/core/modules/system/tests/modules/mail_html_test/src/Plugin/Mail/TestHtmlMailCollector.php b/core/modules/system/tests/modules/mail_html_test/src/Plugin/Mail/TestHtmlMailCollector.php
new file mode 100644
index 000000000000..43e413596d5f
--- /dev/null
+++ b/core/modules/system/tests/modules/mail_html_test/src/Plugin/Mail/TestHtmlMailCollector.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Drupal\mail_html_test\Plugin\Mail;
+
+use Drupal\Core\Mail\MailFormatHelper;
+use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
+
+/**
+ * Defines a mail backend that captures sent HTML messages in the state system.
+ *
+ * This class is for running tests or for development and does not convert HTML
+ * to plaintext.
+ *
+ * @Mail(
+ * id = "test_html_mail_collector",
+ * label = @Translation("HTML mail collector"),
+ * description = @Translation("Does not send the message, but stores its HTML in Drupal within the state system. Used for testing.")
+ * )
+ */
+class TestHtmlMailCollector extends TestMailCollector {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function format(array $message) {
+ // Join the body array into one string.
+ $message['body'] = implode(PHP_EOL, $message['body']);
+ // Wrap the mail body for sending.
+ $message['body'] = MailFormatHelper::wrapMail($message['body']);
+ return $message;
+ }
+
+}
diff --git a/core/modules/system/tests/src/Functional/Mail/MailTest.php b/core/modules/system/tests/src/Functional/Mail/MailTest.php
index e8accddb6f4e..6f32539243f5 100644
--- a/core/modules/system/tests/src/Functional/Mail/MailTest.php
+++ b/core/modules/system/tests/src/Functional/Mail/MailTest.php
@@ -2,8 +2,13 @@
namespace Drupal\Tests\system\Functional\Mail;
+use Drupal\Component\Utility\Random;
use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Mail\MailFormatHelper;
use Drupal\Core\Mail\Plugin\Mail\TestMailCollector;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Url;
+use Drupal\file\Entity\File;
use Drupal\Tests\BrowserTestBase;
use Drupal\system_mail_failure_test\Plugin\Mail\TestPhpMailFailure;
@@ -19,7 +24,7 @@ class MailTest extends BrowserTestBase {
*
* @var array
*/
- public static $modules = ['simpletest', 'system_mail_failure_test'];
+ public static $modules = ['simpletest', 'system_mail_failure_test', 'mail_html_test', 'file', 'image'];
/**
* Assert that the pluggable mail system is functional.
@@ -104,4 +109,178 @@ public function testFromAndReplyToHeader() {
$this->assertFalse(isset($sent_message['headers']['Errors-To']), 'Errors-to header must not be set, it is deprecated.');
}
+ /**
+ * Checks that relative paths in mails are converted into absolute URLs.
+ */
+ public function testConvertRelativeUrlsIntoAbsolute() {
+ $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+
+ // Use the HTML compatible state system collector mail backend.
+ $this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save();
+
+ // Fetch the hostname and port for matching against.
+ $http_host = \Drupal::request()->getSchemeAndHttpHost();
+
+ // Random generator.
+ $random = new Random();
+
+ // One random tag name.
+ $tag_name = strtolower($random->name(8, TRUE));
+
+ // Test root relative urls.
+ foreach (['href', 'src'] as $attribute) {
+ // Reset the state variable that holds sent messages.
+ \Drupal::state()->set('system.test_mail_collector', []);
+
+ $html = "<$tag_name $attribute=\"/root-relative\">root relative url in mail test</$tag_name>";
+ $expected_html = "<$tag_name $attribute=\"{$http_host}/root-relative\">root relative url in mail test</$tag_name>";
+
+ // Prepare render array.
+ $render = ['#markup' => Markup::create($html)];
+
+ // Send a test message that simpletest_mail_alter should cancel.
+ \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
+ // Retrieve sent message.
+ $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+ $sent_message = end($captured_emails);
+
+ // Wrap the expected HTML and assert.
+ $expected_html = MailFormatHelper::wrapMail($expected_html);
+ $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
+ }
+
+ // Test protocol relative urls.
+ foreach (['href', 'src'] as $attribute) {
+ // Reset the state variable that holds sent messages.
+ \Drupal::state()->set('system.test_mail_collector', []);
+
+ $html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test</$tag_name>";
+ $expected_html = "<$tag_name $attribute=\"//example.com/protocol-relative\">protocol relative url in mail test</$tag_name>";
+
+ // Prepare render array.
+ $render = ['#markup' => Markup::create($html)];
+
+ // Send a test message that simpletest_mail_alter should cancel.
+ \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
+ // Retrieve sent message.
+ $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+ $sent_message = end($captured_emails);
+
+ // Wrap the expected HTML and assert.
+ $expected_html = MailFormatHelper::wrapMail($expected_html);
+ $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
+ }
+
+ // Test absolute urls.
+ foreach (['href', 'src'] as $attribute) {
+ // Reset the state variable that holds sent messages.
+ \Drupal::state()->set('system.test_mail_collector', []);
+
+ $html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test</$tag_name>";
+ $expected_html = "<$tag_name $attribute=\"http://example.com/absolute\">absolute url in mail test</$tag_name>";
+
+ // Prepare render array.
+ $render = ['#markup' => Markup::create($html)];
+
+ // Send a test message that simpletest_mail_alter should cancel.
+ \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
+ // Retrieve sent message.
+ $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+ $sent_message = end($captured_emails);
+
+ // Wrap the expected HTML and assert.
+ $expected_html = MailFormatHelper::wrapMail($expected_html);
+ $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$attribute} is properly converted for mails.");
+ }
+ }
+
+ /**
+ * Checks that mails built from render arrays contain absolute paths.
+ *
+ * By default Drupal uses relative paths for images and links. When sending
+ * emails, absolute paths should be used instead.
+ */
+ public function testRenderedElementsUseAbsolutePaths() {
+ $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+
+ // Use the HTML compatible state system collector mail backend.
+ $this->config('system.mail')->set('interface.default', 'test_html_mail_collector')->save();
+
+ // Fetch the hostname and port for matching against.
+ $http_host = \Drupal::request()->getSchemeAndHttpHost();
+
+ // Random generator.
+ $random = new Random();
+ $image_name = $random->name();
+
+ // Create an image file.
+ $file = File::create(['uri' => "public://{$image_name}.png", 'filename' => "{$image_name}.png"]);
+ $file->save();
+
+ $base_path = base_path();
+
+ $path_pairs = [
+ 'root relative' => [$file->getFileUri(), "{$http_host}{$base_path}{$this->publicFilesDirectory}/{$image_name}.png"],
+ 'protocol relative' => ['//example.com/image.png', '//example.com/image.png'],
+ 'absolute' => ['http://example.com/image.png', 'http://example.com/image.png'],
+ ];
+
+ // Test images.
+ foreach ($path_pairs as $test_type => $paths) {
+ list($input_path, $expected_path) = $paths;
+
+ // Reset the state variable that holds sent messages.
+ \Drupal::state()->set('system.test_mail_collector', []);
+
+ // Build the render array.
+ $render = [
+ '#theme' => 'image',
+ '#uri' => $input_path,
+ ];
+ $expected_html = "<img src=\"$expected_path\" alt=\"\" />";
+
+ // Send a test message that simpletest_mail_alter should cancel.
+ \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
+ // Retrieve sent message.
+ $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+ $sent_message = end($captured_emails);
+
+ // Wrap the expected HTML and assert.
+ $expected_html = MailFormatHelper::wrapMail($expected_html);
+ $this->assertSame($expected_html, $sent_message['body'], "Asserting that {$test_type} paths are converted properly.");
+ }
+
+ // Test links.
+ $path_pairs = [
+ 'root relative' => [Url::fromUserInput('/path/to/something'), "{$http_host}{$base_path}path/to/something"],
+ 'protocol relative' => [Url::fromUri('//example.com/image.png'), '//example.com/image.png'],
+ 'absolute' => [Url::fromUri('http://example.com/image.png'), 'http://example.com/image.png'],
+ ];
+
+ foreach ($path_pairs as $paths) {
+ list($input_path, $expected_path) = $paths;
+
+ // Reset the state variable that holds sent messages.
+ \Drupal::state()->set('system.test_mail_collector', []);
+
+ // Build the render array.
+ $render = [
+ '#title' => 'Link',
+ '#type' => 'link',
+ '#url' => $input_path,
+ ];
+ $expected_html = "<a href=\"$expected_path\">Link</a>";
+
+ // Send a test message that simpletest_mail_alter should cancel.
+ \Drupal::service('plugin.manager.mail')->mail('mail_html_test', 'render_from_message_param', 'relative_url@example.com', $language_interface->getId(), ['message' => $render]);
+ // Retrieve sent message.
+ $captured_emails = \Drupal::state()->get('system.test_mail_collector');
+ $sent_message = end($captured_emails);
+
+ // Wrap the expected HTML and assert.
+ $expected_html = MailFormatHelper::wrapMail($expected_html);
+ $this->assertSame($expected_html, $sent_message['body']);
+ }
+ }
+
}
--
GitLab