ParameterizedHeader.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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
  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 = [];
  34. /**
  35. * Creates a new ParameterizedHeader with $name.
  36. *
  37. * @param string $name
  38. */
  39. public function __construct($name, Swift_Mime_HeaderEncoder $encoder, Swift_Encoder $paramEncoder = null)
  40. {
  41. parent::__construct($name, $encoder);
  42. $this->paramEncoder = $paramEncoder;
  43. }
  44. /**
  45. * Get the type of Header that this instance represents.
  46. *
  47. * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX
  48. * @see TYPE_DATE, TYPE_ID, TYPE_PATH
  49. *
  50. * @return int
  51. */
  52. public function getFieldType()
  53. {
  54. return self::TYPE_PARAMETERIZED;
  55. }
  56. /**
  57. * Set the character set used in this Header.
  58. *
  59. * @param string $charset
  60. */
  61. public function setCharset($charset)
  62. {
  63. parent::setCharset($charset);
  64. if (isset($this->paramEncoder)) {
  65. $this->paramEncoder->charsetChanged($charset);
  66. }
  67. }
  68. /**
  69. * Set the value of $parameter.
  70. *
  71. * @param string $parameter
  72. * @param string $value
  73. */
  74. public function setParameter($parameter, $value)
  75. {
  76. $this->setParameters(array_merge($this->getParameters(), [$parameter => $value]));
  77. }
  78. /**
  79. * Get the value of $parameter.
  80. *
  81. * @param string $parameter
  82. *
  83. * @return string
  84. */
  85. public function getParameter($parameter)
  86. {
  87. $params = $this->getParameters();
  88. return $params[$parameter] ?? null;
  89. }
  90. /**
  91. * Set an associative array of parameter names mapped to values.
  92. *
  93. * @param string[] $parameters
  94. */
  95. public function setParameters(array $parameters)
  96. {
  97. $this->clearCachedValueIf($this->params != $parameters);
  98. $this->params = $parameters;
  99. }
  100. /**
  101. * Returns an associative array of parameter names mapped to values.
  102. *
  103. * @return string[]
  104. */
  105. public function getParameters()
  106. {
  107. return $this->params;
  108. }
  109. /**
  110. * Get the value of this header prepared for rendering.
  111. *
  112. * @return string
  113. */
  114. public function getFieldBody() //TODO: Check caching here
  115. {
  116. $body = parent::getFieldBody();
  117. foreach ($this->params as $name => $value) {
  118. if (null !== $value) {
  119. // Add the parameter
  120. $body .= '; '.$this->createParameter($name, $value);
  121. }
  122. }
  123. return $body;
  124. }
  125. /**
  126. * Generate a list of all tokens in the final header.
  127. *
  128. * This doesn't need to be overridden in theory, but it is for implementation
  129. * reasons to prevent potential breakage of attributes.
  130. *
  131. * @param string $string The string to tokenize
  132. *
  133. * @return array An array of tokens as strings
  134. */
  135. protected function toTokens($string = null)
  136. {
  137. $tokens = parent::toTokens(parent::getFieldBody());
  138. // Try creating any parameters
  139. foreach ($this->params as $name => $value) {
  140. if (null !== $value) {
  141. // Add the semi-colon separator
  142. $tokens[\count($tokens) - 1] .= ';';
  143. $tokens = array_merge($tokens, $this->generateTokenLines(
  144. ' '.$this->createParameter($name, $value)
  145. ));
  146. }
  147. }
  148. return $tokens;
  149. }
  150. /**
  151. * Render a RFC 2047 compliant header parameter from the $name and $value.
  152. *
  153. * @param string $name
  154. * @param string $value
  155. *
  156. * @return string
  157. */
  158. private function createParameter($name, $value)
  159. {
  160. $origValue = $value;
  161. $encoded = false;
  162. // Allow room for parameter name, indices, "=" and DQUOTEs
  163. $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1;
  164. $firstLineOffset = 0;
  165. // If it's not already a valid parameter value...
  166. if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
  167. // TODO: text, or something else??
  168. // ... and it's not ascii
  169. if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) {
  170. $encoded = true;
  171. // Allow space for the indices, charset and language
  172. $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1;
  173. $firstLineOffset = \strlen(
  174. $this->getCharset()."'".$this->getLanguage()."'"
  175. );
  176. }
  177. }
  178. // Encode if we need to
  179. if ($encoded || \strlen($value) > $maxValueLength) {
  180. if (isset($this->paramEncoder)) {
  181. $value = $this->paramEncoder->encodeString(
  182. $origValue, $firstLineOffset, $maxValueLength, $this->getCharset()
  183. );
  184. } else {
  185. // We have to go against RFC 2183/2231 in some areas for interoperability
  186. $value = $this->getTokenAsEncodedWord($origValue);
  187. $encoded = false;
  188. }
  189. }
  190. $valueLines = isset($this->paramEncoder) ? explode("\r\n", $value) : [$value];
  191. // Need to add indices
  192. if (\count($valueLines) > 1) {
  193. $paramLines = [];
  194. foreach ($valueLines as $i => $line) {
  195. $paramLines[] = $name.'*'.$i.
  196. $this->getEndOfParameterValue($line, true, 0 == $i);
  197. }
  198. return implode(";\r\n ", $paramLines);
  199. } else {
  200. return $name.$this->getEndOfParameterValue(
  201. $valueLines[0], $encoded, true
  202. );
  203. }
  204. }
  205. /**
  206. * Returns the parameter value from the "=" and beyond.
  207. *
  208. * @param string $value to append
  209. * @param bool $encoded
  210. * @param bool $firstLine
  211. *
  212. * @return string
  213. */
  214. private function getEndOfParameterValue($value, $encoded = false, $firstLine = false)
  215. {
  216. if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) {
  217. $value = '"'.$value.'"';
  218. }
  219. $prepend = '=';
  220. if ($encoded) {
  221. $prepend = '*=';
  222. if ($firstLine) {
  223. $prepend = '*='.$this->getCharset()."'".$this->getLanguage().
  224. "'";
  225. }
  226. }
  227. return $prepend.$value;
  228. }
  229. }