PhpMail.php 5.4 KB

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