diff options
author | Côme Chilliet <come.chilliet@nextcloud.com> | 2021-12-20 11:35:06 +0100 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2023-02-02 10:30:06 +0100 |
commit | dde5c46a3eb7c6dcee47795590619ccd393577d2 (patch) | |
tree | 5351227347b92e5d69a49b632ea138e314eea013 | |
parent | fc4e87a2dfc5ff53bc9f15da13f355dd285769a9 (diff) | |
download | nextcloud-server-dde5c46a3eb7c6dcee47795590619ccd393577d2.tar.gz nextcloud-server-dde5c46a3eb7c6dcee47795590619ccd393577d2.zip |
Migrate to Symfony Mailer
Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
m--------- | 3rdparty | 0 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/Schedule/IMipPlugin.php | 7 | ||||
-rw-r--r-- | apps/settings/lib/Controller/MailSettingsController.php | 2 | ||||
-rw-r--r-- | apps/settings/lib/Settings/Admin/Mail.php | 1 | ||||
-rw-r--r-- | apps/settings/templates/settings/admin/additional-mail.php | 33 | ||||
-rw-r--r-- | apps/settings/tests/Controller/MailSettingsControllerTest.php | 4 | ||||
-rw-r--r-- | apps/settings/tests/Settings/Admin/MailTest.php | 2 | ||||
-rw-r--r-- | config/config.sample.php | 23 | ||||
-rw-r--r-- | lib/private/Mail/Attachment.php | 36 | ||||
-rw-r--r-- | lib/private/Mail/Mailer.php | 172 | ||||
-rw-r--r-- | lib/private/Mail/Message.php | 188 | ||||
-rw-r--r-- | lib/public/Mail/IMailer.php | 2 | ||||
-rw-r--r-- | tests/lib/Mail/MailerTest.php | 128 | ||||
-rw-r--r-- | tests/lib/Mail/MessageTest.php | 366 |
14 files changed, 479 insertions, 485 deletions
diff --git a/3rdparty b/3rdparty -Subproject 1d53ed4d3282427854fca7ee6ecbb945304272d +Subproject b31aba0505a3daf84b016f52873794b618694df diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index be238ae5afb..50390549570 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -172,9 +172,14 @@ class IMipPlugin extends SabreIMipPlugin { return; } - $senderName = $iTipMessage->senderName ?: null; $recipientName = $iTipMessage->recipientName ?: null; + /** @var Parameter|string|null $senderName */ + $senderName = $iTipMessage->senderName ?: null; + if($senderName instanceof Parameter) { + $senderName = $senderName->getValue() ?? null; + } + if ($senderName === null || empty(trim($senderName))) { $senderName = $this->userManager->getDisplayName($this->userId); } diff --git a/apps/settings/lib/Controller/MailSettingsController.php b/apps/settings/lib/Controller/MailSettingsController.php index 22c0622a072..2df79b67731 100644 --- a/apps/settings/lib/Controller/MailSettingsController.php +++ b/apps/settings/lib/Controller/MailSettingsController.php @@ -85,7 +85,6 @@ class MailSettingsController extends Controller { * @param string $mail_smtpmode * @param string $mail_smtpsecure * @param string $mail_smtphost - * @param string $mail_smtpauthtype * @param int $mail_smtpauth * @param string $mail_smtpport * @return DataResponse @@ -95,7 +94,6 @@ class MailSettingsController extends Controller { $mail_smtpmode, $mail_smtpsecure, $mail_smtphost, - $mail_smtpauthtype, $mail_smtpauth, $mail_smtpport, $mail_sendmailmode) { diff --git a/apps/settings/lib/Settings/Admin/Mail.php b/apps/settings/lib/Settings/Admin/Mail.php index f4c546b27a4..1cdb7315713 100644 --- a/apps/settings/lib/Settings/Admin/Mail.php +++ b/apps/settings/lib/Settings/Admin/Mail.php @@ -61,7 +61,6 @@ class Mail implements IDelegatedSettings { 'mail_smtpsecure' => $this->config->getSystemValue('mail_smtpsecure', ''), 'mail_smtphost' => $this->config->getSystemValue('mail_smtphost', ''), 'mail_smtpport' => $this->config->getSystemValue('mail_smtpport', ''), - 'mail_smtpauthtype' => $this->config->getSystemValue('mail_smtpauthtype', ''), 'mail_smtpauth' => $this->config->getSystemValue('mail_smtpauth', false), 'mail_smtpname' => $this->config->getSystemValue('mail_smtpname', ''), 'mail_smtppassword' => $this->config->getSystemValue('mail_smtppassword', ''), diff --git a/apps/settings/templates/settings/admin/additional-mail.php b/apps/settings/templates/settings/admin/additional-mail.php index 6b85fcbe867..6b31ebe879c 100644 --- a/apps/settings/templates/settings/admin/additional-mail.php +++ b/apps/settings/templates/settings/admin/additional-mail.php @@ -24,17 +24,9 @@ /** @var \OCP\IL10N $l */ /** @var array $_ */ -$mail_smtpauthtype = [ - '' => $l->t('None'), - 'LOGIN' => $l->t('Login'), - 'PLAIN' => $l->t('Plain'), - 'NTLM' => $l->t('NT LAN Manager'), -]; - $mail_smtpsecure = [ '' => $l->t('None'), - 'ssl' => $l->t('SSL/TLS'), - 'tls' => $l->t('STARTTLS'), + 'ssl' => $l->t('SSL/TLS') ]; $mail_smtpmode = [ @@ -112,26 +104,7 @@ $mail_sendmailmode = [ value="<?php p($_['mail_domain']) ?>" /> </p> - <p id="setting_smtpauth" <?php if ($_['mail_smtpmode'] !== 'smtp') { - print_unescaped(' class="hidden"'); - } ?>> - <label for="mail_smtpauthtype"><?php p($l->t('Authentication method')); ?></label> - <select name="mail_smtpauthtype" id="mail_smtpauthtype"> - <?php foreach ($mail_smtpauthtype as $authtype => $name): - $selected = ''; - if ($authtype == $_['mail_smtpauthtype']): - $selected = 'selected="selected"'; - endif; ?> - <option value="<?php p($authtype)?>" <?php p($selected) ?>><?php p($name) ?></option> - <?php endforeach;?> - </select> - - <input type="checkbox" name="mail_smtpauth" id="mail_smtpauth" class="checkbox" value="1" - <?php if ($_['mail_smtpauth']) { - print_unescaped('checked="checked"'); - } ?> /> - <label for="mail_smtpauth"><?php p($l->t('Authentication required')); ?></label> - </p> +<!--lo--> <p id="setting_smtphost" <?php if ($_['mail_smtpmode'] !== 'smtp') { print_unescaped(' class="hidden"'); @@ -145,7 +118,7 @@ $mail_sendmailmode = [ </p> </form> <form class="mail_settings" id="mail_credentials_settings"> - <p id="mail_credentials" <?php if (!$_['mail_smtpauth'] || $_['mail_smtpmode'] !== 'smtp') { + <p id="mail_credentials" <?php if ($_['mail_smtpmode'] !== 'smtp') { print_unescaped(' class="hidden"'); } ?>> <label for="mail_smtpname"><?php p($l->t('Credentials')); ?></label> diff --git a/apps/settings/tests/Controller/MailSettingsControllerTest.php b/apps/settings/tests/Controller/MailSettingsControllerTest.php index 54461201201..db6043030d3 100644 --- a/apps/settings/tests/Controller/MailSettingsControllerTest.php +++ b/apps/settings/tests/Controller/MailSettingsControllerTest.php @@ -91,7 +91,6 @@ class MailSettingsControllerTest extends \Test\TestCase { 'mail_smtpmode' => 'smtp', 'mail_smtpsecure' => 'ssl', 'mail_smtphost' => 'mx.nextcloud.org', - 'mail_smtpauthtype' => 'NTLM', 'mail_smtpauth' => 1, 'mail_smtpport' => '25', 'mail_sendmailmode' => null, @@ -102,7 +101,6 @@ class MailSettingsControllerTest extends \Test\TestCase { 'mail_smtpmode' => 'smtp', 'mail_smtpsecure' => 'ssl', 'mail_smtphost' => 'mx.nextcloud.org', - 'mail_smtpauthtype' => 'NTLM', 'mail_smtpauth' => null, 'mail_smtpport' => '25', 'mail_smtpname' => null, @@ -118,7 +116,6 @@ class MailSettingsControllerTest extends \Test\TestCase { 'smtp', 'ssl', 'mx.nextcloud.org', - 'NTLM', 1, '25', null @@ -132,7 +129,6 @@ class MailSettingsControllerTest extends \Test\TestCase { 'smtp', 'ssl', 'mx.nextcloud.org', - 'NTLM', 0, '25', null diff --git a/apps/settings/tests/Settings/Admin/MailTest.php b/apps/settings/tests/Settings/Admin/MailTest.php index 7a70065ff50..c3b08a9d509 100644 --- a/apps/settings/tests/Settings/Admin/MailTest.php +++ b/apps/settings/tests/Settings/Admin/MailTest.php @@ -64,7 +64,6 @@ class MailTest extends TestCase { ['mail_smtpsecure', '', true], ['mail_smtphost', '', 'smtp.nextcloud.com'], ['mail_smtpport', '', 25], - ['mail_smtpauthtype', '', 'login'], ['mail_smtpauth', false, true], ['mail_smtpname', '', 'smtp.sender.com'], ['mail_smtppassword', '', 'mypassword'], @@ -82,7 +81,6 @@ class MailTest extends TestCase { 'mail_smtpsecure' => true, 'mail_smtphost' => 'smtp.nextcloud.com', 'mail_smtpport' => 25, - 'mail_smtpauthtype' => 'login', 'mail_smtpauth' => true, 'mail_smtpname' => 'smtp.sender.com', 'mail_smtppassword' => '********', diff --git a/config/config.sample.php b/config/config.sample.php index f7861c3c94c..806345092f2 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -463,14 +463,17 @@ $CONFIG = [ 'mail_smtptimeout' => 10, /** - * This depends on ``mail_smtpmode``. Specify when you are using ``ssl`` for SSL/TLS or - * ``tls`` for STARTTLS, or leave empty for no encryption. + * This depends on ``mail_smtpmode``. Specify ``ssl`` when you are using SSL/TLS. Any other value will be ignored. + * + * If the server advertises STARTTLS capabilities, they might be used, but they cannot be enforced by + * this config option. * * Defaults to ``''`` (empty string) */ 'mail_smtpsecure' => '', /** + * * This depends on ``mail_smtpmode``. Change this to ``true`` if your mail * server requires authentication. * @@ -479,14 +482,6 @@ $CONFIG = [ 'mail_smtpauth' => false, /** - * This depends on ``mail_smtpmode``. If SMTP authentication is required, choose - * the authentication type as ``LOGIN`` or ``PLAIN``. - * - * Defaults to ``LOGIN`` - */ -'mail_smtpauthtype' => 'LOGIN', - -/** * This depends on ``mail_smtpauth``. Specify the username for authenticating to * the SMTP server. * @@ -1190,14 +1185,14 @@ $CONFIG = [ 'preview_office_cl_parameters' => ' --headless --nologo --nofirststartwizard --invisible --norestore '. '--convert-to png --outdir ', - + /** * custom path for ffmpeg binary - * + * * Defaults to ``null`` and falls back to searching ``avconv`` and ``ffmpeg`` in the configured ``PATH`` environment */ -'preview_ffmpeg_path' => '/usr/bin/ffmpeg', - +'preview_ffmpeg_path' => '/usr/bin/ffmpeg', + /** * Set the URL of the Imaginary service to send image previews to. * Also requires the ``OC\Preview\Imaginary`` provider to be enabled. diff --git a/lib/private/Mail/Attachment.php b/lib/private/Mail/Attachment.php index 12f71df86d9..5bfe0dd0522 100644 --- a/lib/private/Mail/Attachment.php +++ b/lib/private/Mail/Attachment.php @@ -27,6 +27,7 @@ declare(strict_types=1); namespace OC\Mail; use OCP\Mail\IAttachment; +use Symfony\Component\Mime\Email; /** * Class Attachment @@ -35,11 +36,21 @@ use OCP\Mail\IAttachment; * @since 13.0.0 */ class Attachment implements IAttachment { - /** @var \Swift_Mime_Attachment */ - protected $swiftAttachment; + private ?string $body; + private ?string $name; + private ?string $contentType; + private ?string $path; - public function __construct(\Swift_Mime_Attachment $attachment) { - $this->swiftAttachment = $attachment; + public function __construct( + ?string $body, + ?string $name, + ?string $contentType, + ?string $path = null + ) { + $this->body = $body; + $this->name = $name; + $this->contentType = $contentType; + $this->path = $path; } /** @@ -48,7 +59,7 @@ class Attachment implements IAttachment { * @since 13.0.0 */ public function setFilename(string $filename): IAttachment { - $this->swiftAttachment->setFilename($filename); + $this->name = $filename; return $this; } @@ -58,7 +69,7 @@ class Attachment implements IAttachment { * @since 13.0.0 */ public function setContentType(string $contentType): IAttachment { - $this->swiftAttachment->setContentType($contentType); + $this->contentType = $contentType; return $this; } @@ -68,14 +79,15 @@ class Attachment implements IAttachment { * @since 13.0.0 */ public function setBody(string $body): IAttachment { - $this->swiftAttachment->setBody($body); + $this->body = $body; return $this; } - /** - * @return \Swift_Mime_Attachment - */ - public function getSwiftAttachment(): \Swift_Mime_Attachment { - return $this->swiftAttachment; + public function attach(Email $symfonyEmail): void { + if ($this->path !== null) { + $symfonyEmail->attachFromPath($this->path, $this->name, $this->contentType); + } else { + $symfonyEmail->attach($this->body, $this->name, $this->contentType); + } } } diff --git a/lib/private/Mail/Mailer.php b/lib/private/Mail/Mailer.php index d0c3b04eacb..05ef0bf5139 100644 --- a/lib/private/Mail/Mailer.php +++ b/lib/private/Mail/Mailer.php @@ -50,6 +50,15 @@ use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IMessage; use Psr\Log\LoggerInterface; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; +use Symfony\Component\Mailer\Mailer as SymfonyMailer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mailer\Transport\SendmailTransport; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; +use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\RfcComplianceException; /** * Class Mailer provides some basic functions to create a mail message that can be used in combination with @@ -70,12 +79,10 @@ use Psr\Log\LoggerInterface; * @package OC\Mail */ class Mailer implements IMailer { - /** @var \Swift_Mailer Cached mailer */ - private $instance = null; + private ?MailerInterface $instance = null; private IConfig $config; private LoggerInterface $logger; - /** @var Defaults */ - private $defaults; + private Defaults $defaults; private IURLGenerator $urlGenerator; private IL10N $l10n; private IEventDispatcher $dispatcher; @@ -100,11 +107,11 @@ class Mailer implements IMailer { /** * Creates a new message object that can be passed to send() * - * @return IMessage + * @return Message */ - public function createMessage(): IMessage { + public function createMessage(): Message { $plainTextOnly = $this->config->getSystemValue('mail_send_plaintext_only', false); - return new Message(new \Swift_Message(), $plainTextOnly); + return new Message(new Email(), $plainTextOnly); } /** @@ -115,7 +122,7 @@ class Mailer implements IMailer { * @since 13.0.0 */ public function createAttachment($data = null, $filename = null, $contentType = null): IAttachment { - return new Attachment(new \Swift_Attachment($data, $filename, $contentType)); + return new Attachment($data, $filename, $contentType); } /** @@ -125,7 +132,7 @@ class Mailer implements IMailer { * @since 13.0.0 */ public function createAttachmentFromPath(string $path, $contentType = null): IAttachment { - return new Attachment(\Swift_Attachment::fromPath($path, $contentType)); + return new Attachment(null, null, $contentType, $path); } /** @@ -162,49 +169,82 @@ class Mailer implements IMailer { * Send the specified message. Also sets the from address to the value defined in config.php * if no-one has been passed. * - * @param IMessage|Message $message Message to send - * @return string[] Array with failed recipients. Be aware that this depends on the used mail backend and - * therefore should be considered - * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address - * has been supplied.) + * If sending failed, the recipients that failed will be returned (to, cc and bcc). + * Will output additional debug info if 'mail_smtpdebug' => 'true' is set in config.php + * + * @param IMessage $message Message to send + * @return string[] $failedRecipients */ public function send(IMessage $message): array { $debugMode = $this->config->getSystemValue('mail_smtpdebug', false); + if (!($message instanceof Message)) { + throw new InvalidArgumentException('Object not of type ' . Message::class); + } + if (empty($message->getFrom())) { $message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]); } - $failedRecipients = []; - $mailer = $this->getInstance(); - // Enable logger if debug mode is enabled - if ($debugMode) { - $mailLogger = new \Swift_Plugins_Loggers_ArrayLogger(); - $mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger)); - } + $this->dispatcher->dispatchTyped(new BeforeMessageSent($message)); + try { + $message->setRecipients(); + } catch (InvalidArgumentException|RfcComplianceException $e) { + $logMessage = sprintf( + 'Could not send mail to "%s" with subject "%s" as validation for address failed', + print_r(array_merge($message->getTo(), $message->getCc(), $message->getBcc()), true), + $message->getSubject() + ); + $this->logger->debug($logMessage, ['app' => 'core', 'exception' => $e]); + $recipients = array_merge($message->getTo(), $message->getCc(), $message->getBcc()); + $failedRecipients = []; + + array_walk($recipients, function ($value, $key) use (&$failedRecipients) { + if (is_numeric($key)) { + $failedRecipients[] = $value; + } else { + $failedRecipients[] = $key; + } + }); - $this->dispatcher->dispatchTyped(new BeforeMessageSent($message)); + return $failedRecipients; + } - $mailer->send($message->getSwiftMessage(), $failedRecipients); + try { + $mailer->send($message->getSymfonyEmail()); + } catch (TransportExceptionInterface $e) { + $logMessage = sprintf('Sending mail to "%s" with subject "%s" failed', print_r($message->getTo(), true), $message->getSubject()); + $this->logger->debug($logMessage, ['app' => 'core', 'exception' => $e]); + if ($debugMode) { + $this->logger->debug($e->getDebug(), ['app' => 'core']); + } + $recipients = array_merge($message->getTo(), $message->getCc(), $message->getBcc()); + $failedRecipients = []; + + array_walk($recipients, function ($value, $key) use (&$failedRecipients) { + if (is_numeric($key)) { + $failedRecipients[] = $value; + } else { + $failedRecipients[] = $key; + } + }); + + return $failedRecipients; + } // Debugging logging $logMessage = sprintf('Sent mail to "%s" with subject "%s"', print_r($message->getTo(), true), $message->getSubject()); - if (!empty($failedRecipients)) { - $logMessage .= sprintf(' (failed for "%s")', print_r($failedRecipients, true)); - } $this->logger->debug($logMessage, ['app' => 'core']); - if ($debugMode && isset($mailLogger)) { - $this->logger->debug($mailLogger->dump(), ['app' => 'core']); - } - return $failedRecipients; + return []; } /** - * Checks if an e-mail address is valid + * @deprecated 26.0.0 Implicit validation is done in \OC\Mail\Message::setRecipients + * via \Symfony\Component\Mime\Address::__construct * * @param string $email Email address to be validated * @return bool True if the mail address is valid, false otherwise @@ -217,28 +257,10 @@ class Mailer implements IMailer { $validator = new EmailValidator(); $validation = new RFCValidation(); - return $validator->isValid($this->convertEmail($email), $validation); + return $validator->isValid($email, $validation); } - /** - * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains - * - * FIXME: Remove this once SwiftMailer supports IDN - * - * @param string $email - * @return string Converted mail address if `idn_to_ascii` exists - */ - protected function convertEmail(string $email): string { - if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46') || strpos($email, '@') === false) { - return $email; - } - - [$name, $domain] = explode('@', $email, 2); - $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); - return $name.'@'.$domain; - } - - protected function getInstance(): \Swift_Mailer { + protected function getInstance(): MailerInterface { if (!is_null($this->instance)) { return $this->instance; } @@ -255,31 +277,47 @@ class Mailer implements IMailer { break; } - return new \Swift_Mailer($transport); + return new SymfonyMailer($transport); } /** * Returns the SMTP transport * - * @return \Swift_SmtpTransport + * Only supports ssl/tls + * starttls is not enforcable with Symfony Mailer but might be available + * via the automatic config (Symfony Mailer internal) + * + * @return EsmtpTransport */ - protected function getSmtpInstance(): \Swift_SmtpTransport { - $transport = new \Swift_SmtpTransport(); - $transport->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10)); - $transport->setHost($this->config->getSystemValue('mail_smtphost', '127.0.0.1')); - $transport->setPort($this->config->getSystemValue('mail_smtpport', 25)); + protected function getSmtpInstance(): EsmtpTransport { + // either null or true - if nothing is passed, let the symfony mailer figure out the configuration by itself + $mailSmtpsecure = ($this->config->getSystemValue('mail_smtpsecure', null) === 'ssl') ? true : null; + $transport = new EsmtpTransport( + $this->config->getSystemValue('mail_smtphost', '127.0.0.1'), + (int)$this->config->getSystemValue('mail_smtpport', 25), + $mailSmtpsecure, + null, + $this->logger + ); + /** @var SocketStream $stream */ + $stream = $transport->getStream(); + /** @psalm-suppress InternalMethod */ + $stream->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10)); + if ($this->config->getSystemValue('mail_smtpauth', false)) { $transport->setUsername($this->config->getSystemValue('mail_smtpname', '')); $transport->setPassword($this->config->getSystemValue('mail_smtppassword', '')); - $transport->setAuthMode($this->config->getSystemValue('mail_smtpauthtype', 'LOGIN')); - } - $smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', ''); - if (!empty($smtpSecurity)) { - $transport->setEncryption($smtpSecurity); } + $streamingOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []); if (is_array($streamingOptions) && !empty($streamingOptions)) { - $transport->setStreamOptions($streamingOptions); + /** @psalm-suppress InternalMethod */ + $currentStreamingOptions = $stream->getStreamOptions(); + + $currentStreamingOptions = array_merge_recursive($currentStreamingOptions, $streamingOptions); + + /** @psalm-suppress InternalMethod */ + $stream->setStreamOptions($currentStreamingOptions); } $overwriteCliUrl = parse_url( @@ -297,9 +335,9 @@ class Mailer implements IMailer { /** * Returns the sendmail transport * - * @return \Swift_SendmailTransport + * @return SendmailTransport */ - protected function getSendMailInstance(): \Swift_SendmailTransport { + protected function getSendMailInstance(): SendmailTransport { switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) { case 'qmail': $binaryPath = '/var/qmail/bin/sendmail'; @@ -322,6 +360,6 @@ class Mailer implements IMailer { break; } - return new \Swift_SendmailTransport($binaryPath . $binaryParam); + return new SendmailTransport($binaryPath . $binaryParam, null, $this->logger); } } diff --git a/lib/private/Mail/Message.php b/lib/private/Mail/Message.php index 3313b39e2e2..01beefcc6d6 100644 --- a/lib/private/Mail/Message.php +++ b/lib/private/Mail/Message.php @@ -35,64 +35,67 @@ use OCP\Mail\Headers\AutoSubmitted; use OCP\Mail\IAttachment; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMessage; -use Swift_Message; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\InvalidArgumentException; +use Symfony\Component\Mime\Exception\RfcComplianceException; /** - * Class Message provides a wrapper around SwiftMail + * Class Message provides a wrapper around Symfony\Component\Mime\Email (Used to be around SwiftMail) * * @package OC\Mail */ class Message implements IMessage { - /** @var Swift_Message */ - private $swiftMessage; - /** @var bool */ - private $plainTextOnly; + private Email $symfonyEmail; + private bool $plainTextOnly; - public function __construct(Swift_Message $swiftMessage, bool $plainTextOnly) { - $this->swiftMessage = $swiftMessage; + private array $to; + private array $from; + private array $replyTo; + private array $cc; + private array $bcc; + + public function __construct(Email $symfonyEmail, bool $plainTextOnly) { + $this->symfonyEmail = $symfonyEmail; $this->plainTextOnly = $plainTextOnly; + $this->to = []; + $this->from = []; + $this->replyTo = []; + $this->cc = []; + $this->bcc = []; } /** - * @param IAttachment $attachment * @return $this * @since 13.0.0 */ public function attach(IAttachment $attachment): IMessage { /** @var Attachment $attachment */ - $this->swiftMessage->attach($attachment->getSwiftAttachment()); + $attachment->attach($this->symfonyEmail); return $this; } /** - * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains - * FIXME: Remove this once SwiftMailer supports IDN + * Converts the [['displayName' => 'email'], ['displayName2' => 'email2']] arrays to valid Adresses * - * @param array $addresses Array of mail addresses, key will get converted - * @return array Converted addresses if `idn_to_ascii` exists + * @param array $addresses Array of mail addresses + * @return Address[] + * @throws RfcComplianceException|InvalidArgumentException */ protected function convertAddresses(array $addresses): array { - if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46')) { - return $addresses; - } - $convertedAddresses = []; - foreach ($addresses as $email => $readableName) { - $parsableEmail = is_numeric($email) ? $readableName : $email; - if (strpos($parsableEmail, '@') === false) { - $convertedAddresses[$parsableEmail] = $readableName; - continue; - } + if (empty($addresses)) { + return []; + } - [$name, $domain] = explode('@', $parsableEmail, 2); - $domain = idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46); + array_walk($addresses, function ($readableName, $email) use (&$convertedAddresses) { if (is_numeric($email)) { - $convertedAddresses[] = $name . '@' . $domain; + $convertedAddresses[] = new Address($readableName); } else { - $convertedAddresses[$name . '@' . $domain] = $readableName; + $convertedAddresses[] = new Address($email, $readableName); } - } + }); return $convertedAddresses; } @@ -106,41 +109,32 @@ class Message implements IMessage { * @return $this */ public function setFrom(array $addresses): IMessage { - $addresses = $this->convertAddresses($addresses); - - $this->swiftMessage->setFrom($addresses); + $this->from = $addresses; return $this; } /** * Get the from address of this message. - * - * @return array */ public function getFrom(): array { - return $this->swiftMessage->getFrom() ?? []; + return $this->from; } /** * Set the Reply-To address of this message * - * @param array $addresses * @return $this */ public function setReplyTo(array $addresses): IMessage { - $addresses = $this->convertAddresses($addresses); - - $this->swiftMessage->setReplyTo($addresses); + $this->replyTo = $addresses; return $this; } /** * Returns the Reply-To address of this message - * - * @return string */ - public function getReplyTo(): string { - return $this->swiftMessage->getReplyTo(); + public function getReplyTo(): array { + return $this->replyTo; } /** @@ -150,19 +144,15 @@ class Message implements IMessage { * @return $this */ public function setTo(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setTo($recipients); + $this->to = $recipients; return $this; } /** * Get the to address of this message. - * - * @return array */ public function getTo(): array { - return $this->swiftMessage->getTo() ?? []; + return $this->to; } /** @@ -172,19 +162,15 @@ class Message implements IMessage { * @return $this */ public function setCc(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setCc($recipients); + $this->cc = $recipients; return $this; } /** * Get the cc address of this message. - * - * @return array */ public function getCc(): array { - return $this->swiftMessage->getCc() ?? []; + return $this->cc; } /** @@ -194,104 +180,119 @@ class Message implements IMessage { * @return $this */ public function setBcc(array $recipients): IMessage { - $recipients = $this->convertAddresses($recipients); - - $this->swiftMessage->setBcc($recipients); + $this->bcc = $recipients; return $this; } /** * Get the Bcc address of this message. - * - * @return array */ public function getBcc(): array { - return $this->swiftMessage->getBcc() ?? []; + return $this->bcc; } /** * Set the subject of this message. * - * @param string $subject - * @return IMessage + * @return $this */ public function setSubject(string $subject): IMessage { - $this->swiftMessage->setSubject($subject); + $this->symfonyEmail->subject($subject); return $this; } /** * Get the from subject of this message. - * - * @return string */ public function getSubject(): string { - return $this->swiftMessage->getSubject(); + return $this->symfonyEmail->getSubject() ?? ''; } /** * Set the plain-text body of this message. - * - * @param string $body * @return $this */ public function setPlainBody(string $body): IMessage { - $this->swiftMessage->setBody($body); + $this->symfonyEmail->text($body); return $this; } /** * Get the plain body of this message. - * - * @return string */ public function getPlainBody(): string { - return $this->swiftMessage->getBody(); + /** @var string $body */ + $body = $this->symfonyEmail->getTextBody() ?? ''; + return $body; } /** * Set the HTML body of this message. Consider also sending a plain-text body instead of only an HTML one. - * - * @param string $body * @return $this */ - public function setHtmlBody($body) { + public function setHtmlBody(string $body): IMessage { if (!$this->plainTextOnly) { - $this->swiftMessage->addPart($body, 'text/html'); + $this->symfonyEmail->html($body); } return $this; } /** - * Get's the underlying SwiftMessage - * @param Swift_Message $swiftMessage + * Set the underlying Email intance */ - public function setSwiftMessage(Swift_Message $swiftMessage): void { - $this->swiftMessage = $swiftMessage; + public function setSymfonyEmail(Email $symfonyEmail): void { + $this->symfonyEmail = $symfonyEmail; } /** - * Get's the underlying SwiftMessage - * @return Swift_Message + * Get the underlying Email instance */ - public function getSwiftMessage(): Swift_Message { - return $this->swiftMessage; + public function getSymfonyEmail(): Email { + return $this->symfonyEmail; } /** - * @param string $body - * @param string $contentType * @return $this */ - public function setBody($body, $contentType) { + public function setBody(string $body, string $contentType): IMessage { if (!$this->plainTextOnly || $contentType !== 'text/html') { - $this->swiftMessage->setBody($body, $contentType); + if ($contentType === 'text/html') { + $this->symfonyEmail->html($body); + } else { + $this->symfonyEmail->text($body); + } } return $this; } /** - * @param IEMailTemplate $emailTemplate + * Set the recipients on the symphony email + * + * Since + * + * setTo + * setFrom + * setReplyTo + * setCc + * setBcc + * + * could throw a \Symfony\Component\Mime\Exception\RfcComplianceException + * or a \Symfony\Component\Mime\Exception\InvalidArgumentException + * we wrap the calls here. We then have the validation errors all in one place and can + * throw shortly before \OC\Mail\Mailer::send + * + * @return void + * @throws InvalidArgumentException|RfcComplianceException + */ + public function setRecipients() { + $this->symfonyEmail->to(...$this->convertAddresses($this->getTo())); + $this->symfonyEmail->from(...$this->convertAddresses($this->getFrom())); + $this->symfonyEmail->replyTo(...$this->convertAddresses($this->getReplyTo())); + $this->symfonyEmail->cc(...$this->convertAddresses($this->getCc())); + $this->symfonyEmail->bcc(...$this->convertAddresses($this->getBcc())); + } + + /** * @return $this */ public function useTemplate(IEMailTemplate $emailTemplate): IMessage { @@ -311,7 +312,7 @@ class Message implements IMessage { * @return $this */ public function setAutoSubmitted(string $value): IMessage { - $headers = $this->swiftMessage->getHeaders(); + $headers = $this->symfonyEmail->getHeaders(); if ($headers->has(AutoSubmitted::HEADER)) { // if the header already exsists, remove it. @@ -319,6 +320,7 @@ class Message implements IMessage { // of the interface \Swift_Mime_Header, however the // interface doesn't, and this makes the static-code // analysis unhappy. + // @todo check if symfony mailer can modify the autosubmitted header $headers->remove(AutoSubmitted::HEADER); } @@ -334,9 +336,9 @@ class Message implements IMessage { * @return string */ public function getAutoSubmitted(): string { - $headers = $this->swiftMessage->getHeaders(); + $headers = $this->symfonyEmail->getHeaders(); return $headers->has(AutoSubmitted::HEADER) ? - $headers->get(AutoSubmitted::HEADER)->toString() : AutoSubmitted::VALUE_NO; + $headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO; } } diff --git a/lib/public/Mail/IMailer.php b/lib/public/Mail/IMailer.php index 325abd9301e..db46401a3d8 100644 --- a/lib/public/Mail/IMailer.php +++ b/lib/public/Mail/IMailer.php @@ -96,7 +96,7 @@ interface IMailer { public function send(IMessage $message): array; /** - * Checks if an e-mail address is valid + * @deprecated 26.0.0 * * @param string $email Email address to be validated * @return bool True if the mail address is valid, false otherwise diff --git a/tests/lib/Mail/MailerTest.php b/tests/lib/Mail/MailerTest.php index 74c4f2a62ab..39e4d689a37 100644 --- a/tests/lib/Mail/MailerTest.php +++ b/tests/lib/Mail/MailerTest.php @@ -22,19 +22,23 @@ use OCP\IURLGenerator; use OCP\L10N\IFactory; use OCP\Mail\Events\BeforeMessageSent; use Psr\Log\LoggerInterface; -use Swift_SwiftException; use Test\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Mailer\Mailer as SymfonyMailer; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport; +use Symfony\Component\Mailer\Transport\SendmailTransport; class MailerTest extends TestCase { - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IConfig|MockObject */ private $config; - /** @var Defaults|\PHPUnit\Framework\MockObject\MockObject */ + /** @var Defaults|MockObject */ private $defaults; - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ + /** @var LoggerInterface|MockObject */ private $logger; - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IURLGenerator|MockObject */ private $urlGenerator; - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ + /** @var IL10N|MockObject */ private $l10n; /** @var Mailer */ private $mailer; @@ -91,7 +95,7 @@ class MailerTest extends TestCase { $path = '/usr/sbin/sendmail'; } - $expected = new \Swift_SendmailTransport($path . $binaryParam); + $expected = new SendmailTransport($path . $binaryParam, null, $this->logger); $this->assertEquals($expected, self::invokePrivate($this->mailer, 'getSendMailInstance')); } @@ -109,13 +113,22 @@ class MailerTest extends TestCase { ['mail_sendmailmode', 'smtp', $sendmailMode], ]); - $this->assertEquals(new \Swift_SendmailTransport('/var/qmail/bin/sendmail' . $binaryParam), self::invokePrivate($this->mailer, 'getSendMailInstance')); + $sendmail = new SendmailTransport('/var/qmail/bin/sendmail' . $binaryParam, null, $this->logger); + $this->assertEquals($sendmail, self::invokePrivate($this->mailer, 'getSendMailInstance')); } public function testGetInstanceDefault() { + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], + ]); $mailer = self::invokePrivate($this->mailer, 'getInstance'); - $this->assertInstanceOf(\Swift_Mailer::class, $mailer); - $this->assertInstanceOf(\Swift_SmtpTransport::class, $mailer->getTransport()); + $this->assertInstanceOf(SymfonyMailer::class, $mailer); + $transport = self::invokePrivate($mailer, 'transport'); + $this->assertInstanceOf(EsmtpTransport::class, $transport); } public function testGetInstanceSendmail() { @@ -127,11 +140,33 @@ class MailerTest extends TestCase { ]); $mailer = self::invokePrivate($this->mailer, 'getInstance'); - $this->assertInstanceOf(\Swift_Mailer::class, $mailer); - $this->assertInstanceOf(\Swift_SendmailTransport::class, $mailer->getTransport()); + $this->assertInstanceOf(SymfonyMailer::class, $mailer); + $transport = self::invokePrivate($mailer, 'transport'); + $this->assertInstanceOf(SendmailTransport::class, $transport); } public function testEvents() { + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ]); + $this->mailer = $this->getMockBuilder(Mailer::class) + ->setMethods(['getInstance']) + ->setConstructorArgs( + [ + $this->config, + $this->logger, + $this->defaults, + $this->urlGenerator, + $this->l10n, + $this->dispatcher, + $this->createMock(IFactory::class) + ] + ) + ->getMock(); + $message = $this->createMock(Message::class); $event = new BeforeMessageSent($message); @@ -139,11 +174,7 @@ class MailerTest extends TestCase { ->method('dispatchTyped') ->with($this->equalTo($event)); - # We do not care at this point about errors in Swiftmailer - try { - $this->mailer->send($message); - } catch (Swift_SwiftException $e) { - } + $this->mailer->send($message); } public function testCreateMessage() { @@ -157,13 +188,20 @@ class MailerTest extends TestCase { public function testSendInvalidMailException() { + $this->config + ->method('getSystemValue') + ->willReturnMap([ + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], + ]); $this->expectException(\Exception::class); $message = $this->getMockBuilder('\OC\Mail\Message') ->disableOriginalConstructor()->getMock(); $message->expects($this->once()) - ->method('getSwiftMessage') - ->willReturn(new \Swift_Message()); + ->method('getSymfonyEmail') + ->willReturn(new Email()); $this->mailer->send($message); } @@ -202,58 +240,76 @@ class MailerTest extends TestCase { $this->config->method('getSystemValue') ->willReturnMap([ ['mail_smtpmode', 'smtp', 'smtp'], - ['mail_smtpstreamoptions', [], ['foo' => 1]] + ['mail_smtpstreamoptions', [], ['foo' => 1]], + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], ]); $mailer = self::invokePrivate($this->mailer, 'getInstance'); - $this->assertEquals(1, count($mailer->getTransport()->getStreamOptions())); - $this->assertTrue(isset($mailer->getTransport()->getStreamOptions()['foo'])); + /** @var EsmtpTransport $transport */ + $transport = self::invokePrivate($mailer, 'transport'); + $this->assertInstanceOf(EsmtpTransport::class, $transport); + $this->assertEquals(1, count($transport->getStream()->getStreamOptions())); + $this->assertTrue(isset($transport->getStream()->getStreamOptions()['foo'])); } public function testStreamingOptionsWrongType() { $this->config->method('getSystemValue') ->willReturnMap([ ['mail_smtpmode', 'smtp', 'smtp'], - ['mail_smtpstreamoptions', [], 'bar'] + ['mail_smtpstreamoptions', [], 'bar'], + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], ]); $mailer = self::invokePrivate($this->mailer, 'getInstance'); - $this->assertEquals(0, count($mailer->getTransport()->getStreamOptions())); + /** @var EsmtpTransport $transport */ + $transport = self::invokePrivate($mailer, 'transport'); + $this->assertInstanceOf(EsmtpTransport::class, $transport); + $this->assertEquals(0, count($transport->getStream()->getStreamOptions())); } public function testLocalDomain(): void { $this->config->method('getSystemValue') ->willReturnMap([ - ['mail_smtpmode', 'smtp', 'smtp'] + ['mail_smtpmode', 'smtp', 'smtp'], + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], ]); $this->config->method('getSystemValueString') ->with('overwrite.cli.url', '') ->willReturn('https://some.valid.url.com:8080'); - /** @var \Swift_Mailer $mailer */ + /** @var SymfonyMailer $mailer */ $mailer = self::invokePrivate($this->mailer, 'getInstance'); - self::assertInstanceOf(\Swift_Mailer::class, $mailer); + self::assertInstanceOf(SymfonyMailer::class, $mailer); - /** @var \Swift_Transport_EsmtpTransport $transport */ - $transport = $mailer->getTransport(); - self::assertInstanceOf(\Swift_Transport_EsmtpTransport::class, $transport); + /** @var EsmtpTransport $transport */ + $transport = self::invokePrivate($mailer, 'transport'); + self::assertInstanceOf(EsmtpTransport::class, $transport); self::assertEquals('some.valid.url.com', $transport->getLocalDomain()); } public function testLocalDomainInvalidUrl(): void { $this->config->method('getSystemValue') ->willReturnMap([ - ['mail_smtpmode', 'smtp', 'smtp'] + ['mail_smtpmode', 'smtp', 'smtp'], + ['mail_smtphost', '127.0.0.1', '127.0.0.1'], + ['mail_smtpport', 25, 25], + ['mail_smtptimeout', 10, 10], ]); $this->config->method('getSystemValueString') ->with('overwrite.cli.url', '') ->willReturn('https:only.slash.does.not.work:8080'); - /** @var \Swift_Mailer $mailer */ + /** @var SymfonyMailer $mailer */ $mailer = self::invokePrivate($this->mailer, 'getInstance'); - self::assertInstanceOf(\Swift_Mailer::class, $mailer); + self::assertInstanceOf(SymfonyMailer::class, $mailer); - /** @var \Swift_Transport_EsmtpTransport $transport */ - $transport = $mailer->getTransport(); - self::assertInstanceOf(\Swift_Transport_EsmtpTransport::class, $transport); + /** @var EsmtpTransport $transport */ + $transport = self::invokePrivate($mailer, 'transport'); + self::assertInstanceOf(EsmtpTransport::class, $transport); self::assertEquals('[127.0.0.1]', $transport->getLocalDomain()); } } diff --git a/tests/lib/Mail/MessageTest.php b/tests/lib/Mail/MessageTest.php index b97240d1336..2becc4d2081 100644 --- a/tests/lib/Mail/MessageTest.php +++ b/tests/lib/Mail/MessageTest.php @@ -11,12 +11,17 @@ namespace Test\Mail; use OC\Mail\Message; use OCP\Mail\Headers\AutoSubmitted; use OCP\Mail\IEMailTemplate; -use Swift_Message; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; +use Symfony\Component\Mime\Exception\RfcComplianceException; +use Symfony\Component\Mime\Header\HeaderInterface; +use Symfony\Component\Mime\Header\Headers; use Test\TestCase; +use PHPUnit\Framework\MockObject\MockObject; class MessageTest extends TestCase { - /** @var Swift_Message */ - private $swiftMessage; + /** @var Email */ + private $symfonyEmail; /** @var Message */ private $message; @@ -25,10 +30,26 @@ class MessageTest extends TestCase { */ public function mailAddressProvider() { return [ - [['lukas@owncloud.com' => 'Lukas Reschke'], ['lukas@owncloud.com' => 'Lukas Reschke']], - [['lukas@owncloud.com' => 'Lukas Reschke', 'lukas@öwnclöüd.com', 'lukäs@owncloud.örg' => 'Lükäs Réschke'], - ['lukas@owncloud.com' => 'Lukas Reschke', 'lukas@xn--wncld-iuae2c.com', 'lukäs@owncloud.xn--rg-eka' => 'Lükäs Réschke']], - [['lukas@öwnclöüd.com'], ['lukas@xn--wncld-iuae2c.com']], + [ + ['lukas@owncloud.com' => 'Lukas Reschke'], + [new Address('lukas@owncloud.com', 'Lukas Reschke')] + ], + [ + [ + 'lukas@owncloud.com' => 'Lukas Reschke', + 'lukas@öwnclöüd.com', + 'lukäs@owncloud.örg' => 'Lükäs Réschke' + ], + [ + new Address('lukas@owncloud.com', 'Lukas Reschke'), + new Address('lukas@öwnclöüd.com'), + new Address('lukäs@owncloud.örg', 'Lükäs Réschke') + ] + ], + [ + ['lukas@öwnclöüd.com'], + [new Address('lukas@öwnclöüd.com')] + ], ]; } @@ -37,7 +58,7 @@ class MessageTest extends TestCase { */ public function getMailAddressProvider() { return [ - [null, []], + [[], []], [['lukas@owncloud.com' => 'Lukas Reschke'], ['lukas@owncloud.com' => 'Lukas Reschke']], ]; } @@ -45,189 +66,158 @@ class MessageTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->swiftMessage = $this->getMockBuilder('\Swift_Message') + $this->symfonyEmail = $this->getMockBuilder(Email::class) ->disableOriginalConstructor()->getMock(); - $this->message = new Message($this->swiftMessage, false); + $this->message = new Message($this->symfonyEmail, false); } /** - * @requires function idn_to_ascii * @dataProvider mailAddressProvider * * @param string $unconverted * @param string $expected */ public function testConvertAddresses($unconverted, $expected) { - $this->assertSame($expected, self::invokePrivate($this->message, 'convertAddresses', [$unconverted])); - } - - public function testSetFrom() { - $this->swiftMessage - ->expects($this->once()) - ->method('setFrom') - ->with(['lukas@owncloud.com']); - $this->message->setFrom(['lukas@owncloud.com']); + $this->assertEquals($expected, self::invokePrivate($this->message, 'convertAddresses', [$unconverted])); } + public function testSetRecipients(): void { + $this->message = $this->message->setFrom(['pierres-general-store@stardewvalley.com' => 'Pierres General Store']); + $this->message = $this->message->setTo(['lewis-tent@stardewvalley.com' => "Lewis' Tent Life"]); + $this->message = $this->message->setReplyTo(['penny@stardewvalley-library.co.edu' => 'Penny']); + $this->message = $this->message->setCc(['gunther@stardewvalley-library.co.edu' => 'Gunther']); + $this->message = $this->message->setBcc(['pam@stardewvalley-bus.com' => 'Pam']); - /** - * @dataProvider getMailAddressProvider - * - * @param $swiftresult - * @param $return - */ - public function testGetFrom($swiftresult, $return) { - $this->swiftMessage + $this->symfonyEmail + ->expects($this->once()) + ->method('from') + ->willReturn(new Address('pierres-general-store@stardewvalley.com', 'Pierres General Store')); + $this->symfonyEmail + ->expects($this->once()) + ->method('to') + ->willReturn(new Address('lewis-tent@stardewvalley.com', "Lewis' Tent Life")); + $this->symfonyEmail + ->expects($this->once()) + ->method('replyTo') + ->willReturn(new Address('penny@stardewvalley-library.co.edu', 'Penny')); + $this->symfonyEmail + ->expects($this->once()) + ->method('cc') + ->willReturn(new Address('gunther@stardewvalley-library.co.edu', 'Gunther')); + $this->symfonyEmail ->expects($this->once()) - ->method('getFrom') - ->willReturn($swiftresult); + ->method('bcc') + ->willReturn(new Address('pam@stardewvalley-bus.com', 'Pam')); - $this->assertSame($return, $this->message->getFrom()); + $this->message->setRecipients(); } - public function testSetReplyTo() { - $this->swiftMessage - ->expects($this->once()) - ->method('setReplyTo') - ->with(['lukas@owncloud.com']); - $this->message->setReplyTo(['lukas@owncloud.com']); - } + public function testSetTo() { + $expected = ['pierres-general-store@stardewvalley.com' => 'Pierres General Store']; - public function testGetReplyTo() { - $this->swiftMessage - ->expects($this->once()) - ->method('getReplyTo') - ->willReturn('lukas@owncloud.com'); + $message = $this->message->setTo(['pierres-general-store@stardewvalley.com' => 'Pierres General Store']); - $this->assertSame('lukas@owncloud.com', $this->message->getReplyTo()); + $this->assertEquals($expected, $message->getTo()); } + public function testSetRecipientsException(): void { + $message = $this->message->setTo(['lewis-tent@~~~~.com' => "Lewis' Tent Life"]); - /** @dataProvider dataSetTo */ - public function testSetTo(array $to, array $expected) { - $this->swiftMessage + $this->symfonyEmail ->expects($this->once()) - ->method('setTo') - ->with($expected); - $this->message->setTo($to); - } + ->method('to') + ->willThrowException(new RfcComplianceException()); - public function dataSetTo(): array { - return [ - [['robot@example.com'], ['robot@example.com']], - [['robot'], ['robot' => 'robot']], - [['robot' => 'robot display name'], ['robot' => 'robot display name']], - [['example@🤖.com'], ['example@xn--yp9h.com']], - [['example@🤖.com' => 'A robot'], ['example@xn--yp9h.com' => 'A robot']], - ]; + $this->expectException(RfcComplianceException::class); + $message->setRecipients(); } - /** - * @dataProvider getMailAddressProvider - */ - public function testGetTo($swiftresult, $return) { - $this->swiftMessage + public function testSetRecipientsEmptyValues(): void { + $message = $this->message->setTo([]); + + $this->symfonyEmail ->expects($this->once()) - ->method('getTo') - ->willReturn($swiftresult); + ->method('to'); - $this->assertSame($return, $this->message->getTo()); + $message->setRecipients(); } - public function testSetCc() { - $this->swiftMessage - ->expects($this->once()) - ->method('setCc') - ->with(['lukas@owncloud.com']); - $this->message->setCc(['lukas@owncloud.com']); - } + public function testSetGetFrom() { + $expected = ['pierres-general-store@stardewvalley.com' => 'Pierres General Store']; - /** - * @dataProvider getMailAddressProvider - */ - public function testGetCc($swiftresult, $return) { - $this->swiftMessage - ->expects($this->once()) - ->method('getCc') - ->willReturn($swiftresult); + $message = $this->message->setFrom(['pierres-general-store@stardewvalley.com' => 'Pierres General Store']); - $this->assertSame($return, $this->message->getCc()); + $this->assertEquals($expected, $message->getFrom()); } - public function testSetBcc() { - $this->swiftMessage - ->expects($this->once()) - ->method('setBcc') - ->with(['lukas@owncloud.com']); - $this->message->setBcc(['lukas@owncloud.com']); + public function testSetGetTo() { + $expected = ['lewis-tent@stardewvalley.com' => "Lewis' Tent Life"]; + + $message = $this->message->setTo(['lewis-tent@stardewvalley.com' => "Lewis' Tent Life"]); + + $this->assertEquals($expected, $message->getTo()); } - /** - * @dataProvider getMailAddressProvider - */ - public function testGetBcc($swiftresult, $return) { - $this->swiftMessage - ->expects($this->once()) - ->method('getBcc') - ->willReturn($swiftresult); + public function testSetGetReplyTo() { + $expected = ['penny@stardewvalley-library.co.edu' => 'Penny']; - $this->assertSame($return, $this->message->getBcc()); + $message = $this->message->setReplyTo(['penny@stardewvalley-library.co.edu' => 'Penny']); + + $this->assertEquals($expected, $message->getReplyTo()); } - public function testSetSubject() { - $this->swiftMessage - ->expects($this->once()) - ->method('setSubject') - ->with('Fancy Subject'); + public function testSetGetCC() { + $expected = ['gunther@stardewvalley-library.co.edu' => 'Gunther']; - $this->message->setSubject('Fancy Subject'); + $message = $this->message->setCc(['gunther@stardewvalley-library.co.edu' => 'Gunther']); + + $this->assertEquals($expected, $message->getCc()); } - public function testGetSubject() { - $this->swiftMessage - ->expects($this->once()) - ->method('getSubject') - ->willReturn('Fancy Subject'); + public function testSetGetBCC() { + $expected = ['pam@stardewvalley-bus.com' => 'Pam']; + + $message = $this->message->setBcc(['pam@stardewvalley-bus.com' => 'Pam']); - $this->assertSame('Fancy Subject', $this->message->getSubject()); + $this->assertEquals($expected, $message->getBcc()); } public function testSetPlainBody() { - $this->swiftMessage + $this->symfonyEmail ->expects($this->once()) - ->method('setBody') + ->method('text') ->with('Fancy Body'); $this->message->setPlainBody('Fancy Body'); } public function testGetPlainBody() { - $this->swiftMessage + $this->symfonyEmail ->expects($this->once()) - ->method('getBody') + ->method('getTextBody') ->willReturn('Fancy Body'); $this->assertSame('Fancy Body', $this->message->getPlainBody()); } public function testSetHtmlBody() { - $this->swiftMessage + $this->symfonyEmail ->expects($this->once()) - ->method('addPart') - ->with('<blink>Fancy Body</blink>', 'text/html'); + ->method('html') + ->with('<blink>Fancy Body</blink>', 'utf-8'); $this->message->setHtmlBody('<blink>Fancy Body</blink>'); } public function testPlainTextRenderOption() { - /** @var \PHPUnit\Framework\MockObject\MockObject|Swift_Message $swiftMessage */ - $swiftMessage = $this->getMockBuilder('\Swift_Message') + /** @var MockObject|Email $symfonyEmail */ + $symfonyEmail = $this->getMockBuilder(Email::class) ->disableOriginalConstructor()->getMock(); - /** @var \PHPUnit\Framework\MockObject\MockObject|IEMailTemplate $template */ - $template = $this->getMockBuilder('\OCP\Mail\IEMailTemplate') + /** @var MockObject|IEMailTemplate $template */ + $template = $this->getMockBuilder(IEMailTemplate::class) ->disableOriginalConstructor()->getMock(); - $message = new Message($swiftMessage, true); + $message = new Message($symfonyEmail, true); $template ->expects($this->never()) @@ -243,14 +233,14 @@ class MessageTest extends TestCase { } public function testBothRenderingOptions() { - /** @var \PHPUnit\Framework\MockObject\MockObject|Swift_Message $swiftMessage */ - $swiftMessage = $this->getMockBuilder('\Swift_Message') + /** @var MockObject|Email $symfonyEmail */ + $symfonyEmail = $this->getMockBuilder(Email::class) ->disableOriginalConstructor()->getMock(); - /** @var \PHPUnit\Framework\MockObject\MockObject|IEMailTemplate $template */ - $template = $this->getMockBuilder('\OCP\Mail\IEMailTemplate') + /** @var MockObject|IEMailTemplate $template */ + $template = $this->getMockBuilder(IEMailTemplate::class) ->disableOriginalConstructor()->getMock(); - $message = new Message($swiftMessage, false); + $message = new Message($symfonyEmail, false); $template ->expects($this->once()) @@ -266,108 +256,40 @@ class MessageTest extends TestCase { } public function testSetAutoSubmitted1() { - $swiftMimeSimpleHeaderSet = $this->getMockBuilder('\Swift_Mime_SimpleHeaderSet') - ->disableOriginalConstructor() - ->getMock(); - $swiftMessage = $this->getMockBuilder('\Swift_Message') - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); - - $swiftMessage->method('getHeaders')->willReturn($swiftMimeSimpleHeaderSet); - - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('has') - ->with('Auto-Submitted'); - $swiftMimeSimpleHeaderSet->expects($this->never()) - ->method('remove'); - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('addTextHeader') - ->with('Auto-Submitted', AutoSubmitted::VALUE_AUTO_GENERATED); - - $message = new Message($swiftMessage, false); + $headers = new Headers($this->createMock(HeaderInterface::class)); + $headers->addTextHeader(AutoSubmitted::HEADER, "yes"); + $symfonyEmail = $this->createMock(Email::class); + + $symfonyEmail->method('getHeaders') + ->willReturn($headers); + + $message = new Message($symfonyEmail, false); $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED); + $this->assertNotSame('no', $message->getAutoSubmitted()); } public function testSetAutoSubmitted2() { - $swiftMimeSimpleHeaderSet = $this->getMockBuilder('\Swift_Mime_SimpleHeaderSet') - ->disableOriginalConstructor() - ->getMock(); - $swiftMessage = $this->getMockBuilder('\Swift_Message') - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); - - $swiftMessage->method('getHeaders')->willReturn($swiftMimeSimpleHeaderSet); - - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('has') - ->with('Auto-Submitted') - ->willReturn(true); - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('remove') - ->with('Auto-Submitted'); - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('addTextHeader') - ->with('Auto-Submitted', AutoSubmitted::VALUE_AUTO_GENERATED); - - $message = new Message($swiftMessage, false); + $headers = new Headers($this->createMock(HeaderInterface::class)); + $headers->addTextHeader(AutoSubmitted::HEADER, 'no'); + $symfonyEmail = $this->createMock(Email::class); + + $symfonyEmail->method('getHeaders') + ->willReturn($headers); + + $message = new Message($symfonyEmail, false); $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED); + $this->assertSame('auto-generated', $message->getAutoSubmitted()); } - public function testGetAutoSubmitted1() { - $swiftMimeSimpleHeaderSet = $this->getMockBuilder('\Swift_Mime_SimpleHeaderSet') - ->disableOriginalConstructor() - ->getMock(); - $swiftMessage = $this->getMockBuilder('\Swift_Message') - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); - - $swiftMessage->method('getHeaders')->willReturn($swiftMimeSimpleHeaderSet); - - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('has') - ->with('Auto-Submitted'); - $swiftMimeSimpleHeaderSet->expects($this->never()) - ->method('get'); - - $message = new Message($swiftMessage, false); + public function testGetAutoSubmitted() { + $headers = new Headers($this->createMock(HeaderInterface::class)); + $headers->addTextHeader(AutoSubmitted::HEADER, 'no'); + $symfonyEmail = $this->createMock(Email::class); + + $symfonyEmail->method('getHeaders') + ->willReturn($headers); + + $message = new Message($symfonyEmail, false); $this->assertSame("no", $message->getAutoSubmitted()); } - public function testGetAutoSubmitted2() { - $swiftMimeHeader = $this->getMockBuilder('\Swift_Mime_Header') - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $swiftMimeSimpleHeaderSet = $this->getMockBuilder('\Swift_Mime_SimpleHeaderSet') - ->disableOriginalConstructor() - ->getMock(); - $swiftMessage = $this->getMockBuilder('\Swift_Message') - ->disableOriginalConstructor() - ->disableOriginalClone() - ->disableArgumentCloning() - ->disallowMockingUnknownTypes() - ->getMock(); - - - $swiftMessage->method('getHeaders')->willReturn($swiftMimeSimpleHeaderSet); - $swiftMimeHeader->method('toString')->willReturn(AutoSubmitted::VALUE_AUTO_GENERATED); - - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('has') - ->with('Auto-Submitted') - ->willReturn(true); - $swiftMimeSimpleHeaderSet->expects($this->once()) - ->method('get') - ->willReturn($swiftMimeHeader); - - $message = new Message($swiftMessage, false); - $this->assertSame(AutoSubmitted::VALUE_AUTO_GENERATED, $message->getAutoSubmitted()); - } } |