00001 <?php
00031 class Zend_Mail_Protocol_Imap
00032 {
00036 const TIMEOUT_CONNECTION = 30;
00037
00042 protected $_socket;
00043
00048 protected $_tagCount = 0;
00049
00058 function __construct($host = '', $port = null, $ssl = false)
00059 {
00060 if ($host) {
00061 $this->connect($host, $port, $ssl);
00062 }
00063 }
00064
00068 public function __destruct()
00069 {
00070 $this->logout();
00071 }
00072
00082 public function connect($host, $port = null, $ssl = false)
00083 {
00084 if ($ssl == 'SSL') {
00085 $host = 'ssl://' . $host;
00086 }
00087
00088 if ($port === null) {
00089 $port = $ssl === 'SSL' ? 993 : 143;
00090 }
00091
00092 $errno = 0;
00093 $errstr = '';
00094 $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
00095 if (!$this->_socket) {
00099 require_once 'Zend/Mail/Protocol/Exception.php';
00100 throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
00101 ' (errno = ' . $errno . ' )');
00102 }
00103
00104 if (!$this->_assumedNextLine('* OK')) {
00108 require_once 'Zend/Mail/Protocol/Exception.php';
00109 throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
00110 }
00111
00112 if ($ssl === 'TLS') {
00113 $result = $this->requestAndResponse('STARTTLS');
00114 $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
00115 if (!$result) {
00119 require_once 'Zend/Mail/Protocol/Exception.php';
00120 throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
00121 }
00122 }
00123 }
00124
00131 protected function _nextLine()
00132 {
00133 $line = @fgets($this->_socket);
00134 if ($line === false) {
00138 require_once 'Zend/Mail/Protocol/Exception.php';
00139 throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
00140 }
00141
00142 return $line;
00143 }
00144
00153 protected function _assumedNextLine($start)
00154 {
00155 $line = $this->_nextLine();
00156 return strpos($line, $start) === 0;
00157 }
00158
00166 protected function _nextTaggedLine(&$tag)
00167 {
00168 $line = $this->_nextLine();
00169
00170
00171 list($tag, $line) = explode(' ', $line, 2);
00172
00173 return $line;
00174 }
00175
00183 protected function _decodeLine($line)
00184 {
00185 $tokens = array();
00186 $stack = array();
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203 $line = rtrim($line) . ' ';
00204 while (($pos = strpos($line, ' ')) !== false) {
00205 $token = substr($line, 0, $pos);
00206 while ($token[0] == '(') {
00207 array_push($stack, $tokens);
00208 $tokens = array();
00209 $token = substr($token, 1);
00210 }
00211 if ($token[0] == '"') {
00212 if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
00213 $tokens[] = $matches[1];
00214 $line = substr($line, strlen($matches[0]));
00215 continue;
00216 }
00217 }
00218 if ($token[0] == '{') {
00219 $endPos = strpos($token, '}');
00220 $chars = substr($token, 1, $endPos - 1);
00221 if (is_numeric($chars)) {
00222 $token = '';
00223 while (strlen($token) < $chars) {
00224 $token .= $this->_nextLine();
00225 }
00226 $line = '';
00227 if (strlen($token) > $chars) {
00228 $line = substr($token, $chars);
00229 $token = substr($token, 0, $chars);
00230 } else {
00231 $line .= $this->_nextLine();
00232 }
00233 $tokens[] = $token;
00234 $line = trim($line) . ' ';
00235 continue;
00236 }
00237 }
00238 if ($stack && $token[strlen($token) - 1] == ')') {
00239
00240 $braces = strlen($token);
00241 $token = rtrim($token, ')');
00242
00243 $braces -= strlen($token) + 1;
00244
00245 if (rtrim($token) != '') {
00246 $tokens[] = rtrim($token);
00247 }
00248 $token = $tokens;
00249 $tokens = array_pop($stack);
00250
00251 while ($braces-- > 0) {
00252 $tokens[] = $token;
00253 $token = $tokens;
00254 $tokens = array_pop($stack);
00255 }
00256 }
00257 $tokens[] = $token;
00258 $line = substr($line, $pos + 1);
00259 }
00260
00261
00262 while ($stack) {
00263 $child = $tokens;
00264 $tokens = array_pop($stack);
00265 $tokens[] = $child;
00266 }
00267
00268 return $tokens;
00269 }
00270
00283 public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
00284 {
00285 $line = $this->_nextTaggedLine($tag);
00286 if (!$dontParse) {
00287 $tokens = $this->_decodeLine($line);
00288 } else {
00289 $tokens = $line;
00290 }
00291
00292
00293 return $tag == $wantedTag;
00294 }
00295
00307 public function readResponse($tag, $dontParse = false)
00308 {
00309 $lines = array();
00310 while (!$this->readLine($tokens, $tag, $dontParse)) {
00311 $lines[] = $tokens;
00312 }
00313
00314 if ($dontParse) {
00315
00316 $tokens = array(substr($tokens, 0, 2));
00317 }
00318
00319 if ($tokens[0] == 'OK') {
00320 return $lines ? $lines : true;
00321 } else if ($tokens[0] == 'NO'){
00322 return false;
00323 }
00324 return null;
00325 }
00326
00336 public function sendRequest($command, $tokens = array(), &$tag = null)
00337 {
00338 if (!$tag) {
00339 ++$this->_tagCount;
00340 $tag = 'TAG' . $this->_tagCount;
00341 }
00342
00343 $line = $tag . ' ' . $command;
00344
00345 foreach ($tokens as $token) {
00346 if (is_array($token)) {
00347 if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
00351 require_once 'Zend/Mail/Protocol/Exception.php';
00352 throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
00353 }
00354 if (!$this->_assumedNextLine('+ ')) {
00358 require_once 'Zend/Mail/Protocol/Exception.php';
00359 throw new Zend_Mail_Protocol_Exception('cannot send literal string');
00360 }
00361 $line = $token[1];
00362 } else {
00363 $line .= ' ' . $token;
00364 }
00365 }
00366
00367 if (@fputs($this->_socket, $line . "\r\n") === false) {
00371 require_once 'Zend/Mail/Protocol/Exception.php';
00372 throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
00373 }
00374 }
00375
00385 public function requestAndResponse($command, $tokens = array(), $dontParse = false)
00386 {
00387 $this->sendRequest($command, $tokens, $tag);
00388 $response = $this->readResponse($tag, $dontParse);
00389
00390 return $response;
00391 }
00392
00400 public function escapeString($string)
00401 {
00402 if (func_num_args() < 2) {
00403 if (strpos($string, "\n") !== false) {
00404 return array('{' . strlen($string) . '}', $string);
00405 } else {
00406 return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
00407 }
00408 }
00409 $result = array();
00410 foreach (func_get_args() as $string) {
00411 $result[] = $this->escapeString($string);
00412 }
00413 return $result;
00414 }
00415
00422 public function escapeList($list)
00423 {
00424 $result = array();
00425 foreach ($list as $k => $v) {
00426 if (!is_array($v)) {
00427
00428 $result[] = $v;
00429 continue;
00430 }
00431 $result[] = $this->escapeList($v);
00432 }
00433 return '(' . implode(' ', $result) . ')';
00434 }
00435
00444 public function login($user, $password)
00445 {
00446 return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
00447 }
00448
00454 public function logout()
00455 {
00456 $result = false;
00457 if ($this->_socket) {
00458 try {
00459 $result = $this->requestAndResponse('LOGOUT', array(), true);
00460 } catch (Zend_Mail_Protocol_Exception $e) {
00461
00462 }
00463 fclose($this->_socket);
00464 $this->_socket = null;
00465 }
00466 return $result;
00467 }
00468
00469
00476 public function capability()
00477 {
00478 $response = $this->requestAndResponse('CAPABILITY');
00479
00480 if (!$response) {
00481 return $response;
00482 }
00483
00484 $capabilities = array();
00485 foreach ($response as $line) {
00486 $capabilities = array_merge($capabilities, $line);
00487 }
00488 return $capabilities;
00489 }
00490
00501 public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
00502 {
00503 $this->sendRequest($command, array($this->escapeString($box)), $tag);
00504
00505 $result = array();
00506 while (!$this->readLine($tokens, $tag)) {
00507 if ($tokens[0] == 'FLAGS') {
00508 array_shift($tokens);
00509 $result['flags'] = $tokens;
00510 continue;
00511 }
00512 switch ($tokens[1]) {
00513 case 'EXISTS':
00514 case 'RECENT':
00515 $result[strtolower($tokens[1])] = $tokens[0];
00516 break;
00517 case '[UIDVALIDITY':
00518 $result['uidvalidity'] = (int)$tokens[2];
00519 break;
00520 default:
00521
00522 }
00523 }
00524
00525 if ($tokens[0] != 'OK') {
00526 return false;
00527 }
00528 return $result;
00529 }
00530
00538 public function select($box = 'INBOX')
00539 {
00540 return $this->examineOrSelect('SELECT', $box);
00541 }
00542
00550 public function examine($box = 'INBOX')
00551 {
00552 return $this->examineOrSelect('EXAMINE', $box);
00553 }
00554
00569 public function fetch($items, $from, $to = null)
00570 {
00571 if (is_array($from)) {
00572 $set = implode(',', $from);
00573 } else if ($to === null) {
00574 $set = (int)$from;
00575 } else if ($to === INF) {
00576 $set = (int)$from . ':*';
00577 } else {
00578 $set = (int)$from . ':' . (int)$to;
00579 }
00580
00581 $items = (array)$items;
00582 $itemList = $this->escapeList($items);
00583
00584 $this->sendRequest('FETCH', array($set, $itemList), $tag);
00585
00586 $result = array();
00587 while (!$this->readLine($tokens, $tag)) {
00588
00589 if ($tokens[1] != 'FETCH') {
00590 continue;
00591 }
00592
00593 if ($to === null && !is_array($from) && $tokens[0] != $from) {
00594 continue;
00595 }
00596
00597 if (count($items) == 1) {
00598 if ($tokens[2][0] == $items[0]) {
00599 $data = $tokens[2][1];
00600 } else {
00601
00602 $count = count($tokens[2]);
00603
00604 for ($i = 2; $i < $count; $i += 2) {
00605 if ($tokens[2][$i] != $items[0]) {
00606 continue;
00607 }
00608 $data = $tokens[2][$i + 1];
00609 break;
00610 }
00611 }
00612 } else {
00613 $data = array();
00614 while (key($tokens[2]) !== null) {
00615 $data[current($tokens[2])] = next($tokens[2]);
00616 next($tokens[2]);
00617 }
00618 }
00619
00620 if ($to === null && !is_array($from) && $tokens[0] == $from) {
00621
00622 while (!$this->readLine($tokens, $tag));
00623 return $data;
00624 }
00625 $result[$tokens[0]] = $data;
00626 }
00627
00628 if ($to === null && !is_array($from)) {
00632 require_once 'Zend/Mail/Protocol/Exception.php';
00633 throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
00634 }
00635
00636 return $result;
00637 }
00638
00649 public function listMailbox($reference = '', $mailbox = '*')
00650 {
00651 $result = array();
00652 $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
00653 if (!$list || $list === true) {
00654 return $result;
00655 }
00656
00657 foreach ($list as $item) {
00658 if (count($item) != 4 || $item[0] != 'LIST') {
00659 continue;
00660 }
00661 $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
00662 }
00663
00664 return $result;
00665 }
00666
00679 public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
00680 {
00681 $item = 'FLAGS';
00682 if ($mode == '+' || $mode == '-') {
00683 $item = $mode . $item;
00684 }
00685 if ($silent) {
00686 $item .= '.SILENT';
00687 }
00688
00689 $flags = $this->escapeList($flags);
00690 $set = (int)$from;
00691 if ($to != null) {
00692 $set .= ':' . ($to == INF ? '*' : (int)$to);
00693 }
00694
00695 $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
00696
00697 if ($silent) {
00698 return $result ? true : false;
00699 }
00700
00701 $tokens = $result;
00702 $result = array();
00703 foreach ($tokens as $token) {
00704 if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
00705 continue;
00706 }
00707 $result[$token[0]] = $token[2][1];
00708 }
00709
00710 return $result;
00711 }
00712
00723 public function append($folder, $message, $flags = null, $date = null)
00724 {
00725 $tokens = array();
00726 $tokens[] = $this->escapeString($folder);
00727 if ($flags !== null) {
00728 $tokens[] = $this->escapeList($flags);
00729 }
00730 if ($date !== null) {
00731 $tokens[] = $this->escapeString($date);
00732 }
00733 $tokens[] = $this->escapeString($message);
00734
00735 return $this->requestAndResponse('APPEND', $tokens, true);
00736 }
00737
00747 public function copy($folder, $from, $to = null)
00748 {
00749 $set = (int)$from;
00750 if ($to != null) {
00751 $set .= ':' . ($to == INF ? '*' : (int)$to);
00752 }
00753
00754 return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
00755 }
00756
00763 public function create($folder)
00764 {
00765 return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
00766 }
00767
00775 public function rename($old, $new)
00776 {
00777 return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
00778 }
00779
00786 public function delete($folder)
00787 {
00788 return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
00789 }
00790
00796 public function expunge()
00797 {
00798
00799 return $this->requestAndResponse('EXPUNGE');
00800 }
00801
00807 public function noop()
00808 {
00809
00810 return $this->requestAndResponse('NOOP');
00811 }
00812
00822 public function search(array $params)
00823 {
00824 $response = $this->requestAndResponse('SEARCH', $params);
00825 if (!$response) {
00826 return $response;
00827 }
00828
00829 foreach ($response as $ids) {
00830 if ($ids[0] == 'SEARCH') {
00831 array_shift($ids);
00832 return $ids;
00833 }
00834 }
00835 return array();
00836 }
00837
00838 }