vendor/swiftmailer/lib/classes/Swift/Mime/Headers/AbstractHeader.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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  * @package Swift
       
    14  * @subpackage Mime
       
    15  * @author Chris Corbyn
       
    16  */
       
    17 abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
       
    18 {
       
    19   
       
    20   /**
       
    21    * The name of this Header.
       
    22    * @var string
       
    23    * @access private
       
    24    */
       
    25   private $_name;
       
    26   
       
    27   /**
       
    28    * The Grammar used for this Header.
       
    29    * @var Swift_Mime_Grammar
       
    30    * @access private
       
    31    */
       
    32   private $_grammar;
       
    33   
       
    34   /**
       
    35    * The Encoder used to encode this Header.
       
    36    * @var Swift_Encoder
       
    37    * @access private
       
    38    */
       
    39   private $_encoder;
       
    40   
       
    41   /**
       
    42    * The maximum length of a line in the header.
       
    43    * @var int
       
    44    * @access private
       
    45    */
       
    46   private $_lineLength = 78;
       
    47   
       
    48   /**
       
    49    * The language used in this Header.
       
    50    * @var string
       
    51    */
       
    52   private $_lang;
       
    53   
       
    54   /**
       
    55    * The character set of the text in this Header.
       
    56    * @var string
       
    57    * @access private
       
    58    */
       
    59   private $_charset = 'utf-8';
       
    60   
       
    61   /**
       
    62    * The value of this Header, cached.
       
    63    * @var string
       
    64    * @access private
       
    65    */
       
    66   private $_cachedValue = null;
       
    67   
       
    68   /**
       
    69    * Creates a new Header.
       
    70    * @param Swift_Mime_Grammar $grammar
       
    71    */ 
       
    72   public function __construct(Swift_Mime_Grammar $grammar)
       
    73   {
       
    74     $this->setGrammar($grammar);
       
    75   }
       
    76   
       
    77   /**
       
    78    * Set the character set used in this Header.
       
    79    * @param string $charset
       
    80    */
       
    81   public function setCharset($charset)
       
    82   {
       
    83     $this->clearCachedValueIf($charset != $this->_charset);
       
    84     $this->_charset = $charset;
       
    85     if (isset($this->_encoder))
       
    86     {
       
    87       $this->_encoder->charsetChanged($charset);
       
    88     }
       
    89   }
       
    90   
       
    91   /**
       
    92    * Get the character set used in this Header.
       
    93    * @return string
       
    94    */
       
    95   public function getCharset()
       
    96   {
       
    97     return $this->_charset;
       
    98   }
       
    99   
       
   100   /**
       
   101    * Set the language used in this Header.
       
   102    * For example, for US English, 'en-us'.
       
   103    * This can be unspecified.
       
   104    * @param string $lang
       
   105    */
       
   106   public function setLanguage($lang)
       
   107   {
       
   108     $this->clearCachedValueIf($this->_lang != $lang);
       
   109     $this->_lang = $lang;
       
   110   }
       
   111   
       
   112   /**
       
   113    * Get the language used in this Header.
       
   114    * @return string
       
   115    */
       
   116   public function getLanguage()
       
   117   {
       
   118     return $this->_lang;
       
   119   }
       
   120   
       
   121   /**
       
   122    * Set the encoder used for encoding the header.
       
   123    * @param Swift_Mime_HeaderEncoder $encoder
       
   124    */
       
   125   public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
       
   126   {
       
   127     $this->_encoder = $encoder;
       
   128     $this->setCachedValue(null);
       
   129   }
       
   130   
       
   131   /**
       
   132    * Get the encoder used for encoding this Header.
       
   133    * @return Swift_Mime_HeaderEncoder
       
   134    */
       
   135   public function getEncoder()
       
   136   {
       
   137     return $this->_encoder;
       
   138   }
       
   139   
       
   140   /**
       
   141    * Set the grammar used for the header.
       
   142    * @param Swift_Mime_Grammar $grammar
       
   143    */
       
   144   public function setGrammar(Swift_Mime_Grammar $grammar)
       
   145   {
       
   146     $this->_grammar = $grammar;
       
   147     $this->setCachedValue(null);
       
   148   }
       
   149   
       
   150   /**
       
   151    * Get the grammar used for this Header.
       
   152    * @return Swift_Mime_Grammar
       
   153    */
       
   154   public function getGrammar()
       
   155   {
       
   156     return $this->_grammar;
       
   157   }
       
   158   
       
   159   /**
       
   160    * Get the name of this header (e.g. charset).
       
   161    * @return string
       
   162    */
       
   163   public function getFieldName()
       
   164   {
       
   165     return $this->_name;
       
   166   }
       
   167   
       
   168   /**
       
   169    * Set the maximum length of lines in the header (excluding EOL).
       
   170    * @param int $lineLength
       
   171    */
       
   172   public function setMaxLineLength($lineLength)
       
   173   {
       
   174     $this->clearCachedValueIf($this->_lineLength != $lineLength);
       
   175     $this->_lineLength = $lineLength;
       
   176   }
       
   177   
       
   178   /**
       
   179    * Get the maximum permitted length of lines in this Header.
       
   180    * @return int
       
   181    */
       
   182   public function getMaxLineLength()
       
   183   {
       
   184     return $this->_lineLength;
       
   185   }
       
   186   
       
   187   /**
       
   188    * Get this Header rendered as a RFC 2822 compliant string.
       
   189    * @return string
       
   190    * @throws Swift_RfcComplianceException
       
   191    */
       
   192   public function toString()
       
   193   {
       
   194     return $this->_tokensToString($this->toTokens());
       
   195   }
       
   196   
       
   197   /**
       
   198    * Returns a string representation of this object.
       
   199    *
       
   200    * @return string
       
   201    *
       
   202    * @see toString()
       
   203    */
       
   204   public function __toString()
       
   205   {
       
   206     return $this->toString();
       
   207   }
       
   208   
       
   209   // -- Points of extension
       
   210   
       
   211   /**
       
   212    * Set the name of this Header field.
       
   213    * @param string $name
       
   214    * @access protected
       
   215    */
       
   216   protected function setFieldName($name)
       
   217   {
       
   218     $this->_name = $name;
       
   219   }
       
   220   
       
   221   /**
       
   222    * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
       
   223    * @param Swift_Mime_Header $header
       
   224    * @param string $string as displayed
       
   225    * @param string $charset of the text
       
   226    * @param Swift_Mime_HeaderEncoder $encoder
       
   227    * @param boolean $shorten the first line to make remove for header name
       
   228    * @return string
       
   229    */
       
   230   protected function createPhrase(Swift_Mime_Header $header, $string, $charset,
       
   231     Swift_Mime_HeaderEncoder $encoder = null, $shorten = false)
       
   232   {
       
   233     //Treat token as exactly what was given
       
   234     $phraseStr = $string;
       
   235     //If it's not valid
       
   236     if (!preg_match('/^' . $this->getGrammar()->getDefinition('phrase') . '$/D', $phraseStr))
       
   237     {
       
   238       // .. but it is just ascii text, try escaping some characters
       
   239       // and make it a quoted-string
       
   240       if (preg_match('/^' . $this->getGrammar()->getDefinition('text') . '*$/D', $phraseStr))
       
   241       {
       
   242         $phraseStr = $this->getGrammar()->escapeSpecials(
       
   243           $phraseStr, array('"'), $this->getGrammar()->getSpecials()
       
   244           );
       
   245         $phraseStr = '"' . $phraseStr . '"';
       
   246       }
       
   247       else // ... otherwise it needs encoding
       
   248       {
       
   249         //Determine space remaining on line if first line
       
   250         if ($shorten)
       
   251         {
       
   252           $usedLength = strlen($header->getFieldName() . ': ');
       
   253         }
       
   254         else
       
   255         {
       
   256           $usedLength = 0;
       
   257         }
       
   258         $phraseStr = $this->encodeWords($header, $string, $usedLength);
       
   259       }
       
   260     }
       
   261     
       
   262     return $phraseStr;
       
   263   }
       
   264   
       
   265   /**
       
   266    * Encode needed word tokens within a string of input.
       
   267    * @param string $input
       
   268    * @param string $usedLength, optional
       
   269    * @return string
       
   270    */
       
   271   protected function encodeWords(Swift_Mime_Header $header, $input,
       
   272     $usedLength = -1)
       
   273   {
       
   274     $value = '';
       
   275     
       
   276     $tokens = $this->getEncodableWordTokens($input);
       
   277     
       
   278     foreach ($tokens as $token)
       
   279     {
       
   280       //See RFC 2822, Sect 2.2 (really 2.2 ??)
       
   281       if ($this->tokenNeedsEncoding($token))
       
   282       {
       
   283         //Don't encode starting WSP
       
   284         $firstChar = substr($token, 0, 1);
       
   285         switch($firstChar)
       
   286         {
       
   287           case ' ':
       
   288           case "\t":
       
   289             $value .= $firstChar;
       
   290             $token = substr($token, 1);
       
   291         }
       
   292         
       
   293         if (-1 == $usedLength)
       
   294         {
       
   295           $usedLength = strlen($header->getFieldName() . ': ') + strlen($value);
       
   296         }
       
   297         $value .= $this->getTokenAsEncodedWord($token, $usedLength);
       
   298         
       
   299         $header->setMaxLineLength(76); //Forefully override
       
   300       }
       
   301       else
       
   302       {
       
   303         $value .= $token;
       
   304       }
       
   305     }
       
   306     
       
   307     return $value;
       
   308   }
       
   309   
       
   310   /**
       
   311    * Test if a token needs to be encoded or not.
       
   312    * @param string $token
       
   313    * @return boolean
       
   314    */
       
   315   protected function tokenNeedsEncoding($token)
       
   316   {
       
   317     return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
       
   318   }
       
   319   
       
   320   /**
       
   321    * Splits a string into tokens in blocks of words which can be encoded quickly.
       
   322    * @param string $string
       
   323    * @return string[]
       
   324    */
       
   325   protected function getEncodableWordTokens($string)
       
   326   {
       
   327     $tokens = array();
       
   328     
       
   329     $encodedToken = '';
       
   330     //Split at all whitespace boundaries
       
   331     foreach (preg_split('~(?=[\t ])~', $string) as $token)
       
   332     {
       
   333       if ($this->tokenNeedsEncoding($token))
       
   334       {
       
   335         $encodedToken .= $token;
       
   336       }
       
   337       else
       
   338       {
       
   339         if (strlen($encodedToken) > 0)
       
   340         {
       
   341           $tokens[] = $encodedToken;
       
   342           $encodedToken = '';
       
   343         }
       
   344         $tokens[] = $token;
       
   345       }
       
   346     }
       
   347     if (strlen($encodedToken))
       
   348     {
       
   349       $tokens[] = $encodedToken;
       
   350     }
       
   351     
       
   352     return $tokens;
       
   353   }
       
   354   
       
   355   /**
       
   356    * Get a token as an encoded word for safe insertion into headers.
       
   357    * @param string $token to encode
       
   358    * @param int $firstLineOffset, optional
       
   359    * @return string
       
   360    */
       
   361   protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
       
   362   {
       
   363     //Adjust $firstLineOffset to account for space needed for syntax
       
   364     $charsetDecl = $this->_charset;
       
   365     if (isset($this->_lang))
       
   366     {
       
   367       $charsetDecl .= '*' . $this->_lang;
       
   368     }
       
   369     $encodingWrapperLength = strlen(
       
   370       '=?' . $charsetDecl . '?' . $this->_encoder->getName() . '??='
       
   371       );
       
   372     
       
   373     if ($firstLineOffset >= 75) //Does this logic need to be here?
       
   374     {
       
   375       $firstLineOffset = 0;
       
   376     }
       
   377     
       
   378     $encodedTextLines = explode("\r\n",
       
   379       $this->_encoder->encodeString(
       
   380         $token, $firstLineOffset, 75 - $encodingWrapperLength
       
   381         )
       
   382       );
       
   383     
       
   384     foreach ($encodedTextLines as $lineNum => $line)
       
   385     {
       
   386       $encodedTextLines[$lineNum] = '=?' . $charsetDecl .
       
   387         '?' . $this->_encoder->getName() .
       
   388         '?' . $line . '?=';
       
   389     }
       
   390     
       
   391     return implode("\r\n ", $encodedTextLines);
       
   392   }
       
   393   
       
   394   /**
       
   395    * Generates tokens from the given string which include CRLF as individual tokens.
       
   396    * @param string $token
       
   397    * @return string[]
       
   398    * @access protected
       
   399    */
       
   400   protected function generateTokenLines($token)
       
   401   {
       
   402     return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
       
   403   }
       
   404   
       
   405   /**
       
   406    * Set a value into the cache.
       
   407    * @param string $value
       
   408    * @access protected
       
   409    */
       
   410   protected function setCachedValue($value)
       
   411   {
       
   412     $this->_cachedValue = $value;
       
   413   }
       
   414   
       
   415   /**
       
   416    * Get the value in the cache.
       
   417    * @return string
       
   418    * @access protected
       
   419    */
       
   420   protected function getCachedValue()
       
   421   {
       
   422     return $this->_cachedValue;
       
   423   }
       
   424   
       
   425   /**
       
   426    * Clear the cached value if $condition is met.
       
   427    * @param boolean $condition
       
   428    * @access protected
       
   429    */
       
   430   protected function clearCachedValueIf($condition)
       
   431   {
       
   432     if ($condition)
       
   433     {
       
   434       $this->setCachedValue(null);
       
   435     }
       
   436   }
       
   437   
       
   438   // -- Private methods
       
   439   
       
   440   /**
       
   441    * Generate a list of all tokens in the final header.
       
   442    * @param string $string input, optional
       
   443    * @return string[]
       
   444    * @access private
       
   445    */
       
   446   protected function toTokens($string = null)
       
   447   {
       
   448     if (is_null($string))
       
   449     {
       
   450       $string = $this->getFieldBody();
       
   451     }
       
   452     
       
   453     $tokens = array();
       
   454     
       
   455     //Generate atoms; split at all invisible boundaries followed by WSP
       
   456     foreach (preg_split('~(?=[ \t])~', $string) as $token)
       
   457     {
       
   458       $tokens = array_merge($tokens, $this->generateTokenLines($token));
       
   459     }
       
   460     
       
   461     return $tokens;
       
   462   }
       
   463   
       
   464   /**
       
   465    * Takes an array of tokens which appear in the header and turns them into
       
   466    * an RFC 2822 compliant string, adding FWSP where needed.
       
   467    * @param string[] $tokens
       
   468    * @return string
       
   469    * @access private
       
   470    */
       
   471   private function _tokensToString(array $tokens)
       
   472   {
       
   473     $lineCount = 0;
       
   474     $headerLines = array();
       
   475     $headerLines[] = $this->_name . ': ';
       
   476     $currentLine =& $headerLines[$lineCount++];
       
   477     
       
   478     //Build all tokens back into compliant header
       
   479     foreach ($tokens as $i => $token)
       
   480     {
       
   481       //Line longer than specified maximum or token was just a new line
       
   482       if (("\r\n" == $token) ||
       
   483         ($i > 0 && strlen($currentLine . $token) > $this->_lineLength)
       
   484         && 0 < strlen($currentLine))
       
   485       {
       
   486         $headerLines[] = '';
       
   487         $currentLine =& $headerLines[$lineCount++];
       
   488       }
       
   489       
       
   490       //Append token to the line
       
   491       if ("\r\n" != $token)
       
   492       {
       
   493         $currentLine .= $token;
       
   494       }
       
   495     }
       
   496     
       
   497     //Implode with FWS (RFC 2822, 2.2.3)
       
   498     return implode("\r\n", $headerLines) . "\r\n";
       
   499   }
       
   500   
       
   501 }