PageRenderTime 92ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/libs/clearbricks/mail.mime/class.mime.message.php

https://bitbucket.org/dotclear/dotclear/
PHP | 467 lines | 396 code | 45 blank | 26 comment | 37 complexity | c3a41bf94dc11a1387a8f2d606111e64 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # This file is part of Clearbricks.
  4. # Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
  5. # All rights reserved.
  6. #
  7. # Clearbricks is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Clearbricks is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with Clearbricks; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. #
  21. # ***** END LICENSE BLOCK *****
  22. class mimeMessage
  23. {
  24. protected $from_email = null;
  25. protected $from_name = null;
  26. protected $ctype_primary = null;
  27. protected $ctype_secondary = null;
  28. protected $ctype_parameters = array();
  29. protected $d_parameters = array();
  30. protected $disposition = null;
  31. protected $headers = array();
  32. protected $body = null;
  33. protected $parts = array();
  34. public function __construct($message)
  35. {
  36. list($header,$body) = $this->splitBodyHeader($message);
  37. $this->decode($header,$body);
  38. }
  39. public function getMainBody()
  40. {
  41. # Look for the main body part (text/plain or text/html)
  42. if ($this->body && $this->ctype_primary == 'text' && $this->ctype_secondary == 'plain') {
  43. return $this->body;
  44. }
  45. foreach ($this->parts as $part)
  46. {
  47. if (($body = $part->getBody()) !== null) {
  48. return $body;
  49. }
  50. }
  51. return null;
  52. }
  53. public function getBody()
  54. {
  55. return $this->body;
  56. }
  57. public function getHeaders()
  58. {
  59. return $this->headers;
  60. }
  61. public function getHeader($hdr,$only_last=false)
  62. {
  63. $hdr = strtolower($hdr);
  64. if (!isset($this->headers[$hdr])) {
  65. return null;
  66. }
  67. if ($only_last && is_array($this->headers[$hdr])) {
  68. $r = $this->headers[$hdr];
  69. return array_pop($r);
  70. }
  71. return $this->headers[$hdr];
  72. }
  73. public function getFrom()
  74. {
  75. return array($this->from_email,$this->from_name);
  76. }
  77. public function getAllFiles()
  78. {
  79. $parts = array();
  80. foreach ($this->parts as $part)
  81. {
  82. $body = $part->getBody();
  83. $filename = $part->getFileName();
  84. if ($body && $filename)
  85. {
  86. $parts[] = array(
  87. 'filename' => $filename,
  88. 'content' => $part->getBody()
  89. );
  90. }
  91. else
  92. {
  93. $parts = array_merge($parts,$part->getAllFiles());
  94. }
  95. }
  96. return $parts;
  97. }
  98. public function getFileName()
  99. {
  100. if (isset($this->ctype_parameters['name'])) {
  101. return $this->ctype_parameters['name'];
  102. }
  103. if (isset($this->d_parameters['filename'])) {
  104. return $this->d_parameters['filename'];
  105. }
  106. return null;
  107. }
  108. protected function decode($headers,$body,$default_ctype='text/plain')
  109. {
  110. $headers = $this->parseHeaders($headers);
  111. foreach ($headers as $v)
  112. {
  113. if (isset($this->headers[strtolower($v['name'])]) &&
  114. !is_array($this->headers[strtolower($v['name'])]))
  115. {
  116. $this->headers[strtolower($v['name'])] = array($this->headers[strtolower($v['name'])]);
  117. $this->headers[strtolower($v['name'])][] = $v['value'];
  118. }
  119. elseif (isset($this->headers[strtolower($v['name'])]))
  120. {
  121. $this->headers[strtolower($v['name'])][] = $v['value'];
  122. }
  123. else
  124. {
  125. $this->headers[strtolower($v['name'])] = $v['value'];
  126. }
  127. }
  128. foreach ($headers as $k => $v)
  129. {
  130. $headers[$k]['name'] = strtolower($headers[$k]['name']);
  131. switch ($headers[$k]['name'])
  132. {
  133. case 'from':
  134. list($this->from_name,$this->from_email) = $this->decodeSender($headers[$k]['value']);
  135. break;
  136. case 'content-type':
  137. $content_type = $this->parseHeaderValue($headers[$k]['value']);
  138. if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
  139. $this->ctype_primary = strtolower($regs[1]);
  140. $this->ctype_secondary = strtolower($regs[2]);
  141. }
  142. if (isset($content_type['other']))
  143. {
  144. while (list($p_name, $p_value) = each($content_type['other'])) {
  145. $this->ctype_parameters[$p_name] = $p_value;
  146. }
  147. }
  148. break;
  149. case 'content-disposition':
  150. $content_disposition = $this->parseHeaderValue($headers[$k]['value']);
  151. $this->disposition = $content_disposition['value'];
  152. if (isset($content_disposition['other'])) {
  153. while (list($p_name, $p_value) = each($content_disposition['other'])) {
  154. $this->d_parameters[$p_name] = $p_value;
  155. }
  156. }
  157. break;
  158. case 'content-transfer-encoding':
  159. $content_transfer_encoding = $this->parseHeaderValue($headers[$k]['value']);
  160. break;
  161. }
  162. }
  163. if (isset($content_type))
  164. {
  165. switch (strtolower($content_type['value']))
  166. {
  167. case 'text/plain':
  168. case 'text/html':
  169. $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
  170. $charset = isset($this->ctype_parameters['charset']) ? $this->ctype_parameters['charset'] : null;
  171. $this->body = $this->decodeBody($body,$encoding);
  172. $this->body = text::cleanUTF8(@text::toUTF8($this->body,$charset));
  173. break;
  174. case 'multipart/parallel':
  175. case 'multipart/appledouble': // Appledouble mail
  176. case 'multipart/report': // RFC1892
  177. case 'multipart/signed': // PGP
  178. case 'multipart/digest':
  179. case 'multipart/alternative':
  180. case 'multipart/related':
  181. case 'multipart/mixed':
  182. if (!isset($content_type['other']['boundary'])) {
  183. throw new Exception('No boundary found');
  184. }
  185. $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
  186. $parts = $this->boundarySplit($body,$content_type['other']['boundary']);
  187. for ($i = 0; $i < count($parts); $i++)
  188. {
  189. $this->parts[] = new self($parts[$i]);
  190. }
  191. break;
  192. case 'message/rfc822':
  193. $this->parts[] = new self($body);
  194. break;
  195. default:
  196. if(!isset($content_transfer_encoding['value'])) {
  197. $content_transfer_encoding['value'] = '7bit';
  198. }
  199. $this->body = $this->decodeBody($body, $content_transfer_encoding['value']);
  200. break;
  201. }
  202. }
  203. else
  204. {
  205. $ctype = explode('/', $default_ctype);
  206. $this->ctype_primary = $ctype[0];
  207. $this->ctype_secondary = $ctype[1];
  208. $this->body = $this->decodeBody($body,'7bit');
  209. $this->body = text::cleanUTF8(@text::toUTF8($this->body));
  210. }
  211. }
  212. protected function splitBodyHeader($input)
  213. {
  214. if (preg_match('/^(.*?)\r?\n\r?\n(.*)/s', $input, $match)) {
  215. return array($match[1], $match[2]);
  216. } else { # No body found
  217. return array($input,'');
  218. }
  219. }
  220. protected function parseHeaders($input)
  221. {
  222. if (!$input) {
  223. return array();
  224. }
  225. # Unfold the input
  226. $input = preg_replace("/\r?\n/", "\r\n", $input);
  227. $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
  228. $headers = explode("\r\n", trim($input));
  229. $res = array();
  230. # Remove first From line if exists
  231. if (strpos($headers[0],'From ') === 0) {
  232. array_shift($headers);
  233. }
  234. foreach ($headers as $value)
  235. {
  236. $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
  237. $hdr_value = substr($value, $pos+1);
  238. if($hdr_value[0] == ' ') {
  239. $hdr_value = substr($hdr_value, 1);
  240. }
  241. $res[] = array(
  242. 'name' => $hdr_name,
  243. 'value' => $this->decodeHeader($hdr_value)
  244. );
  245. }
  246. return $res;
  247. }
  248. protected function parseHeaderValue($input)
  249. {
  250. if (($pos = strpos($input, ';')) !== false)
  251. {
  252. $return['value'] = trim(substr($input, 0, $pos));
  253. $input = trim(substr($input, $pos+1));
  254. if (strlen($input) > 0)
  255. {
  256. # This splits on a semi-colon, if there's no preceeding backslash
  257. # Now works with quoted values; had to glue the \; breaks in PHP
  258. # the regex is already bordering on incomprehensible
  259. $splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
  260. preg_match_all($splitRegex, $input, $matches);
  261. $parameters = array();
  262. for ($i=0; $i<count($matches[0]); $i++)
  263. {
  264. $param = $matches[0][$i];
  265. while (substr($param, -2) == '\;') {
  266. $param .= $matches[0][++$i];
  267. }
  268. $parameters[] = $param;
  269. }
  270. for ($i = 0; $i < count($parameters); $i++)
  271. {
  272. $param_name = trim(substr($parameters[$i], 0, $pos = strpos($parameters[$i], '=')), "'\";\t\\ ");
  273. $param_value = trim(str_replace('\;', ';', substr($parameters[$i], $pos + 1)), "'\";\t\\ ");
  274. if ($param_value[0] == '"') {
  275. $param_value = substr($param_value, 1, -1);
  276. }
  277. if (preg_match('/\*$/',$param_name)) {
  278. $return['other'][strtolower(substr($param_name,0,-1))] = $this->parserHeaderSpecialValue($param_value);
  279. } else {
  280. $return['other'][strtolower($param_name)] = $param_value;
  281. }
  282. }
  283. }
  284. }
  285. else
  286. {
  287. $return['value'] = trim($input);
  288. }
  289. return $return;
  290. }
  291. protected function parserHeaderSpecialValue($value)
  292. {
  293. if (strpos($value,"''") === false) {
  294. return $value;
  295. }
  296. list($charset,$value) = explode("''",$value);
  297. return @text::toUTF8(rawurldecode($value),$charset);
  298. }
  299. protected function boundarySplit($input, $boundary)
  300. {
  301. $parts = array();
  302. $bs_possible = substr($boundary, 2, -2);
  303. $bs_check = '\"' . $bs_possible . '\"';
  304. if ($boundary == $bs_check) {
  305. $boundary = $bs_possible;
  306. }
  307. $tmp = explode('--' . $boundary, $input);
  308. for ($i = 1; $i < count($tmp) - 1; $i++) {
  309. $parts[] = $tmp[$i];
  310. }
  311. return $parts;
  312. }
  313. protected function decodeHeader($input)
  314. {
  315. # Remove white space between encoded-words
  316. $input = preg_replace('/(=\?[^?]+\?(q|b)\?[^?]*\?=)(\s)+=\?/i', '\1=?', $input);
  317. # Non encoded
  318. if (!preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i',$input)) {
  319. return @text::toUTF8($input);
  320. }
  321. # For each encoded-word...
  322. while (preg_match('/(=\?([^?]+)\?(q|b)\?([^?]*)\?=)/i', $input, $matches))
  323. {
  324. $encoded = $matches[1];
  325. $charset = $matches[2];
  326. $encoding = $matches[3];
  327. $text = $matches[4];
  328. switch (strtolower($encoding))
  329. {
  330. case 'b':
  331. $text = base64_decode($text);
  332. break;
  333. case 'q':
  334. $text = str_replace('_', ' ', $text);
  335. preg_match_all('/=([a-f0-9]{2})/i', $text, $matches);
  336. foreach($matches[1] as $value) {
  337. $text = str_replace('='.$value, chr(hexdec($value)), $text);
  338. }
  339. break;
  340. }
  341. $text = @text::toUTF8($text,$charset);
  342. $input = str_replace($encoded, $text, $input);
  343. }
  344. return text::cleanUTF8($input);
  345. }
  346. protected function decodeSender($sender)
  347. {
  348. if (preg_match('/([\'|"])?(.*)(?(1)[\'|"])\s+<([\w\-=!#$%^*\'+\\.={}|?~]+@[\w\-=!#$%^*\'+\\.={}|?~]+[\w\-=!#$%^*\'+\\={}|?~])>/', $sender, $matches)) {
  349. # Match address in the form: Name <email@host>
  350. $result[0] = $matches[2];
  351. $result[1] = $matches[sizeof($matches) - 1];
  352. } elseif (preg_match('/([\w\-=!#$%^*\'+\\.={}|?~]+@[\w\-=!#$%^*\'+\\.={}|?~]+[\w\-=!#$%^*\'+\\={}|?~])\s+\((.*)\)/', $sender, $matches)) {
  353. # Match address in the form: email@host (Name)
  354. $result[0] = $matches[1];
  355. $result[1] = $matches[2];
  356. } else {
  357. # Only the email address present
  358. $result[0] = $sender;
  359. $result[1] = $sender;
  360. }
  361. $result[0] = str_replace("\"", "", $result[0]);
  362. $result[0] = str_replace("'", "", $result[0]);
  363. return $result;
  364. }
  365. protected function decodeBody($input, $encoding = '7bit')
  366. {
  367. switch (strtolower($encoding))
  368. {
  369. case '7bit':
  370. return $input;
  371. break;
  372. case 'quoted-printable':
  373. return $this->quotedPrintableDecode($input);
  374. break;
  375. case 'base64':
  376. return base64_decode($input);
  377. break;
  378. default:
  379. return $input;
  380. }
  381. }
  382. protected function quotedPrintableDecode($input)
  383. {
  384. // Remove soft line breaks
  385. $input = preg_replace("/=\r?\n/", '', $input);
  386. // Replace encoded characters
  387. $input = preg_replace_callback('/=([a-f0-9]{2})/i',array($this,'quotedPrintableDecodeHandler'),$input);
  388. return $input;
  389. }
  390. protected function quotedPrintableDecodeHandler($m)
  391. {
  392. return chr(hexdec($m[1]));
  393. }
  394. }
  395. ?>