Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 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 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   1  <?php
   2  /**
   3   * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
   4   *
   5   * See the enclosed file LICENSE for license information (LGPL). If you
   6   * did not receive this file, see http://www.horde.org/licenses/lgpl21.
   7   *
   8   * @category  Horde
   9   * @copyright 2002-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11   * @package   Mime
  12   */
  13  
  14  /**
  15   * This class represents the collection of header values for a single mail
  16   * message part.
  17   *
  18   * It supports the base e-mail spec (RFC 5322) and the MIME extensions to that
  19   * spec (RFC 2045).
  20   *
  21   * @author    Michael Slusarz <slusarz@horde.org>
  22   * @category  Horde
  23   * @copyright 2002-2017 Horde LLC
  24   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  25   * @package   Mime
  26   */
  27  class Horde_Mime_Headers
  28  implements ArrayAccess, IteratorAggregate, Serializable
  29  {
  30      /* Serialized version. */
  31      const VERSION = 3;
  32  
  33      /**
  34       * The default charset to use when parsing text parts with no charset
  35       * information.
  36       *
  37       * @todo Make this a non-static property or pass as parameter to static
  38       *       methods in Horde 6.
  39       * @var string
  40       */
  41      public static $defaultCharset = 'us-ascii';
  42  
  43      /**
  44       * Cached handler information for Header Element objects.
  45       *
  46       * @var array
  47       */
  48      protected static $_handlers = array();
  49  
  50      /**
  51       * The internal headers array.
  52       *
  53       * @var Horde_Support_CaseInsensitiveArray
  54       */
  55      protected $_headers;
  56  
  57      /**
  58       * Constructor.
  59       */
  60      public function __construct()
  61      {
  62          $this->_headers = new Horde_Support_CaseInsensitiveArray();
  63      }
  64  
  65      /**
  66       */
  67      public function __clone()
  68      {
  69          $copy = new Horde_Support_CaseInsensitiveArray();
  70          foreach ($this->_headers as $key => $val) {
  71              $copy[$key] = clone $val;
  72          }
  73          $this->_headers = $copy;
  74      }
  75  
  76      /**
  77       * Returns the headers in array format.
  78       *
  79       * @param array $opts  Optional parameters:
  80       * <pre>
  81       *   - broken_rfc2231: (boolean) Attempt to work around non-RFC
  82       *                     2231-compliant MUAs by generating both a RFC
  83       *                     2047-like parameter name and also the correct RFC
  84       *                     2231 parameter
  85       *                     DEFAULT: false
  86       *   - canonical: (boolean) Use canonical (RFC 822/2045) CRLF EOLs?
  87       *                DEFAULT: Uses "\n"
  88       *   - charset: (string) Encodes the headers using this charset. If empty,
  89       *              encodes using UTF-8.
  90       *              DEFAULT: No encoding.
  91       *   - defserver: (string) The default domain to append to mailboxes.
  92       *                DEFAULT: No default name.
  93       *   - lang: (string) The language to use when encoding.
  94       *           DEFAULT: None specified
  95       *   - nowrap: (integer) Don't wrap the headers.
  96       *             DEFAULT: Headers are wrapped.
  97       * </pre>
  98       *
  99       * @return array  The headers in array format. Keys are header names, but
 100       *                case sensitivity cannot be guaranteed. Values are
 101       *                header values.
 102       */
 103      public function toArray(array $opts = array())
 104      {
 105          $charset = array_key_exists('charset', $opts)
 106              ? (empty($opts['charset']) ? 'UTF-8' : $opts['charset'])
 107              : null;
 108          $eol = empty($opts['canonical'])
 109              ? $this->_eol
 110              : "\r\n";
 111          $ret = array();
 112  
 113          foreach ($this->_headers as $ob) {
 114              $sopts = array(
 115                  'charset' => $charset
 116              );
 117  
 118              if (($ob instanceof Horde_Mime_Headers_Addresses) ||
 119                  ($ob instanceof Horde_Mime_Headers_AddressesMulti)) {
 120                  if (!empty($opts['defserver'])) {
 121                      $sopts['defserver'] = $opts['defserver'];
 122                  }
 123              } elseif ($ob instanceof Horde_Mime_Headers_ContentParam) {
 124                  $sopts['broken_rfc2231'] = !empty($opts['broken_rfc2231']);
 125                  if (!empty($opts['lang'])) {
 126                      $sopts['lang'] = $opts['lang'];
 127                  }
 128              }
 129  
 130              $tmp = array();
 131  
 132              foreach ($ob->sendEncode(array_filter($sopts)) as $val) {
 133                  if (empty($opts['nowrap'])) {
 134                      /* Remove any existing linebreaks and wrap the line. */
 135                      $htext = $ob->name . ': ';
 136                      $val = ltrim(
 137                          substr(
 138                              wordwrap(
 139                                  $htext . strtr(trim($val), array("\r" => '', "\n" => '')),
 140                                  76,
 141                                  $eol . ' '
 142                              ),
 143                              strlen($htext)
 144                          )
 145                      );
 146                  }
 147  
 148                  $tmp[] = $val;
 149              }
 150  
 151              $ret[$ob->name] = (count($tmp) == 1)
 152                  ? reset($tmp)
 153                  : $tmp;
 154          }
 155  
 156          return $ret;
 157      }
 158  
 159      /**
 160       * Returns all headers concatenated into a single string.
 161       *
 162       * @param array $opts  See toArray().
 163       *
 164       * @return string  The headers in string format.
 165       */
 166      public function toString(array $opts = array())
 167      {
 168          $eol = empty($opts['canonical'])
 169              ? $this->_eol
 170              : "\r\n";
 171          $text = '';
 172  
 173          foreach ($this->toArray($opts) as $key => $val) {
 174              foreach ((is_array($val) ? $val : array($val)) as $entry) {
 175                  $text .= $key . ': ' . $entry . $eol;
 176              }
 177          }
 178  
 179          return $text . $eol;
 180      }
 181  
 182      /**
 183       * Add/append/replace a header.
 184       *
 185       * @param string $header  The header name.
 186       * @param string $value   The header value (UTF-8).
 187       * @param array $opts     DEPRECATED
 188       */
 189      public function addHeader($header, $value, array $opts = array())
 190      {
 191          /* Existing header? Add to that object. */
 192          $header = trim($header);
 193          if ($hdr = $this[$header]) {
 194              $hdr->setValue($value);
 195              return;
 196          }
 197  
 198          $classname = $this->_getHeaderClassName($header);
 199  
 200          try {
 201              $ob = new $classname($header, $value);
 202          } catch (InvalidArgumentException $e) {
 203              /* Ignore an invalid header. */
 204              return;
 205          } catch (Horde_Mime_Exception $e) {
 206              return;
 207          }
 208  
 209          switch ($classname) {
 210          case 'Horde_Mime_Headers_ContentParam_ContentDisposition':
 211          case 'Horde_Mime_Headers_ContentParam_ContentType':
 212              /* BC */
 213              if (!empty($opts['params'])) {
 214                  foreach ($opts['params'] as $key => $val) {
 215                      $ob[$key] = $val;
 216                  }
 217              }
 218              break;
 219          }
 220  
 221          $this->_headers[$ob->name] = $ob;
 222      }
 223  
 224      /**
 225       * Add a Horde_Mime_Headers_Element object to the current header list.
 226       *
 227       * @since 2.5.0
 228       *
 229       * @param Horde_Mime_Headers_Element $ob  Header object to add.
 230       * @param boolean $check                  Check that the header and object
 231       *                                        type match?
 232       *
 233       * @throws InvalidArgumentException
 234       */
 235      public function addHeaderOb(Horde_Mime_Headers_Element $ob, $check = false)
 236      {
 237          if ($check) {
 238              $cname = $this->_getHeaderClassName($ob->name);
 239              if (!($ob instanceof $cname)) {
 240                  throw new InvalidArgumentException(sprintf(
 241                      'Object is not correct class: %s',
 242                      $cname
 243                  ));
 244              }
 245          }
 246  
 247          /* Existing header? Add to that object. */
 248          if ($hdr = $this[$ob->name]) {
 249              $hdr->setValue($ob);
 250          } else {
 251              $this->_headers[$ob->name] = $ob;
 252          }
 253      }
 254  
 255      /**
 256       * Return the header class to use for a header name.
 257       *
 258       * @param string $header  The header name.
 259       *
 260       * @return string  The Horde_Mime_Headers_* class to use.
 261       */
 262      protected function _getHeaderClassName($header)
 263      {
 264          if (empty(self::$_handlers)) {
 265              $search = array(
 266                  'Horde_Mime_Headers_Element_Single',
 267                  'Horde_Mime_Headers_AddressesMulti',
 268                  'Horde_Mime_Headers_Addresses',
 269                  'Horde_Mime_Headers_ContentDescription',
 270                  'Horde_Mime_Headers_ContentId',
 271                  'Horde_Mime_Headers_ContentLanguage',
 272                  'Horde_Mime_Headers_ContentParam_ContentDisposition',
 273                  'Horde_Mime_Headers_ContentParam_ContentType',
 274                  'Horde_Mime_Headers_ContentTransferEncoding',
 275                  'Horde_Mime_Headers_Date',
 276                  'Horde_Mime_Headers_Identification',
 277                  'Horde_Mime_Headers_MessageId',
 278                  'Horde_Mime_Headers_Mime',
 279                  'Horde_Mime_Headers_MimeVersion',
 280                  'Horde_Mime_Headers_Received',
 281                  'Horde_Mime_Headers_Subject',
 282                  'Horde_Mime_Headers_UserAgent'
 283              );
 284  
 285              foreach ($search as $val) {
 286                  foreach ($val::getHandles() as $hdr) {
 287                      self::$_handlers[$hdr] = $val;
 288                  }
 289              }
 290          }
 291  
 292          $header = Horde_String::lower($header);
 293  
 294          return isset(self::$_handlers[$header])
 295              ? self::$_handlers[$header]
 296              : 'Horde_Mime_Headers_Element_Multiple';
 297      }
 298  
 299      /**
 300       * Get a header from the header array.
 301       *
 302       * @param string $header  The header name.
 303       *
 304       * @return Horde_Mime_Headers_Element  Element object, or null if not
 305       *                                     found.
 306       */
 307      public function getHeader($header)
 308      {
 309          return $this[$header];
 310      }
 311  
 312      /**
 313       * Remove a header from the header array.
 314       *
 315       * @param string $header  The header name.
 316       */
 317      public function removeHeader($header)
 318      {
 319          unset($this[$header]);
 320      }
 321  
 322      /* Static methods. */
 323  
 324      /**
 325       * Builds a Horde_Mime_Headers object from header text.
 326       *
 327       * @param mixed $text  A text string (or, as of 2.3.0, a Horde_Stream
 328       *                     object or stream resource) containing the headers.
 329       *
 330       * @return Horde_Mime_Headers  A new Horde_Mime_Headers object.
 331       */
 332      public static function parseHeaders($text)
 333      {
 334          $curr = null;
 335          $headers = new Horde_Mime_Headers();
 336          $hdr_list = array();
 337  
 338          if ($text instanceof Horde_Stream) {
 339              $stream = $text;
 340              $stream->rewind();
 341          } else {
 342              $stream = new Horde_Stream_Temp();
 343              $stream->add($text, true);
 344          }
 345  
 346          while (!$stream->eof()) {
 347              if (!($val = rtrim($stream->getToChar("\n", false), "\r"))) {
 348                  break;
 349              }
 350  
 351              if ($curr && (($val[0] == ' ') || ($val[0] == "\t"))) {
 352                  $curr->text .= ' ' . ltrim($val);
 353              } else {
 354                  $pos = strpos($val, ':');
 355  
 356                  $curr = new stdClass;
 357                  $curr->header = substr($val, 0, $pos);
 358                  $curr->text = ltrim(substr($val, $pos + 1));
 359  
 360                  $hdr_list[] = $curr;
 361              }
 362          }
 363  
 364          foreach ($hdr_list as $val) {
 365              /* When parsing, only keep the FIRST header seen for single value
 366               * text-only headers, since newer headers generally are appended
 367               * to the top of the message. */
 368              if (!($ob = $headers[$val->header]) ||
 369                  !($ob instanceof Horde_Mime_Headers_Element_Single) ||
 370                  ($ob instanceof Horde_Mime_Headers_Addresses)) {
 371                  $headers->addHeader($val->header, rtrim($val->text));
 372              }
 373          }
 374  
 375          if (!($text instanceof Horde_Stream)) {
 376              $stream->close();
 377          }
 378  
 379          return $headers;
 380      }
 381  
 382      /* Serializable methods. */
 383  
 384      /**
 385       * Serialization.
 386       *
 387       * @return string  Serialized data.
 388       */
 389      public function serialize()
 390      {
 391          $data = array(
 392              // Serialized data ID.
 393              self::VERSION,
 394              $this->_headers->getArrayCopy(),
 395              // TODO: BC
 396              $this->_eol
 397          );
 398  
 399          return serialize($data);
 400      }
 401  
 402      /**
 403       * Unserialization.
 404       *
 405       * @param string $data  Serialized data.
 406       *
 407       * @throws Exception
 408       */
 409      public function unserialize($data)
 410      {
 411          $data = @unserialize($data);
 412          if (!is_array($data) ||
 413              !isset($data[0]) ||
 414              ($data[0] != self::VERSION)) {
 415              throw new Horde_Mime_Exception('Cache version change');
 416          }
 417  
 418          $this->_headers = new Horde_Support_CaseInsensitiveArray($data[1]);
 419          // TODO: BC
 420          $this->_eol = $data[2];
 421      }
 422  
 423      /* ArrayAccess methods. */
 424  
 425      /**
 426       * Does header exist?
 427       *
 428       * @since 2.5.0
 429       *
 430       * @param string $header  Header name.
 431       *
 432       * @return boolean  True if header exists.
 433       */
 434      public function offsetExists($offset)
 435      {
 436          return isset($this->_headers[trim($offset)]);
 437      }
 438  
 439      /**
 440       * Return header element object.
 441       *
 442       * @since 2.5.0
 443       *
 444       * @param string $header  Header name.
 445       *
 446       * @return Horde_Mime_Headers_Element  Element object, or null if not
 447       *                                     found.
 448       */
 449      public function offsetGet($offset)
 450      {
 451          return $this->_headers[trim($offset)];
 452      }
 453  
 454      /**
 455       * Store a header element object.
 456       *
 457       * @since 2.5.0
 458       *
 459       * @param string $offset                   Not used.
 460       * @param Horde_Mime_Headers_Element $elt  Header element.
 461       */
 462      public function offsetSet($offset, $value)
 463      {
 464          $this->addHeaderOb($value);
 465      }
 466  
 467      /**
 468       * Remove a header element object.
 469       *
 470       * @since 2.5.0
 471       *
 472       * @param string $offset  Header name.
 473       */
 474      public function offsetUnset($offset)
 475      {
 476          unset($this->_headers[trim($offset)]);
 477      }
 478  
 479      /* IteratorAggregate function */
 480  
 481      /**
 482       * @since 2.5.0
 483       */
 484      public function getIterator()
 485      {
 486          return new ArrayIterator($this->_headers);
 487      }
 488  
 489      /* Deprecated functions */
 490  
 491      /**
 492       * Handle deprecated methods.
 493       */
 494      public function __call($name, $arguments)
 495      {
 496          $d = new Horde_Mime_Headers_Deprecated($this);
 497          return call_user_func_array(array($d, $name), $arguments);
 498      }
 499  
 500      /**
 501       * Handle deprecated static methods.
 502       */
 503      public static function __callStatic($name, $arguments)
 504      {
 505          $d = new Horde_Mime_Headers_Deprecated();
 506          return call_user_func_array(array($d, $name), $arguments);
 507      }
 508  
 509      /**
 510       * @deprecated
 511       */
 512      protected $_eol = "\n";
 513  
 514      /**
 515       * @deprecated
 516       */
 517      public function setEOL($eol)
 518      {
 519          $this->_eol = $eol;
 520      }
 521  
 522      /**
 523       * @deprecated
 524       */
 525      public function getEOL()
 526      {
 527          return $this->_eol;
 528      }
 529  
 530      /* Constants for getValue(). @deprecated */
 531      const VALUE_STRING = 1;
 532      const VALUE_BASE = 2;
 533      const VALUE_PARAMS = 3;
 534  
 535  }