2
0

Imap.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Mail
  17. * @subpackage Protocol
  18. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @category Zend
  24. * @package Zend_Mail
  25. * @subpackage Protocol
  26. * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
  27. * @license http://framework.zend.com/license/new-bsd New BSD License
  28. */
  29. class Zend_Mail_Protocol_Imap
  30. {
  31. /**
  32. * Default timeout in seconds for initiating session
  33. */
  34. const TIMEOUT_CONNECTION = 30;
  35. /**
  36. * socket to imap server
  37. * @var resource|null
  38. */
  39. protected $_socket;
  40. /**
  41. * counter for request tag
  42. * @var int
  43. */
  44. protected $_tagCount = 0;
  45. /**
  46. * Public constructor
  47. *
  48. * @param string $host hostname of IP address of IMAP server, if given connect() is called
  49. * @param int|null $port port of IMAP server, null for default (143 or 993 for ssl)
  50. * @param bool $ssl use ssl? 'SSL', 'TLS' or false
  51. * @throws Zend_Mail_Protocol_Exception
  52. */
  53. function __construct($host = '', $port = null, $ssl = false)
  54. {
  55. if ($host) {
  56. $this->connect($host, $port, $ssl);
  57. }
  58. }
  59. /**
  60. * Public destructor
  61. */
  62. public function __destruct()
  63. {
  64. $this->logout();
  65. }
  66. /**
  67. * Open connection to POP3 server
  68. *
  69. * @param string $host hostname of IP address of POP3 server
  70. * @param int|null $port of IMAP server, default is 143 (993 for ssl)
  71. * @param string|bool $ssl use 'SSL', 'TLS' or false
  72. * @return string welcome message
  73. * @throws Zend_Mail_Protocol_Exception
  74. */
  75. public function connect($host, $port = null, $ssl = false)
  76. {
  77. if ($ssl == 'SSL') {
  78. $host = 'ssl://' . $host;
  79. }
  80. if ($port === null) {
  81. $port = $ssl === 'SSL' ? 993 : 143;
  82. }
  83. $errno = 0;
  84. $errstr = '';
  85. $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
  86. if (!$this->_socket) {
  87. /**
  88. * @see Zend_Mail_Protocol_Exception
  89. */
  90. require_once 'Zend/Mail/Protocol/Exception.php';
  91. throw new Zend_Mail_Protocol_Exception('cannot connect to host : ' . $errno . ' : ' . $errstr);
  92. }
  93. if (!$this->_assumedNextLine('* OK')) {
  94. /**
  95. * @see Zend_Mail_Protocol_Exception
  96. */
  97. require_once 'Zend/Mail/Protocol/Exception.php';
  98. throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
  99. }
  100. if ($ssl === 'TLS') {
  101. $result = $this->requestAndResponse('STARTTLS');
  102. $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
  103. if (!$result) {
  104. /**
  105. * @see Zend_Mail_Protocol_Exception
  106. */
  107. require_once 'Zend/Mail/Protocol/Exception.php';
  108. throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
  109. }
  110. }
  111. }
  112. /**
  113. * get the next line from socket with error checking, but nothing else
  114. *
  115. * @return string next line
  116. * @throws Zend_Mail_Protocol_Exception
  117. */
  118. protected function _nextLine()
  119. {
  120. $line = @fgets($this->_socket);
  121. if ($line === false) {
  122. /**
  123. * @see Zend_Mail_Protocol_Exception
  124. */
  125. require_once 'Zend/Mail/Protocol/Exception.php';
  126. throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
  127. }
  128. return $line;
  129. }
  130. /**
  131. * get next line and assume it starts with $start. some requests give a simple
  132. * feedback so we can quickly check if we can go on.
  133. *
  134. * @param string $start the first bytes we assume to be in the next line
  135. * @return bool line starts with $start
  136. * @throws Zend_Mail_Protocol_Exception
  137. */
  138. protected function _assumedNextLine($start)
  139. {
  140. $line = $this->_nextLine();
  141. return strpos($line, $start) === 0;
  142. }
  143. /**
  144. * get next line and split the tag. that's the normal case for a response line
  145. *
  146. * @param string $tag tag of line is returned by reference
  147. * @return string next line
  148. * @throws Zend_Mail_Protocol_Exception
  149. */
  150. protected function _nextTaggedLine(&$tag)
  151. {
  152. $line = $this->_nextLine();
  153. // seperate tag from line
  154. list($tag, $line) = explode(' ', $line, 2);
  155. return $line;
  156. }
  157. /**
  158. * split a given line in tokens. a token is literal of any form or a list
  159. *
  160. * @param string $line line to decode
  161. * @return array tokens, literals are returned as string, lists as array
  162. * @throws Zend_Mail_Protocol_Exception
  163. */
  164. protected function _decodeLine($line)
  165. {
  166. $tokens = array();
  167. $stack = array();
  168. /*
  169. We start to decode the response here. The unterstood tokens are:
  170. literal
  171. "literal" or also "lit\\er\"al"
  172. {bytes}<NL>literal
  173. (literals*)
  174. All tokens are returned in an array. Literals in braces (the last unterstood
  175. token in the list) are returned as an array of tokens. I.e. the following response:
  176. "foo" baz {3}<NL>bar ("f\\\"oo" bar)
  177. would be returned as:
  178. array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
  179. // TODO: add handling of '[' and ']' to parser for easier handling of response text
  180. */
  181. // replace any trailling <NL> including spaces with a single space
  182. $line = rtrim($line) . ' ';
  183. while (($pos = strpos($line, ' ')) !== false) {
  184. $token = substr($line, 0, $pos);
  185. while ($token[0] == '(') {
  186. array_push($stack, $tokens);
  187. $tokens = array();
  188. $token = substr($token, 1);
  189. }
  190. if ($token[0] == '"') {
  191. if (preg_match('%^"((.|\\\\|\\")*?)" *%', $line, $matches)) {
  192. $tokens[] = $matches[1];
  193. $line = substr($line, strlen($matches[0]));
  194. continue;
  195. }
  196. }
  197. if ($token[0] == '{') {
  198. $endPos = strpos($token, '}');
  199. $chars = substr($token, 1, $endPos - 1);
  200. if (is_numeric($chars)) {
  201. $token = '';
  202. while (strlen($token) < $chars) {
  203. $token .= $this->_nextLine();
  204. }
  205. $line = '';
  206. if (strlen($token) > $chars) {
  207. $line = substr($token, $chars);
  208. $token = substr($token, 0, $chars);
  209. } else {
  210. $line .= $this->_nextLine();
  211. }
  212. $tokens[] = $token;
  213. $line = trim($line) . ' ';
  214. continue;
  215. }
  216. }
  217. if ($stack && $token[strlen($token) - 1] == ')') {
  218. // closing braces are not seperated by spaces, so we need to count them
  219. $braces = strlen($token);
  220. $token = rtrim($token, ')');
  221. // only count braces if more than one
  222. $braces -= strlen($token) + 1;
  223. // only add if token had more than just closing braces
  224. if ($token) {
  225. $tokens[] = $token;
  226. }
  227. $token = $tokens;
  228. $tokens = array_pop($stack);
  229. // special handline if more than one closing brace
  230. while ($braces-- > 0) {
  231. $tokens[] = $token;
  232. $token = $tokens;
  233. $tokens = array_pop($stack);
  234. }
  235. }
  236. $tokens[] = $token;
  237. $line = substr($line, $pos + 1);
  238. }
  239. // maybe the server forgot to send some closing braces
  240. while ($stack) {
  241. $child = $tokens;
  242. $tokens = array_pop($stack);
  243. $tokens[] = $child;
  244. }
  245. return $tokens;
  246. }
  247. /**
  248. * read a response "line" (could also be more than one real line if response has {..}<NL>)
  249. * and do a simple decode
  250. *
  251. * @param array|string $tokens decoded tokens are returned by reference, if $dontParse
  252. * is true the unparsed line is returned here
  253. * @param string $wantedTag check for this tag for response code. Default '*' is
  254. * continuation tag.
  255. * @param bool $dontParse if true only the unparsed line is returned $tokens
  256. * @return bool if returned tag matches wanted tag
  257. * @throws Zend_Mail_Protocol_Exception
  258. */
  259. public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
  260. {
  261. $line = $this->_nextTaggedLine($tag);
  262. if (!$dontParse) {
  263. $tokens = $this->_decodeLine($line);
  264. } else {
  265. $tokens = $line;
  266. }
  267. // if tag is wanted tag we might be at the end of a multiline response
  268. return $tag == $wantedTag;
  269. }
  270. /**
  271. * read all lines of response until given tag is found (last line of response)
  272. *
  273. * @param string $tag the tag of your request
  274. * @param string|array $filter you can filter the response so you get only the
  275. * given response lines
  276. * @param bool $dontParse if true every line is returned unparsed instead of
  277. * the decoded tokens
  278. * @return null|bool|array tokens if success, false if error, null if bad request
  279. * @throws Zend_Mail_Protocol_Exception
  280. */
  281. public function readResponse($tag, $dontParse = false)
  282. {
  283. $lines = array();
  284. while (!$this->readLine($tokens, $tag, $dontParse)) {
  285. $lines[] = $tokens;
  286. }
  287. if ($dontParse) {
  288. // last to chars are still needed for response code
  289. $tokens = array(substr($tokens, 0, 2));
  290. }
  291. // last line has response code
  292. if ($tokens[0] == 'OK') {
  293. return $lines ? $lines : true;
  294. } else if ($tokens[0] == 'NO'){
  295. return false;
  296. }
  297. return null;
  298. }
  299. /**
  300. * send a request
  301. *
  302. * @param string $command your request command
  303. * @param array $tokens additional parameters to command, use escapeString() to prepare
  304. * @param string $tag provide a tag otherwise an autogenerated is returned
  305. * @return null
  306. * @throws Zend_Mail_Protocol_Exception
  307. */
  308. public function sendRequest($command, $tokens = array(), &$tag = null)
  309. {
  310. if (!$tag) {
  311. ++$this->_tagCount;
  312. $tag = 'TAG' . $this->_tagCount;
  313. }
  314. $line = $tag . ' ' . $command;
  315. foreach ($tokens as $token) {
  316. if (is_array($token)) {
  317. if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
  318. /**
  319. * @see Zend_Mail_Protocol_Exception
  320. */
  321. require_once 'Zend/Mail/Protocol/Exception.php';
  322. throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
  323. }
  324. if (!$this->_assumedNextLine('+ ')) {
  325. /**
  326. * @see Zend_Mail_Protocol_Exception
  327. */
  328. require_once 'Zend/Mail/Protocol/Exception.php';
  329. throw new Zend_Mail_Protocol_Exception('cannot send literal string');
  330. }
  331. $line = $token[1];
  332. } else {
  333. $line .= ' ' . $token;
  334. }
  335. }
  336. if (@fputs($this->_socket, $line . "\r\n") === false) {
  337. /**
  338. * @see Zend_Mail_Protocol_Exception
  339. */
  340. require_once 'Zend/Mail/Protocol/Exception.php';
  341. throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
  342. }
  343. }
  344. /**
  345. * send a request and get response at once
  346. *
  347. * @param string $command command as in sendRequest()
  348. * @param array $tokens parameters as in sendRequest()
  349. * @param bool $dontParse if true unparsed lines are returned instead of tokens
  350. * @return mixed response as in readResponse()
  351. * @throws Zend_Mail_Protocol_Exception
  352. */
  353. public function requestAndResponse($command, $tokens = array(), $dontParse = false)
  354. {
  355. $this->sendRequest($command, $tokens, $tag);
  356. $response = $this->readResponse($tag, $dontParse);
  357. return $response;
  358. }
  359. /**
  360. * escape one or more literals i.e. for sendRequest
  361. *
  362. * @param string|array $string the literal/-s
  363. * @return string|array escape literals, literals with newline ar returned
  364. * as array('{size}', 'string');
  365. */
  366. public function escapeString($string)
  367. {
  368. if (func_num_args() < 2) {
  369. if (strpos($string, "\n") !== false) {
  370. return array('{' . strlen($string) . '}', $string);
  371. } else {
  372. return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
  373. }
  374. }
  375. $result = array();
  376. foreach (func_get_args() as $string) {
  377. $result[] = $this->escapeString($string);
  378. }
  379. return $result;
  380. }
  381. /**
  382. * escape a list with literals or lists
  383. *
  384. * @param array $list list with literals or lists as PHP array
  385. * @return string escaped list for imap
  386. */
  387. public function escapeList($list)
  388. {
  389. $result = array();
  390. foreach ($list as $k => $v) {
  391. if (!is_array($v)) {
  392. // $result[] = $this->escapeString($v);
  393. $result[] = $v;
  394. continue;
  395. }
  396. $result[] = $this->escapeList($v);
  397. }
  398. return '(' . implode(' ', $result) . ')';
  399. }
  400. /**
  401. * Login to IMAP server.
  402. *
  403. * @param string $user username
  404. * @param string $password password
  405. * @return bool success
  406. * @throws Zend_Mail_Protocol_Exception
  407. */
  408. public function login($user, $password)
  409. {
  410. return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
  411. }
  412. /**
  413. * logout of imap server
  414. *
  415. * @return bool success
  416. */
  417. public function logout()
  418. {
  419. $result = false;
  420. if ($this->_socket) {
  421. try {
  422. $result = $this->requestAndResponse('LOGOUT', array(), true);
  423. } catch (Zend_Mail_Protocol_Exception $e) {
  424. // ignoring exception
  425. }
  426. fclose($this->_socket);
  427. $this->_socket = null;
  428. }
  429. return $result;
  430. }
  431. /**
  432. * Get capabilities from IMAP server
  433. *
  434. * @return array list of capabilities
  435. * @throws Zend_Mail_Protocol_Exception
  436. */
  437. public function capability()
  438. {
  439. $response = $this->requestAndResponse('CAPABILITY');
  440. if (!$response) {
  441. return $response;
  442. }
  443. $capabilities = array();
  444. foreach ($response as $line) {
  445. $capabilities = array_merge($capabilities, $line);
  446. }
  447. return $capabilities;
  448. }
  449. /**
  450. * Examine and select have the same response. The common code for both
  451. * is in this method
  452. *
  453. * @param string $command can be 'EXAMINE' or 'SELECT' and this is used as command
  454. * @param string $box which folder to change to or examine
  455. * @return bool|array false if error, array with returned information
  456. * otherwise (flags, exists, recent, uidvalidity)
  457. * @throws Zend_Mail_Protocol_Exception
  458. */
  459. public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
  460. {
  461. $this->sendRequest($command, array($this->escapeString($box)), $tag);
  462. $result = array();
  463. while (!$this->readLine($tokens, $tag)) {
  464. if ($tokens[0] == 'FLAGS') {
  465. array_shift($tokens);
  466. $result['flags'] = $tokens;
  467. continue;
  468. }
  469. switch ($tokens[1]) {
  470. case 'EXISTS':
  471. case 'RECENT':
  472. $result[strtolower($tokens[1])] = $tokens[0];
  473. break;
  474. case '[UIDVALIDITY':
  475. $result['uidvalidity'] = (int)$tokens[2];
  476. break;
  477. default:
  478. // ignore
  479. }
  480. }
  481. if ($tokens[0] != 'OK') {
  482. return false;
  483. }
  484. return $result;
  485. }
  486. /**
  487. * change folder
  488. *
  489. * @param string $box change to this folder
  490. * @return bool|array see examineOrselect()
  491. * @throws Zend_Mail_Protocol_Exception
  492. */
  493. public function select($box = 'INBOX')
  494. {
  495. return $this->examineOrSelect('SELECT', $box);
  496. }
  497. /**
  498. * examine folder
  499. *
  500. * @param string $box examine this folder
  501. * @return bool|array see examineOrselect()
  502. * @throws Zend_Mail_Protocol_Exception
  503. */
  504. public function examine($box = 'INBOX')
  505. {
  506. return $this->examineOrSelect('EXAMINE', $box);
  507. }
  508. /**
  509. * fetch one or more items of one or more messages
  510. *
  511. * @param string|array $items items to fetch from message(s) as string (if only one item)
  512. * or array of strings
  513. * @param int $from message for items or start message if $to !== null
  514. * @param int|null $to if null only one message ($from) is fetched, else it's the
  515. * last message, INF means last message avaible
  516. * @return string|array if only one item of one message is fetched it's returned as string
  517. * if items of one message are fetched it's returned as (name => value)
  518. * if one items of messages are fetched it's returned as (msgno => value)
  519. * if items of messages are fetchted it's returned as (msgno => (name => value))
  520. * @throws Zend_Mail_Protocol_Exception
  521. */
  522. public function fetch($items, $from, $to = null)
  523. {
  524. if (is_array($from)) {
  525. $set = implode(',', $from);
  526. } else if ($to === null) {
  527. $set = (int)$from;
  528. } else if ($to === INF) {
  529. $set = (int)$from . ':*';
  530. } else {
  531. $set = (int)$from . ':' . (int)$to;
  532. }
  533. $items = (array)$items;
  534. $itemList = $this->escapeList($items);
  535. $this->sendRequest('FETCH', array($set, $itemList), $tag);
  536. $result = array();
  537. while (!$this->readLine($tokens, $tag)) {
  538. // ignore other responses
  539. if ($tokens[1] != 'FETCH') {
  540. continue;
  541. }
  542. // ignore other messages
  543. if ($to === null && !is_array($from) && $tokens[0] != $from) {
  544. continue;
  545. }
  546. // if we only want one item we return that one directly
  547. if (count($items) == 1) {
  548. if ($tokens[2][0] == $items[0]) {
  549. $data = $tokens[2][1];
  550. } else {
  551. // maybe the server send an other field we didn't wanted
  552. $count = count($tokens[2]);
  553. // we start with 2, because 0 was already checked
  554. for ($i = 2; $i < $count; $i += 2) {
  555. if ($tokens[2][$i] != $items[0]) {
  556. continue;
  557. }
  558. $data = $tokens[2][$i + 1];
  559. break;
  560. }
  561. }
  562. } else {
  563. $data = array();
  564. while (key($tokens[2]) !== null) {
  565. $data[current($tokens[2])] = next($tokens[2]);
  566. next($tokens[2]);
  567. }
  568. }
  569. // if we want only one message we can ignore everything else and just return
  570. if ($to === null && !is_array($from) && $tokens[0] == $from) {
  571. // we still need to read all lines
  572. while (!$this->readLine($tokens, $tag));
  573. return $data;
  574. }
  575. $result[$tokens[0]] = $data;
  576. }
  577. if ($to === null && !is_array($from)) {
  578. /**
  579. * @see Zend_Mail_Protocol_Exception
  580. */
  581. require_once 'Zend/Mail/Protocol/Exception.php';
  582. throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
  583. }
  584. return $result;
  585. }
  586. /**
  587. * get mailbox list
  588. *
  589. * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword
  590. *
  591. * @param string $reference mailbox reference for list
  592. * @param string $mailbox mailbox name match with wildcards
  593. * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
  594. * @throws Zend_Mail_Protocol_Exception
  595. */
  596. public function listMailbox($reference = '', $mailbox = '*')
  597. {
  598. $result = array();
  599. $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
  600. if (!$list || $list === true) {
  601. return $result;
  602. }
  603. foreach ($list as $item) {
  604. if (count($item) != 4 || $item[0] != 'LIST') {
  605. continue;
  606. }
  607. $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
  608. }
  609. return $result;
  610. }
  611. /**
  612. * set flags
  613. *
  614. * @param array $flags flags to set, add or remove - see $mode
  615. * @param int $from message for items or start message if $to !== null
  616. * @param int|null $to if null only one message ($from) is fetched, else it's the
  617. * last message, INF means last message avaible
  618. * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
  619. * @param bool $silent if false the return values are the new flags for the wanted messages
  620. * @return bool|array new flags if $silent is false, else true or false depending on success
  621. * @throws Zend_Mail_Protocol_Exception
  622. */
  623. public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
  624. {
  625. $item = 'FLAGS';
  626. if ($mode == '+' || $mode == '-') {
  627. $item = $mode . $item;
  628. }
  629. if ($silent) {
  630. $item .= '.SILENT';
  631. }
  632. $flags = $this->escapeList($flags);
  633. $set = (int)$from;
  634. if ($to != null) {
  635. $set .= ':' . ($to == INF ? '*' : (int)$to);
  636. }
  637. $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
  638. if ($silent) {
  639. return $result ? true : false;
  640. }
  641. $tokens = $result;
  642. $result = array();
  643. foreach ($tokens as $token) {
  644. if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
  645. continue;
  646. }
  647. $result[$token[0]] = $token[2][1];
  648. }
  649. return $result;
  650. }
  651. /**
  652. * append a new message to given folder
  653. *
  654. * @param string $folder name of target folder
  655. * @param string $message full message content
  656. * @param array $flags flags for new message
  657. * @param string $date date for new message
  658. * @return bool success
  659. * @throws Zend_Mail_Protocol_Exception
  660. */
  661. public function append($folder, $message, $flags = null, $date = null)
  662. {
  663. $tokens = array();
  664. $tokens[] = $this->escapeString($folder);
  665. if ($flags !== null) {
  666. $tokens[] = $this->escapeList($flags);
  667. }
  668. if ($date !== null) {
  669. $tokens[] = $this->escapeString($date);
  670. }
  671. $tokens[] = $this->escapeString($message);
  672. return $this->requestAndResponse('APPEND', $tokens, true);
  673. }
  674. /**
  675. * copy message set from current folder to other folder
  676. *
  677. * @param string $folder destination folder
  678. * @param int|null $to if null only one message ($from) is fetched, else it's the
  679. * last message, INF means last message avaible
  680. * @return bool success
  681. * @throws Zend_Mail_Protocol_Exception
  682. */
  683. public function copy($folder, $from, $to = null)
  684. {
  685. $set = (int)$from;
  686. if ($to != null) {
  687. $set .= ':' . ($to == INF ? '*' : (int)$to);
  688. }
  689. return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
  690. }
  691. /**
  692. * create a new folder (and parent folders if needed)
  693. *
  694. * @param string $folder folder name
  695. * @return bool success
  696. */
  697. public function create($folder)
  698. {
  699. return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
  700. }
  701. /**
  702. * rename an existing folder
  703. *
  704. * @param string $old old name
  705. * @param string $new new name
  706. * @return bool success
  707. */
  708. public function rename($old, $new)
  709. {
  710. return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
  711. }
  712. /**
  713. * remove a folder
  714. *
  715. * @param string $folder folder name
  716. * @return bool success
  717. */
  718. public function delete($folder)
  719. {
  720. return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
  721. }
  722. /**
  723. * permanently remove messages
  724. *
  725. * @return bool success
  726. */
  727. public function expunge()
  728. {
  729. // TODO: parse response?
  730. return $this->requestAndResponse('EXPUNGE');
  731. }
  732. /**
  733. * send noop
  734. *
  735. * @return bool success
  736. */
  737. public function noop()
  738. {
  739. // TODO: parse response
  740. return $this->requestAndResponse('NOOP');
  741. }
  742. /**
  743. * do a search request
  744. *
  745. * This method is currently marked as internal as the API might change and is not
  746. * safe if you don't take precautions.
  747. *
  748. * @internal
  749. * @return array message ids
  750. */
  751. public function search(array $params)
  752. {
  753. $response = $this->requestAndResponse('SEARCH', $params);
  754. if (!$response) {
  755. return $response;
  756. }
  757. foreach ($response as $ids) {
  758. if ($ids[0] == 'SEARCH') {
  759. array_shift($ids);
  760. return $ids;
  761. }
  762. }
  763. return array();
  764. }
  765. }