Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  /**
   3   * Copyright 1997-2017 Horde LLC (http://www.horde.org/)
   4   * Copyright (c) 2002-2007, Richard Heyes
   5   * All rights reserved.
   6   *
   7   * Redistribution and use in source and binary forms, with or without
   8   * modification, are permitted provided that the following conditions
   9   * are met:
  10   *
  11   * o Redistributions of source code must retain the above copyright
  12   *   notice, this list of conditions and the following disclaimer.
  13   * o Redistributions in binary form must reproduce the above copyright
  14   *   notice, this list of conditions and the following disclaimer in the
  15   *   documentation and/or other materials provided with the distribution.
  16   * o The names of the authors may not be used to endorse or promote
  17   *   products derived from this software without specific prior written
  18   *   permission.
  19   *
  20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31   *
  32   * @category  Horde
  33   * @copyright 1997-2017 Horde LLC (http://www.horde.org/)
  34   * @copyright 2002-2007 Richard Heyes
  35   * @license   http://www.horde.org/licenses/bsd New BSD License
  36   * @package   Mail
  37   */
  38  
  39  /**
  40   * Mail transport base class.
  41   *
  42   * @author    Chuck Hagenbuch <chuck@horde.org>
  43   * @author    Richard Heyes <richard@phpguru.org>
  44   * @author    Michael Slusarz <slusarz@horde.org>
  45   * @category  Horde
  46   * @copyright 1997-2017 Horde LLC (http://www.horde.org/)
  47   * @copyright 2002-2007 Richard Heyes
  48   * @license   http://www.horde.org/licenses/bsd New BSD License
  49   * @package   Mail
  50   *
  51   * @property-read boolean $eai  Does the transport driver support EAI (RFC
  52   *                              6532) headers? (@since 2.5.0)
  53   */
  54  abstract class Horde_Mail_Transport
  55  {
  56      /**
  57       * Line terminator used for separating header lines.
  58       *
  59       * @var string
  60       */
  61      public $sep = PHP_EOL;
  62  
  63      /**
  64       * Configuration parameters.
  65       *
  66       * @var array
  67       */
  68      protected $_params = array();
  69  
  70      /**
  71       */
  72      public function __get($name)
  73      {
  74          switch ($name) {
  75          case 'eai':
  76              return false;
  77          }
  78      }
  79  
  80      /**
  81       * Send a message.
  82       *
  83       * @param mixed $recipients  Either a comma-seperated list of recipients
  84       *                           (RFC822 compliant), or an array of
  85       *                           recipients, each RFC822 valid. This may
  86       *                           contain recipients not specified in the
  87       *                           headers, for Bcc:, resending messages, etc.
  88       * @param array $headers     The headers to send with the mail, in an
  89       *                           associative array, where the array key is the
  90       *                           header name (ie, 'Subject'), and the array
  91       *                           value is the header value (ie, 'test'). The
  92       *                           header produced from those values would be
  93       *                           'Subject: test'.
  94       *                           If the '_raw' key exists, the value of this
  95       *                           key will be used as the exact text for
  96       *                           sending the message.
  97       * @param mixed $body        The full text of the message body, including
  98       *                           any Mime parts, etc. Either a string or a
  99       *                           stream resource.
 100       *
 101       * @throws Horde_Mail_Exception
 102       */
 103      abstract public function send($recipients, array $headers, $body);
 104  
 105      /**
 106       * Take an array of mail headers and return a string containing text
 107       * usable in sending a message.
 108       *
 109       * @param array $headers  The array of headers to prepare, in an
 110       *                        associative array, where the array key is the
 111       *                        header name (ie, 'Subject'), and the array value
 112       *                        is the header value (ie, 'test'). The header
 113       *                        produced from those values would be 'Subject:
 114       *                        test'.
 115       *                        If the '_raw' key exists, the value of this key
 116       *                        will be used as the exact text for sending the
 117       *                        message.
 118       *
 119       * @return mixed  Returns false if it encounters a bad address; otherwise
 120       *                returns an array containing two elements: Any From:
 121       *                address found in the headers, and the plain text version
 122       *                of the headers.
 123       * @throws Horde_Mail_Exception
 124       */
 125      public function prepareHeaders(array $headers)
 126      {
 127          $from = null;
 128          $lines = array();
 129          $raw = isset($headers['_raw'])
 130              ? $headers['_raw']
 131              : null;
 132  
 133          foreach ($headers as $key => $value) {
 134              if (strcasecmp($key, 'From') === 0) {
 135                  $parser = new Horde_Mail_Rfc822();
 136                  $addresses = $parser->parseAddressList($value, array(
 137                      'validate' => $this->eai ? 'eai' : true
 138                  ));
 139                  $from = $addresses[0]->bare_address;
 140  
 141                  // Reject envelope From: addresses with spaces.
 142                  if (strstr($from, ' ')) {
 143                      return false;
 144                  }
 145  
 146                  $lines[] = $key . ': ' . $this->_normalizeEOL($value);
 147              } elseif (!$raw && (strcasecmp($key, 'Received') === 0)) {
 148                  $received = array();
 149                  if (!is_array($value)) {
 150                      $value = array($value);
 151                  }
 152  
 153                  foreach ($value as $line) {
 154                      $received[] = $key . ': ' . $this->_normalizeEOL($line);
 155                  }
 156  
 157                  // Put Received: headers at the top.  Spam detectors often
 158                  // flag messages with Received: headers after the Subject:
 159                  // as spam.
 160                  $lines = array_merge($received, $lines);
 161              } elseif (!$raw) {
 162                  // If $value is an array (i.e., a list of addresses), convert
 163                  // it to a comma-delimited string of its elements (addresses).
 164                  if (is_array($value)) {
 165                      $value = implode(', ', $value);
 166                  }
 167                  $lines[] = $key . ': ' . $this->_normalizeEOL($value);
 168              }
 169          }
 170  
 171          return array($from, $raw ? $raw : implode($this->sep, $lines));
 172      }
 173  
 174      /**
 175       * Take a set of recipients and parse them, returning an array of bare
 176       * addresses (forward paths) that can be passed to sendmail or an SMTP
 177       * server with the 'RCPT TO:' command.
 178       *
 179       * @param mixed $recipients  Either a comma-separated list of recipients
 180       *                           (RFC822 compliant), or an array of
 181       *                           recipients, each RFC822 valid.
 182       *
 183       * @return array  Forward paths (bare addresses, IDN encoded).
 184       * @throws Horde_Mail_Exception
 185       */
 186      public function parseRecipients($recipients)
 187      {
 188          // Parse recipients, leaving out all personal info. This is
 189          // for smtp recipients, etc. All relevant personal information
 190          // should already be in the headers.
 191          $rfc822 = new Horde_Mail_Rfc822();
 192          return $rfc822->parseAddressList($recipients, array(
 193              'validate' => $this->eai ? 'eai' : true
 194          ))->bare_addresses_idn;
 195      }
 196  
 197      /**
 198       * Sanitize an array of mail headers by removing any additional header
 199       * strings present in a legitimate header's value.  The goal of this
 200       * filter is to prevent mail injection attacks.
 201       *
 202       * Raw headers are sent as-is.
 203       *
 204       * @param array $headers  The associative array of headers to sanitize.
 205       *
 206       * @return array  The sanitized headers.
 207       */
 208      protected function _sanitizeHeaders($headers)
 209      {
 210          foreach (array_diff(array_keys($headers), array('_raw')) as $key) {
 211              $headers[$key] = preg_replace('=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]);
 212          }
 213  
 214          return $headers;
 215      }
 216  
 217      /**
 218       * Normalizes EOLs in string data.
 219       *
 220       * @param string $data  Data.
 221       *
 222       * @return string  Normalized data.
 223       */
 224      protected function _normalizeEOL($data)
 225      {
 226          return strtr($data, array(
 227              "\r\n" => $this->sep,
 228              "\r" => $this->sep,
 229              "\n" => $this->sep
 230          ));
 231      }
 232  
 233      /**
 234       * Get the from address.
 235       *
 236       * @param string $from    From address.
 237       * @param array $headers  Headers array.
 238       *
 239       * @return string  Address object.
 240       * @throws Horde_Mail_Exception
 241       */
 242      protected function _getFrom($from, $headers)
 243      {
 244          /* Since few MTAs are going to allow this header to be forged unless
 245           * it's in the MAIL FROM: exchange, we'll use Return-Path instead of
 246           * From: if it's set. */
 247          foreach (array_keys($headers) as $hdr) {
 248              if (strcasecmp($hdr, 'Return-Path') === 0) {
 249                  $from = $headers[$hdr];
 250                  break;
 251              }
 252          }
 253  
 254          if (!strlen($from)) {
 255              throw new Horde_Mail_Exception('No from address provided.');
 256          }
 257  
 258          $from = new Horde_Mail_Rfc822_Address($from);
 259  
 260          return $from->bare_address_idn;
 261      }
 262  
 263  }