Browse Source

Migrate to Symfony Mailer

Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com>
tags/v26.0.0beta2
Côme Chilliet 2 years ago
parent
commit
dde5c46a3e
No account linked to committer's email address

+ 1
- 1
3rdparty

Subproject commit 1d53ed4d3282427854fca7ee6ecbb945304272db
Subproject commit b31aba0505a3daf84b016f52873794b618694dff

+ 6
- 1
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php View File

return; return;
} }


$senderName = $iTipMessage->senderName ?: null;
$recipientName = $iTipMessage->recipientName ?: 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))) { if ($senderName === null || empty(trim($senderName))) {
$senderName = $this->userManager->getDisplayName($this->userId); $senderName = $this->userManager->getDisplayName($this->userId);
} }

+ 0
- 2
apps/settings/lib/Controller/MailSettingsController.php View File

* @param string $mail_smtpmode * @param string $mail_smtpmode
* @param string $mail_smtpsecure * @param string $mail_smtpsecure
* @param string $mail_smtphost * @param string $mail_smtphost
* @param string $mail_smtpauthtype
* @param int $mail_smtpauth * @param int $mail_smtpauth
* @param string $mail_smtpport * @param string $mail_smtpport
* @return DataResponse * @return DataResponse
$mail_smtpmode, $mail_smtpmode,
$mail_smtpsecure, $mail_smtpsecure,
$mail_smtphost, $mail_smtphost,
$mail_smtpauthtype,
$mail_smtpauth, $mail_smtpauth,
$mail_smtpport, $mail_smtpport,
$mail_sendmailmode) { $mail_sendmailmode) {

+ 0
- 1
apps/settings/lib/Settings/Admin/Mail.php View File

'mail_smtpsecure' => $this->config->getSystemValue('mail_smtpsecure', ''), 'mail_smtpsecure' => $this->config->getSystemValue('mail_smtpsecure', ''),
'mail_smtphost' => $this->config->getSystemValue('mail_smtphost', ''), 'mail_smtphost' => $this->config->getSystemValue('mail_smtphost', ''),
'mail_smtpport' => $this->config->getSystemValue('mail_smtpport', ''), 'mail_smtpport' => $this->config->getSystemValue('mail_smtpport', ''),
'mail_smtpauthtype' => $this->config->getSystemValue('mail_smtpauthtype', ''),
'mail_smtpauth' => $this->config->getSystemValue('mail_smtpauth', false), 'mail_smtpauth' => $this->config->getSystemValue('mail_smtpauth', false),
'mail_smtpname' => $this->config->getSystemValue('mail_smtpname', ''), 'mail_smtpname' => $this->config->getSystemValue('mail_smtpname', ''),
'mail_smtppassword' => $this->config->getSystemValue('mail_smtppassword', ''), 'mail_smtppassword' => $this->config->getSystemValue('mail_smtppassword', ''),

+ 3
- 30
apps/settings/templates/settings/admin/additional-mail.php View File

/** @var \OCP\IL10N $l */ /** @var \OCP\IL10N $l */
/** @var array $_ */ /** @var array $_ */


$mail_smtpauthtype = [
'' => $l->t('None'),
'LOGIN' => $l->t('Login'),
'PLAIN' => $l->t('Plain'),
'NTLM' => $l->t('NT LAN Manager'),
];

$mail_smtpsecure = [ $mail_smtpsecure = [
'' => $l->t('None'), '' => $l->t('None'),
'ssl' => $l->t('SSL/TLS'),
'tls' => $l->t('STARTTLS'),
'ssl' => $l->t('SSL/TLS')
]; ];


$mail_smtpmode = [ $mail_smtpmode = [
value="<?php p($_['mail_domain']) ?>" /> value="<?php p($_['mail_domain']) ?>" />
</p> </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') { <p id="setting_smtphost" <?php if ($_['mail_smtpmode'] !== 'smtp') {
print_unescaped(' class="hidden"'); print_unescaped(' class="hidden"');
</p> </p>
</form> </form>
<form class="mail_settings" id="mail_credentials_settings"> <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"'); print_unescaped(' class="hidden"');
} ?>> } ?>>
<label for="mail_smtpname"><?php p($l->t('Credentials')); ?></label> <label for="mail_smtpname"><?php p($l->t('Credentials')); ?></label>

+ 0
- 4
apps/settings/tests/Controller/MailSettingsControllerTest.php View File

'mail_smtpmode' => 'smtp', 'mail_smtpmode' => 'smtp',
'mail_smtpsecure' => 'ssl', 'mail_smtpsecure' => 'ssl',
'mail_smtphost' => 'mx.nextcloud.org', 'mail_smtphost' => 'mx.nextcloud.org',
'mail_smtpauthtype' => 'NTLM',
'mail_smtpauth' => 1, 'mail_smtpauth' => 1,
'mail_smtpport' => '25', 'mail_smtpport' => '25',
'mail_sendmailmode' => null, 'mail_sendmailmode' => null,
'mail_smtpmode' => 'smtp', 'mail_smtpmode' => 'smtp',
'mail_smtpsecure' => 'ssl', 'mail_smtpsecure' => 'ssl',
'mail_smtphost' => 'mx.nextcloud.org', 'mail_smtphost' => 'mx.nextcloud.org',
'mail_smtpauthtype' => 'NTLM',
'mail_smtpauth' => null, 'mail_smtpauth' => null,
'mail_smtpport' => '25', 'mail_smtpport' => '25',
'mail_smtpname' => null, 'mail_smtpname' => null,
'smtp', 'smtp',
'ssl', 'ssl',
'mx.nextcloud.org', 'mx.nextcloud.org',
'NTLM',
1, 1,
'25', '25',
null null
'smtp', 'smtp',
'ssl', 'ssl',
'mx.nextcloud.org', 'mx.nextcloud.org',
'NTLM',
0, 0,
'25', '25',
null null

+ 0
- 2
apps/settings/tests/Settings/Admin/MailTest.php View File

['mail_smtpsecure', '', true], ['mail_smtpsecure', '', true],
['mail_smtphost', '', 'smtp.nextcloud.com'], ['mail_smtphost', '', 'smtp.nextcloud.com'],
['mail_smtpport', '', 25], ['mail_smtpport', '', 25],
['mail_smtpauthtype', '', 'login'],
['mail_smtpauth', false, true], ['mail_smtpauth', false, true],
['mail_smtpname', '', 'smtp.sender.com'], ['mail_smtpname', '', 'smtp.sender.com'],
['mail_smtppassword', '', 'mypassword'], ['mail_smtppassword', '', 'mypassword'],
'mail_smtpsecure' => true, 'mail_smtpsecure' => true,
'mail_smtphost' => 'smtp.nextcloud.com', 'mail_smtphost' => 'smtp.nextcloud.com',
'mail_smtpport' => 25, 'mail_smtpport' => 25,
'mail_smtpauthtype' => 'login',
'mail_smtpauth' => true, 'mail_smtpauth' => true,
'mail_smtpname' => 'smtp.sender.com', 'mail_smtpname' => 'smtp.sender.com',
'mail_smtppassword' => '********', 'mail_smtppassword' => '********',

+ 9
- 14
config/config.sample.php View File

'mail_smtptimeout' => 10, '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) * Defaults to ``''`` (empty string)
*/ */
'mail_smtpsecure' => '', 'mail_smtpsecure' => '',


/** /**
*
* This depends on ``mail_smtpmode``. Change this to ``true`` if your mail * This depends on ``mail_smtpmode``. Change this to ``true`` if your mail
* server requires authentication. * server requires authentication.
* *
*/ */
'mail_smtpauth' => false, '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 * This depends on ``mail_smtpauth``. Specify the username for authenticating to
* the SMTP server. * the SMTP server.
'preview_office_cl_parameters' => 'preview_office_cl_parameters' =>
' --headless --nologo --nofirststartwizard --invisible --norestore '. ' --headless --nologo --nofirststartwizard --invisible --norestore '.
'--convert-to png --outdir ', '--convert-to png --outdir ',
/** /**
* custom path for ffmpeg binary * custom path for ffmpeg binary
*
*
* Defaults to ``null`` and falls back to searching ``avconv`` and ``ffmpeg`` in the configured ``PATH`` environment * 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. * Set the URL of the Imaginary service to send image previews to.
* Also requires the ``OC\Preview\Imaginary`` provider to be enabled. * Also requires the ``OC\Preview\Imaginary`` provider to be enabled.

+ 24
- 12
lib/private/Mail/Attachment.php View File

namespace OC\Mail; namespace OC\Mail;


use OCP\Mail\IAttachment; use OCP\Mail\IAttachment;
use Symfony\Component\Mime\Email;


/** /**
* Class Attachment * Class Attachment
* @since 13.0.0 * @since 13.0.0
*/ */
class Attachment implements IAttachment { 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;
} }


