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 /lib/private/Mail/Mailer.php | |
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>
Diffstat (limited to 'lib/private/Mail/Mailer.php')
-rw-r--r-- | lib/private/Mail/Mailer.php | 172 |
1 files changed, 105 insertions, 67 deletions
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); } } |