See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body