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