/** /**
* @since 13.0.0 * @since 13.0.0
*/ */
public function setFilename(string $filename): IAttachment { public function setFilename(string $filename): IAttachment {
$this->swiftAttachment->setFilename($filename);
$this->name = $filename;
return $this; return $this;
} }


* @since 13.0.0 * @since 13.0.0
*/ */
public function setContentType(string $contentType): IAttachment { public function setContentType(string $contentType): IAttachment {
$this->swiftAttachment->setContentType($contentType);
$this->contentType = $contentType;
return $this; return $this;
} }


* @since 13.0.0 * @since 13.0.0
*/ */
public function setBody(string $body): IAttachment { public function setBody(string $body): IAttachment {
$this->swiftAttachment->setBody($body);
$this->body = $body;
return $this; 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);
}
} }
} }

+ 105
- 67
lib/private/Mail/Mailer.php View File

use OCP\Mail\IMailer; use OCP\Mail\IMailer;
use OCP\Mail\IMessage; use OCP\Mail\IMessage;
use Psr\Log\LoggerInterface; 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 * Class Mailer provides some basic functions to create a mail message that can be used in combination with
* @package OC\Mail * @package OC\Mail
*/ */
class Mailer implements IMailer { class Mailer implements IMailer {
/** @var \Swift_Mailer Cached mailer */
private $instance = null;
private ?MailerInterface $instance = null;
private IConfig $config; private IConfig $config;
private LoggerInterface $logger; private LoggerInterface $logger;
/** @var Defaults */
private $defaults;
private Defaults $defaults;
private IURLGenerator $urlGenerator; private IURLGenerator $urlGenerator;
private IL10N $l10n; private IL10N $l10n;
private IEventDispatcher $dispatcher; private IEventDispatcher $dispatcher;
/** /**
* Creates a new message object that can be passed to send() * 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); $plainTextOnly = $this->config->getSystemValue('mail_send_plaintext_only', false);
return new Message(new \Swift_Message(), $plainTextOnly);
return new Message(new Email(), $plainTextOnly);
} }


/** /**
* @since 13.0.0 * @since 13.0.0
*/ */
public function createAttachment($data = null, $filename = null, $contentType = null): IAttachment { 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);
} }


