1<?php 2 3/* 4 * This file is part of SwiftMailer. 5 * (c) 2004-2009 Chris Corbyn 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * An abstract base MIME Header. 13 * 14 * @author Chris Corbyn 15 */ 16class Swift_Mime_Headers_ParameterizedHeader extends Swift_Mime_Headers_UnstructuredHeader implements Swift_Mime_ParameterizedHeader 17{ 18 /** 19 * RFC 2231's definition of a token. 20 * 21 * @var string 22 */ 23 const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)'; 24 25 /** 26 * The Encoder used to encode the parameters. 27 * 28 * @var Swift_Encoder 29 */ 30 private $_paramEncoder; 31 32 /** 33 * The parameters as an associative array. 34 * 35 * @var string[] 36 */ 37 private $_params = array(); 38 39 /** 40 * Creates a new ParameterizedHeader with $name. 41 * 42 * @param string $name 43 * @param Swift_Mime_HeaderEncoder $encoder 44 * @param Swift_Encoder $paramEncoder, optional 45 * @param Swift_Mime_Grammar $grammar 46 */ 47 public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder = null, Swift_Mime_Grammar $grammar) 48 { 49 parent::__construct($name, $encoder, $grammar); 50 $this->_paramEncoder = $paramEncoder; 51 } 52 53 /** 54 * Get the type of Header that this instance represents. 55 * 56 * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX 57 * @see TYPE_DATE, TYPE_ID, TYPE_PATH 58 * 59 * @return int 60 */ 61 public function getFieldType() 62 { 63 return self::TYPE_PARAMETERIZED; 64 } 65 66 /** 67 * Set the character set used in this Header. 68 * 69 * @param string $charset 70 */ 71 public function setCharset($charset) 72 { 73 parent::setCharset($charset); 74 if (isset($this->_paramEncoder)) { 75 $this->_paramEncoder->charsetChanged($charset); 76 } 77 } 78 79 /** 80 * Set the value of $parameter. 81 * 82 * @param string $parameter 83 * @param string $value 84 */ 85 public function setParameter($parameter, $value) 86 { 87 $this->setParameters(array_merge($this->getParameters(), array($parameter => $value))); 88 } 89 90 /** 91 * Get the value of $parameter. 92 * 93 * @param string $parameter 94 * 95 * @return string 96 */ 97 public function getParameter($parameter) 98 { 99 $params = $this->getParameters(); 100 101 return array_key_exists($parameter, $params) ? $params[$parameter] : null; 102 } 103 104 /** 105 * Set an associative array of parameter names mapped to values. 106 * 107 * @param string[] $parameters 108 */ 109 public function setParameters(array $parameters) 110 { 111 $this->clearCachedValueIf($this->_params != $parameters); 112 $this->_params = $parameters; 113 } 114 115 /** 116 * Returns an associative array of parameter names mapped to values. 117 * 118 * @return string[] 119 */ 120 public function getParameters() 121 { 122 return $this->_params; 123 } 124 125 /** 126 * Get the value of this header prepared for rendering. 127 * 128 * @return string 129 */ 130 public function getFieldBody() //TODO: Check caching here 131 { 132 $body = parent::getFieldBody(); 133 foreach ($this->_params as $name => $value) { 134 if (null !== $value) { 135 // Add the parameter 136 $body .= '; '.$this->_createParameter($name, $value); 137 } 138 } 139 140 return $body; 141 } 142 143 /** 144 * Generate a list of all tokens in the final header. 145 * 146 * This doesn't need to be overridden in theory, but it is for implementation 147 * reasons to prevent potential breakage of attributes. 148 * 149 * @param string $string The string to tokenize 150 * 151 * @return array An array of tokens as strings 152 */ 153 protected function toTokens($string = null) 154 { 155 $tokens = parent::toTokens(parent::getFieldBody()); 156 157 // Try creating any parameters 158 foreach ($this->_params as $name => $value) { 159 if (null !== $value) { 160 // Add the semi-colon separator 161 $tokens[count($tokens) - 1] .= ';'; 162 $tokens = array_merge($tokens, $this->generateTokenLines( 163 ' '.$this->_createParameter($name, $value) 164 )); 165 } 166 } 167 168 return $tokens; 169 } 170 171 /** 172 * Render a RFC 2047 compliant header parameter from the $name and $value. 173 * 174 * @param string $name 175 * @param string $value 176 * 177 * @return string 178 */ 179 private function _createParameter($name, $value) 180 { 181 $origValue = $value; 182 183 $encoded = false; 184 // Allow room for parameter name, indices, "=" and DQUOTEs 185 $maxValueLength = $this->getMaxLineLength() - strlen($name.'=*N"";') - 1; 186 $firstLineOffset = 0; 187 188 // If it's not already a valid parameter value... 189 if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { 190 // TODO: text, or something else?? 191 // ... and it's not ascii 192 if (!preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $value)) { 193 $encoded = true; 194 // Allow space for the indices, charset and language 195 $maxValueLength = $this->getMaxLineLength() - strlen($name.'*N*="";') - 1; 196 $firstLineOffset = strlen( 197 $this->getCharset()."'".$this->getLanguage()."'" 198 ); 199 } 200 } 201 202 // Encode if we need to 203 if ($encoded || strlen($value) > $maxValueLength) { 204 if (isset($this->_paramEncoder)) { 205 $value = $this->_paramEncoder->encodeString( 206 $origValue, $firstLineOffset, $maxValueLength, $this->getCharset() 207 ); 208 } else { 209 // We have to go against RFC 2183/2231 in some areas for interoperability 210 $value = $this->getTokenAsEncodedWord($origValue); 211 $encoded = false; 212 } 213 } 214 215 $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value); 216 217 // Need to add indices 218 if (count($valueLines) > 1) { 219 $paramLines = array(); 220 foreach ($valueLines as $i => $line) { 221 $paramLines[] = $name.'*'.$i. 222 $this->_getEndOfParameterValue($line, true, $i == 0); 223 } 224 225 return implode(";\r\n ", $paramLines); 226 } else { 227 return $name.$this->_getEndOfParameterValue( 228 $valueLines[0], $encoded, true 229 ); 230 } 231 } 232 233 /** 234 * Returns the parameter value from the "=" and beyond. 235 * 236 * @param string $value to append 237 * @param bool $encoded 238 * @param bool $firstLine 239 * 240 * @return string 241 */ 242 private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false) 243 { 244 if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { 245 $value = '"'.$value.'"'; 246 } 247 $prepend = '='; 248 if ($encoded) { 249 $prepend = '*='; 250 if ($firstLine) { 251 $prepend = '*='.$this->getCharset()."'".$this->getLanguage(). 252 "'"; 253 } 254 } 255 256 return $prepend.$value; 257 } 258} 259