PageRenderTime 87ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/code/classes/Daemon/PMaild/Mail.class.php

https://github.com/blekkzor/pinetd2
PHP | 474 lines | 408 code | 51 blank | 15 comment | 80 complexity | 0c580b815bbc0d115144e4abad936bd4 MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. namespace Daemon\PMaild;
  3. class Mail {
  4. private $info; // domain
  5. private $data; // mail
  6. private $file; // mail file
  7. private $sql;
  8. const MIME_CACHE_MAGIC = 0xb0ca1;
  9. public function __construct($info, $mail_data, $file, $sql) {
  10. $this->info = $info;
  11. $this->data = $mail_data;
  12. $this->file = $file;
  13. $this->sql = $sql;
  14. }
  15. public function __get($offset) {
  16. return $this->data->$offset;
  17. }
  18. public function valid() {
  19. return file_exists($this->file);
  20. }
  21. public function getBean() {
  22. return $this->data;
  23. }
  24. public function getId() {
  25. return $this->data->mailid;
  26. }
  27. public function size() {
  28. return filesize($this->file);
  29. }
  30. public function creationTime() {
  31. return filectime($this->file);
  32. }
  33. public function DAO($table) {
  34. switch($table) {
  35. case 'mime': $key = 'mimeid'; break;
  36. case 'mime_header': $key = 'headerid'; break;
  37. default: var_dump($table);exit;
  38. }
  39. return $this->sql->DAO('z'.$this->info['domainid'].'_'.$table, $key);
  40. }
  41. public function where() {
  42. return array('mailid' => $this->data->mailid, 'userid' => $this->data->userid);
  43. }
  44. public function delete() {
  45. $this->clearMimeCache();
  46. return $this->data->delete();
  47. }
  48. public function clearMimeCache() {
  49. $this->DAO('mime')->delete($this->where());
  50. $this->DAO('mime_header')->delete($this->where());
  51. $this->data->mime_cache = 0;
  52. $this->data->commit();
  53. }
  54. public function generateMimeCache() {
  55. $m = mailparse_msg_parse_file($this->file);
  56. $struct = mailparse_msg_get_structure($m);
  57. $depth = array();
  58. $imap_count = array();
  59. $part_info = array();
  60. $info_keep = array(
  61. 'charset','transfer_encoding','content_name','content_type','content_disposition','content_base','content_id','content_description','content_boundary','disposition_filename',
  62. 'starting_pos','starting_pos_body','ending_pos','ending_pos_body','line_count','body_line_count','content_language','content_charset'
  63. );
  64. $info_keep = array_flip($info_keep); // index on values
  65. foreach($struct as $part) {
  66. $p = mailparse_msg_get_part($m, $part);
  67. $info = mailparse_msg_get_part_data($p);
  68. $part_info[$part] = $info;
  69. list($type, $subtype) = explode('/', strtolower($info['content-type']));
  70. $pos = strrpos($part, '.');
  71. if ($pos !== false) {
  72. $parent = substr($part, 0, $pos);
  73. } else {
  74. $parent = NULL;
  75. }
  76. if (($type == 'multipart') && ($part == '1')) {
  77. $depth[$part] = 0;
  78. $imap_part = 'TEXT';
  79. } elseif (($type == 'multipart') && (($part == '1') || ($part_info[$parent]['content-type'] == 'message/rfc822'))) {
  80. $depth[$part] = 0;
  81. $imap_part = $part_info[$parent]['imap-part'].'.TEXT';
  82. } else {
  83. $depth[$part] = 1;
  84. $cur_depth = 0;
  85. $part_p = explode('.', $part);
  86. $tmp = '';
  87. foreach($part_p as $n) {
  88. $tmp .= ($tmp == ''?'':'.').$n;
  89. $cur_depth += $depth[$tmp];
  90. }
  91. if (!isset($imap_count[$cur_depth-1])) $imap_count[$cur_depth-1] = 0;
  92. $imap_count[$cur_depth-1]++;
  93. $imap_part = '';
  94. for($i = 0; $i < $cur_depth; $i++) {
  95. $imap_part .= ($i?'.':'').$imap_count[$i];
  96. }
  97. }
  98. $part_info[$part]['imap-part'] = $imap_part;
  99. $insert = array(
  100. 'userid' => $this->data->userid,
  101. 'mailid' => $this->data->mailid,
  102. 'part' => $part,
  103. 'imap_part' => $imap_part
  104. );
  105. foreach($info as $var => $val) {
  106. if ($var == 'headers') continue;
  107. $var = str_replace('-', '_', $var);
  108. if (!isset($info_keep[$var])) {
  109. var_dump($var);
  110. continue;
  111. }
  112. $insert[$var] = $val;
  113. }
  114. if (!$this->DAO('mime')->insertValues($insert)) return false;
  115. $id = $this->sql->insert_id;
  116. // now, insert headers
  117. foreach($info['headers'] as $header => $values) {
  118. if (!is_array($values)) $values = array($values);
  119. foreach($values as $value) {
  120. $insert = array(
  121. 'mimeid' => $id,
  122. 'userid' => $this->data->userid,
  123. 'mailid' => $this->data->mailid,
  124. 'header' => $header,
  125. 'content' => $value,
  126. );
  127. $this->DAO('mime_header')->insertValues($insert);
  128. }
  129. }
  130. }
  131. $this->data->mime_cache = self::MIME_CACHE_MAGIC;
  132. $this->data->commit();
  133. return true;
  134. }
  135. public function needMime() {
  136. if ($this->data->mime_cache != self::MIME_CACHE_MAGIC) {
  137. if ($this->data->mime_cache != 0) $this->clearMimeCache();
  138. $this->generateMimeCache();
  139. }
  140. }
  141. public function getHeaders($part = '1') {
  142. $this->needMime();
  143. $part = $this->DAO('mime')->loadByField($this->where()+array('part' => $part));
  144. if (!$part) return NULL;
  145. $part = $part[0];
  146. $list = $this->DAO('mime_header')->loadByField($this->where()+array('mimeid' => $part->mimeid));
  147. $res = array();
  148. foreach($list as $h) $res[$h->header][] = $h->content;
  149. return $res;
  150. }
  151. public function fetchRfc822Headers() {
  152. $head = "";
  153. // read file
  154. $fp = fopen($this->file, 'r'); // read headers
  155. if (!$fp) break;
  156. while(!feof($fp)) {
  157. $lin = fgets($fp);
  158. if (trim($lin) === '') break;
  159. $head .= $lin;
  160. }
  161. return $head;
  162. break;
  163. }
  164. public function getStructure($add_extra = false) {
  165. $this->needMime();
  166. $stack = array();
  167. $append = array();
  168. $append2 = array();
  169. $parts = $this->DAO('mime')->loadByField($this->where());
  170. foreach($parts as $part_bean) {
  171. $part = $part_bean->part;
  172. $info = $part_bean->getProperties();
  173. $list = explode('.', $part);
  174. $tmp = 'p';
  175. $prev = array(); // avoid warnings/notices
  176. while($list) {
  177. $tmp .= ($tmp=='p'?'':'.').array_shift($list);
  178. if (!isset($stack[$tmp])) {
  179. $new = array();
  180. $prev[] = &$new;
  181. $stack[$tmp] = &$new;
  182. $prev = &$new;
  183. unset($new);
  184. continue;
  185. }
  186. $prev = &$stack[$tmp];
  187. }
  188. unset($prev);
  189. $type = explode('/', $info['content_type']);
  190. if ($type[0] == 'multipart') {
  191. $append['p'.$part] = new Quoted(strtoupper($type[1]));
  192. } else {
  193. $props = array();
  194. if (isset($info['charset'])) {
  195. $props[] = new Quoted('CHARSET');
  196. $props[] = new Quoted($info['charset']);
  197. }
  198. if (isset($info['content_name'])) {
  199. $props[] = new Quoted('NAME');
  200. $props[] = new Quoted($info['content_name']);
  201. }
  202. $cid = NULL;
  203. if (isset($info['content_id'])) $cid = new Quoted('<'.$info['content_id'].'>');
  204. $desc = NULL;
  205. if (isset($info['content_description'])) $desc = new Quoted($info['content_description']);
  206. $res = array(new Quoted(strtoupper($type[0])), new Quoted(strtoupper($type[1])), $props, $cid, $desc, new Quoted(strtoupper($info['transfer_encoding'])), ($info['ending_pos_body'] - $info['starting_pos_body']));
  207. if (strtolower($type[0]) == 'text') {
  208. $res[] = $info['body_line_count'];
  209. }
  210. if ($info['content_type'] == 'message/rfc822') {
  211. $append2['p'.$part][] = $info['body_line_count'];
  212. $res[] = $this->getEnvelope($part.'.1'); // get envelope of contents
  213. }
  214. }
  215. if ($add_extra) {
  216. // get disposition if any
  217. $disposition = NULL;
  218. if (!is_null($info['content_disposition'])) {
  219. // ("ATTACHMENT" ("FILENAME" "test1.png"))
  220. $disposition = array(new Quoted(strtoupper($info['content_disposition'])), array());
  221. if (!is_null($info['disposition_filename'])) {
  222. $disposition[1][] = new Quoted('FILENAME');
  223. $disposition[1][] = new Quoted($info['disposition_filename']);
  224. }
  225. if (!$disposition[1]) $disposition[1] = NULL;
  226. }
  227. if ($type[0] == 'multipart') {
  228. $multipart_props = array();
  229. if (!is_null($info['content_boundary'])) {
  230. $multipart_props[] = new Quoted('BOUNDARY');
  231. $multipart_props[] = new Quoted($info['content_boundary']);
  232. }
  233. if (!$multipart_props) $multipart_props = null;
  234. $append2['p'.$part][] = $multipart_props; // array of parameters, eg ("TYPE" "multipart/alternative" "BOUNDARY" "=-qHsc855UymA7s4jLqdMw") as defined in [MIME-IMB]
  235. $append2['p'.$part][] = $disposition; // array of disposition as defined in [DISPOSITION] (RFC2183)
  236. $append2['p'.$part][] = $info['content_language']; // body language as defined in [LANGUAGE-TAGS] RFC3066
  237. // $append2['p'.$part][] = null; // body location as defined in [LOCATION] RFC2557
  238. } else {
  239. $append2['p'.$part][] = null; // content_md5 [MD5]
  240. $append2['p'.$part][] = $disposition; // body disposition (array) as defined in [DISPOSITION]
  241. $append2['p'.$part][] = $info['content_language']; // Content-Language as defined in [LANGUAGE-TAGS]
  242. // $append2['p'.$part][] = null; // content location as defined in [LOCATION]
  243. }
  244. }
  245. if ($type[0] == 'multipart')
  246. continue;
  247. $stack[$tmp] = $res;
  248. }
  249. foreach($append as $part => $what) {
  250. $stack[$part] = array(new ArrayList($stack[$part]), $what);
  251. }
  252. foreach($append2 as $part => $what) {
  253. foreach($what as $swhat) {
  254. $stack[$part][] = $swhat;// = array(new ArrayList($stack[$part]), $what);
  255. }
  256. }
  257. return $stack['p1'];
  258. }
  259. public function fetchBody($param) {
  260. if (count($param) == 0)
  261. $param = array('');
  262. $len = sizeof($param);
  263. $var = array();
  264. $res = array();
  265. for($i=0;$i<$len;$i++) {
  266. $p = $param[$i];
  267. switch(strtoupper($p)) {
  268. case 'HEADER.FIELDS':
  269. $list = $param[++$i];
  270. foreach($list as &$ref) $ref = strtoupper($ref); // toupper
  271. unset($ref); // avoid overwrite
  272. $list = array_flip($list);
  273. $head = "";
  274. $add = false;
  275. // read file
  276. $fp = fopen($this->file, 'r'); // read headers
  277. if (!$fp) break;
  278. while(!feof($fp)) {
  279. $lin = fgets($fp);
  280. if (trim($lin) === '') break;
  281. if (($lin[0] == "\t") || ($lin[0] == ' ')) {
  282. if ($add) $head .= $lin;
  283. continue;
  284. }
  285. $add = false;
  286. $pos = strpos($lin, ':');
  287. if ($pos === false) continue;
  288. $h = strtoupper(rtrim(substr($lin, 0, $pos)));
  289. if (!isset($list[$h])) continue;
  290. $head .= $lin;
  291. $add = true;
  292. }
  293. $var[] = 'HEADER.FIELDS';
  294. $var[] = array_flip($list);
  295. $res[] = $head;
  296. break;
  297. case 'HEADER':
  298. $head = "";
  299. // read file
  300. $fp = fopen($this->file, 'r'); // read headers
  301. if (!$fp) break;
  302. while(!feof($fp)) {
  303. $lin = fgets($fp);
  304. if (trim($lin) === '') break;
  305. $head .= $lin;
  306. }
  307. $var[] = strtoupper($p);
  308. $res[] = $head;
  309. break;
  310. case 'TEXT':
  311. // fetch body text
  312. // read file
  313. $fp = fopen($this->file, 'r'); // read headers
  314. if (!$fp) break;
  315. $str = '';
  316. $start = false;
  317. while(!feof($fp)) {
  318. $lin = fgets($fp);
  319. if (!$start) {
  320. if (trim($lin) == '') $start = true;
  321. continue;
  322. }
  323. $str .= $lin;
  324. }
  325. $var[] = strtoupper($p);
  326. $res[] = $str;
  327. break;
  328. case '':
  329. // fetch whole file
  330. $var[] = '';
  331. $res[] = file_get_contents($this->file);
  332. break;
  333. default:
  334. // check if this is a part request
  335. $this->needMime();
  336. $only_header = false;
  337. $p_search = $p;
  338. if (strtoupper(substr($p_search, -5)) == '.MIME') {
  339. $only_header = true;
  340. $p_search = substr($p_search, 0, -5);
  341. }
  342. if (strtoupper(substr($p_search, -7)) == '.HEADER') {
  343. $only_header = true;
  344. $p_search = substr($p_search, 0, -7).'.TEXT'; // dirty hack
  345. }
  346. $part = $this->DAO('mime')->loadByField($this->where()+array('imap_part' => $p_search));
  347. if ($part) {
  348. // partial body request, answer it!
  349. $part = $part[0];
  350. $fp = fopen($this->file, 'r');
  351. if (!$fp) break;
  352. if ($only_header) {
  353. fseek($fp, $part->starting_pos);
  354. $data_len = $part->starting_pos_body - $part->starting_pos;
  355. } else {
  356. fseek($fp, $part->starting_pos_body);
  357. $data_len = $part->ending_pos_body - $part->starting_pos_body;
  358. }
  359. $var[] = $p;
  360. $res[] = fread($fp, $data_len);
  361. break;
  362. }
  363. var_dump('BODY UNKNOWN: '.$p);
  364. }
  365. }
  366. $var = array('BODY' => $var);
  367. foreach($res as $r) $var[] = $r;
  368. return $var;
  369. }
  370. public function getEnvelope($part = '1') {
  371. $fields = array(
  372. 'date' => 's', // string
  373. 'subject' => 's', // string
  374. 'from' => 'm', // list
  375. 'sender' => 'm',
  376. 'reply-to' => 'm',
  377. 'to' => 'm',
  378. 'cc' => 'm',
  379. 'bcc' => 'm',
  380. 'in-reply-to' => 's',
  381. 'message-id' => 's',
  382. );
  383. // load mail headers
  384. $headers = $this->getHeaders($part);
  385. // RFC 3501, page 77
  386. if (!isset($headers['sender'])) $headers['sender'] = $headers['from'];
  387. if (!isset($headers['reply-to'])) $headers['reply-to'] = $headers['from'];
  388. $envelope = array();
  389. foreach($fields as $head => $type) {
  390. if (!isset($headers[$head])) {
  391. $envelope[] = null;
  392. continue;
  393. }
  394. switch($type) {
  395. case 's':
  396. $envelope[] = new Quoted($headers[$head][0]);
  397. break;
  398. case 'm':
  399. $tmp = array();
  400. foreach($headers[$head] as $h) {
  401. $infolist = imap_rfc822_parse_adrlist($h, '');
  402. foreach($infolist as $info) {
  403. if ($info->host === '') $info->host = null;
  404. $tmp[] = array(new Quoted($info->personal), new Quoted($info->adl), new Quoted($info->mailbox), new Quoted($info->host));
  405. }
  406. }
  407. $envelope[] = $tmp;
  408. break;
  409. case 'l':
  410. $tmp = array();
  411. foreach($headers[$head] as $h) {
  412. $tmp[] = new Quoted($h);
  413. }
  414. $envelope[] = $tmp;
  415. break;
  416. default:
  417. $envelope[] = $head;
  418. break;
  419. }
  420. }
  421. return $envelope;
  422. }
  423. }