/** /**
* @since 13.0.0 * @since 13.0.0
*/ */
public function createAttachmentFromPath(string $path, $contentType = null): IAttachment { public function createAttachmentFromPath(string $path, $contentType = null): IAttachment {
return new Attachment(\Swift_Attachment::fromPath($path, $contentType));
return new Attachment(null, null, $contentType, $path);
} }


/** /**
* Send the specified message. Also sets the from address to the value defined in config.php * Send the specified message. Also sets the from address to the value defined in config.php
* if no-one has been passed. * 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 { public function send(IMessage $message): array {
$debugMode = $this->config->getSystemValue('mail_smtpdebug', false); $debugMode = $this->config->getSystemValue('mail_smtpdebug', false);


if (!($message instanceof Message)) {
throw new InvalidArgumentException('Object not of type ' . Message::class);
}

if (empty($message->getFrom())) { if (empty($message->getFrom())) {
$message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]); $message->setFrom([\OCP\Util::getDefaultEmailAddress('no-reply') => $this->defaults->getName()]);
} }


$failedRecipients = [];

$mailer = $this->getInstance(); $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 // Debugging logging
$logMessage = sprintf('Sent mail to "%s" with subject "%s"', print_r($message->getTo(), true), $message->getSubject()); $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']); $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 * @param string $email Email address to be validated
* @return bool True if the mail address is valid, false otherwise * @return bool True if the mail address is valid, false otherwise
$validator = new EmailValidator(); $validator = new EmailValidator();
$validation = new RFCValidation(); $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)) { if (!is_null($this->instance)) {
return $this->instance; return $this->instance;
} }
break; break;
} }


return new \Swift_Mailer($transport);
return new SymfonyMailer($transport);
} }


/** /**
* Returns the SMTP 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)) { if ($this->config->getSystemValue('mail_smtpauth', false)) {
$transport->setUsername($this->config->getSystemValue('mail_smtpname', '')); $transport->setUsername($this->config->getSystemValue('mail_smtpname', ''));
$transport->setPassword($this->config->getSystemValue('mail_smtppassword', '')); $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', []); $streamingOptions = $this->config->getSystemValue('mail_smtpstreamoptions', []);
if (is_array($streamingOptions) && !empty($streamingOptions)) { 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( $overwriteCliUrl = parse_url(
/** /**
* Returns the sendmail transport * 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')) { switch ($this->config->getSystemValue('mail_smtpmode', 'smtp')) {
case 'qmail': case 'qmail':
$binaryPath = '/var/qmail/bin/sendmail'; $binaryPath = '/var/qmail/bin/sendmail';
break; break;
} }


return new \Swift_SendmailTransport($binaryPath . $binaryParam);
return new SendmailTransport($binaryPath . $binaryParam, null, $this->logger);
} }
} }

+ 95
- 93
lib/private/Mail/Message.php View File

use OCP\Mail\IAttachment; use OCP\Mail\IAttachment;
use OCP\Mail\IEMailTemplate; use OCP\Mail\IEMailTemplate;
use OCP\Mail\IMessage; 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 * @package OC\Mail
*/ */
class Message implements IMessage { 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->plainTextOnly = $plainTextOnly;
$this->to = [];
$this->from = [];
$this->replyTo = [];
$this->cc = [];
$this->bcc = [];
} }


