File indexing completed on 2024-05-26 06:03:11

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Mail
0017  * @subpackage Protocol
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 
0024 /**
0025  * @category   Zend
0026  * @package    Zend_Mail
0027  * @subpackage Protocol
0028  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0029  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0030  */
0031 class Zend_Mail_Protocol_Imap
0032 {
0033     /**
0034      * Default timeout in seconds for initiating session
0035      */
0036     const TIMEOUT_CONNECTION = 30;
0037 
0038     /**
0039      * socket to imap server
0040      * @var resource|null
0041      */
0042     protected $_socket;
0043 
0044     /**
0045      * counter for request tag
0046      * @var int
0047      */
0048     protected $_tagCount = 0;
0049 
0050     /**
0051      * Public constructor
0052      *
0053      * @param  string   $host  hostname or IP address of IMAP server, if given connect() is called
0054      * @param  int|null $port  port of IMAP server, null for default (143 or 993 for ssl)
0055      * @param  bool     $ssl   use ssl? 'SSL', 'TLS' or false
0056      * @throws Zend_Mail_Protocol_Exception
0057      */
0058     function __construct($host = '', $port = null, $ssl = false)
0059     {
0060         if ($host) {
0061             $this->connect($host, $port, $ssl);
0062         }
0063     }
0064 
0065     /**
0066      * Public destructor
0067      */
0068     public function __destruct()
0069     {
0070         $this->logout();
0071     }
0072 
0073     /**
0074      * Open connection to IMAP server
0075      *
0076      * @param  string      $host  hostname or IP address of IMAP server
0077      * @param  int|null    $port  of IMAP server, default is 143 (993 for ssl)
0078      * @param  string|bool $ssl   use 'SSL', 'TLS' or false
0079      * @return string welcome message
0080      * @throws Zend_Mail_Protocol_Exception
0081      */
0082     public function connect($host, $port = null, $ssl = false)
0083     {
0084         if ($ssl == 'SSL') {
0085             $host = 'ssl://' . $host;
0086         }
0087 
0088         if ($port === null) {
0089             $port = $ssl === 'SSL' ? 993 : 143;
0090         }
0091 
0092         $errno  =  0;
0093         $errstr = '';
0094         $this->_socket = @fsockopen($host, $port, $errno, $errstr, self::TIMEOUT_CONNECTION);
0095         if (!$this->_socket) {
0096             /**
0097              * @see Zend_Mail_Protocol_Exception
0098              */
0099             // require_once 'Zend/Mail/Protocol/Exception.php';
0100             throw new Zend_Mail_Protocol_Exception('cannot connect to host; error = ' . $errstr .
0101                                                    ' (errno = ' . $errno . ' )');
0102         }
0103 
0104         if (!$this->_assumedNextLine('* OK')) {
0105             /**
0106              * @see Zend_Mail_Protocol_Exception
0107              */
0108             // require_once 'Zend/Mail/Protocol/Exception.php';
0109             throw new Zend_Mail_Protocol_Exception('host doesn\'t allow connection');
0110         }
0111 
0112         if ($ssl === 'TLS') {
0113             $result = $this->requestAndResponse('STARTTLS');
0114             $result = $result && stream_socket_enable_crypto($this->_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
0115             if (!$result) {
0116                 /**
0117                  * @see Zend_Mail_Protocol_Exception
0118                  */
0119                 // require_once 'Zend/Mail/Protocol/Exception.php';
0120                 throw new Zend_Mail_Protocol_Exception('cannot enable TLS');
0121             }
0122         }
0123     }
0124 
0125     /**
0126      * get the next line from socket with error checking, but nothing else
0127      *
0128      * @return string next line
0129      * @throws Zend_Mail_Protocol_Exception
0130      */
0131     protected function _nextLine()
0132     {
0133         $line = @fgets($this->_socket);
0134         if ($line === false) {
0135             /**
0136              * @see Zend_Mail_Protocol_Exception
0137              */
0138             // require_once 'Zend/Mail/Protocol/Exception.php';
0139             throw new Zend_Mail_Protocol_Exception('cannot read - connection closed?');
0140         }
0141 
0142         return $line;
0143     }
0144 
0145     /**
0146      * get next line and assume it starts with $start. some requests give a simple
0147      * feedback so we can quickly check if we can go on.
0148      *
0149      * @param  string $start the first bytes we assume to be in the next line
0150      * @return bool line starts with $start
0151      * @throws Zend_Mail_Protocol_Exception
0152      */
0153     protected function _assumedNextLine($start)
0154     {
0155         $line = $this->_nextLine();
0156         return strpos($line, $start) === 0;
0157     }
0158 
0159     /**
0160      * get next line and split the tag. that's the normal case for a response line
0161      *
0162      * @param  string $tag tag of line is returned by reference
0163      * @return string next line
0164      * @throws Zend_Mail_Protocol_Exception
0165      */
0166     protected function _nextTaggedLine(&$tag)
0167     {
0168         $line = $this->_nextLine();
0169 
0170         // seperate tag from line
0171         list($tag, $line) = explode(' ', $line, 2);
0172 
0173         return $line;
0174     }
0175 
0176     /**
0177      * split a given line in tokens. a token is literal of any form or a list
0178      *
0179      * @param  string $line line to decode
0180      * @return array tokens, literals are returned as string, lists as array
0181      * @throws Zend_Mail_Protocol_Exception
0182      */
0183     protected function _decodeLine($line)
0184     {
0185         $tokens = array();
0186         $stack = array();
0187 
0188         /*
0189             We start to decode the response here. The unterstood tokens are:
0190                 literal
0191                 "literal" or also "lit\\er\"al"
0192                 {bytes}<NL>literal
0193                 (literals*)
0194             All tokens are returned in an array. Literals in braces (the last unterstood
0195             token in the list) are returned as an array of tokens. I.e. the following response:
0196                 "foo" baz {3}<NL>bar ("f\\\"oo" bar)
0197             would be returned as:
0198                 array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
0199 
0200             // TODO: add handling of '[' and ']' to parser for easier handling of response text
0201         */
0202         //  replace any trailling <NL> including spaces with a single space
0203         $line = rtrim($line) . ' ';
0204         while (($pos = strpos($line, ' ')) !== false) {
0205             $token = substr($line, 0, $pos);
0206             while ($token[0] == '(') {
0207                 array_push($stack, $tokens);
0208                 $tokens = array();
0209                 $token = substr($token, 1);
0210             }
0211             if ($token[0] == '"') {
0212                 if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
0213                     $tokens[] = $matches[1];
0214                     $line = substr($line, strlen($matches[0]));
0215                     continue;
0216                 }
0217             }
0218             if ($token[0] == '{') {
0219                 $endPos = strpos($token, '}');
0220                 $chars = substr($token, 1, $endPos - 1);
0221                 if (is_numeric($chars)) {
0222                     $token = '';
0223                     while (strlen($token) < $chars) {
0224                         $token .= $this->_nextLine();
0225                     }
0226                     $line = '';
0227                     if (strlen($token) > $chars) {
0228                         $line = substr($token, $chars);
0229                         $token = substr($token, 0, $chars);
0230                     } else {
0231                         $line .= $this->_nextLine();
0232                     }
0233                     $tokens[] = $token;
0234                     $line = trim($line) . ' ';
0235                     continue;
0236                 }
0237             }
0238             if ($stack && $token[strlen($token) - 1] == ')') {
0239                 // closing braces are not seperated by spaces, so we need to count them
0240                 $braces = strlen($token);
0241                 $token = rtrim($token, ')');
0242                 // only count braces if more than one
0243                 $braces -= strlen($token) + 1;
0244                 // only add if token had more than just closing braces
0245                 if (rtrim($token) != '') {
0246                     $tokens[] = rtrim($token);
0247                 }
0248                 $token = $tokens;
0249                 $tokens = array_pop($stack);
0250                 // special handline if more than one closing brace
0251                 while ($braces-- > 0) {
0252                     $tokens[] = $token;
0253                     $token = $tokens;
0254                     $tokens = array_pop($stack);
0255                 }
0256             }
0257             $tokens[] = $token;
0258             $line = substr($line, $pos + 1);
0259         }
0260 
0261         // maybe the server forgot to send some closing braces
0262         while ($stack) {
0263             $child = $tokens;
0264             $tokens = array_pop($stack);
0265             $tokens[] = $child;
0266         }
0267 
0268         return $tokens;
0269     }
0270 
0271     /**
0272      * read a response "line" (could also be more than one real line if response has {..}<NL>)
0273      * and do a simple decode
0274      *
0275      * @param  array|string  $tokens    decoded tokens are returned by reference, if $dontParse
0276      *                                  is true the unparsed line is returned here
0277      * @param  string        $wantedTag check for this tag for response code. Default '*' is
0278      *                                  continuation tag.
0279      * @param  bool          $dontParse if true only the unparsed line is returned $tokens
0280      * @return bool if returned tag matches wanted tag
0281      * @throws Zend_Mail_Protocol_Exception
0282      */
0283     public function readLine(&$tokens = array(), $wantedTag = '*', $dontParse = false)
0284     {
0285         $line = $this->_nextTaggedLine($tag);
0286         if (!$dontParse) {
0287             $tokens = $this->_decodeLine($line);
0288         } else {
0289             $tokens = $line;
0290         }
0291 
0292         // if tag is wanted tag we might be at the end of a multiline response
0293         return $tag == $wantedTag;
0294     }
0295 
0296     /**
0297      * read all lines of response until given tag is found (last line of response)
0298      *
0299      * @param  string       $tag       the tag of your request
0300      * @param  string|array $filter    you can filter the response so you get only the
0301      *                                 given response lines
0302      * @param  bool         $dontParse if true every line is returned unparsed instead of
0303      *                                 the decoded tokens
0304      * @return null|bool|array tokens if success, false if error, null if bad request
0305      * @throws Zend_Mail_Protocol_Exception
0306      */
0307     public function readResponse($tag, $dontParse = false)
0308     {
0309         $lines = array();
0310         while (!$this->readLine($tokens, $tag, $dontParse)) {
0311             $lines[] = $tokens;
0312         }
0313 
0314         if ($dontParse) {
0315             // last to chars are still needed for response code
0316             $tokens = array(substr($tokens, 0, 2));
0317         }
0318         // last line has response code
0319         if ($tokens[0] == 'OK') {
0320             return $lines ? $lines : true;
0321         } else if ($tokens[0] == 'NO'){
0322             return false;
0323         }
0324         return null;
0325     }
0326 
0327     /**
0328      * send a request
0329      *
0330      * @param  string $command your request command
0331      * @param  array  $tokens  additional parameters to command, use escapeString() to prepare
0332      * @param  string $tag     provide a tag otherwise an autogenerated is returned
0333      * @return null
0334      * @throws Zend_Mail_Protocol_Exception
0335      */
0336     public function sendRequest($command, $tokens = array(), &$tag = null)
0337     {
0338         if (!$tag) {
0339             ++$this->_tagCount;
0340             $tag = 'TAG' . $this->_tagCount;
0341         }
0342 
0343         $line = $tag . ' ' . $command;
0344 
0345         foreach ($tokens as $token) {
0346             if (is_array($token)) {
0347                 if (@fputs($this->_socket, $line . ' ' . $token[0] . "\r\n") === false) {
0348                     /**
0349                      * @see Zend_Mail_Protocol_Exception
0350                      */
0351                     // require_once 'Zend/Mail/Protocol/Exception.php';
0352                     throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
0353                 }
0354                 if (!$this->_assumedNextLine('+ ')) {
0355                     /**
0356                      * @see Zend_Mail_Protocol_Exception
0357                      */
0358                     // require_once 'Zend/Mail/Protocol/Exception.php';
0359                     throw new Zend_Mail_Protocol_Exception('cannot send literal string');
0360                 }
0361                 $line = $token[1];
0362             } else {
0363                 $line .= ' ' . $token;
0364             }
0365         }
0366 
0367         if (@fputs($this->_socket, $line . "\r\n") === false) {
0368             /**
0369              * @see Zend_Mail_Protocol_Exception
0370              */
0371             // require_once 'Zend/Mail/Protocol/Exception.php';
0372             throw new Zend_Mail_Protocol_Exception('cannot write - connection closed?');
0373         }
0374     }
0375 
0376     /**
0377      * send a request and get response at once
0378      *
0379      * @param  string $command   command as in sendRequest()
0380      * @param  array  $tokens    parameters as in sendRequest()
0381      * @param  bool   $dontParse if true unparsed lines are returned instead of tokens
0382      * @return mixed response as in readResponse()
0383      * @throws Zend_Mail_Protocol_Exception
0384      */
0385     public function requestAndResponse($command, $tokens = array(), $dontParse = false)
0386     {
0387         $this->sendRequest($command, $tokens, $tag);
0388         $response = $this->readResponse($tag, $dontParse);
0389 
0390         return $response;
0391     }
0392 
0393     /**
0394      * escape one or more literals i.e. for sendRequest
0395      *
0396      * @param  string|array $string the literal/-s
0397      * @return string|array escape literals, literals with newline ar returned
0398      *                      as array('{size}', 'string');
0399      */
0400     public function escapeString($string)
0401     {
0402         if (func_num_args() < 2) {
0403             if (strpos($string, "\n") !== false) {
0404                 return array('{' . strlen($string) . '}', $string);
0405             } else {
0406                 return '"' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $string) . '"';
0407             }
0408         }
0409         $result = array();
0410         foreach (func_get_args() as $string) {
0411             $result[] = $this->escapeString($string);
0412         }
0413         return $result;
0414     }
0415 
0416     /**
0417      * escape a list with literals or lists
0418      *
0419      * @param  array $list list with literals or lists as PHP array
0420      * @return string escaped list for imap
0421      */
0422     public function escapeList($list)
0423     {
0424         $result = array();
0425         foreach ($list as $k => $v) {
0426             if (!is_array($v)) {
0427 //              $result[] = $this->escapeString($v);
0428                 $result[] = $v;
0429                 continue;
0430             }
0431             $result[] = $this->escapeList($v);
0432         }
0433         return '(' . implode(' ', $result) . ')';
0434     }
0435 
0436     /**
0437      * Login to IMAP server.
0438      *
0439      * @param  string $user      username
0440      * @param  string $password  password
0441      * @return bool success
0442      * @throws Zend_Mail_Protocol_Exception
0443      */
0444     public function login($user, $password)
0445     {
0446         return $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
0447     }
0448 
0449     /**
0450      * logout of imap server
0451      *
0452      * @return bool success
0453      */
0454     public function logout()
0455     {
0456         $result = false;
0457         if ($this->_socket) {
0458             try {
0459                 $result = $this->requestAndResponse('LOGOUT', array(), true);
0460             } catch (Zend_Mail_Protocol_Exception $e) {
0461                 // ignoring exception
0462             }
0463             fclose($this->_socket);
0464             $this->_socket = null;
0465         }
0466         return $result;
0467     }
0468 
0469 
0470     /**
0471      * Get capabilities from IMAP server
0472      *
0473      * @return array list of capabilities
0474      * @throws Zend_Mail_Protocol_Exception
0475      */
0476     public function capability()
0477     {
0478         $response = $this->requestAndResponse('CAPABILITY');
0479 
0480         if (!$response) {
0481             return $response;
0482         }
0483 
0484         $capabilities = array();
0485         foreach ($response as $line) {
0486             $capabilities = array_merge($capabilities, $line);
0487         }
0488         return $capabilities;
0489     }
0490 
0491     /**
0492      * Examine and select have the same response. The common code for both
0493      * is in this method
0494      *
0495      * @param  string $command can be 'EXAMINE' or 'SELECT' and this is used as command
0496      * @param  string $box which folder to change to or examine
0497      * @return bool|array false if error, array with returned information
0498      *                    otherwise (flags, exists, recent, uidvalidity)
0499      * @throws Zend_Mail_Protocol_Exception
0500      */
0501     public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
0502     {
0503         $this->sendRequest($command, array($this->escapeString($box)), $tag);
0504 
0505         $result = array();
0506         while (!$this->readLine($tokens, $tag)) {
0507             if ($tokens[0] == 'FLAGS') {
0508                 array_shift($tokens);
0509                 $result['flags'] = $tokens;
0510                 continue;
0511             }
0512             switch ($tokens[1]) {
0513                 case 'EXISTS':
0514                 case 'RECENT':
0515                     $result[strtolower($tokens[1])] = $tokens[0];
0516                     break;
0517                 case '[UIDVALIDITY':
0518                     $result['uidvalidity'] = (int)$tokens[2];
0519                     break;
0520                 default:
0521                     // ignore
0522             }
0523         }
0524 
0525         if ($tokens[0] != 'OK') {
0526             return false;
0527         }
0528         return $result;
0529     }
0530 
0531     /**
0532      * change folder
0533      *
0534      * @param  string $box change to this folder
0535      * @return bool|array see examineOrselect()
0536      * @throws Zend_Mail_Protocol_Exception
0537      */
0538     public function select($box = 'INBOX')
0539     {
0540         return $this->examineOrSelect('SELECT', $box);
0541     }
0542 
0543     /**
0544      * examine folder
0545      *
0546      * @param  string $box examine this folder
0547      * @return bool|array see examineOrselect()
0548      * @throws Zend_Mail_Protocol_Exception
0549      */
0550     public function examine($box = 'INBOX')
0551     {
0552         return $this->examineOrSelect('EXAMINE', $box);
0553     }
0554 
0555     /**
0556      * fetch one or more items of one or more messages
0557      *
0558      * @param  string|array $items items to fetch from message(s) as string (if only one item)
0559      *                             or array of strings
0560      * @param  int          $from  message for items or start message if $to !== null
0561      * @param  int|null     $to    if null only one message ($from) is fetched, else it's the
0562      *                             last message, INF means last message avaible
0563      * @return string|array if only one item of one message is fetched it's returned as string
0564      *                      if items of one message are fetched it's returned as (name => value)
0565      *                      if one items of messages are fetched it's returned as (msgno => value)
0566      *                      if items of messages are fetchted it's returned as (msgno => (name => value))
0567      * @throws Zend_Mail_Protocol_Exception
0568      */
0569     public function fetch($items, $from, $to = null)
0570     {
0571         if (is_array($from)) {
0572             $set = implode(',', $from);
0573         } else if ($to === null) {
0574             $set = (int)$from;
0575         } else if ($to === INF) {
0576             $set = (int)$from . ':*';
0577         } else {
0578             $set = (int)$from . ':' . (int)$to;
0579         }
0580 
0581         $items = (array)$items;
0582         $itemList = $this->escapeList($items);
0583 
0584         $this->sendRequest('FETCH', array($set, $itemList), $tag);
0585 
0586         $result = array();
0587         while (!$this->readLine($tokens, $tag)) {
0588             // ignore other responses
0589             if ($tokens[1] != 'FETCH') {
0590                 continue;
0591             }
0592             // ignore other messages
0593             if ($to === null && !is_array($from) && $tokens[0] != $from) {
0594                 continue;
0595             }
0596             // if we only want one item we return that one directly
0597             if (count($items) == 1) {
0598                 if ($tokens[2][0] == $items[0]) {
0599                     $data = $tokens[2][1];
0600                 } else {
0601                     // maybe the server send an other field we didn't wanted
0602                     $count = count($tokens[2]);
0603                     // we start with 2, because 0 was already checked
0604                     for ($i = 2; $i < $count; $i += 2) {
0605                         if ($tokens[2][$i] != $items[0]) {
0606                             continue;
0607                         }
0608                         $data = $tokens[2][$i + 1];
0609                         break;
0610                     }
0611                 }
0612             } else {
0613                 $data = array();
0614                 while (key($tokens[2]) !== null) {
0615                     $data[current($tokens[2])] = next($tokens[2]);
0616                     next($tokens[2]);
0617                 }
0618             }
0619             // if we want only one message we can ignore everything else and just return
0620             if ($to === null && !is_array($from) && $tokens[0] == $from) {
0621                 // we still need to read all lines
0622                 while (!$this->readLine($tokens, $tag));
0623                 return $data;
0624             }
0625             $result[$tokens[0]] = $data;
0626         }
0627 
0628         if ($to === null && !is_array($from)) {
0629             /**
0630              * @see Zend_Mail_Protocol_Exception
0631              */
0632             // require_once 'Zend/Mail/Protocol/Exception.php';
0633             throw new Zend_Mail_Protocol_Exception('the single id was not found in response');
0634         }
0635 
0636         return $result;
0637     }
0638 
0639     /**
0640      * get mailbox list
0641      *
0642      * this method can't be named after the IMAP command 'LIST', as list is a reserved keyword
0643      *
0644      * @param  string $reference mailbox reference for list
0645      * @param  string $mailbox   mailbox name match with wildcards
0646      * @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
0647      * @throws Zend_Mail_Protocol_Exception
0648      */
0649     public function listMailbox($reference = '', $mailbox = '*')
0650     {
0651         $result = array();
0652         $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
0653         if (!$list || $list === true) {
0654             return $result;
0655         }
0656 
0657         foreach ($list as $item) {
0658             if (count($item) != 4 || $item[0] != 'LIST') {
0659                 continue;
0660             }
0661             $result[$item[3]] = array('delim' => $item[2], 'flags' => $item[1]);
0662         }
0663 
0664         return $result;
0665     }
0666 
0667     /**
0668      * set flags
0669      *
0670      * @param  array       $flags  flags to set, add or remove - see $mode
0671      * @param  int         $from   message for items or start message if $to !== null
0672      * @param  int|null    $to     if null only one message ($from) is fetched, else it's the
0673      *                             last message, INF means last message avaible
0674      * @param  string|null $mode   '+' to add flags, '-' to remove flags, everything else sets the flags as given
0675      * @param  bool        $silent if false the return values are the new flags for the wanted messages
0676      * @return bool|array new flags if $silent is false, else true or false depending on success
0677      * @throws Zend_Mail_Protocol_Exception
0678      */
0679     public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
0680     {
0681         $item = 'FLAGS';
0682         if ($mode == '+' || $mode == '-') {
0683             $item = $mode . $item;
0684         }
0685         if ($silent) {
0686             $item .= '.SILENT';
0687         }
0688 
0689         $flags = $this->escapeList($flags);
0690         $set = (int)$from;
0691         if ($to != null) {
0692             $set .= ':' . ($to == INF ? '*' : (int)$to);
0693         }
0694 
0695         $result = $this->requestAndResponse('STORE', array($set, $item, $flags), $silent);
0696 
0697         if ($silent) {
0698             return $result ? true : false;
0699         }
0700 
0701         $tokens = $result;
0702         $result = array();
0703         foreach ($tokens as $token) {
0704             if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
0705                 continue;
0706             }
0707             $result[$token[0]] = $token[2][1];
0708         }
0709 
0710         return $result;
0711     }
0712 
0713     /**
0714      * append a new message to given folder
0715      *
0716      * @param string $folder  name of target folder
0717      * @param string $message full message content
0718      * @param array  $flags   flags for new message
0719      * @param string $date    date for new message
0720      * @return bool success
0721      * @throws Zend_Mail_Protocol_Exception
0722      */
0723     public function append($folder, $message, $flags = null, $date = null)
0724     {
0725         $tokens = array();
0726         $tokens[] = $this->escapeString($folder);
0727         if ($flags !== null) {
0728             $tokens[] = $this->escapeList($flags);
0729         }
0730         if ($date !== null) {
0731             $tokens[] = $this->escapeString($date);
0732         }
0733         $tokens[] = $this->escapeString($message);
0734 
0735         return $this->requestAndResponse('APPEND', $tokens, true);
0736     }
0737 
0738     /**
0739      * copy message set from current folder to other folder
0740      *
0741      * @param string   $folder destination folder
0742      * @param int|null $to     if null only one message ($from) is fetched, else it's the
0743      *                         last message, INF means last message avaible
0744      * @return bool success
0745      * @throws Zend_Mail_Protocol_Exception
0746      */
0747     public function copy($folder, $from, $to = null)
0748     {
0749         $set = (int)$from;
0750         if ($to != null) {
0751             $set .= ':' . ($to == INF ? '*' : (int)$to);
0752         }
0753 
0754         return $this->requestAndResponse('COPY', array($set, $this->escapeString($folder)), true);
0755     }
0756 
0757     /**
0758      * create a new folder (and parent folders if needed)
0759      *
0760      * @param string $folder folder name
0761      * @return bool success
0762      */
0763     public function create($folder)
0764     {
0765         return $this->requestAndResponse('CREATE', array($this->escapeString($folder)), true);
0766     }
0767 
0768     /**
0769      * rename an existing folder
0770      *
0771      * @param string $old old name
0772      * @param string $new new name
0773      * @return bool success
0774      */
0775     public function rename($old, $new)
0776     {
0777         return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
0778     }
0779 
0780     /**
0781      * remove a folder
0782      *
0783      * @param string $folder folder name
0784      * @return bool success
0785      */
0786     public function delete($folder)
0787     {
0788         return $this->requestAndResponse('DELETE', array($this->escapeString($folder)), true);
0789     }
0790 
0791     /**
0792      * permanently remove messages
0793      *
0794      * @return bool success
0795      */
0796     public function expunge()
0797     {
0798         // TODO: parse response?
0799         return $this->requestAndResponse('EXPUNGE');
0800     }
0801 
0802     /**
0803      * send noop
0804      *
0805      * @return bool success
0806      */
0807     public function noop()
0808     {
0809         // TODO: parse response
0810         return $this->requestAndResponse('NOOP');
0811     }
0812 
0813     /**
0814      * do a search request
0815      *
0816      * This method is currently marked as internal as the API might change and is not
0817      * safe if you don't take precautions.
0818      *
0819      * @internal
0820      * @return array message ids
0821      */
0822     public function search(array $params)
0823     {
0824         $response = $this->requestAndResponse('SEARCH', $params);
0825         if (!$response) {
0826             return $response;
0827         }
0828 
0829         foreach ($response as $ids) {
0830             if ($ids[0] == 'SEARCH') {
0831                 array_shift($ids);
0832                 return $ids;
0833             }
0834         }
0835         return array();
0836     }
0837 
0838 }