Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]

   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          return serialize($this->__serialize());
 392      }
 393  
 394      /**
 395       * Serialization.
 396       *
 397       * @return array  Serialized data.
 398       */
 399      public function __serialize(): array
 400      {
 401          return array(
 402              // Serialized data ID.
 403              self::VERSION,
 404              $this->_headers->getArrayCopy(),
 405              // TODO: BC
 406              $this->_eol
 407          );
 408      }
 409  
 410      /**
 411       * Unserialization.
 412       *
 413       * @param array $data  Serialized data.
 414       *
 415       * @throws Horde_Mime_Exception
 416       */
 417      public function __unserialize(array $data): void
 418      {
 419          if (!isset($data[0]) || ($data[0] != self::VERSION)) {
 420              throw new Horde_Mime_Exception('Cache version change');
 421          }
 422  
 423          $this->_headers = new Horde_Support_CaseInsensitiveArray($data[1]);
 424          // TODO: BC
 425          $this->_eol = $data[2];
 426      }
 427  
 428      /**
 429       * Unserialization.
 430       *
 431       * @param string $data  Serialized data.
 432       *
 433       * @throws Exception
 434       */
 435      public function unserialize($data)
 436      {
 437          $data = @unserialize($data);
 438          if (!is_array($data)) {
 439              throw new Horde_Mime_Exception('Cache version change');
 440          }
 441          $this->__unserialize($data);
 442      }
 443  
 444      /* ArrayAccess methods. */
 445  
 446      /**
 447       * Does header exist?
 448       *
 449       * @since 2.5.0
 450       *
 451       * @param string $header  Header name.
 452       *
 453       * @return boolean  True if header exists.
 454       */
 455      #[ReturnTypeWillChange]
 456      public function offsetExists($offset)
 457      {
 458          return isset($this->_headers[trim($offset)]);
 459      }
 460  
 461      /**
 462       * Return header element object.
 463       *
 464       * @since 2.5.0
 465       *
 466       * @param string $header  Header name.
 467       *
 468       * @return Horde_Mime_Headers_Element  Element object, or null if not
 469       *                                     found.
 470       */
 471      #[ReturnTypeWillChange]
 472      public function offsetGet($offset)
 473      {
 474          return $this->_headers[trim($offset)];
 475      }
 476  
 477      /**
 478       * Store a header element object.
 479       *
 480       * @since 2.5.0
 481       *
 482       * @param string $offset                   Not used.
 483       * @param Horde_Mime_Headers_Element $elt  Header element.
 484       */
 485      #[ReturnTypeWillChange]
 486      public function offsetSet($offset, $value)
 487      {
 488          $this->addHeaderOb($value);
 489      }
 490  
 491      /**
 492       * Remove a header element object.
 493       *
 494       * @since 2.5.0
 495       *
 496       * @param string $offset  Header name.
 497       */
 498      #[ReturnTypeWillChange]
 499      public function offsetUnset($offset)
 500      {
 501          unset($this->_headers[trim($offset)]);
 502      }
 503  
 504      /* IteratorAggregate function */
 505  
 506      /**
 507       * @since 2.5.0
 508       */
 509      #[ReturnTypeWillChange]
 510      public function getIterator()
 511      {
 512          return new ArrayIterator($this->_headers);
 513      }
 514  
 515      /* Deprecated functions */
 516  
 517      /**
 518       * Handle deprecated methods.
 519       */
 520      public function __call($name, $arguments)
 521      {
 522          $d = new Horde_Mime_Headers_Deprecated($this);
 523          return call_user_func_array(array($d, $name), $arguments);
 524      }
 525  
 526      /**
 527       * Handle deprecated static methods.
 528       */
 529      public static function __callStatic($name, $arguments)
 530      {
 531          $d = new Horde_Mime_Headers_Deprecated();
 532          return call_user_func_array(array($d, $name), $arguments);
 533      }
 534  
 535      /**
 536       * @deprecated
 537       */
 538      protected $_eol = "\n";
 539  
 540      /**
 541       * @deprecated
 542       */
 543      public function setEOL($eol)
 544      {
 545          $this->_eol = $eol;
 546      }
 547  
 548      /**
 549       * @deprecated
 550       */
 551      public function getEOL()
 552      {
 553          return $this->_eol;
 554      }
 555  
 556      /* Constants for getValue(). @deprecated */
 557      const VALUE_STRING = 1;
 558      const VALUE_BASE = 2;
 559      const VALUE_PARAMS = 3;
 560  
 561  }