Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

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

   1  <?php
   2  /**
   3   * Copyright 2014-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 2014-2017 Horde LLC
  10   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  11   * @package   Mime
  12   */
  13  
  14  /**
  15   * This class represents a header element that contains MIME content
  16   * parameters (RFCs 2045, 2183, 2231).
  17   *
  18   * @author    Michael Slusarz <slusarz@horde.org>
  19   * @category  Horde
  20   * @copyright 2014-2017 Horde LLC
  21   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  22   * @package   Mime
  23   * @since     2.5.0
  24   *
  25   * @property-read array $params  Content parameters.
  26   */
  27  class Horde_Mime_Headers_ContentParam
  28  extends Horde_Mime_Headers_Element_Single
  29  implements ArrayAccess, Horde_Mime_Headers_Extension_Mime, Serializable
  30  {
  31      /**
  32       * Content parameters.
  33       *
  34       * @var Horde_Support_CaseInsensitiveArray
  35       */
  36      protected $_params;
  37  
  38      /**
  39       */
  40      public function __clone()
  41      {
  42          $this->_params = new Horde_Support_CaseInsensitiveArray(
  43              $this->_params->getArrayCopy()
  44          );
  45      }
  46  
  47      /**
  48       */
  49      public function __get($name)
  50      {
  51          switch ($name) {
  52          case 'full_value':
  53              $tmp = $this->value;
  54              foreach ($this->_escapeParams($this->params) as $key => $val) {
  55                  $tmp .= '; ' . $key . '=' . $val;
  56              }
  57              return $tmp;
  58  
  59          case 'params':
  60              return $this->_params->getArrayCopy();
  61          }
  62  
  63          return parent::__get($name);
  64      }
  65  
  66      /**
  67       * @param mixed $data  Either an array (interpreted as a list of
  68       *                     parameters), a string (interpreted as a RFC
  69       *                     encoded parameter list), an object with two
  70       *                     properties: value and params, or a
  71       *                     Horde_Mime_Headers_ContentParam object.
  72       */
  73      protected function _setValue($data)
  74      {
  75          if (!$this->_params) {
  76              $this->_params = new Horde_Support_CaseInsensitiveArray();
  77          }
  78  
  79          if ($data instanceof Horde_Mime_Headers_ContentParam) {
  80              if (empty($this->_values)) {
  81                  $this->setContentParamValue($data->value);
  82              }
  83              foreach ($data->params as $key => $val) {
  84                  $this[$key] = $val;
  85              }
  86          } elseif (is_object($data)) {
  87              if (!empty($data->value)) {
  88                  $this->setContentParamValue($data->value);
  89              }
  90              if (!empty($data->params)) {
  91                  $this->decode($data->params);
  92              }
  93          } else {
  94              $this->decode($data);
  95          }
  96      }
  97  
  98      /**
  99       * @param array $opts  See encode().
 100       */
 101      protected function _sendEncode($opts)
 102      {
 103          $out = $this->value;
 104  
 105          foreach ($this->encode($opts) as $key => $val) {
 106              $out .= '; ' . $key . '=' . $val;
 107          }
 108  
 109          return array($out);
 110      }
 111  
 112      /**
 113       */
 114      public static function getHandles()
 115      {
 116          return array();
 117      }
 118  
 119      /**
 120       * Encodes a MIME content parameter string pursuant to RFC 2183 & 2231
 121       * (Content-Type and Content-Disposition headers).
 122       *
 123       * @param array $opts  Options:
 124       *   - broken_rfc2231: (boolean) Attempt to work around non-RFC
 125       *                     2231-compliant MUAs by generating both a RFC
 126       *                     2047-like parameter name and also the correct RFC
 127       *                     2231 parameter
 128       *                     DEFAULT: false
 129       *   - charset: (string) The charset to encode to.
 130       *              DEFAULT: UTF-8
 131       *   - lang: (string) The language to use when encoding.
 132       *           DEFAULT: None specified
 133       *
 134       * @return array  The encoded parameter string (US-ASCII).
 135       */
 136      public function encode(array $opts = array())
 137      {
 138          $opts = array_merge(array(
 139              'charset' => 'UTF-8',
 140          ), $opts);
 141  
 142          $out = array();
 143  
 144          foreach ($this->params as $key => $val) {
 145              $out = array_merge($out, $this->_encode($key, $val, $opts));
 146          }
 147  
 148          return $out;
 149      }
 150  
 151      /**
 152       * @see encode()
 153       */
 154      protected function _encode($name, $val, $opts)
 155      {
 156          $curr = 0;
 157          $encode = $wrap = false;
 158          $out = array();
 159  
 160          // 2 = '=', ';'
 161          $pre_len = strlen($name) + 2;
 162  
 163          /* Several possibilities:
 164           *   - String is ASCII. Output as ASCII (duh).
 165           *   - Language information has been provided. We MUST encode output
 166           *     to include this information.
 167           *   - String is non-ASCII, but can losslessly translate to ASCII.
 168           *     Output as ASCII (most efficient).
 169           *   - String is in non-ASCII, but doesn't losslessly translate to
 170           *     ASCII. MUST encode output (duh). */
 171          if (empty($opts['lang']) && !Horde_Mime::is8bit($val, 'UTF-8')) {
 172              $string = $val;
 173          } else {
 174              $cval = Horde_String::convertCharset($val, 'UTF-8', $opts['charset']);
 175              $string = Horde_String::lower($opts['charset']) . '\'' . (empty($opts['lang']) ? '' : Horde_String::lower($opts['lang'])) . '\'' . rawurlencode($cval);
 176              $encode = true;
 177              /* Account for trailing '*'. */
 178              ++$pre_len;
 179          }
 180  
 181          if (($pre_len + strlen($string)) > 75) {
 182              /* Account for continuation '*'. */
 183              ++$pre_len;
 184              $wrap = true;
 185  
 186              while ($string) {
 187                  $chunk = 75 - $pre_len - strlen($curr);
 188                  $pos = min($chunk, strlen($string) - 1);
 189  
 190                  /* Don't split in the middle of an encoded char. */
 191                  if (($chunk == $pos) && ($pos > 2)) {
 192                      for ($i = 0; $i <= 2; ++$i) {
 193                          if ($string[$pos - $i] == '%') {
 194                              $pos -= $i + 1;
 195                              break;
 196                          }
 197                      }
 198                  }
 199  
 200                  $lines[] = substr($string, 0, $pos + 1);
 201                  $string = substr($string, $pos + 1);
 202                  ++$curr;
 203              }
 204          } else {
 205              $lines = array($string);
 206          }
 207  
 208          foreach ($lines as $i => $line) {
 209              $out[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line;
 210          }
 211  
 212          if (!empty($opts['broken_rfc2231']) && !isset($out[$name])) {
 213              $out = array_merge(array(
 214                  $name => Horde_Mime::encode($val, $opts['charset'])
 215              ), $out);
 216          }
 217  
 218          /* Escape characters in params (See RFC 2045 [Appendix A]).
 219           * Must be quoted-string if one of these exists. */
 220          return $this->_escapeParams($out);
 221      }
 222  
 223      /**
 224       * Escape the parameter array.
 225       *
 226       * @param array $params  Parameter array.
 227       *
 228       * @return array  Escaped parameter array.
 229       */
 230      protected function _escapeParams($params)
 231      {
 232          foreach ($params as $k => $v) {
 233              foreach (str_split($v) as $c) {
 234                  if (!Horde_Mime_ContentParam_Decode::isAtextNonTspecial($c)) {
 235                      $params[$k] = '"' . addcslashes($v, '\\"') . '"';
 236                      break;
 237                  }
 238              }
 239          }
 240  
 241          return $params;
 242      }
 243  
 244      /**
 245       * Set the content-parameter base value.
 246       *
 247       * @since 2.8.0
 248       *
 249       * @param string $data  Value.
 250       */
 251      public function setContentParamValue($data)
 252      {
 253          $data = $this->_sanityCheck(trim($data));
 254          if (($pos = strpos($data, ';')) !== false) {
 255              $data = substr($data, 0, $pos);
 256          }
 257  
 258          $this->_values = array($data);
 259      }
 260  
 261      /**
 262       * Decodes a MIME content parameter string pursuant to RFC 2183 & 2231
 263       * (Content-Type and Content-Disposition headers).
 264       *
 265       * Stores value/parameter data in the current object.
 266       *
 267       * @param mixed $data  Parameter data. Either an array or a string.
 268       */
 269      public function decode($data)
 270      {
 271          $add = $convert = array();
 272  
 273          if (is_array($data)) {
 274              $params = $data;
 275          } else {
 276              $parts = explode(';', $data, 2);
 277              if (isset($parts[0]) && (strpos($parts[0], '=') === false)) {
 278                  $this->setContentParamValue($parts[0]);
 279                  $param = isset($parts[1]) ? $parts[1] : null;
 280              } else {
 281                  $param = $data;
 282              }
 283  
 284              if (empty($param)) {
 285                  $params = array();
 286              } else {
 287                  $decode = new Horde_Mime_ContentParam_Decode();
 288                  $params = $decode->decode($param);
 289              }
 290          }
 291  
 292          $to_add = array();
 293  
 294          foreach ($params as $name => $val) {
 295              /* Asterisk at end indicates encoded value. */
 296              if (substr($name, -1) == '*') {
 297                  $name = substr($name, 0, -1);
 298                  $encoded = true;
 299              } else {
 300                  $encoded = false;
 301              }
 302  
 303              /* This asterisk indicates continuation parameter. */
 304              if ((($pos = strrpos($name, '*')) !== false) &&
 305                  is_numeric($order = substr($name, $pos + 1))) {
 306                  $name = substr($name, 0, $pos);
 307                  $to_add[Horde_String::lower($name)][$order] = $val;
 308              } else {
 309                  $to_add[$name] = array($val);
 310              }
 311  
 312              if ($encoded) {
 313                  $convert[$name] = true;
 314              }
 315          }
 316  
 317          foreach ($to_add as $key => $val) {
 318              ksort($val);
 319              $add[$key] = implode('', $val);
 320          }
 321  
 322          foreach (array_keys($convert) as $name) {
 323              $val = $add[$name];
 324              $quote = strpos($val, "'");
 325  
 326              if ($quote === false) {
 327                  $add[$name] = urldecode($val);
 328              } else {
 329                  $orig_charset = substr($val, 0, $quote);
 330                  if (Horde_String::lower($orig_charset) == 'iso-8859-1') {
 331                      $orig_charset = 'windows-1252';
 332                  }
 333  
 334                  /* Ignore language. */
 335                  $quote = strpos($val, "'", $quote + 1);
 336                  substr($val, $quote + 1);
 337                  $add[$name] = Horde_String::convertCharset(
 338                      urldecode(substr($val, $quote + 1)),
 339                      $orig_charset,
 340                      'UTF-8'
 341                  );
 342              }
 343          }
 344  
 345          /* MIME parameters are supposed to be encoded via RFC 2231, but many
 346           * mailers do RFC 2045 encoding instead. However, if we see at least
 347           * one RFC 2231 encoding, then assume the sending mailer knew what
 348           * it was doing and didn't send any parameters RFC 2045 encoded. */
 349          if (empty($convert)) {
 350              foreach ($add as $key => $val) {
 351                  $add[$key] = Horde_Mime::decode($val);
 352              }
 353          }
 354  
 355          if (count($add)) {
 356              foreach ($add as $key => $val) {
 357                  /* When parsing a content-param string, lowercase all
 358                   * parameter names to normalize. Only maintain case of
 359                   * parameters explicitly added by calling code. */
 360                  $this[Horde_String::lower($key)] = $val;
 361              }
 362          } elseif (is_string($data)) {
 363              $this->setContentParamValue($parts[0]);
 364          }
 365      }
 366  
 367      /* ArrayAccess methods */
 368  
 369      /**
 370       */
 371      public function offsetExists($offset)
 372      {
 373          return isset($this->_params[$offset]);
 374      }
 375  
 376      /**
 377       */
 378      public function offsetGet($offset)
 379      {
 380          return $this->_params[$offset];
 381      }
 382  
 383      /**
 384       */
 385      public function offsetSet($offset, $value)
 386      {
 387          $this->_params[$offset] = $this->_sanityCheck($value);
 388      }
 389  
 390      /**
 391       */
 392      public function offsetUnset($offset)
 393      {
 394          unset($this->_params[$offset]);
 395      }
 396  
 397      /* Serializable methods */
 398  
 399      /**
 400       */
 401      public function serialize()
 402      {
 403          $vars = array_filter(get_object_vars($this));
 404          if (isset($vars['_params'])) {
 405              $vars['_params'] = $vars['_params']->getArrayCopy();
 406          }
 407          return serialize($vars);
 408      }
 409  
 410      /**
 411       */
 412      public function unserialize($data)
 413      {
 414          $data = unserialize($data);
 415  
 416          foreach ($data as $key => $val) {
 417              switch ($key) {
 418              case '_params':
 419                  $this->_params = new Horde_Support_CaseInsensitiveArray($val);
 420                  break;
 421  
 422              default:
 423                  $this->$key = $val;
 424                  break;
 425              }
 426          }
 427      }
 428  
 429  }