|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /* 00004 * This file is part of SwiftMailer. 00005 * (c) 2004-2009 Chris Corbyn 00006 * 00007 * For the full copyright and license information, please view the LICENSE 00008 * file that was distributed with this source code. 00009 */ 00010 00011 //@require 'Swift/Mime/Header.php'; 00012 //@require 'Swift/Mime/HeaderEncoder.php'; 00013 //@require 'Swift/RfcComplianceException.php'; 00014 00015 /** 00016 * An abstract base MIME Header. 00017 * @package Swift 00018 * @subpackage Mime 00019 * @author Chris Corbyn 00020 */ 00021 abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header 00022 { 00023 00024 /** 00025 * Special characters used in the syntax which need to be escaped. 00026 * @var string[] 00027 * @access private 00028 */ 00029 private $_specials = array(); 00030 00031 /** 00032 * Tokens defined in RFC 2822 (and some related RFCs). 00033 * @var string[] 00034 * @access private 00035 */ 00036 private $_grammar = array(); 00037 00038 /** 00039 * The name of this Header. 00040 * @var string 00041 * @access private 00042 */ 00043 private $_name; 00044 00045 /** 00046 * The Encoder used to encode this Header. 00047 * @var Swift_Encoder 00048 * @access private 00049 */ 00050 private $_encoder; 00051 00052 /** 00053 * The maximum length of a line in the header. 00054 * @var int 00055 * @access private 00056 */ 00057 private $_lineLength = 78; 00058 00059 /** 00060 * The language used in this Header. 00061 * @var string 00062 */ 00063 private $_lang; 00064 00065 /** 00066 * The character set of the text in this Header. 00067 * @var string 00068 * @access private 00069 */ 00070 private $_charset = 'utf-8'; 00071 00072 /** 00073 * The value of this Header, cached. 00074 * @var string 00075 * @access private 00076 */ 00077 private $_cachedValue = null; 00078 00079 /** 00080 * Set the character set used in this Header. 00081 * @param string $charset 00082 */ 00083 public function setCharset($charset) 00084 { 00085 $this->clearCachedValueIf($charset != $this->_charset); 00086 $this->_charset = $charset; 00087 if (isset($this->_encoder)) 00088 { 00089 $this->_encoder->charsetChanged($charset); 00090 } 00091 } 00092 00093 /** 00094 * Get the character set used in this Header. 00095 * @return string 00096 */ 00097 public function getCharset() 00098 { 00099 return $this->_charset; 00100 } 00101 00102 /** 00103 * Set the language used in this Header. 00104 * For example, for US English, 'en-us'. 00105 * This can be unspecified. 00106 * @param string $lang 00107 */ 00108 public function setLanguage($lang) 00109 { 00110 $this->clearCachedValueIf($this->_lang != $lang); 00111 $this->_lang = $lang; 00112 } 00113 00114 /** 00115 * Get the language used in this Header. 00116 * @return string 00117 */ 00118 public function getLanguage() 00119 { 00120 return $this->_lang; 00121 } 00122 00123 /** 00124 * Set the encoder used for encoding the header. 00125 * @param Swift_Mime_HeaderEncoder $encoder 00126 */ 00127 public function setEncoder(Swift_Mime_HeaderEncoder $encoder) 00128 { 00129 $this->_encoder = $encoder; 00130 $this->setCachedValue(null); 00131 } 00132 00133 /** 00134 * Get the encoder used for encoding this Header. 00135 * @return Swift_Mime_HeaderEncoder 00136 */ 00137 public function getEncoder() 00138 { 00139 return $this->_encoder; 00140 } 00141 00142 /** 00143 * Get the name of this header (e.g. charset). 00144 * @return string 00145 */ 00146 public function getFieldName() 00147 { 00148 return $this->_name; 00149 } 00150 00151 /** 00152 * Set the maximum length of lines in the header (excluding EOL). 00153 * @param int $lineLength 00154 */ 00155 public function setMaxLineLength($lineLength) 00156 { 00157 $this->clearCachedValueIf($this->_lineLength != $lineLength); 00158 $this->_lineLength = $lineLength; 00159 } 00160 00161 /** 00162 * Get the maximum permitted length of lines in this Header. 00163 * @return int 00164 */ 00165 public function getMaxLineLength() 00166 { 00167 return $this->_lineLength; 00168 } 00169 00170 /** 00171 * Get this Header rendered as a RFC 2822 compliant string. 00172 * @return string 00173 * @throws Swift_RfcComplianceException 00174 */ 00175 public function toString() 00176 { 00177 return $this->_tokensToString($this->toTokens()); 00178 } 00179 00180 /** 00181 * Returns a string representation of this object. 00182 * 00183 * @return string 00184 * 00185 * @see toString() 00186 */ 00187 public function __toString() 00188 { 00189 return $this->toString(); 00190 } 00191 00192 // -- Points of extension 00193 00194 /** 00195 * Set the name of this Header field. 00196 * @param string $name 00197 * @access protected 00198 */ 00199 protected function setFieldName($name) 00200 { 00201 $this->_name = $name; 00202 } 00203 00204 /** 00205 * Initialize some RFC 2822 (and friends) ABNF grammar definitions. 00206 * @access protected 00207 */ 00208 protected function initializeGrammar() 00209 { 00210 $this->_specials = array( 00211 '(', ')', '<', '>', '[', ']', 00212 ':', ';', '@', ',', '.', '"' 00213 ); 00214 00215 /*** Refer to RFC 2822 for ABNF grammar ***/ 00216 00217 //All basic building blocks 00218 $this->_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]'; 00219 $this->_grammar['WSP'] = '[ \t]'; 00220 $this->_grammar['CRLF'] = '(?:\r\n)'; 00221 $this->_grammar['FWS'] = '(?:(?:' . $this->_grammar['WSP'] . '*' . 00222 $this->_grammar['CRLF'] . ')?' . $this->_grammar['WSP'] . ')'; 00223 $this->_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]'; 00224 $this->_grammar['quoted-pair'] = '(?:\\\\' . $this->_grammar['text'] . ')'; 00225 $this->_grammar['ctext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . 00226 '|[\x21-\x27\x2A-\x5B\x5D-\x7E])'; 00227 //Uses recursive PCRE (?1) -- could be a weak point?? 00228 $this->_grammar['ccontent'] = '(?:' . $this->_grammar['ctext'] . '|' . 00229 $this->_grammar['quoted-pair'] . '|(?1))'; 00230 $this->_grammar['comment'] = '(\((?:' . $this->_grammar['FWS'] . '|' . 00231 $this->_grammar['ccontent']. ')*' . $this->_grammar['FWS'] . '?\))'; 00232 $this->_grammar['CFWS'] = '(?:(?:' . $this->_grammar['FWS'] . '?' . 00233 $this->_grammar['comment'] . ')*(?:(?:' . $this->_grammar['FWS'] . '?' . 00234 $this->_grammar['comment'] . ')|' . $this->_grammar['FWS'] . '))'; 00235 $this->_grammar['qtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . 00236 '|[\x21\x23-\x5B\x5D-\x7E])'; 00237 $this->_grammar['qcontent'] = '(?:' . $this->_grammar['qtext'] . '|' . 00238 $this->_grammar['quoted-pair'] . ')'; 00239 $this->_grammar['quoted-string'] = '(?:' . $this->_grammar['CFWS'] . '?"' . 00240 '(' . $this->_grammar['FWS'] . '?' . $this->_grammar['qcontent'] . ')*' . 00241 $this->_grammar['FWS'] . '?"' . $this->_grammar['CFWS'] . '?)'; 00242 $this->_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]'; 00243 $this->_grammar['atom'] = '(?:' . $this->_grammar['CFWS'] . '?' . 00244 $this->_grammar['atext'] . '+' . $this->_grammar['CFWS'] . '?)'; 00245 $this->_grammar['dot-atom-text'] = '(?:' . $this->_grammar['atext'] . '+' . 00246 '(\.' . $this->_grammar['atext'] . '+)*)'; 00247 $this->_grammar['dot-atom'] = '(?:' . $this->_grammar['CFWS'] . '?' . 00248 $this->_grammar['dot-atom-text'] . '+' . $this->_grammar['CFWS'] . '?)'; 00249 $this->_grammar['word'] = '(?:' . $this->_grammar['atom'] . '|' . 00250 $this->_grammar['quoted-string'] . ')'; 00251 $this->_grammar['phrase'] = '(?:' . $this->_grammar['word'] . '+?)'; 00252 $this->_grammar['no-fold-quote'] = '(?:"(?:' . $this->_grammar['qtext'] . 00253 '|' . $this->_grammar['quoted-pair'] . ')*")'; 00254 $this->_grammar['dtext'] = '(?:' . $this->_grammar['NO-WS-CTL'] . 00255 '|[\x21-\x5A\x5E-\x7E])'; 00256 $this->_grammar['no-fold-literal'] = '(?:\[(?:' . $this->_grammar['dtext'] . 00257 '|' . $this->_grammar['quoted-pair'] . ')*\])'; 00258 00259 //Message IDs 00260 $this->_grammar['id-left'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' . 00261 $this->_grammar['no-fold-quote'] . ')'; 00262 $this->_grammar['id-right'] = '(?:' . $this->_grammar['dot-atom-text'] . '|' . 00263 $this->_grammar['no-fold-literal'] . ')'; 00264 00265 //Addresses, mailboxes and paths 00266 $this->_grammar['local-part'] = '(?:' . $this->_grammar['dot-atom'] . '|' . 00267 $this->_grammar['quoted-string'] . ')'; 00268 $this->_grammar['dcontent'] = '(?:' . $this->_grammar['dtext'] . '|' . 00269 $this->_grammar['quoted-pair'] . ')'; 00270 $this->_grammar['domain-literal'] = '(?:' . $this->_grammar['CFWS'] . '?\[(' . 00271 $this->_grammar['FWS'] . '?' . $this->_grammar['dcontent'] . ')*?' . 00272 $this->_grammar['FWS'] . '?\]' . $this->_grammar['CFWS'] . '?)'; 00273 $this->_grammar['domain'] = '(?:' . $this->_grammar['dot-atom'] . '|' . 00274 $this->_grammar['domain-literal'] . ')'; 00275 $this->_grammar['addr-spec'] = '(?:' . $this->_grammar['local-part'] . '@' . 00276 $this->_grammar['domain'] . ')'; 00277 } 00278 00279 /** 00280 * Get the grammar defined for $name token. 00281 * @param string $name execatly as written in the RFC 00282 * @return string 00283 */ 00284 protected function getGrammar($name) 00285 { 00286 if (array_key_exists($name, $this->_grammar)) 00287 { 00288 return $this->_grammar[$name]; 00289 } 00290 else 00291 { 00292 throw new Swift_RfcComplianceException( 00293 "No such grammar '" . $name . "' defined." 00294 ); 00295 } 00296 } 00297 00298 /** 00299 * Escape special characters in a string (convert to quoted-pairs). 00300 * @param string $token 00301 * @param string[] $include additonal chars to escape 00302 * @param string[] $exclude chars from escaping 00303 * @return string 00304 */ 00305 protected function escapeSpecials($token, $include = array(), 00306 $exclude = array()) 00307 { 00308 foreach ( 00309 array_merge(array('\\'), array_diff($this->_specials, $exclude), $include) as $char) 00310 { 00311 $token = str_replace($char, '\\' . $char, $token); 00312 } 00313 return $token; 00314 } 00315 00316 /** 00317 * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. 00318 * @param Swift_Mime_Header $header 00319 * @param string $string as displayed 00320 * @param string $charset of the text 00321 * @param Swift_Mime_HeaderEncoder $encoder 00322 * @param boolean $shorten the first line to make remove for header name 00323 * @return string 00324 */ 00325 protected function createPhrase(Swift_Mime_Header $header, $string, $charset, 00326 Swift_Mime_HeaderEncoder $encoder = null, $shorten = false) 00327 { 00328 //Treat token as exactly what was given 00329 $phraseStr = $string; 00330 //If it's not valid 00331 if (!preg_match('/^' . $this->_grammar['phrase'] . '$/D', $phraseStr)) 00332 { 00333 // .. but it is just ascii text, try escaping some characters 00334 // and make it a quoted-string 00335 if (preg_match('/^' . $this->_grammar['text'] . '*$/D', $phraseStr)) 00336 { 00337 $phraseStr = $this->escapeSpecials( 00338 $phraseStr, array('"'), $this->_specials 00339 ); 00340 $phraseStr = '"' . $phraseStr . '"'; 00341 } 00342 else // ... otherwise it needs encoding 00343 { 00344 //Determine space remaining on line if first line 00345 if ($shorten) 00346 { 00347 $usedLength = strlen($header->getFieldName() . ': '); 00348 } 00349 else 00350 { 00351 $usedLength = 0; 00352 } 00353 $phraseStr = $this->encodeWords($header, $string, $usedLength); 00354 } 00355 } 00356 00357 return $phraseStr; 00358 } 00359 00360 /** 00361 * Encode needed word tokens within a string of input. 00362 * @param string $input 00363 * @param string $usedLength, optional 00364 * @return string 00365 */ 00366 protected function encodeWords(Swift_Mime_Header $header, $input, 00367 $usedLength = -1) 00368 { 00369 $value = ''; 00370 00371 $tokens = $this->getEncodableWordTokens($input); 00372 00373 foreach ($tokens as $token) 00374 { 00375 //See RFC 2822, Sect 2.2 (really 2.2 ??) 00376 if ($this->tokenNeedsEncoding($token)) 00377 { 00378 //Don't encode starting WSP 00379 $firstChar = substr($token, 0, 1); 00380 switch($firstChar) 00381 { 00382 case ' ': 00383 case "\t": 00384 $value .= $firstChar; 00385 $token = substr($token, 1); 00386 } 00387 00388 if (-1 == $usedLength) 00389 { 00390 $usedLength = strlen($header->getFieldName() . ': ') + strlen($value); 00391 } 00392 $value .= $this->getTokenAsEncodedWord($token, $usedLength); 00393 00394 $header->setMaxLineLength(76); //Forefully override 00395 } 00396 else 00397 { 00398 $value .= $token; 00399 } 00400 } 00401 00402 return $value; 00403 } 00404 00405 /** 00406 * Test if a token needs to be encoded or not. 00407 * @param string $token 00408 * @return boolean 00409 */ 00410 protected function tokenNeedsEncoding($token) 00411 { 00412 return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); 00413 } 00414 00415 /** 00416 * Splits a string into tokens in blocks of words which can be encoded quickly. 00417 * @param string $string 00418 * @return string[] 00419 */ 00420 protected function getEncodableWordTokens($string) 00421 { 00422 $tokens = array(); 00423 00424 $encodedToken = ''; 00425 //Split at all whitespace boundaries 00426 foreach (preg_split('~(?=[\t ])~', $string) as $token) 00427 { 00428 if ($this->tokenNeedsEncoding($token)) 00429 { 00430 $encodedToken .= $token; 00431 } 00432 else 00433 { 00434 if (strlen($encodedToken) > 0) 00435 { 00436 $tokens[] = $encodedToken; 00437 $encodedToken = ''; 00438 } 00439 $tokens[] = $token; 00440 } 00441 } 00442 if (strlen($encodedToken)) 00443 { 00444 $tokens[] = $encodedToken; 00445 } 00446 00447 return $tokens; 00448 } 00449 00450 /** 00451 * Get a token as an encoded word for safe insertion into headers. 00452 * @param string $token to encode 00453 * @param int $firstLineOffset, optional 00454 * @return string 00455 */ 00456 protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) 00457 { 00458 //Adjust $firstLineOffset to account for space needed for syntax 00459 $charsetDecl = $this->_charset; 00460 if (isset($this->_lang)) 00461 { 00462 $charsetDecl .= '*' . $this->_lang; 00463 } 00464 $encodingWrapperLength = strlen( 00465 '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??=' 00466 ); 00467 00468 if ($firstLineOffset >= 75) //Does this logic need to be here? 00469 { 00470 $firstLineOffset = 0; 00471 } 00472 00473 $encodedTextLines = explode("\r\n", 00474 $this->_encoder->encodeString( 00475 $token, $firstLineOffset, 75 - $encodingWrapperLength 00476 ) 00477 ); 00478 00479 foreach ($encodedTextLines as $lineNum => $line) 00480 { 00481 $encodedTextLines[$lineNum] = '=?' . $charsetDecl . 00482 '?' . $this->_encoder->getName() . 00483 '?' . $line . '?='; 00484 } 00485 00486 return implode("\r\n ", $encodedTextLines); 00487 } 00488 00489 /** 00490 * Generates tokens from the given string which include CRLF as individual tokens. 00491 * @param string $token 00492 * @return string[] 00493 * @access protected 00494 */ 00495 protected function generateTokenLines($token) 00496 { 00497 return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); 00498 } 00499 00500 /** 00501 * Set a value into the cache. 00502 * @param string $value 00503 * @access protected 00504 */ 00505 protected function setCachedValue($value) 00506 { 00507 $this->_cachedValue = $value; 00508 } 00509 00510 /** 00511 * Get the value in the cache. 00512 * @return string 00513 * @access protected 00514 */ 00515 protected function getCachedValue() 00516 { 00517 return $this->_cachedValue; 00518 } 00519 00520 /** 00521 * Clear the cached value if $condition is met. 00522 * @param boolean $condition 00523 * @access protected 00524 */ 00525 protected function clearCachedValueIf($condition) 00526 { 00527 if ($condition) 00528 { 00529 $this->setCachedValue(null); 00530 } 00531 } 00532 00533 // -- Private methods 00534 00535 /** 00536 * Generate a list of all tokens in the final header. 00537 * @param string $string input, optional 00538 * @return string[] 00539 * @access private 00540 */ 00541 protected function toTokens($string = null) 00542 { 00543 if (is_null($string)) 00544 { 00545 $string = $this->getFieldBody(); 00546 } 00547 00548 $tokens = array(); 00549 00550 //Generate atoms; split at all invisible boundaries followed by WSP 00551 foreach (preg_split('~(?=[ \t])~', $string) as $token) 00552 { 00553 $tokens = array_merge($tokens, $this->generateTokenLines($token)); 00554 } 00555 00556 return $tokens; 00557 } 00558 00559 /** 00560 * Takes an array of tokens which appear in the header and turns them into 00561 * an RFC 2822 compliant string, adding FWSP where needed. 00562 * @param string[] $tokens 00563 * @return string 00564 * @access private 00565 */ 00566 private function _tokensToString(array $tokens) 00567 { 00568 $lineCount = 0; 00569 $headerLines = array(); 00570 $headerLines[] = $this->_name . ': '; 00571 $currentLine =& $headerLines[$lineCount++]; 00572 00573 //Build all tokens back into compliant header 00574 foreach ($tokens as $i => $token) 00575 { 00576 //Line longer than specified maximum or token was just a new line 00577 if (("\r\n" == $token) || 00578 ($i > 0 && strlen($currentLine . $token) > $this->_lineLength) 00579 && 0 < strlen($currentLine)) 00580 { 00581 $headerLines[] = ''; 00582 $currentLine =& $headerLines[$lineCount++]; 00583 } 00584 00585 //Append token to the line 00586 if ("\r\n" != $token) 00587 { 00588 $currentLine .= $token; 00589 } 00590 } 00591 00592 //Implode with FWS (RFC 2822, 2.2.3) 00593 return implode("\r\n", $headerLines) . "\r\n"; 00594 } 00595 00596 }
1.8.0