PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/Swift/Mime/Headers/AbstractHeader.php

http://github.com/swiftmailer/swiftmailer
PHP | 476 lines | 216 code | 52 blank | 208 comment | 29 complexity | 082f47c8fba6fe20cdd07a49fd2aa42d MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  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. abstract class Swift_Mime_Headers_AbstractHeader implements Swift_Mime_Header
  15. {
  16. const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
  17. /**
  18. * The name of this Header.
  19. *
  20. * @var string
  21. */
  22. private $name;
  23. /**
  24. * The Encoder used to encode this Header.
  25. *
  26. * @var Swift_Encoder
  27. */
  28. private $encoder;
  29. /**
  30. * The maximum length of a line in the header.
  31. *
  32. * @var int
  33. */
  34. private $lineLength = 78;
  35. /**
  36. * The language used in this Header.
  37. *
  38. * @var string
  39. */
  40. private $lang;
  41. /**
  42. * The character set of the text in this Header.
  43. *
  44. * @var string
  45. */
  46. private $charset = 'utf-8';
  47. /**
  48. * The value of this Header, cached.
  49. *
  50. * @var string
  51. */
  52. private $cachedValue = null;
  53. /**
  54. * Set the character set used in this Header.
  55. *
  56. * @param string $charset
  57. */
  58. public function setCharset($charset)
  59. {
  60. $this->clearCachedValueIf($charset != $this->charset);
  61. $this->charset = $charset;
  62. if (isset($this->encoder)) {
  63. $this->encoder->charsetChanged($charset);
  64. }
  65. }
  66. /**
  67. * Get the character set used in this Header.
  68. *
  69. * @return string
  70. */
  71. public function getCharset()
  72. {
  73. return $this->charset;
  74. }
  75. /**
  76. * Set the language used in this Header.
  77. *
  78. * For example, for US English, 'en-us'.
  79. * This can be unspecified.
  80. *
  81. * @param string $lang
  82. */
  83. public function setLanguage($lang)
  84. {
  85. $this->clearCachedValueIf($this->lang != $lang);
  86. $this->lang = $lang;
  87. }
  88. /**
  89. * Get the language used in this Header.
  90. *
  91. * @return string
  92. */
  93. public function getLanguage()
  94. {
  95. return $this->lang;
  96. }
  97. /**
  98. * Set the encoder used for encoding the header.
  99. */
  100. public function setEncoder(Swift_Mime_HeaderEncoder $encoder)
  101. {
  102. $this->encoder = $encoder;
  103. $this->setCachedValue(null);
  104. }
  105. /**
  106. * Get the encoder used for encoding this Header.
  107. *
  108. * @return Swift_Mime_HeaderEncoder
  109. */
  110. public function getEncoder()
  111. {
  112. return $this->encoder;
  113. }
  114. /**
  115. * Get the name of this header (e.g. charset).
  116. *
  117. * @return string
  118. */
  119. public function getFieldName()
  120. {
  121. return $this->name;
  122. }
  123. /**
  124. * Set the maximum length of lines in the header (excluding EOL).
  125. *
  126. * @param int $lineLength
  127. */
  128. public function setMaxLineLength($lineLength)
  129. {
  130. $this->clearCachedValueIf($this->lineLength != $lineLength);
  131. $this->lineLength = $lineLength;
  132. }
  133. /**
  134. * Get the maximum permitted length of lines in this Header.
  135. *
  136. * @return int
  137. */
  138. public function getMaxLineLength()
  139. {
  140. return $this->lineLength;
  141. }
  142. /**
  143. * Get this Header rendered as a RFC 2822 compliant string.
  144. *
  145. * @return string
  146. *
  147. * @throws Swift_RfcComplianceException
  148. */
  149. public function toString()
  150. {
  151. return $this->tokensToString($this->toTokens());
  152. }
  153. /**
  154. * Returns a string representation of this object.
  155. *
  156. * @return string
  157. *
  158. * @see toString()
  159. */
  160. public function __toString()
  161. {
  162. return $this->toString();
  163. }
  164. /**
  165. * Set the name of this Header field.
  166. *
  167. * @param string $name
  168. */
  169. protected function setFieldName($name)
  170. {
  171. $this->name = $name;
  172. }
  173. /**
  174. * Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
  175. *
  176. * @param string $string as displayed
  177. * @param string $charset of the text
  178. * @param bool $shorten the first line to make remove for header name
  179. *
  180. * @return string
  181. */
  182. protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false)
  183. {
  184. // Treat token as exactly what was given
  185. $phraseStr = $string;
  186. // If it's not valid
  187. if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) {
  188. // .. but it is just ascii text, try escaping some characters
  189. // and make it a quoted-string
  190. if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
  191. $phraseStr = $this->escapeSpecials($phraseStr, ['"']);
  192. $phraseStr = '"'.$phraseStr.'"';
  193. } else {
  194. // ... otherwise it needs encoding
  195. // Determine space remaining on line if first line
  196. if ($shorten) {
  197. $usedLength = strlen($header->getFieldName().': ');
  198. } else {
  199. $usedLength = 0;
  200. }
  201. $phraseStr = $this->encodeWords($header, $string, $usedLength);
  202. }
  203. }
  204. return $phraseStr;
  205. }
  206. /**
  207. * Escape special characters in a string (convert to quoted-pairs).
  208. *
  209. * @param string $token
  210. * @param string[] $include additional chars to escape
  211. *
  212. * @return string
  213. */
  214. private function escapeSpecials($token, $include = [])
  215. {
  216. foreach (array_merge(['\\'], $include) as $char) {
  217. $token = str_replace($char, '\\'.$char, $token);
  218. }
  219. return $token;
  220. }
  221. /**
  222. * Encode needed word tokens within a string of input.
  223. *
  224. * @param string $input
  225. * @param string $usedLength optional
  226. *
  227. * @return string
  228. */
  229. protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1)
  230. {
  231. $value = '';
  232. $tokens = $this->getEncodableWordTokens($input);
  233. foreach ($tokens as $token) {
  234. // See RFC 2822, Sect 2.2 (really 2.2 ??)
  235. if ($this->tokenNeedsEncoding($token)) {
  236. // Don't encode starting WSP
  237. $firstChar = substr($token, 0, 1);
  238. switch ($firstChar) {
  239. case ' ':
  240. case "\t":
  241. $value .= $firstChar;
  242. $token = substr($token, 1);
  243. }
  244. if (-1 == $usedLength) {
  245. $usedLength = strlen($header->getFieldName().': ') + strlen($value);
  246. }
  247. $value .= $this->getTokenAsEncodedWord($token, $usedLength);
  248. $header->setMaxLineLength(76); // Forcefully override
  249. } else {
  250. $value .= $token;
  251. }
  252. }
  253. return $value;
  254. }
  255. /**
  256. * Test if a token needs to be encoded or not.
  257. *
  258. * @param string $token
  259. *
  260. * @return bool
  261. */
  262. protected function tokenNeedsEncoding($token)
  263. {
  264. return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
  265. }
  266. /**
  267. * Splits a string into tokens in blocks of words which can be encoded quickly.
  268. *
  269. * @param string $string
  270. *
  271. * @return string[]
  272. */
  273. protected function getEncodableWordTokens($string)
  274. {
  275. $tokens = [];
  276. $encodedToken = '';
  277. // Split at all whitespace boundaries
  278. foreach (preg_split('~(?=[\t ])~', $string) as $token) {
  279. if ($this->tokenNeedsEncoding($token)) {
  280. $encodedToken .= $token;
  281. } else {
  282. if (strlen($encodedToken) > 0) {
  283. $tokens[] = $encodedToken;
  284. $encodedToken = '';
  285. }
  286. $tokens[] = $token;
  287. }
  288. }
  289. if (strlen($encodedToken)) {
  290. $tokens[] = $encodedToken;
  291. }
  292. return $tokens;
  293. }
  294. /**
  295. * Get a token as an encoded word for safe insertion into headers.
  296. *
  297. * @param string $token token to encode
  298. * @param int $firstLineOffset optional
  299. *
  300. * @return string
  301. */
  302. protected function getTokenAsEncodedWord($token, $firstLineOffset = 0)
  303. {
  304. // Adjust $firstLineOffset to account for space needed for syntax
  305. $charsetDecl = $this->charset;
  306. if (isset($this->lang)) {
  307. $charsetDecl .= '*'.$this->lang;
  308. }
  309. $encodingWrapperLength = strlen(
  310. '=?'.$charsetDecl.'?'.$this->encoder->getName().'??='
  311. );
  312. if ($firstLineOffset >= 75) {
  313. //Does this logic need to be here?
  314. $firstLineOffset = 0;
  315. }
  316. $encodedTextLines = explode("\r\n",
  317. $this->encoder->encodeString(
  318. $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->charset
  319. )
  320. );
  321. if ('iso-2022-jp' !== strtolower($this->charset)) {
  322. // special encoding for iso-2022-jp using mb_encode_mimeheader
  323. foreach ($encodedTextLines as $lineNum => $line) {
  324. $encodedTextLines[$lineNum] = '=?'.$charsetDecl.
  325. '?'.$this->encoder->getName().
  326. '?'.$line.'?=';
  327. }
  328. }
  329. return implode("\r\n ", $encodedTextLines);
  330. }
  331. /**
  332. * Generates tokens from the given string which include CRLF as individual tokens.
  333. *
  334. * @param string $token
  335. *
  336. * @return string[]
  337. */
  338. protected function generateTokenLines($token)
  339. {
  340. return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE);
  341. }
  342. /**
  343. * Set a value into the cache.
  344. *
  345. * @param string $value
  346. */
  347. protected function setCachedValue($value)
  348. {
  349. $this->cachedValue = $value;
  350. }
  351. /**
  352. * Get the value in the cache.
  353. *
  354. * @return string
  355. */
  356. protected function getCachedValue()
  357. {
  358. return $this->cachedValue;
  359. }
  360. /**
  361. * Clear the cached value if $condition is met.
  362. *
  363. * @param bool $condition
  364. */
  365. protected function clearCachedValueIf($condition)
  366. {
  367. if ($condition) {
  368. $this->setCachedValue(null);
  369. }
  370. }
  371. /**
  372. * Generate a list of all tokens in the final header.
  373. *
  374. * @param string $string The string to tokenize
  375. *
  376. * @return array An array of tokens as strings
  377. */
  378. protected function toTokens($string = null)
  379. {
  380. if (null === $string) {
  381. $string = $this->getFieldBody();
  382. }
  383. $tokens = [];
  384. // Generate atoms; split at all invisible boundaries followed by WSP
  385. foreach (preg_split('~(?=[ \t])~', $string) as $token) {
  386. $newTokens = $this->generateTokenLines($token);
  387. foreach ($newTokens as $newToken) {
  388. $tokens[] = $newToken;
  389. }
  390. }
  391. return $tokens;
  392. }
  393. /**
  394. * Takes an array of tokens which appear in the header and turns them into
  395. * an RFC 2822 compliant string, adding FWSP where needed.
  396. *
  397. * @param string[] $tokens
  398. *
  399. * @return string
  400. */
  401. private function tokensToString(array $tokens)
  402. {
  403. $lineCount = 0;
  404. $headerLines = [];
  405. $headerLines[] = $this->name.': ';
  406. $currentLine = &$headerLines[$lineCount++];
  407. // Build all tokens back into compliant header
  408. foreach ($tokens as $i => $token) {
  409. // Line longer than specified maximum or token was just a new line
  410. if (("\r\n" == $token) ||
  411. ($i > 0 && strlen($currentLine.$token) > $this->lineLength)
  412. && 0 < strlen($currentLine)) {
  413. $headerLines[] = '';
  414. $currentLine = &$headerLines[$lineCount++];
  415. }
  416. // Append token to the line
  417. if ("\r\n" != $token) {
  418. $currentLine .= $token;
  419. }
  420. }
  421. // Implode with FWS (RFC 2822, 2.2.3)
  422. return implode("\r\n", $headerLines)."\r\n";
  423. }
  424. }