owncloud/core

View on GitHub
lib/private/Mail/Mailer.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php
/**
 * @author Lukas Reschke <lukas@statuscode.ch>
 *
 * @copyright Copyright (c) 2018, ownCloud GmbH
 * @license AGPL-3.0
 *
 * This code is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License, version 3,
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

namespace OC\Mail;

use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use OCP\IConfig;
use OCP\Mail\IMailer;
use OCP\ILogger;

/**
 * Class Mailer provides some basic functions to create a mail message that can be used in combination with
 * \OC\Mail\Message.
 *
 * Example usage:
 *
 *     $mailer = \OC::$server->getMailer();
 *     $message = $mailer->createMessage();
 *     $message->setSubject('Your Subject');
 *     $message->setFrom(array('cloud@domain.org' => 'ownCloud Notifier');
 *     $message->setTo(array('recipient@domain.org' => 'Recipient');
 *     $message->setBody('The message text');
 *     $mailer->send($message);
 *
 * This message can then be passed to send() of \OC\Mail\Mailer
 *
 * @package OC\Mail
 */
class Mailer implements IMailer {
    /** @var \Swift_SmtpTransport|\Swift_SendmailTransport Cached transport */
    private $instance = null;
    /** @var IConfig */
    private $config;
    /** @var ILogger */
    private $logger;
    /** @var \OC_Defaults */
    private $defaults;

    /**
     * @param IConfig $config
     * @param ILogger $logger
     * @param \OC_Defaults $defaults
     */
    public function __construct(
        IConfig $config,
        ILogger $logger,
        \OC_Defaults $defaults
    ) {
        $this->config = $config;
        $this->logger = $logger;
        $this->defaults = $defaults;
    }

    /**
     * Creates a new message object that can be passed to send()
     *
     * @return Message
     */
    public function createMessage() {
        return new Message(new \Swift_Message());
    }

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

        if (!\is_array($message->getFrom()) || \count($message->getFrom()) === 0) {
            $message->setFrom([\OCP\Util::getDefaultEmailAddress($this->defaults->getName())]);
        }

        $failedRecipients = [];

        $mailer = $this->getInstance();

        $mailer->send($message->getSwiftMessage(), $failedRecipients);

        $allRecipients = [];
        if (!empty($message->getTo())) {
            $allRecipients = \array_merge($allRecipients, $message->getTo());
        }
        if (!empty($message->getCc())) {
            $allRecipients = \array_merge($allRecipients, $message->getCc());
        }
        if (!empty($message->getBcc())) {
            $allRecipients = \array_merge($allRecipients, $message->getBcc());
        }

        // Debugging logging
        $logMessage = 'Sent mail from "{from}" to "{recipients}" with subject "{subject}"';
        $this->logger->debug($logMessage, [
            'app' => 'core',
            'from' => \json_encode($message->getFrom()),
            'recipients' => \json_encode($allRecipients),
            'subject' => $message->getSubject()
        ]);

        return $failedRecipients;
    }

    /**
     * Checks if an e-mail address is valid
     *
     * @param string $email Email address to be validated
     * @return bool True if the mail address is valid, false otherwise
     */
    public function validateMailAddress($email) {
        $validator = new EmailValidator();
        return $validator->isValid($this->convertEmail($email), new RFCValidation());
    }

    /**
     * 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($email) {
        if (!\function_exists('idn_to_ascii') || \strpos($email, '@') === false) {
            return $email;
        }

        list($name, $domain) = \explode('@', $email, 2);
        if (\defined('INTL_IDNA_VARIANT_UTS46')) {
            $domain = \idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
        } else {
            $domain = \idn_to_ascii($domain);
        }
        return $name.'@'.$domain;
    }

    /**
     * Returns whatever transport is configured within the config
     *
     * @return \Swift_SmtpTransport|\Swift_SendmailTransport
     */
    protected function getInstance() {
        if ($this->instance !== null) {
            return $this->instance;
        }

        $mailMode = $this->config->getSystemValue('mail_smtpmode', 'php');
        if ($mailMode === 'smtp') {
            $instance = $this->getSmtpInstance();
        } else {
            // FIXME: Move into the return statement but requires proper testing
            //       for SMTP and mail as well. Thus not really doable for a
            //       minor release.
            $instance = new \Swift_Mailer($this->getSendMailInstance());
        }

        // Register plugins

        // Enable logger if debug mode is enabled
        if ($this->config->getSystemValue('mail_smtpdebug', false)) {
            $mailLogger = new \Swift_Plugins_Loggers_ArrayLogger();
            $instance->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger));
        }

        // Enable antiflood on smtp connection (defaults to 100 mails before reconnect)
        $instance->registerPlugin(new \Swift_Plugins_AntiFloodPlugin());

        $this->instance = $instance;

        return $this->instance;
    }

    /**
     * Returns the SMTP transport
     *
     * @return \Swift_SmtpTransport
     */
    protected function getSmtpInstance() {
        $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));
        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);
        }
        $transport->start();
        return $transport;
    }

    /**
     * Returns the sendmail transport
     *
     * @return \Swift_SendmailTransport
     */
    protected function getSendMailInstance() {
        switch ($this->config->getSystemValue('mail_smtpmode', 'sendmail')) {
            case 'qmail':
                $binaryPath = '/var/qmail/bin/sendmail';
                break;
            default:
                $binaryPath = '/usr/sbin/sendmail';
                break;
        }

        return new \Swift_SendmailTransport($binaryPath . ' -bs');
    }
}