/** /**
* @param IAttachment $attachment
* @return $this * @return $this
* @since 13.0.0 * @since 13.0.0
*/ */
public function attach(IAttachment $attachment): IMessage { public function attach(IAttachment $attachment): IMessage {
/** @var Attachment $attachment */ /** @var Attachment $attachment */
$this->swiftMessage->attach($attachment->getSwiftAttachment());
$attachment->attach($this->symfonyEmail);
return $this; 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 { protected function convertAddresses(array $addresses): array {
if (!function_exists('idn_to_ascii') || !defined('INTL_IDNA_VARIANT_UTS46')) {
return $addresses;
}

$convertedAddresses = []; $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)) { if (is_numeric($email)) {
$convertedAddresses[] = $name . '@' . $domain;
$convertedAddresses[] = new Address($readableName);
} else { } else {
$convertedAddresses[$name . '@' . $domain] = $readableName;
$convertedAddresses[] = new Address($email, $readableName);
} }
}
});


return $convertedAddresses; return $convertedAddresses;
} }
* @return $this * @return $this
*/ */
public function setFrom(array $addresses): IMessage { public function setFrom(array $addresses): IMessage {
$addresses = $this->convertAddresses($addresses);

$this->swiftMessage->setFrom($addresses);
$this->from = $addresses;
return $this; return $this;
} }


/** /**
* Get the from address of this message. * Get the from address of this message.
*
* @return array
*/ */
public function getFrom(): array { public function getFrom(): array {
return $this->swiftMessage->getFrom() ?? [];
return $this->from;
} }


/** /**
* Set the Reply-To address of this message * Set the Reply-To address of this message
* *
* @param array $addresses
* @return $this * @return $this
*/ */
public function setReplyTo(array $addresses): IMessage { public function setReplyTo(array $addresses): IMessage {
$addresses = $this->convertAddresses($addresses);

$this->swiftMessage->setReplyTo($addresses);
$this->replyTo = $addresses;
return $this; return $this;
} }


/** /**
* Returns the Reply-To address of this message * 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;
} }


/** /**
* @return $this * @return $this
*/ */
public function setTo(array $recipients): IMessage { public function setTo(array $recipients): IMessage {
$recipients = $this->convertAddresses($recipients);

$this->swiftMessage->setTo($recipients);
$this->to = $recipients;
return $this; return $this;
} }


