PageRenderTime 85ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/swift/Swift/Message/Headers.php

https://github.com/congpine/PrestaShop
PHP | 578 lines | 363 code | 23 blank | 192 comment | 62 complexity | 1f03297c385d217938331573f26154e0 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * Swift Mailer MIME Library Headers component
  4. * Please read the LICENSE file
  5. * @copyright Chris Corbyn <chris@w3style.co.uk>
  6. * @author Chris Corbyn <chris@w3style.co.uk>
  7. * @package Swift_Message
  8. * @license GNU Lesser General Public License
  9. */
  10. require_once dirname(__FILE__) . "/../ClassLoader.php";
  11. /**
  12. * Contains and constructs the headers for a MIME document
  13. * @package Swift_Message
  14. * @author Chris Corbyn <chris@w3style.co.uk>
  15. */
  16. class Swift_Message_Headers
  17. {
  18. /**
  19. * Headers which may contain email addresses, and therefore should take notice when encoding
  20. * @var array headers
  21. */
  22. protected $emailContainingHeaders = array(
  23. "To", "From", "Reply-To", "Cc", "Bcc", "Return-Path", "Sender");
  24. /**
  25. * The encoding format used for the body of the document
  26. * @var string format
  27. */
  28. protected $encoding = "B";
  29. /**
  30. * The charset used in the headers
  31. * @var string
  32. */
  33. protected $charset = false;
  34. /**
  35. * A collection of headers
  36. * @var array headers
  37. */
  38. protected $headers = array();
  39. /**
  40. * A container of references to the headers
  41. * @var array
  42. */
  43. protected $lowerHeaders = array();
  44. /**
  45. * Attributes appended to headers
  46. * @var array
  47. */
  48. protected $attributes = array();
  49. /**
  50. * If QP or Base64 encoding should be forced
  51. * @var boolean
  52. */
  53. protected $forceEncoding = false;
  54. /**
  55. * The language used in the headers (doesn't really matter much)
  56. * @var string
  57. */
  58. protected $language = "en-us";
  59. /**
  60. * Cached, pre-built headers
  61. * @var string
  62. */
  63. protected $cached = array();
  64. /**
  65. * The line ending used in the headers
  66. * @var string
  67. */
  68. protected $LE = "\r\n";
  69. /**
  70. * Set the line ending character to use
  71. * @param string The line ending sequence
  72. * @return boolean
  73. */
  74. public function setLE($le)
  75. {
  76. if (in_array($le, array("\r", "\n", "\r\n")))
  77. {
  78. foreach (array_keys($this->cached) as $k) $this->cached[$k] = null;
  79. $this->LE = $le;
  80. return true;
  81. }
  82. else return false;
  83. }
  84. /**
  85. * Get the line ending sequence
  86. * @return string
  87. */
  88. public function getLE()
  89. {
  90. return $this->LE;
  91. }
  92. /**
  93. * Reset the cache state in these headers
  94. */
  95. public function uncacheAll()
  96. {
  97. foreach (array_keys($this->cached) as $k)
  98. {
  99. $this->cached[$k] = null;
  100. }
  101. }
  102. /**
  103. * Add a header or change an existing header value
  104. * @param string The header name, for example "From" or "Subject"
  105. * @param string The value to be inserted into the header. This is safe from header injection.
  106. */
  107. public function set($name, $value)
  108. {
  109. $lname = strtolower($name);
  110. if (!isset($this->lowerHeaders[$lname]))
  111. {
  112. $this->headers[$name] = null;
  113. $this->lowerHeaders[$lname] =& $this->headers[$name];
  114. }
  115. $this->cached[$lname] = null;
  116. Swift_ClassLoader::load("Swift_Message_Encoder");
  117. if (is_array($value))
  118. {
  119. foreach ($value as $v)
  120. {
  121. if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($v))
  122. {
  123. $this->setCharset("utf-8");
  124. break;
  125. }
  126. }
  127. }
  128. elseif ($value !== null)
  129. {
  130. if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($value))
  131. {
  132. $this->setCharset("utf-8");
  133. }
  134. }
  135. if (!is_array($value) && $value !== null) $this->lowerHeaders[$lname] = (string) $value;
  136. else $this->lowerHeaders[$lname] = $value;
  137. }
  138. /**
  139. * Get the value at a given header
  140. * @param string The name of the header, for example "From" or "Subject"
  141. * @return string
  142. * @throws Swift_Message_MimeException If no such header exists
  143. * @see hasHeader
  144. */
  145. public function get($name)
  146. {
  147. $lname = strtolower($name);
  148. if ($this->has($name))
  149. {
  150. return $this->lowerHeaders[$lname];
  151. }
  152. }
  153. /**
  154. * Remove a header from the list
  155. * @param string The name of the header
  156. */
  157. public function remove($name)
  158. {
  159. $lname = strtolower($name);
  160. if ($this->has($name))
  161. {
  162. unset($this->headers[$name]);
  163. unset($this->lowerHeaders[$lname]);
  164. unset($this->cached[$lname]);
  165. if (isset($this->attributes[$lname])) unset($this->attributes[$lname]);
  166. }
  167. }
  168. /**
  169. * Just fetch the array containing the headers
  170. * @return array
  171. */
  172. public function getList()
  173. {
  174. return $this->headers;
  175. }
  176. /**
  177. * Check if a header has been set or not
  178. * @param string The name of the header, for example "From" or "Subject"
  179. * @return boolean
  180. */
  181. public function has($name)
  182. {
  183. $lname = strtolower($name);
  184. return (array_key_exists($lname, $this->lowerHeaders) && $this->lowerHeaders[$lname] !== null);
  185. }
  186. /**
  187. * Set the language used in the headers to $lang (e.g. en-us, en-gb, sv etc)
  188. * @param string The language to use
  189. */
  190. public function setLanguage($lang)
  191. {
  192. $this->language = (string) $lang;
  193. }
  194. /**
  195. * Get the language used in the headers to $lang (e.g. en-us, en-gb, sv etc)
  196. * @return string
  197. */
  198. public function getLanguage()
  199. {
  200. return $this->language;
  201. }
  202. /**
  203. * Set the charset used in the headers
  204. * @param string The charset name
  205. */
  206. public function setCharset($charset)
  207. {
  208. $this->charset = (string) $charset;
  209. }
  210. /**
  211. * Get the current charset used
  212. * @return string
  213. */
  214. public function getCharset()
  215. {
  216. return $this->charset;
  217. }
  218. /**
  219. * Specify the encoding to use for the headers if characters outside the 7-bit-printable ascii range are found
  220. * This encoding will never be used if only 7-bit-printable characters are found in the headers.
  221. * Possible values are:
  222. * - QP
  223. * - Q
  224. * - Quoted-Printable
  225. * - B
  226. * - Base64
  227. * NOTE: Q, QP, Quoted-Printable are all the same; as are B and Base64
  228. * @param string The encoding format to use
  229. * @return boolean
  230. */
  231. public function setEncoding($encoding)
  232. {
  233. switch (strtolower($encoding))
  234. {
  235. case "qp": case "q": case "quoted-printable":
  236. $this->encoding = "Q";
  237. return true;
  238. case "base64": case "b":
  239. $this->encoding = "B";
  240. return true;
  241. default: return false;
  242. }
  243. }
  244. /**
  245. * Get the encoding format used in this document
  246. * @return string
  247. */
  248. public function getEncoding()
  249. {
  250. return $this->encoding;
  251. }
  252. /**
  253. * Turn on or off forced header encoding
  254. * @param boolean On/Off
  255. */
  256. public function forceEncoding($force=true)
  257. {
  258. $this->forceEncoding = (boolean) $force;
  259. }
  260. /**
  261. * Set an attribute in a major header
  262. * For example $headers->setAttribute("Content-Type", "format", "flowed")
  263. * @param string The main header these values exist in
  264. * @param string The name for this value
  265. * @param string The value to set
  266. * @throws Swift_Message_MimeException If no such header exists
  267. */
  268. public function setAttribute($header, $name, $value)
  269. {
  270. $name = strtolower($name);
  271. $lheader = strtolower($header);
  272. $this->cached[$lheader] = null;
  273. if (!$this->has($header))
  274. {
  275. throw new Swift_Message_MimeException(
  276. "Cannot set attribute '" . $name . "' for header '" . $header . "' as the header does not exist. " .
  277. "Consider using Swift_Message_Headers-&gt;has() to check.");
  278. }
  279. else
  280. {
  281. Swift_ClassLoader::load("Swift_Message_Encoder");
  282. if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($value)) $this->setCharset("utf-8");
  283. if (!isset($this->attributes[$lheader])) $this->attributes[$lheader] = array();
  284. if ($value !== null) $this->attributes[$lheader][$name] = (string) $value;
  285. else $this->attributes[$lheader][$name] = $value;
  286. }
  287. }
  288. /**
  289. * Check if a header has a given attribute applied to it
  290. * @param string The name of the main header
  291. * @param string The name of the attribute
  292. * @return boolean
  293. */
  294. public function hasAttribute($header, $name)
  295. {
  296. $name = strtolower($name);
  297. $lheader = strtolower($header);
  298. if (!$this->has($header))
  299. {
  300. return false;
  301. }
  302. else
  303. {
  304. return (isset($this->attributes[$lheader]) && isset($this->attributes[$lheader][$name]) && ($this->attributes[$lheader][$name] !== null));
  305. }
  306. }
  307. /**
  308. * Get the value for a given attribute on a given header
  309. * @param string The name of the main header
  310. * @param string The name of the attribute
  311. * @return string
  312. * @throws Swift_Message_MimeException If no header is set
  313. */
  314. public function getAttribute($header, $name)
  315. {
  316. if (!$this->has($header))
  317. {
  318. throw new Swift_Message_MimeException(
  319. "Cannot locate attribute '" . $name . "' for header '" . $header . "' as the header does not exist. " .
  320. "Consider using Swift_Message_Headers-&gt;has() to check.");
  321. }
  322. $name = strtolower($name);
  323. $lheader = strtolower($header);
  324. if ($this->hasAttribute($header, $name))
  325. {
  326. return $this->attributes[$lheader][$name];
  327. }
  328. }
  329. /**
  330. * Remove an attribute from a header
  331. * @param string The name of the header to remove the attribute from
  332. * @param string The name of the attribute to remove
  333. */
  334. public function removeAttribute($header, $name)
  335. {
  336. $name = strtolower($name);
  337. $lheader = strtolower($header);
  338. if ($this->has($header))
  339. {
  340. unset($this->attributes[$lheader][$name]);
  341. }
  342. }
  343. /**
  344. * Get a list of all the attributes in the given header.
  345. * @param string The name of the header
  346. * @return array
  347. */
  348. public function listAttributes($header)
  349. {
  350. $header = strtolower($header);
  351. if (array_key_exists($header, $this->attributes))
  352. {
  353. return $this->attributes[$header];
  354. }
  355. else return array();
  356. }
  357. /**
  358. * Get the header in it's compliant, encoded form
  359. * @param string The name of the header
  360. * @return string
  361. * @throws Swift_Message_MimeException If the header doesn't exist
  362. */
  363. public function getEncoded($name)
  364. {
  365. if (!$this->getCharset()) $this->setCharset("iso-8859-1");
  366. Swift_ClassLoader::load("Swift_Message_Encoder");
  367. //I'll try as best I can to walk through this...
  368. $lname = strtolower($name);
  369. if ($this->cached[$lname] !== null) return $this->cached[$lname];
  370. $value = $this->get($name);
  371. $is_email = in_array($name, $this->emailContainingHeaders);
  372. $encoded_value = (array) $value; //Turn strings into arrays (just to make the following logic simpler)
  373. //Look at each value in this header
  374. // There will only be 1 value if it was a string to begin with, and usually only address lists will be multiple
  375. foreach ($encoded_value as $key => $row)
  376. {
  377. $spec = ""; //The bit which specifies the encoding of the header (if any)
  378. $end = ""; //The end delimiter for an encoded header
  379. //If the header is 7-bit printable it's at no risk of injection
  380. if (Swift_Message_Encoder::instance()->isHeaderSafe($row) && !$this->forceEncoding)
  381. {
  382. //Keeps the total line length at less than 76 chars, taking into account the Header name length
  383. $encoded_value[$key] = Swift_Message_Encoder::instance()->header7BitEncode(
  384. $row, 72, ($key > 0 ? 0 : (75-(strlen($name)+5))), $this->LE);
  385. }
  386. elseif ($this->encoding == "Q") //QP encode required
  387. {
  388. $spec = "=?" . $this->getCharset() . "?Q?"; //e.g. =?iso-8859-1?Q?
  389. $end = "?=";
  390. //Calculate the length of, for example: "From: =?iso-8859-1?Q??="
  391. $used_length = strlen($name) + 2 + strlen($spec) + 2;
  392. //Encode to QP, excluding the specification for now but keeping the lines short enough to be compliant
  393. $encoded_value[$key] = str_replace(" ", "_", Swift_Message_Encoder::instance()->QPEncode(
  394. $row, (75-(strlen($spec)+6)), ($key > 0 ? 0 : (75-$used_length)), true, $this->LE));
  395. }
  396. elseif ($this->encoding == "B") //Need to Base64 encode
  397. {
  398. //See the comments in the elseif() above since the logic is the same (refactor?)
  399. $spec = "=?" . $this->getCharset() . "?B?";
  400. $end = "?=";
  401. $used_length = strlen($name) + 2 + strlen($spec) + 2;
  402. $encoded_value[$key] = Swift_Message_Encoder::instance()->base64Encode(
  403. $row, (75-(strlen($spec)+5)), ($key > 0 ? 0 : (76-($used_length+3))), true, $this->LE);
  404. }
  405. if (false !== $p = strpos($encoded_value[$key], $this->LE))
  406. {
  407. $encoded_value[$key] = preg_replace_callback("/<([^>]+)>/", array($this, 'prestaShopReplace'), $encoded_value[$key]);
  408. }
  409. //Turn our header into an array of lines ready for wrapping around the encoding specification
  410. $lines = explode($this->LE, $encoded_value[$key]);
  411. for ($i = 0, $len = count($lines); $i < $len; $i++)
  412. {
  413. //Don't allow commas in address fields without quotes unless they're encoded
  414. if (empty($spec) && $is_email && (false !== $p = strpos($lines[$i], ",")))
  415. {
  416. $s = strpos($lines[$i], " <");
  417. $e = strpos($lines[$i], ">");
  418. if ($s < $e)
  419. {
  420. $addr = substr($lines[$i], $s);
  421. $lines[$i] = "\"" . substr($lines[$i], 0, $s) . "\"" . $addr;
  422. }
  423. else
  424. {
  425. $lines[$i] = "\"" . $lines[$i] . "\"";
  426. }
  427. }
  428. if ($this->encoding == "Q") $lines[$i] = rtrim($lines[$i], "=");
  429. if ($lines[$i] == "" && $i > 0)
  430. {
  431. unset($lines[$i]); //Empty line, we'd rather not have these in the headers thank you!
  432. continue;
  433. }
  434. if ($i > 0)
  435. {
  436. //Don't stick the specification part around the line if it's an address
  437. if (substr($lines[$i], 0, 1) == '<' && substr($lines[$i], -1) == '>') $lines[$i] = " " . $lines[$i];
  438. else $lines[$i] = " " . $spec . $lines[$i] . $end;
  439. }
  440. else
  441. {
  442. if (substr($lines[$i], 0, 1) != '<' || substr($lines[$i], -1) != '>') $lines[$i] = $spec . $lines[$i] . $end;
  443. }
  444. }
  445. //Build back into a string, now includes the specification
  446. $encoded_value[$key] = implode($this->LE, $lines);
  447. $lines = null;
  448. }
  449. //If there are multiple values in this header, put them on separate lines, cleared by commas
  450. $this->cached[$lname] = implode("," . $this->LE . " ", $encoded_value);
  451. //Append attributes if there are any
  452. if (!empty($this->attributes[$lname])) $this->cached[$lname] .= $this->buildAttributes($this->cached[$lname], $lname);
  453. return $this->cached[$lname];
  454. }
  455. /**
  456. * Build the list of attributes for appending to the given header
  457. * This is RFC 2231 & 2047 compliant.
  458. * A HUGE thanks to Joaquim Homrighausen for heaps of help, advice
  459. * and testing to get this working rock solid.
  460. * @param string The header built without attributes
  461. * @param string The lowercase name of the header
  462. * @return string
  463. * @throws Swift_Message_MimeException If no such header exists or there are no attributes
  464. */
  465. protected function buildAttributes($header_line, $header_name)
  466. {
  467. Swift_ClassLoader::load("Swift_Message_Encoder");
  468. $lines = explode($this->LE, $header_line);
  469. $used_len = strlen($lines[count($lines)-1]);
  470. $lines= null;
  471. $ret = "";
  472. foreach ($this->attributes[$header_name] as $attribute => $att_value)
  473. {
  474. if ($att_value === null) continue;
  475. // 70 to account for LWSP, CRLF, quotes and a semi-colon
  476. // + length of attribute
  477. // + 4 for a 2 digit number and 2 asterisks
  478. $avail_len = 70 - (strlen($attribute) + 4);
  479. $encoded = Swift_Message_Encoder::instance()->rfc2047Encode($att_value, $this->charset, $this->language, $avail_len, $this->LE);
  480. $lines = explode($this->LE, $encoded);
  481. foreach ($lines as $i => $line)
  482. {
  483. //Add quotes if needed (RFC 2045)
  484. if (preg_match("~[\\s\";,<>\\(\\)@:\\\\/\\[\\]\\?=]~", $line)) $lines[$i] = '"' . $line . '"';
  485. }
  486. $encoded = implode($this->LE, $lines);
  487. //If we can fit this entire attribute onto the same line as the header then do it!
  488. if ((strlen($encoded) + $used_len + strlen($attribute) + 4) < 74)
  489. {
  490. if (strpos($encoded, "'") !== false) $attribute .= "*";
  491. $append = "; " . $attribute . "=" . $encoded;
  492. $ret .= $append;
  493. $used_len += strlen($append);
  494. }
  495. else //... otherwise list of underneath
  496. {
  497. $ret .= ";";
  498. if (count($lines) > 1)
  499. {
  500. $loop = false;
  501. $add_asterisk = false;
  502. foreach ($lines as $i => $line)
  503. {
  504. $att_copy = $attribute; //Because it's multi-line it needs asterisks with decimal indices
  505. $att_copy .= "*" . $i;
  506. if ($add_asterisk || strpos($encoded, "'") !== false)
  507. {
  508. $att_copy .= "*"; //And if it's got a ' then it needs another asterisk
  509. $add_asterisk = true;
  510. }
  511. $append = "";
  512. if ($loop) $append .= ";";
  513. $append .= $this->LE . " " . $att_copy . "=" . $line;
  514. $ret .= $append;
  515. $used_len = strlen($append)+1;
  516. $loop = true;
  517. }
  518. }
  519. else
  520. {
  521. if (strpos($encoded, "'") !== false) $attribute .= "*";
  522. $append = $this->LE . " " . $attribute . "=" . $encoded;
  523. $used_len = strlen($append)+1;
  524. $ret .= $append;
  525. }
  526. }
  527. $lines= null;
  528. }
  529. return $ret;
  530. }
  531. public function prestaShopReplace($matches)
  532. {
  533. return str_replace("' . $this->LE . '", "", "<$matches[1]>");
  534. }
  535. /**
  536. * Compile the list of headers which have been set and return an ascii string
  537. * The return value should always be 7-bit ascii and will have been cleaned for header injection
  538. * If this looks complicated it's probably because it is!! Keeping everything compliant is not easy.
  539. * This is RFC 2822 compliant
  540. * @return string
  541. */
  542. public function build()
  543. {
  544. $ret = "";
  545. foreach ($this->headers as $name => $value) //Look at each header
  546. {
  547. if ($value === null) continue;
  548. $ret .= ltrim($name, ".") . ": " . $this->getEncoded($name) . $this->LE;
  549. }
  550. return trim($ret);
  551. }
  552. }