Completed
Pull Request — master (#29943)
by Thomas
12:00
created

Mailer::getMailInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Lukas Reschke <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\Mail;
23
24
use Egulias\EmailValidator\EmailValidator;
25
use Egulias\EmailValidator\Validation\RFCValidation;
26
use OCP\IConfig;
27
use OCP\Mail\IMailer;
28
use OCP\ILogger;
29
use org\bovigo\vfs\vfsStreamWrapperAlreadyRegisteredTestCase;
30
31
/**
32
 * Class Mailer provides some basic functions to create a mail message that can be used in combination with
33
 * \OC\Mail\Message.
34
 *
35
 * Example usage:
36
 *
37
 * 	$mailer = \OC::$server->getMailer();
38
 * 	$message = $mailer->createMessage();
39
 * 	$message->setSubject('Your Subject');
40
 * 	$message->setFrom(array('[email protected]' => 'ownCloud Notifier');
41
 * 	$message->setTo(array('[email protected]' => 'Recipient');
42
 * 	$message->setBody('The message text');
43
 * 	$mailer->send($message);
44
 *
45
 * This message can then be passed to send() of \OC\Mail\Mailer
46
 *
47
 * @package OC\Mail
48
 */
49
class Mailer implements IMailer {
50
	/** @var \Swift_SmtpTransport|\Swift_SendmailTransport Cached transport */
51
	private $instance = null;
52
	/** @var IConfig */
53
	private $config;
54
	/** @var ILogger */
55
	private $logger;
56
	/** @var \OC_Defaults */
57
	private $defaults;
58
59
	/**
60
	 * @param IConfig $config
61
	 * @param ILogger $logger
62
	 * @param \OC_Defaults $defaults
63
	 */
64
	public function __construct(IConfig $config,
65
						 ILogger $logger,
66
						 \OC_Defaults $defaults) {
67
		$this->config = $config;
68
		$this->logger = $logger;
69
		$this->defaults = $defaults;
70
	}
71
72
	/**
73
	 * Creates a new message object that can be passed to send()
74
	 *
75
	 * @return Message
76
	 */
77
	public function createMessage() {
78
		return new Message(new \Swift_Message());
79
	}
80
81
	/**
82
	 * Send the specified message. Also sets the from address to the value defined in config.php
83
	 * if no-one has been passed.
84
	 *
85
	 * @param Message $message Message to send
86
	 * @return string[] Array with failed recipients. Be aware that this depends on the used mail backend and
87
	 * therefore should be considered
88
	 * @throws \Exception In case it was not possible to send the message. (for example if an invalid mail address
89
	 * has been supplied.)
90
	 */
91
	public function send(Message $message) {
92
		$debugMode = $this->config->getSystemValue('mail_smtpdebug', false);
93
94
		if (!\is_array($message->getFrom()) || \count($message->getFrom()) === 0) {
95
			$message->setFrom([\OCP\Util::getDefaultEmailAddress($this->defaults->getName())]);
96
		}
97
98
		$failedRecipients = [];
99
100
		$mailer = $this->getInstance();
101
102
		$mailer->send($message->getSwiftMessage(), $failedRecipients);
103
104
		$allRecipients = [];
105
		if (!empty($message->getTo())) {
106
			$allRecipients = \array_merge($allRecipients, $message->getTo());
107
		}
108
		if (!empty($message->getCc())) {
109
			$allRecipients = \array_merge($allRecipients, $message->getCc());
110
		}
111
		if (!empty($message->getBcc())) {
112
			$allRecipients = \array_merge($allRecipients, $message->getBcc());
113
		}
114
115
		// Debugging logging
116
		$logMessage = 'Sent mail from "{from}" to "{recipients}" with subject "{subject}"';
117
		$this->logger->debug($logMessage, [
118
			'app' => 'core',
119
			'from' => \json_encode($message->getFrom()),
120
			'recipients' => \json_encode($allRecipients),
121
			'subject' => $message->getSubject()
122
		]);
123
124
		return $failedRecipients;
125
	}
126
127
	/**
128
	 * Checks if an e-mail address is valid
129
	 *
130
	 * @param string $email Email address to be validated
131
	 * @return bool True if the mail address is valid, false otherwise
132
	 */
133
	public function validateMailAddress($email) {
134
		$validator = new EmailValidator();
135
		return $validator->isValid($this->convertEmail($email), new RFCValidation());
136
	}
137
138
	/**
139
	 * SwiftMailer does currently not work with IDN domains, this function therefore converts the domains
140
	 *
141
	 * FIXME: Remove this once SwiftMailer supports IDN
142
	 *
143
	 * @param string $email
144
	 * @return string Converted mail address if `idn_to_ascii` exists
145
	 */
146
	protected function convertEmail($email) {
147
		if (!\function_exists('idn_to_ascii') || \strpos($email, '@') === false) {
148
			return $email;
149
		}
150
151
		list($name, $domain) = \explode('@', $email, 2);
152 View Code Duplication
		if (\defined('INTL_IDNA_VARIANT_UTS46')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
153
			$domain = \idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46);
154
		} else {
155
			$domain = \idn_to_ascii($domain);
156
		}
157
		return $name.'@'.$domain;
158
	}
159
160
	/**
161
	 * Returns whatever transport is configured within the config
162
	 *
163
	 * @return \Swift_SmtpTransport|\Swift_SendmailTransport
164
	 */
165
	protected function getInstance() {
166
		if ($this->instance !== null) {
167
			return $this->instance;
168
		}
169
170
		$mailMode = $this->config->getSystemValue('mail_smtpmode', 'php');
171
		if ($mailMode === 'smtp') {
172
			$instance = $this->getSmtpInstance();
173
		} else {
174
			// FIXME: Move into the return statement but requires proper testing
175
			//       for SMTP and mail as well. Thus not really doable for a
176
			//       minor release.
177
			$instance = new \Swift_Mailer($this->getSendMailInstance());
178
		}
179
180
		// Register plugins
181
182
		// Enable logger if debug mode is enabled
183
		if ($this->config->getSystemValue('mail_smtpdebug', false)) {
184
			$mailLogger = new \Swift_Plugins_Loggers_ArrayLogger();
185
			$instance->registerPlugin(new \Swift_Plugins_LoggerPlugin($mailLogger));
186
		}
187
188
		// Enable antiflood on smtp connection (defaults to 100 mails before reconnect)
189
		$instance->registerPlugin(new \Swift_Plugins_AntiFloodPlugin());
190
191
		$this->instance = $instance;
0 ignored issues
show
Documentation Bug introduced by
It seems like $instance can also be of type object<Swift_Mailer>. However, the property $instance is declared as type object<Swift_SmtpTranspo...wift_SendmailTransport>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
192
193
		return $this->instance;
194
	}
195
196
	/**
197
	 * Returns the SMTP transport
198
	 *
199
	 * @return \Swift_SmtpTransport
200
	 */
201
	protected function getSmtpInstance() {
202
		$transport = new \Swift_SmtpTransport();
203
		$transport->setTimeout($this->config->getSystemValue('mail_smtptimeout', 10));
204
		$transport->setHost($this->config->getSystemValue('mail_smtphost', '127.0.0.1'));
205
		$transport->setPort($this->config->getSystemValue('mail_smtpport', 25));
206
		if ($this->config->getSystemValue('mail_smtpauth', false)) {
207
			$transport->setUsername($this->config->getSystemValue('mail_smtpname', ''));
208
			$transport->setPassword($this->config->getSystemValue('mail_smtppassword', ''));
209
			$transport->setAuthMode($this->config->getSystemValue('mail_smtpauthtype', 'LOGIN'));
210
		}
211
		$smtpSecurity = $this->config->getSystemValue('mail_smtpsecure', '');
212
		if (!empty($smtpSecurity)) {
213
			$transport->setEncryption($smtpSecurity);
214
		}
215
		$transport->start();
216
		return $transport;
217
	}
218
219
	/**
220
	 * Returns the sendmail transport
221
	 *
222
	 * @return \Swift_SendmailTransport
223
	 */
224
	protected function getSendMailInstance() {
225
		switch ($this->config->getSystemValue('mail_smtpmode', 'sendmail')) {
226
			case 'qmail':
227
				$binaryPath = '/var/qmail/bin/sendmail';
228
				break;
229
			default:
230
				$binaryPath = '/usr/sbin/sendmail';
231
				break;
232
		}
233
234
		return new \Swift_SendmailTransport($binaryPath . ' -bs');
235
	}
236
}
237