/** /**
* Get the to address of this message. * Get the to address of this message.
*
* @return array
*/ */
public function getTo(): array { public function getTo(): array {
return $this->swiftMessage->getTo() ?? [];
return $this->to;
} }


/** /**
* @return $this * @return $this
*/ */
public function setCc(array $recipients): IMessage { public function setCc(array $recipients): IMessage {
$recipients = $this->convertAddresses($recipients);

$this->swiftMessage->setCc($recipients);
$this->cc = $recipients;
return $this; return $this;
} }


/** /**
* Get the cc address of this message. * Get the cc address of this message.
*
* @return array
*/ */
public function getCc(): array { public function getCc(): array {
return $this->swiftMessage->getCc() ?? [];
return $this->cc;
} }


/** /**
* @return $this * @return $this
*/ */
public function setBcc(array $recipients): IMessage { public function setBcc(array $recipients): IMessage {
$recipients = $this->convertAddresses($recipients);

$this->swiftMessage->setBcc($recipients);
$this->bcc = $recipients;
return $this; return $this;
} }


/** /**
* Get the Bcc address of this message. * Get the Bcc address of this message.
*
* @return array
*/ */
public function getBcc(): array { public function getBcc(): array {
return $this->swiftMessage->getBcc() ?? [];
return $this->bcc;
} }


/** /**
* Set the subject of this message. * Set the subject of this message.
* *
* @param string $subject
* @return IMessage
* @return $this
*/ */
public function setSubject(string $subject): IMessage { public function setSubject(string $subject): IMessage {
$this->swiftMessage->setSubject($subject);
$this->symfonyEmail->subject($subject);
return $this; return $this;
} }


/** /**
* Get the from subject of this message. * Get the from subject of this message.
*
* @return string
*/ */
public function getSubject(): string { public function getSubject(): string {
return $this->swiftMessage->getSubject();
return $this->symfonyEmail->getSubject() ?? '';
} }


/** /**
* Set the plain-text body of this message. * Set the plain-text body of this message.
*
* @param string $body
* @return $this * @return $this
*/ */
public function setPlainBody(string $body): IMessage { public function setPlainBody(string $body): IMessage {
$this->swiftMessage->setBody($body);
$this->symfonyEmail->text($body);
return $this; return $this;
} }


