ParameterizedHeader.php 7.3 KB

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