Skip to content

Commit 3f94e7c

Browse files
committed
LOOP-1158: Added custom mail system
1 parent 9ef8f84 commit 3f94e7c

File tree

1 file changed

+173
-0
lines changed
  • web/profiles/custom/os2loop/modules/os2loop_mail_notifications/src/Plugin/Mail

1 file changed

+173
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
3+
namespace Drupal\os2loop_mail_notifications\Plugin\Mail;
4+
5+
use Drupal\Core\Mail\MailInterface;
6+
use Drupal\Core\Site\Settings;
7+
use Symfony\Component\Mime\Header\Headers;
8+
use Symfony\Component\Mime\Header\UnstructuredHeader;
9+
10+
/**
11+
* Copy of the default Drupal mail backend, using PHP's native mail() function.
12+
*
13+
* The only thing changed is the namespace, id, format() method and
14+
* $message['headers']['Content-Type'] in mail() method.
15+
*
16+
* @Mail(
17+
* id = "os2loop_mail_notifications",
18+
* label = @Translation("Custom PHP mailer"),
19+
* description = @Translation("Sends the message as plain text, using PHP's native mail() function.")
20+
* )
21+
*/
22+
class PhpMail implements MailInterface {
23+
24+
/**
25+
* A list of headers that can contain multiple email addresses.
26+
*
27+
* @see \Symfony\Component\Mime\Header\Headers::HEADER_CLASS_MAP
28+
*/
29+
private const MAILBOX_LIST_HEADERS = ['from', 'to', 'reply-to', 'cc', 'bcc'];
30+
31+
/**
32+
* The configuration factory.
33+
*
34+
* @var \Drupal\Core\Config\ConfigFactoryInterface
35+
*/
36+
protected $configFactory;
37+
38+
/**
39+
* PhpMail constructor.
40+
*/
41+
public function __construct() {
42+
$this->configFactory = \Drupal::configFactory();
43+
}
44+
45+
/**
46+
* Concatenates and wraps the email body for plain-text mails.
47+
*
48+
* @param array $message
49+
* A message array, as described in hook_mail_alter().
50+
*
51+
* @return array
52+
* The formatted $message.
53+
*/
54+
public function format(array $message) {
55+
$message['body'] = $message['params']['messages_with_headings'];
56+
57+
return $message;
58+
}
59+
60+
/**
61+
* Sends an email message.
62+
*
63+
* @param array $message
64+
* A message array, as described in hook_mail_alter().
65+
*
66+
* @return bool
67+
* TRUE if the mail was successfully accepted, otherwise FALSE.
68+
*
69+
* @see http://php.net/manual/function.mail.php
70+
* @see \Drupal\Core\Mail\MailManagerInterface::mail()
71+
*/
72+
public function mail(array $message) {
73+
// If 'Return-Path' isn't already set in php.ini, we pass it separately
74+
// as an additional parameter instead of in the header.
75+
if (isset($message['headers']['Return-Path'])) {
76+
$return_path_set = strpos(ini_get('sendmail_path'), ' -f');
77+
if (!$return_path_set) {
78+
$message['Return-Path'] = $message['headers']['Return-Path'];
79+
unset($message['headers']['Return-Path']);
80+
}
81+
}
82+
83+
$headers = new Headers();
84+
$message['headers']['Content-Type'] = 'text/html; charset=iso-8859-1;';
85+
foreach ($message['headers'] as $name => $value) {
86+
if (in_array(strtolower($name), self::MAILBOX_LIST_HEADERS, TRUE)) {
87+
$value = explode(',', $value);
88+
}
89+
$headers->addHeader($name, $value);
90+
}
91+
$line_endings = Settings::get('mail_line_endings', PHP_EOL);
92+
// Prepare mail commands.
93+
$mail_subject = (new UnstructuredHeader('subject', $message['subject']))->getBodyAsString();
94+
// Note: email uses CRLF for line-endings. PHP's API requires LF
95+
// on Unix and CRLF on Windows. Drupal automatically guesses the
96+
// line-ending format appropriate for your system. If you need to
97+
// override this, adjust $settings['mail_line_endings'] in settings.php.
98+
$mail_body = preg_replace('@\r?\n@', $line_endings, $message['body']);
99+
// For headers, PHP's API suggests that we use CRLF normally,
100+
// but some MTAs incorrectly replace LF with CRLF. See #234403.
101+
$mail_headers = str_replace("\r\n", "\n", $headers->toString());
102+
$mail_subject = str_replace("\r\n", "\n", $mail_subject);
103+
104+
$request = \Drupal::request();
105+
106+
// We suppress warnings and notices from mail() because of issues on some
107+
// hosts. The return value of this method will still indicate whether mail
108+
// was sent successfully.
109+
if (!$request->server->has('WINDIR') && strpos($request->server->get('SERVER_SOFTWARE'), 'Win32') === FALSE) {
110+
// On most non-Windows systems, the "-f" option to the sendmail command
111+
// is used to set the Return-Path. There is no space between -f and
112+
// the value of the return path.
113+
// We validate the return path, unless it is equal to the site mail, which
114+
// we assume to be safe.
115+
$site_mail = $this->configFactory->get('system.site')->get('mail');
116+
$additional_headers = isset($message['Return-Path']) && ($site_mail === $message['Return-Path'] || static::isShellSafe($message['Return-Path'])) ? '-f' . $message['Return-Path'] : '';
117+
$mail_result = @mail(
118+
$message['to'],
119+
$mail_subject,
120+
$mail_body,
121+
$mail_headers,
122+
$additional_headers
123+
);
124+
}
125+
else {
126+
// On Windows, PHP will use the value of sendmail_from for the
127+
// Return-Path header.
128+
$old_from = ini_get('sendmail_from');
129+
ini_set('sendmail_from', $message['Return-Path']);
130+
$mail_result = @mail(
131+
$message['to'],
132+
$mail_subject,
133+
$mail_body,
134+
$mail_headers
135+
);
136+
ini_set('sendmail_from', $old_from);
137+
}
138+
139+
return $mail_result;
140+
}
141+
142+
/**
143+
* Disallows potentially unsafe shell characters.
144+
*
145+
* Functionally similar to PHPMailer::isShellSafe() which resulted from
146+
* CVE-2016-10045. Note that escapeshellarg and escapeshellcmd are inadequate
147+
* for this purpose.
148+
*
149+
* @param string $string
150+
* The string to be validated.
151+
*
152+
* @return bool
153+
* True if the string is shell-safe.
154+
*
155+
* @see https://github.com/PHPMailer/PHPMailer/issues/924
156+
* @see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.phpmailer.php#L1430
157+
*
158+
* @todo Rename to ::isShellSafe() and/or discuss whether this is the correct
159+
* location for this helper.
160+
*/
161+
protected static function isShellSafe($string) {
162+
if (escapeshellcmd($string) !== $string ||
163+
!in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
164+
) {
165+
return FALSE;
166+
}
167+
if (preg_match('/[^a-zA-Z0-9@_\-.]/', $string) !== 0) {
168+
return FALSE;
169+
}
170+
return TRUE;
171+
}
172+
173+
}

0 commit comments

Comments
 (0)