/** /**
* Get the plain body of this message. * Get the plain body of this message.
*
* @return string
*/ */
public function getPlainBody(): 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. * 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 * @return $this
*/ */
public function setHtmlBody($body) {
public function setHtmlBody(string $body): IMessage {
if (!$this->plainTextOnly) { if (!$this->plainTextOnly) {
$this->swiftMessage->addPart($body, 'text/html');
$this->symfonyEmail->html($body);
} }
return $this; 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 * @return $this
*/ */
public function setBody($body, $contentType) {
public function setBody(string $body, string $contentType): IMessage {
if (!$this->plainTextOnly || $contentType !== 'text/html') { 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; 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 * @return $this
*/ */
public function useTemplate(IEMailTemplate $emailTemplate): IMessage { public function useTemplate(IEMailTemplate $emailTemplate): IMessage {
* @return $this * @return $this
*/ */
public function setAutoSubmitted(string $value): IMessage { public function setAutoSubmitted(string $value): IMessage {
$headers = $this->swiftMessage->getHeaders();
$headers = $this->symfonyEmail->getHeaders();


if ($headers->has(AutoSubmitted::HEADER)) { if ($headers->has(AutoSubmitted::HEADER)) {
// if the header already exsists, remove it. // if the header already exsists, remove it.
// of the interface \Swift_Mime_Header, however the // of the interface \Swift_Mime_Header, however the
// interface doesn't, and this makes the static-code // interface doesn't, and this makes the static-code
// analysis unhappy. // analysis unhappy.
// @todo check if symfony mailer can modify the autosubmitted header
$headers->remove(AutoSubmitted::HEADER); $headers->remove(AutoSubmitted::HEADER);
} }


* @return string * @return string
*/ */
public function getAutoSubmitted(): string { public function getAutoSubmitted(): string {
$headers = $this->swiftMessage->getHeaders();
$headers = $this->symfonyEmail->getHeaders();


return $headers->has(AutoSubmitted::HEADER) ? return $headers->has(AutoSubmitted::HEADER) ?
$headers->get(AutoSubmitted::HEADER)->toString() : AutoSubmitted::VALUE_NO;
$headers->get(AutoSubmitted::HEADER)->getBodyAsString() : AutoSubmitted::VALUE_NO;
} }
} }

+ 1
- 1
lib/public/Mail/IMailer.php View File

public function send(IMessage $message): array; 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 * @param string $email Email address to be validated
* @return bool True if the mail address is valid, false otherwise * @return bool True if the mail address is valid, false otherwise

+ 92
- 36
tests/lib/Mail/MailerTest.php View File

use OCP\L10N\IFactory; use OCP\L10N\IFactory;
use OCP\Mail\Events\BeforeMessageSent; use OCP\Mail\Events\BeforeMessageSent;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Swift_SwiftException;
use Test\TestCase; 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 { class MailerTest extends TestCase {
/** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
/** @var IConfig|MockObject */
private $config; private $config;
/** @var Defaults|\PHPUnit\Framework\MockObject\MockObject */
/** @var Defaults|MockObject */
private $defaults; private $defaults;
/** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
/** @var LoggerInterface|MockObject */
private $logger; private $logger;
/** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */
/** @var IURLGenerator|MockObject */
private $urlGenerator; private $urlGenerator;
/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
/** @var IL10N|MockObject */
private $l10n; private $l10n;
/** @var Mailer */ /** @var Mailer */
private $mailer; private $mailer;
$path = '/usr/sbin/sendmail'; $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')); $this->assertEquals($expected, self::invokePrivate($this->mailer, 'getSendMailInstance'));
} }


['mail_sendmailmode', 'smtp', $sendmailMode], ['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() { 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'); $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() { public function testGetInstanceSendmail() {
]); ]);


$mailer = self::invokePrivate($this->mailer, 'getInstance'); $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() { 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); $message = $this->createMock(Message::class);


$event = new BeforeMessageSent($message); $event = new BeforeMessageSent($message);
->method('dispatchTyped') ->method('dispatchTyped')
->with($this->equalTo($event)); ->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() { public function testCreateMessage() {




public function testSendInvalidMailException() { 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); $this->expectException(\Exception::class);


$message = $this->getMockBuilder('\OC\Mail\Message') $message = $this->getMockBuilder('\OC\Mail\Message')
->disableOriginalConstructor()->getMock(); ->disableOriginalConstructor()->getMock();
$message->expects($this->once()) $message->expects($this->once())
->method('getSwiftMessage')
->willReturn(new \Swift_Message());
->method('getSymfonyEmail')
->willReturn(new Email());


$this->mailer->send($message); $this->mailer->send($message);
} }
$this->config->method('getSystemValue') $this->config->method('getSystemValue')
->willReturnMap([ ->willReturnMap([
['mail_smtpmode', 'smtp', 'smtp'], ['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'); $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() { public function testStreamingOptionsWrongType() {
$this->config->method('getSystemValue') $this->config->method('getSystemValue')
->willReturnMap([ ->willReturnMap([
['mail_smtpmode', 'smtp', 'smtp'], ['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'); $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 { public function testLocalDomain(): void {
$this->config->method('getSystemValue') $this->config->method('getSystemValue')
->willReturnMap([ ->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') $this->config->method('getSystemValueString')
->with('overwrite.cli.url', '') ->with('overwrite.cli.url', '')
->willReturn('https://some.valid.url.com:8080'); ->willReturn('https://some.valid.url.com:8080');


/** @var \Swift_Mailer $mailer */
/** @var SymfonyMailer $mailer */
$mailer = self::invokePrivate($this->mailer, 'getInstance'); $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()); self::assertEquals('some.valid.url.com', $transport->getLocalDomain());
} }


public function testLocalDomainInvalidUrl(): void { public function testLocalDomainInvalidUrl(): void {
$this->config->method('getSystemValue') $this->config->method('getSystemValue')
->willReturnMap([ ->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') $this->config->method('getSystemValueString')
->with('overwrite.cli.url', '') ->with('overwrite.cli.url', '')
->willReturn('https:only.slash.does.not.work:8080'); ->willReturn('https:only.slash.does.not.work:8080');


/** @var \Swift_Mailer $mailer */
/** @var SymfonyMailer $mailer */
$mailer = self::invokePrivate($this->mailer, 'getInstance'); $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()); self::assertEquals('[127.0.0.1]', $transport->getLocalDomain());
} }
} }

+ 144
- 222
tests/lib/Mail/MessageTest.php View File

use OC\Mail\Message; use OC\Mail\Message;
use OCP\Mail\Headers\AutoSubmitted; use OCP\Mail\Headers\AutoSubmitted;
use OCP\Mail\IEMailTemplate; 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 Test\TestCase;
use PHPUnit\Framework\MockObject\MockObject;


class MessageTest extends TestCase { class MessageTest extends TestCase {
/** @var Swift_Message */
private $swiftMessage;
/** @var Email */
private $symfonyEmail;
/** @var Message */ /** @var Message */
private $message; private $message;


*/ */
public function mailAddressProvider() { public function mailAddressProvider() {
return [ 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')]
],
]; ];
} }


*/ */
public function getMailAddressProvider() { public function getMailAddressProvider() {
return [ return [
[null, []],
[[], []],
[['lukas@owncloud.com' => 'Lukas Reschke'], ['lukas@owncloud.com' => 'Lukas Reschke']], [['lukas@owncloud.com' => 'Lukas Reschke'], ['lukas@owncloud.com' => 'Lukas Reschke']],
]; ];
} }
protected function setUp(): void { protected function setUp(): void {
parent::setUp(); parent::setUp();


$this->swiftMessage = $this->getMockBuilder('\Swift_Message')
$this->symfonyEmail = $this->getMockBuilder(Email::class)
->disableOriginalConstructor()->getMock(); ->disableOriginalConstructor()->getMock();


$this->message = new Message($this->swiftMessage, false);
$this->message = new Message($this->symfonyEmail, false);
} }


/** /**
* @requires function idn_to_ascii
* @dataProvider mailAddressProvider * @dataProvider mailAddressProvider
* *
* @param string $unconverted * @param string $unconverted
* @param string $expected * @param string $expected
*/ */
public function testConvertAddresses($unconverted, $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()) ->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()) ->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()) ->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() { public function testSetPlainBody() {
$this->swiftMessage
$this->symfonyEmail
->expects($this->once()) ->expects($this->once())
->method('setBody')
->method('text')
->with('Fancy Body'); ->with('Fancy Body');


$this->message->setPlainBody('Fancy Body'); $this->message->setPlainBody('Fancy Body');
} }


public function testGetPlainBody() { public function testGetPlainBody() {
$this->swiftMessage
$this->symfonyEmail
->expects($this->once()) ->expects($this->once())
->method('getBody')
->method('getTextBody')
->willReturn('Fancy Body'); ->willReturn('Fancy Body');


$this->assertSame('Fancy Body', $this->message->getPlainBody()); $this->assertSame('Fancy Body', $this->message->getPlainBody());
} }


public function testSetHtmlBody() { public function testSetHtmlBody() {
$this->swiftMessage
$this->symfonyEmail
->expects($this->once()) ->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>'); $this->message->setHtmlBody('<blink>Fancy Body</blink>');
} }


public function testPlainTextRenderOption() { 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(); ->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(); ->disableOriginalConstructor()->getMock();


$message = new Message($swiftMessage, true);
$message = new Message($symfonyEmail, true);


$template $template
->expects($this->never()) ->expects($this->never())
} }


public function testBothRenderingOptions() { 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(); ->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(); ->disableOriginalConstructor()->getMock();


$message = new Message($swiftMessage, false);
$message = new Message($symfonyEmail, false);


$template $template
->expects($this->once()) ->expects($this->once())
} }


public function testSetAutoSubmitted1() { 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); $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED);
$this->assertNotSame('no', $message->getAutoSubmitted());
} }


public function testSetAutoSubmitted2() { 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); $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()); $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());
}
} }

Loading…
Cancel
Save