1<?php
2/**
3 * Net_IMAP provides an implementation of the IMAP protocol
4 *
5 * PHP Version 4
6 *
7 * @category  Networking
8 * @package   Net_IMAP
9 * @author    Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
10 * @copyright 1997-2003 The PHP Group
11 * @license   PHP license
12 * @version   CVS: $Id$
13 * @link      http://pear.php.net/package/Net_IMAP
14 */
15
16/**
17 * Net_IMAP requires Net_Socket
18 */
19
20// ERP-modification: Need to load included net_socket since it has some required fixes
21// require_once 'Net/Socket.php';
22plugin_require_api( 'core_pear/Net/Socket.php' );
23
24
25/**
26 * Provides an implementation of the IMAP protocol using PEAR's
27 * Net_Socket:: class.
28 *
29 * @category Networking
30 * @package  Net_IMAP
31 * @author   Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar>
32 * @license  PHP license
33 * @link     http://pear.php.net/package/Net_IMAP
34 */
35class Net_IMAPProtocol
36{
37    /**
38     * The auth methods this class support
39     * @var array
40     */
41    var $supportedAuthMethods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN');
42
43
44    /**
45     * The auth methods this class support
46     * @var array
47     */
48    var $supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5');
49
50
51    /**
52     * _serverAuthMethods
53     * @var boolean
54     * @access private
55     */
56    var $_serverAuthMethods = null;
57
58
59    /**
60     * The the current mailbox
61     * @var string
62     */
63    var $currentMailbox = 'INBOX';
64
65
66    /**
67     * The socket resource being used to connect to the IMAP server.
68     * @var resource
69     * @access private
70     */
71    var $_socket = null;
72
73
74    /**
75     * The timeout for the connection to the IMAP server.
76     * @var int
77     * @access private
78     */
79    var $_timeout = null;
80
81
82    /**
83     * The options for SSL/TLS connection
84     * (see documentation for stream_context_create)
85     * @var array
86     * @access private
87     */
88    var $_streamContextOptions = null;
89
90
91    /**
92     * To allow class debuging
93     * @var boolean
94     * @access private
95     */
96    var $_debug    = false;
97    var $dbgDialog = '';
98
99
100    /**
101     * Print error messages
102     * @var boolean
103     * @access private
104     */
105    var $_printErrors = false;
106
107
108    /**
109     * Command Number
110     * @var int
111     * @access private
112     */
113    var $_cmd_counter = 1;
114
115
116    /**
117     * Command Number for IMAP commands
118     * @var int
119     * @access private
120     */
121    var $_lastCmdID = 1;
122
123
124    /**
125     * Command Number
126     * @var boolean
127     * @access private
128     */
129    var $_unParsedReturn = false;
130
131
132    /**
133     * _connected: checks if there is a connection made to a imap server or not
134     * @var boolean
135     * @access private
136     */
137    var $_connected = false;
138
139
140    /**
141     * Capabilities
142     * @var boolean
143     * @access private
144     */
145    var $_serverSupportedCapabilities = null;
146
147
148    /**
149     * Use UTF-7 funcionallity
150     * @var boolean
151     * @access private
152     */
153    var $_useUTF_7 = true;
154
155
156    /**
157     * Character encoding
158     * @var string
159     * @access private
160     */
161    var $_encoding = 'ISO-8859-1';
162
163
164    /**
165     * Constructor
166     *
167     * Instantiates a new Net_IMAP object.
168     *
169     * @since  1.0
170     */
171    function __construct()
172    {
173        $this->_socket = new Net_Socket();
174
175        /*
176         * Include the Auth_SASL package.  If the package is not available,
177         * we disable the authentication methods that depend upon it.
178         */
179		// ERP-modification: Force load included PEAR packages
180//        if ((@include_once 'Auth/SASL.php') == false)
181		plugin_require_api( 'core_pear/Auth/SASL.php' );
182        if (!class_exists('Auth_SASL')) {
183            foreach ($this->supportedSASLAuthMethods as $SASLMethod) {
184                $pos = array_search($SASLMethod, $this->supportedAuthMethods);
185                unset($this->supportedAuthMethods[$pos]);
186            }
187        }
188    }
189
190
191
192    /**
193     * Attempt to connect to the IMAP server.
194     *
195     * @param string $host Hostname of the IMAP server
196     * @param int    $port Port of the IMAP server (default = 143)
197     *
198     * @return mixed Returns a PEAR_Error with an error message on any
199     *               kind of failure, or true on success.
200     * @access public
201     * @since  1.0
202     */
203    function cmdConnect($host = 'localhost', $port = 143)
204    {
205        if ($this->_connected) {
206            return new PEAR_Error('already connected, logout first!');
207        }
208        $error = $this->_socket->connect($host,
209                                        $port,
210                                        null,
211                                        $this->_timeout,
212                                        $this->_streamContextOptions);
213        if ($error instanceOf PEAR_Error) {
214            return $error;
215        }
216/*
217// ERP-modification: Creates more problems then it fixes so we disable it
218        if ($port == 993) {
219            if (!$this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
220                return new PEAR_Error('Failed to set crypto');
221            }
222        }
223*/
224
225        if ($this->_getRawResponse() instanceOf PEAR_Error) {
226            return new PEAR_Error('unable to open socket');
227        }
228        $this->_connected = true;
229        return true;
230    }
231
232
233
234    /**
235     * get the cmd ID
236     *
237     * @return string Returns the CmdID and increment the counter
238     *
239     * @access private
240     * @since  1.0
241     */
242    function _getCmdId()
243    {
244        $this->_lastCmdID = 'A000' . $this->_cmd_counter;
245        $this->_cmd_counter++;
246        return $this->_lastCmdID;
247    }
248
249
250
251    /**
252     * get the last cmd ID
253     *
254     * @return string Returns the last cmdId
255     *
256     * @access public
257     * @since  1.0
258     */
259    function getLastCmdId()
260    {
261        return $this->_lastCmdID;
262    }
263
264
265
266    /**
267     * get current mailbox name
268     *
269     * @return string Returns the current mailbox
270     *
271     * @access public
272     * @since  1.0
273     */
274    function getCurrentMailbox()
275    {
276        return $this->currentMailbox;
277    }
278
279
280
281    /**
282     * Sets the debuging information on or off
283     *
284     * @param boolean $debug Turn debug on (true) or off (false)
285     *
286     * @return nothing
287     * @access public
288     * @since  1.0
289     */
290    function setDebug($debug = true)
291    {
292        $this->_debug = $debug;
293    }
294
295
296
297    /**
298     * get the debug dialog
299     *
300     * @return string debug dialog
301     * @access public
302     */
303    function getDebugDialog()
304    {
305        return $this->dbgDialog;
306    }
307
308
309
310    /**
311     * Sets printed output of errors on or of
312     *
313     * @param boolean $printErrors true to turn on,
314     *                             false to turn off printed output
315     *
316     * @return nothing
317     * @access public
318     * @since 1.1
319     */
320    function setPrintErrors($printErrors = true)
321    {
322        $this->_printErrors = $printErrors;
323    }
324
325
326
327    /**
328     * Send the given string of data to the server.
329     *
330     * @param string $data The string of data to send.
331     *
332     * @return mixed True on success or a PEAR_Error object on failure.
333     *
334     * @access private
335     * @since 1.0
336     */
337    function _send($data)
338    {
339        if ($this->_socket->eof()) {
340            return new PEAR_Error('Failed to write to socket: (connection lost!)');
341        }
342        $error = $this->_socket->write($data);
343        if ($error instanceOf PEAR_Error) {
344            return new PEAR_Error('Failed to write to socket: '
345                                  . $error->getMessage());
346        }
347
348        if ($this->_debug) {
349            // C: means this data was sent by  the client (this class)
350            echo 'C: ' . $data;
351            $this->dbgDialog .= 'C: ' . $data;
352        }
353        return true;
354    }
355
356
357
358    /**
359     * Receive the given string of data from the server.
360     *
361     * @return mixed a line of response on success or a PEAR_Error object on failure.
362     *
363     * @access private
364     * @since 1.0
365     */
366    function _recvLn()
367    {
368        $this->lastline = $this->_socket->gets(8192);
369        if ($this->lastline instanceOf PEAR_Error) {
370            return new PEAR_Error('Failed to write to socket: '
371                                  . $this->lastline->getMessage());
372        }
373        if ($this->_debug) {
374            // S: means this data was sent by  the IMAP Server
375            echo 'S: ' . $this->lastline;
376            $this->dbgDialog .= 'S: ' . $this->lastline;
377        }
378        if ($this->lastline == '') {
379            return new PEAR_Error('Failed to receive from the  socket: ');
380        }
381        return $this->lastline;
382    }
383
384
385
386    /**
387     * Send a command to the server with an optional string of arguments.
388     * A carriage return / linefeed (CRLF) sequence will be appended to each
389     * command string before it is sent to the IMAP server.
390     *
391     * @param string $commandId The IMAP cmdID to send to the server.
392     * @param string $command   The IMAP command to send to the server.
393     * @param string $args      A string of optional arguments to append
394     *                          to the command.
395     *
396     * @return mixed The result of the _send() call.
397     *
398     * @access private
399     * @since 1.0
400     */
401    function _putCMD($commandId , $command, $args = '')
402    {
403        if (!empty($args)) {
404            return $this->_send($commandId
405                                . ' '
406                                . $command
407                                . ' '
408                                . $args
409                                . "\r\n");
410        }
411        return $this->_send($commandId . ' ' . $command . "\r\n");
412    }
413
414
415
416    /**
417     * Get a response from the server with an optional string of commandID.
418     * A carriage return / linefeed (CRLF) sequence will be appended to each
419     * command string before it is sent to the IMAP server.
420     *
421     * @param string $commandId The IMAP commandid retrive from the server.
422     *
423     * @return string The result response.
424     * @access private
425     */
426    function _getRawResponse($commandId = '*')
427    {
428        $arguments = '';
429        while (!$this->_recvLn() instanceOf PEAR_Error) {
430            $reply_code = strtok($this->lastline, ' ');
431            $arguments .= $this->lastline;
432            if (!(strcmp($commandId, $reply_code))) {
433                return $arguments;
434            }
435        }
436        return $arguments;
437    }
438
439
440
441     /**
442     * get the "returning of the unparsed response" feature status
443     *
444     * @return boolean return if the unparsed response is returned or not
445     *
446     * @access public
447     * @since  1.0
448     *
449     */
450    function getUnparsedResponse()
451    {
452        return $this->_unParsedReturn;
453    }
454
455
456
457    /**
458     * set the options for a SSL/TLS connection
459     * (see documentation for stream_context_create)
460     *
461     * @param array $options The options for the SSL/TLS connection
462     *
463     * @return nothing
464     * @access public
465     * @since  1.1
466     */
467    function setStreamContextOptions($options)
468    {
469        $this->_streamContextOptions = $options;
470    }
471
472
473
474    /**
475     * set the the timeout for the connection to the IMAP server.
476     *
477     * @param int $timeout The timeout
478     *
479     * @return nothing
480     * @access public
481     * @since  1.1
482     */
483    function setTimeout($timeout)
484    {
485        $this->_timeout = $timeout;
486    }
487
488
489
490    /**
491     * set the "returning of the unparsed response" feature on or off
492     *
493     * @param boolean $status true: feature is on
494     *
495     * @return nothing
496     * @access public
497     * @since  1.0
498     */
499    function setUnparsedResponse($status)
500    {
501        $this->_unParsedReturn = $status;
502    }
503
504
505
506    /**
507     * Attempt to login to the iMAP server.
508     *
509     * @param string $uid The userid to authenticate as.
510     * @param string $pwd The password to authenticate with.
511     *
512     * @return array Returns an array containing the response
513     * @access public
514     * @since  1.0
515     */
516    function cmdLogin($uid, $pwd)
517    {
518        $param = self::escape($uid) . ' ' . self::escape($pwd);
519        return $this->_genericCommand('LOGIN', $param);
520    }
521
522
523
524    /**
525     * Attempt to authenticate to the iMAP server.
526     *
527     * @param string $uid        The userid to authenticate as.
528     * @param string $pwd        The password to authenticate with.
529     * @param string $userMethod The cmdID.
530     *
531     * @return array Returns an array containing the response
532     * @access public
533     * @since  1.0
534     */
535    function cmdAuthenticate($uid, $pwd, $userMethod = null)
536    {
537        if (!$this->_connected) {
538            return new PEAR_Error('not connected!');
539        }
540
541        $cmdid = $this->_getCmdId();
542
543        $method = $this->_getBestAuthMethod($userMethod);
544        if ($method instanceOf PEAR_Error) {
545            return $method;
546        }
547
548
549        switch ($method) {
550        case 'DIGEST-MD5':
551            $result = $this->_authDigestMD5($uid, $pwd, $cmdid);
552            break;
553        case 'CRAM-MD5':
554            $result = $this->_authCramMD5($uid, $pwd, $cmdid);
555            break;
556        case 'LOGIN':
557            $result = $this->_authLOGIN($uid, $pwd, $cmdid);
558            break;
559        default:
560            $result = new PEAR_Error($method
561                                     . ' is not a supported authentication'
562                                     . ' method');
563            break;
564        }
565
566        $args = $this->_getRawResponse($cmdid);
567        return $this->_genericImapResponseParser($args, $cmdid);
568
569    }
570
571
572
573    /**
574     * Authenticates the user using the DIGEST-MD5 method.
575     *
576     * @param string $uid   The userid to authenticate as.
577     * @param string $pwd   The password to authenticate with.
578     * @param string $cmdid The cmdID.
579     *
580     * @return array Returns an array containing the response
581     * @access private
582     * @since  1.0
583     */
584    function _authDigestMD5($uid, $pwd, $cmdid)
585    {
586        $error = $this->_putCMD($cmdid,
587                                                  'AUTHENTICATE',
588                                                  'DIGEST-MD5');
589        if ($error instanceOf PEAR_Error) {
590            return $error;
591        }
592
593        $args = $this->_recvLn();
594        if ($args instanceOf PEAR_Error) {
595            return $args;
596        }
597
598        $this->_getNextToken($args, $plus);
599        $this->_getNextToken($args, $space);
600        $this->_getNextToken($args, $challenge);
601
602        $challenge = base64_decode($challenge);
603        $digest    = &Auth_SASL::factory('digestmd5');
604        $auth_str  = base64_encode($digest->getResponse($uid,
605                                                        $pwd,
606                                                        $challenge,
607                                                        'localhost',
608                                                        'imap'));
609
610        $error = $this->_send($auth_str . "\r\n");
611        if ($error instanceOf PEAR_Error) {
612            return $error;
613        }
614
615        $args = $this->_recvLn();
616        if ($args instanceOf PEAR_Error) {
617            return $args;
618        }
619
620        // We don't use the protocol's third step because IMAP doesn't allow
621        // subsequent authentication, so we just silently ignore it.
622        $error = $this->_send("\r\n");
623        if ($error instanceOf PEAR_Error) {
624            return $error;
625        }
626    }
627
628
629
630    /**
631     * Authenticates the user using the CRAM-MD5 method.
632     *
633     * @param string $uid   The userid to authenticate as.
634     * @param string $pwd   The password to authenticate with.
635     * @param string $cmdid The cmdID.
636     *
637     * @return array Returns an array containing the response
638     * @access private
639     * @since 1.0
640     */
641    function _authCramMD5($uid, $pwd, $cmdid)
642    {
643        $error = $this->_putCMD($cmdid,
644                                                  'AUTHENTICATE',
645                                                  'CRAM-MD5');
646        if ($error instanceOf PEAR_Error) {
647            return $error;
648        }
649
650        $args = $this->_recvLn();
651        if ($args instanceOf PEAR_Error) {
652            return $args;
653        }
654
655        $this->_getNextToken($args, $plus);
656        $this->_getNextToken($args, $space);
657        $this->_getNextToken($args, $challenge);
658
659        $challenge = base64_decode($challenge);
660        $cram      = &Auth_SASL::factory('crammd5');
661        $auth_str  = base64_encode($cram->getResponse($uid,
662                                                      $pwd,
663                                                      $challenge));
664
665        $error = $this->_send($auth_str . "\r\n");
666        if ($error instanceOf PEAR_Error) {
667            return $error;
668        }
669    }
670
671
672
673    /**
674     * Authenticates the user using the LOGIN method.
675     *
676     * @param string $uid   The userid to authenticate as.
677     * @param string $pwd   The password to authenticate with.
678     * @param string $cmdid The cmdID.
679     *
680     * @return array Returns an array containing the response
681     * @access private
682     * @since 1.0
683     */
684    function _authLOGIN($uid, $pwd, $cmdid)
685    {
686        $error = $this->_putCMD($cmdid,
687                                                  'AUTHENTICATE',
688                                                  'LOGIN');
689        if ($error instanceOf PEAR_Error) {
690            return $error;
691        }
692
693        $args = $this->_recvLn();
694        if ($args instanceOf PEAR_Error) {
695            return $args;
696        }
697
698        $this->_getNextToken($args, $plus);
699        $this->_getNextToken($args, $space);
700        $this->_getNextToken($args, $challenge);
701
702        $challenge = base64_decode($challenge);
703        $auth_str  = base64_encode($uid);
704
705        $error = $this->_send($auth_str . "\r\n");
706        if ($error instanceOf PEAR_Error) {
707            return $error;
708        }
709
710        $args = $this->_recvLn();
711        if ($args instanceOf PEAR_Error) {
712            return $args;
713        }
714
715        $auth_str = base64_encode($pwd);
716
717        $error = $this->_send($auth_str . "\r\n");
718        if ($error instanceOf PEAR_Error) {
719            return $error;
720        }
721    }
722
723
724
725    /**
726     * Returns the name of the best authentication method that the server
727     * has advertised.
728     *
729     * @param string $userMethod If !=null, authenticate with this
730     *                           method ($userMethod).
731     *
732     * @return mixed Returns a string containing the name of the best
733     *               supported authentication method or a PEAR_Error object
734     *               if a failure condition is encountered.
735     * @access private
736     * @since 1.0
737     */
738    function _getBestAuthMethod($userMethod = null)
739    {
740        $this->cmdCapability();
741
742        if ($userMethod != null) {
743            $methods   = array();
744            $methods[] = $userMethod;
745        } else {
746            $methods = $this->supportedAuthMethods;
747        }
748
749        if (($methods != null) && ($this->_serverAuthMethods != null)) {
750            foreach ($methods as $method) {
751                if (in_array($method, $this->_serverAuthMethods)) {
752                    return $method;
753                }
754            }
755            $serverMethods = implode(',', $this->_serverAuthMethods);
756            $myMethods     = implode(',', $this->supportedAuthMethods);
757
758            return new PEAR_Error($method . ' NOT supported authentication'
759                                  . ' method! This IMAP server supports these'
760                                  . ' methods: ' . $serverMethods . ', but I'
761                                  . ' support ' . $myMethods);
762        } else {
763            return new PEAR_Error('This IMAP server don\'t support any Auth'
764                                  . ' methods');
765        }
766    }
767
768
769
770    /**
771     * Attempt to disconnect from the iMAP server.
772     *
773     * @return array Returns an array containing the response
774     *
775     * @access public
776     * @since  1.0
777     */
778    function cmdLogout()
779    {
780        if (!$this->_connected) {
781            return new PEAR_Error('not connected!');
782        }
783
784        $args = $this->_genericCommand('LOGOUT');
785        if ($args instanceOf PEAR_Error) {
786            return $args;
787        }
788        if ($this->_socket->disconnect() instanceOf PEAR_Error) {
789            return new PEAR_Error('socket disconnect failed');
790        }
791
792        return $args;
793        // not for now
794        //return $this->_genericImapResponseParser($args,$cmdid);
795    }
796
797
798
799    /**
800     * Send the NOOP command.
801     *
802     * @return array Returns an array containing the response
803     * @access public
804     * @since  1.0
805     */
806    function cmdNoop()
807    {
808        return $this->_genericCommand('NOOP');
809    }
810
811
812
813    /**
814     * Send the CHECK command.
815     *
816     * @return array Returns an array containing the response
817     * @access public
818     * @since  1.0
819     */
820    function cmdCheck()
821    {
822        return $this->_genericCommand('CHECK');
823    }
824
825
826
827    /**
828     * Send the  Select Mailbox Command
829     *
830     * @param string $mailbox The mailbox to select.
831     *
832     * @return array Returns an array containing the response
833     * @access public
834     * @since  1.0
835     */
836    function cmdSelect($mailbox)
837    {
838        $mailbox_name = $this->_createQuotedString($mailbox);
839        $ret = $this->_genericCommand('SELECT',
840            $mailbox_name);
841        if (!$ret instanceOf PEAR_Error) {
842            $this->currentMailbox = $mailbox;
843        }
844        return $ret;
845    }
846
847
848
849    /**
850     * Send the  EXAMINE  Mailbox Command
851     *
852     * @param string $mailbox The mailbox to examine.
853     *
854     * @return array Returns an array containing the response
855     * @access public
856     * @since  1.0
857     */
858    function cmdExamine($mailbox)
859    {
860        $mailbox_name = $this->_createQuotedString($mailbox);
861        $ret          = $this->_genericCommand('EXAMINE', $mailbox_name);
862        // ERP-modification: Fixed issue with PHP 7.1.x
863        $parsed       = array();
864
865        if (isset($ret['PARSED'])) {
866            for ($i=0; $i<count($ret['PARSED']); $i++) {
867                if (array_key_exists('EXT', $ret['PARSED'][$i])
868                // ERP-modification: Added line to fix an error with Gmail and a possible empty array that is returned
869                   && !empty($ret['PARSED'][$i]['EXT'])
870                   && is_array($ret['PARSED'][$i]['EXT'])) {
871                    $command               = $ret['PARSED'][$i]['EXT'];
872                    $parsed[key($command)] = $command[key($command)];
873                }
874            }
875        }
876
877        return array('PARSED'   => $parsed,
878                     'RESPONSE' => $ret['RESPONSE']);
879    }
880
881
882
883    /**
884     * Send the  CREATE Mailbox Command
885     *
886     * @param string $mailbox The mailbox to create.
887     * @param array  $options Options to pass to create
888     *
889     * @return array Returns an array containing the response
890     * @access public
891     * @since 1.0
892     */
893    function cmdCreate($mailbox, $options = null)
894    {
895        $args         = '';
896        $mailbox_name = $this->_createQuotedString($mailbox);
897        $args         = $this->_getCreateParams($options);
898
899        return $this->_genericCommand('CREATE', $mailbox_name . $args);
900    }
901
902
903
904    /**
905     * Send the  RENAME Mailbox Command
906     *
907     * @param string $mailbox     The old mailbox name.
908     * @param string $new_mailbox The new (renamed) mailbox name.
909     * @param array  $options     options to pass to create
910     *
911     * @return array Returns an array containing the response
912     * @access public
913     * @since  1.0
914     */
915    function cmdRename($mailbox, $new_mailbox, $options = null)
916    {
917        $mailbox_name     = $this->_createQuotedString($mailbox);
918        $new_mailbox_name = $this->_createQuotedString($new_mailbox);
919        $args             = $this->_getCreateParams($options);
920
921        return $this->_genericCommand('RENAME',
922                                      $mailbox_name . ' ' . $new_mailbox_name
923                                      . $args);
924    }
925
926
927
928    /**
929     * Send the  DELETE Mailbox Command
930     *
931     * @param string $mailbox The mailbox name to delete.
932     *
933     * @return array Returns an array containing the response
934     * @access public
935     * @since 1.0
936     */
937    function cmdDelete($mailbox)
938    {
939        $mailbox_name = $this->_createQuotedString($mailbox);
940        return $this->_genericCommand('DELETE', $mailbox_name);
941    }
942
943
944
945    /**
946     * Send the  SUSCRIBE  Mailbox Command
947     *
948     * @param string $mailbox The mailbox name to suscribe.
949     *
950     * @return array Returns an array containing the response
951     * @access public
952     * @since 1.0
953     */
954    function cmdSubscribe($mailbox)
955    {
956        $mailbox_name = $this->_createQuotedString($mailbox);
957        return $this->_genericCommand('SUBSCRIBE', $mailbox_name);
958    }
959
960
961
962    /**
963     * Send the  UNSUBSCRIBE  Mailbox Command
964     *
965     * @param string $mailbox The mailbox name to unsubscribe
966     *
967     * @return mixed Returns a PEAR_Error with an error message on any
968     *               kind of failure, or true on success.
969     * @access public
970     * @since 1.0
971     */
972    function cmdUnsubscribe($mailbox)
973    {
974        $mailbox_name = $this->_createQuotedString($mailbox);
975        return $this->_genericCommand('UNSUBSCRIBE', $mailbox_name);
976    }
977
978
979
980    /**
981     * Send the  FETCH Command
982     *
983     * @param string $msgset     msgset
984     * @param string $fetchparam fetchparam
985     *
986     * @return mixed Returns a PEAR_Error with an error message on any
987     *               kind of failure, or true on success.
988     * @access public
989     * @since 1.0
990     */
991    function cmdFetch($msgset, $fetchparam)
992    {
993        return $this->_genericCommand('FETCH', $msgset . ' ' . $fetchparam);
994    }
995
996
997
998    /**
999     * Send the  CAPABILITY Command
1000     *
1001     * @return mixed Returns a PEAR_Error with an error message on any
1002     *               kind of failure, or true on success.
1003     * @access public
1004     * @since 1.0
1005     */
1006    function cmdCapability()
1007    {
1008        $ret = $this->_genericCommand('CAPABILITY');
1009
1010        if (isset($ret['PARSED'])) {
1011            $ret['PARSED'] = $ret['PARSED'][0]['EXT']['CAPABILITY'];
1012
1013            // fill the $this->_serverAuthMethods
1014            // and $this->_serverSupportedCapabilities arrays
1015            foreach ($ret['PARSED']['CAPABILITIES'] as $auth_method) {
1016                if (strtoupper(substr($auth_method, 0, 5)) == 'AUTH=') {
1017                    $this->_serverAuthMethods[] = substr($auth_method, 5);
1018                }
1019            }
1020
1021            // Keep the capabilities response to use ir later
1022            $this->_serverSupportedCapabilities = $ret['PARSED']['CAPABILITIES'];
1023        }
1024
1025        return $ret;
1026    }
1027
1028
1029
1030    /**
1031     * Send the  CAPABILITY Command
1032     *
1033     * @return mixed Returns a PEAR_Error with an error message on any
1034     *               kind of failure, or true on success.
1035     * @access public
1036     * @since 1.0
1037     */
1038    function cmdNamespace()
1039    {
1040        $ret = $this->_genericCommand('NAMESPACE');
1041
1042        if (isset($ret['PARSED'])) {
1043            $ret['PARSED'] = $ret['PARSED'][0]['EXT']['NAMESPACE'];
1044
1045            // Keep the namespace response for later use
1046            $this->_namespace = $ret['PARSED']['NAMESPACES'];
1047        }
1048
1049        return $ret;
1050    }
1051
1052
1053
1054    /**
1055     * Send the  STATUS Mailbox Command
1056     *
1057     * @param string $mailbox The mailbox name
1058     * @param mixed  $request The request status
1059     *                        it could be an array or space separated string of
1060     *                        MESSAGES | RECENT | UIDNEXT
1061     *                        UIDVALIDITY | UNSEEN
1062     *
1063     * @return array Returns a Parsed Response
1064     * @access public
1065     * @since 1.0
1066     */
1067    function cmdStatus($mailbox, $request)
1068    {
1069        $mailbox_name = $this->_createQuotedString($mailbox);
1070
1071        // make array from $request if it is none
1072        if (!is_array($request)) {
1073            $request = explode(' ', $request);
1074        }
1075
1076        // see RFC 3501
1077        $valid_status_data = array('MESSAGES',
1078                                   'RECENT',
1079                                   'UIDNEXT',
1080                                   'UIDVALIDITY',
1081                                   'UNSEEN');
1082
1083        foreach ($request as $status_data) {
1084            if (!in_array($status_data, $valid_status_data)) {
1085                $this->_protError('request "' . $status_data . '" is invalid! '
1086                                  . 'See RFC 3501!!!!',
1087                                  __LINE__,
1088                                  __FILE__);
1089            }
1090        }
1091
1092        // back to space separated string
1093        $request = implode(' ', $request);
1094
1095        $ret = $this->_genericCommand('STATUS',
1096                                      $mailbox_name . ' (' . $request . ')');
1097        if (!$ret instanceOf PEAR_Error && isset($ret['PARSED'])) {
1098            $ret['PARSED'] = $ret['PARSED'][count($ret['PARSED'])-1]['EXT'];
1099        }
1100        return $ret;
1101    }
1102
1103
1104
1105    /**
1106     * Send the  LIST  Command
1107     *
1108     * @param string $mailbox_base mailbox_base
1109     * @param string $mailbox      The mailbox name
1110     *
1111     * @return mixed Returns a PEAR_Error with an error message on any
1112     *               kind of failure, or true on success.
1113     * @access public
1114     * @since  1.0
1115     */
1116    function cmdList($mailbox_base, $mailbox)
1117    {
1118        $mailbox_name = $this->_createQuotedString($mailbox);
1119        $mailbox_base = $this->_createQuotedString($mailbox_base);
1120        return $this->_genericCommand('LIST',
1121                                      $mailbox_base . ' ' . $mailbox_name);
1122    }
1123
1124
1125
1126    /**
1127     * Send the  LSUB  Command
1128     *
1129     * @param string $mailbox_base mailbox_base
1130     * @param string $mailbox      The mailbox name
1131     *
1132     * @return mixed Returns a PEAR_Error with an error message on any
1133     *               kind of failure, or true on success.
1134     * @access public
1135     * @since 1.0
1136     */
1137    function cmdLsub($mailbox_base, $mailbox)
1138    {
1139        $mailbox_name = $this->_createQuotedString($mailbox);
1140        $mailbox_base = $this->_createQuotedString($mailbox_base);
1141        return $this->_genericCommand('LSUB',
1142                                      $mailbox_base . ' ' . $mailbox_name);
1143    }
1144
1145
1146
1147    /**
1148     * Send the  APPEND  Command
1149     *
1150     * @param string $mailbox    Mailbox name
1151     * @param string $msg        Message
1152     * @param string $flags_list Flags list
1153     * @param string $time       Time
1154     *
1155     * @return mixed Returns a PEAR_Error with an error message on any
1156     *               kind of failure, or true on success.
1157     * @access public
1158     * @since 1.0
1159     */
1160    function cmdAppend($mailbox, $msg, $flags_list = '', $time = '')
1161    {
1162        if (!$this->_connected) {
1163            return new PEAR_Error('not connected!');
1164        }
1165
1166        $cmdid    = $this->_getCmdId();
1167        $msg_size = $this->_getLineLength($msg);
1168
1169        $mailbox_name = $this->_createQuotedString($mailbox);
1170        if ($flags_list != '') {
1171            $flags_list = ' (' . $flags_list . ')';
1172        }
1173
1174        if ($this->hasCapability('LITERAL+') == true) {
1175            if ($time != '') {
1176                $timeAsString = date("d-M-Y H:i:s O", $time);
1177                $param        = sprintf("%s %s\"%s\"{%s+}\r\n%s",
1178                                        $mailbox_name,
1179                                        $flags_list,
1180                                        $timeAsString,
1181                                        $msg_size,
1182                                        $msg);
1183            } else {
1184                $param = sprintf("%s%s {%s+}\r\n%s",
1185                                 $mailbox_name,
1186                                 $flags_list,
1187                                 $msg_size,
1188                                 $msg);
1189            }
1190            $error = $this->_putCMD($cmdid,
1191                                                      'APPEND',
1192                                                      $param);
1193            if ($error instanceOf PEAR_Error) {
1194                    return $error;
1195            }
1196        } else {
1197            $param = sprintf("%s%s {%s}",
1198                             $mailbox_name,
1199                             $flags_list,
1200                             $msg_size);
1201            $error = $this->_putCMD($cmdid,
1202                                                      'APPEND',
1203                                                      $param);
1204            if ($error instanceOf PEAR_Error) {
1205                return $error;
1206            }
1207            $error = $this->_recvLn();
1208            if ($error instanceOf PEAR_Error) {
1209                return $error;
1210            }
1211
1212            $error = $this->_send($msg . "\r\n");
1213            if ($error instanceOf PEAR_Error) {
1214                return $error;
1215            }
1216        }
1217
1218        $args = $this->_getRawResponse($cmdid);
1219        $ret  = $this->_genericImapResponseParser($args, $cmdid);
1220        return $ret;
1221    }
1222
1223
1224
1225    /**
1226     * Send the CLOSE command.
1227     *
1228     * @return mixed Returns a PEAR_Error with an error message on any
1229     *               kind of failure, or true on success.
1230     * @access public
1231     * @since 1.0
1232     */
1233    function cmdClose()
1234    {
1235        return $this->_genericCommand('CLOSE');
1236    }
1237
1238
1239
1240    /**
1241     * Send the EXPUNGE command.
1242     *
1243     * @return mixed Returns a PEAR_Error with an error message on any
1244     *               kind of failure, or true on success.
1245     * @access public
1246     * @since  1.0
1247     */
1248    function cmdExpunge()
1249    {
1250        $ret = $this->_genericCommand('EXPUNGE');
1251
1252        if ($ret instanceOf PEAR_Error) {
1253            return new PEAR_Error('could not Expunge!');
1254        }
1255
1256        if (isset($ret['PARSED'])) {
1257            $parsed = $ret['PARSED'];
1258            unset($ret["PARSED"]);
1259            foreach ($parsed as $command) {
1260                if (strtoupper($command['COMMAND']) == 'EXPUNGE') {
1261                    $ret['PARSED'][$command['COMMAND']][] = $command['NRO'];
1262                } else {
1263                    $ret['PARSED'][$command['COMMAND']] = $command['NRO'];
1264                }
1265            }
1266        }
1267        return $ret;
1268    }
1269
1270
1271
1272    /**
1273     * Send the SEARCH command.
1274     *
1275     * @param string $search_cmd Search command
1276     *
1277     * @return mixed Returns a PEAR_Error with an error message on any
1278     *               kind of failure, or true on success.
1279     * @access public
1280     * @since 1.0
1281     */
1282    function cmdSearch($search_cmd)
1283    {
1284        /*        if($_charset != '' )
1285                    $_charset = "[$_charset] ";
1286                $param=sprintf("%s%s",$charset,$search_cmd);
1287        */
1288        $ret = $this->_genericCommand('SEARCH', $search_cmd);
1289        if (isset($ret['PARSED'])) {
1290            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1291        }
1292        return $ret;
1293    }
1294
1295
1296
1297    /**
1298     * Send the SORT command.
1299     *
1300     * @param string $sort_cmd Sort command
1301     *
1302     * @return mixed Returns a PEAR_Error with an error message on any
1303     *               kind of failure, or true on success.
1304     * @access public
1305     * @since 1.1
1306     */
1307    function cmdSort($sort_cmd)
1308    {
1309        /*
1310        if ($_charset != '' )
1311            $_charset = "[$_charset] ";
1312        $param = sprintf("%s%s",$charset,$search_cmd);
1313        */
1314        $ret = $this->_genericCommand('SORT', $sort_cmd);
1315        if (isset($ret['PARSED'])) {
1316            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1317        }
1318        return $ret;
1319    }
1320
1321
1322
1323    /**
1324     * Send the STORE command.
1325     *
1326     * @param string $message_set The sessage_set
1327     * @param string $dataitem    The way we store the flags
1328     *                            FLAGS: replace the flags whith $value
1329     *                            FLAGS.SILENT: replace the flags whith $value
1330     *                             but don't return untagged responses
1331     *                            +FLAGS: Add the flags whith $value
1332     *                            +FLAGS.SILENT: Add the flags whith $value
1333     *                             but don't return untagged responses
1334     *                            -FLAGS: Remove the flags whith $value
1335     *                            -FLAGS.SILENT: Remove the flags whith $value
1336     *                             but don't return untagged responses
1337     * @param string $value       Value
1338     *
1339     * @return mixed Returns a PEAR_Error with an error message on any
1340     *               kind of failure, or true on success.
1341     * @access public
1342     * @since 1.0
1343     */
1344    function cmdStore($message_set, $dataitem, $value)
1345    {
1346        /* As said in RFC2060...
1347        C: A003 STORE 2:4 +FLAGS (\Deleted)
1348        S: * 2 FETCH FLAGS (\Deleted \Seen)
1349        S: * 3 FETCH FLAGS (\Deleted)
1350        S: * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
1351        S: A003 OK STORE completed
1352        */
1353        if ($dataitem != 'FLAGS'
1354            && $dataitem != 'FLAGS.SILENT'
1355            && $dataitem != '+FLAGS'
1356            && $dataitem != '+FLAGS.SILENT'
1357            && $dataitem != '-FLAGS'
1358            && $dataitem != '-FLAGS.SILENT') {
1359            $this->_protError('dataitem "' . $dataitem . '" is invalid! '
1360                              . 'See RFC2060!!!!',
1361                              __LINE__,
1362                              __FILE__);
1363        }
1364        $param = sprintf("%s %s (%s)", $message_set, $dataitem, $value);
1365        return $this->_genericCommand('STORE', $param);
1366    }
1367
1368
1369
1370    /**
1371     * Send the COPY command.
1372     *
1373     * @param string $message_set Message set
1374     * @param string $mailbox     Mailbox name
1375     *
1376     * @return mixed Returns a PEAR_Error with an error message on any
1377     *               kind of failure, or true on success.
1378     * @access public
1379     * @since 1.0
1380     */
1381    function cmdCopy($message_set, $mailbox)
1382    {
1383        $mailbox_name = $this->_createQuotedString($mailbox);
1384        return $this->_genericCommand('COPY',
1385                                      sprintf("%s %s",
1386                                              $message_set,
1387                                              $mailbox_name));
1388    }
1389
1390
1391
1392    /**
1393     * The UID FETH command
1394     *
1395     * @param string $msgset     Msgset
1396     * @param string $fetchparam Fetchparm
1397     *
1398     * @return mixed Returns a PEAR_Error with an error message on any
1399     *               kind of failure, or true on success.
1400     * @access public
1401     * @since 1.0
1402     */
1403    function cmdUidFetch($msgset, $fetchparam)
1404    {
1405        return $this->_genericCommand('UID FETCH',
1406                                      sprintf("%s %s", $msgset, $fetchparam));
1407    }
1408
1409
1410
1411    /**
1412     * The UID COPY command
1413     *
1414     * @param string $message_set Msgset
1415     * @param string $mailbox     Mailbox name
1416     *
1417     * @return mixed Returns a PEAR_Error with an error message on any
1418     *               kind of failure, or true on success.
1419     * @access public
1420     * @since 1.0
1421     */
1422    function cmdUidCopy($message_set, $mailbox)
1423    {
1424        $mailbox_name = $this->_createQuotedString($mailbox);
1425        return $this->_genericCommand('UID COPY',
1426                                      sprintf("%s %s",
1427                                              $message_set,
1428                                              $mailbox_name));
1429    }
1430
1431
1432
1433    /**
1434     * Send the UID STORE command.
1435     *
1436     * @param string $message_set The sessage_set
1437     * @param string $dataitem    The way we store the flags
1438     *                            FLAGS: replace the flags whith $value
1439     *                            FLAGS.SILENT: replace the flags whith $value
1440     *                             but don't return untagged responses
1441     *                            +FLAGS: Add the flags whith $value
1442     *                            +FLAGS.SILENT: Add the flags whith $value
1443     *                             but don't return untagged responses
1444     *                            -FLAGS: Remove the flags whith $value
1445     *                            -FLAGS.SILENT: Remove the flags whith $value
1446     *                             but don't return untagged responses
1447     * @param string $value       Value
1448     *
1449     * @return mixed Returns a PEAR_Error with an error message on any
1450     *               kind of failure, or true on success.
1451     * @access public
1452     * @since  1.0
1453     */
1454    function cmdUidStore($message_set, $dataitem, $value)
1455    {
1456        /* As said in RFC2060...
1457        C: A003 STORE 2:4 +FLAGS (\Deleted)
1458        S: * 2 FETCH FLAGS (\Deleted \Seen)
1459        S: * 3 FETCH FLAGS (\Deleted)
1460        S: * 4 FETCH FLAGS (\Deleted \Flagged \Seen)
1461        S: A003 OK STORE completed
1462        */
1463        if ($dataitem != 'FLAGS'
1464            && $dataitem != 'FLAGS.SILENT'
1465            && $dataitem != '+FLAGS'
1466            && $dataitem != '+FLAGS.SILENT'
1467            && $dataitem != '-FLAGS'
1468            && $dataitem != '-FLAGS.SILENT') {
1469                $this->_protError('dataitem "' . $dataitem . '" is invalid! '
1470                                  . 'See RFC2060!!!!',
1471                                  __LINE__,
1472                                  __FILE__);
1473        }
1474
1475        return $this->_genericCommand('UID STORE',
1476                                      sprintf("%s %s (%s)",
1477                                              $message_set,
1478                                              $dataitem,
1479                                              $value));
1480    }
1481
1482
1483
1484    /**
1485     * Send the SEARCH command.
1486     *
1487     * @param string $search_cmd Search command
1488     *
1489     * @return mixed Returns a PEAR_Error with an error message on any
1490     *               kind of failure, or true on success.
1491     * @access public
1492     * @since 1.0
1493     */
1494    function cmdUidSearch($search_cmd)
1495    {
1496        $ret = $this->_genericCommand('UID SEARCH',
1497                                      sprintf("%s", $search_cmd));
1498        if (isset($ret['PARSED'])) {
1499            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1500        }
1501        return $ret;
1502    }
1503
1504
1505
1506    /**
1507     * Send the UID SORT command.
1508     *
1509     * @param string $sort_cmd Sort command
1510     *
1511     * @return mixed Returns a PEAR_Error with an error message on any
1512     *               kind of failure, or true on success.
1513     * @access public
1514     * @since 1.1
1515     */
1516    function cmdUidSort($sort_cmd)
1517    {
1518        $ret = $this->_genericCommand('UID SORT', sprintf("%s", $sort_cmd));
1519        if (isset($ret['PARSED'])) {
1520            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1521        }
1522        return $ret;
1523    }
1524
1525
1526
1527    /**
1528     * Send the X command.
1529     *
1530     * @param string $atom       Atom
1531     * @param string $parameters Parameters
1532     *
1533     * @return mixed Returns a PEAR_Error with an error message on any
1534     *               kind of failure, or true on success.
1535     * @access public
1536     * @since 1.0
1537     */
1538    function cmdX($atom, $parameters)
1539    {
1540        return $this->_genericCommand('X' . $atom, $parameters);
1541    }
1542
1543
1544
1545    /********************************************************************
1546    ***
1547    **             HERE ENDS the RFC2060 IMAPS FUNCTIONS
1548    **             AND BEGIN THE EXTENSIONS FUNCTIONS
1549    **
1550    *******************************************************************/
1551
1552
1553
1554    /*******************************************************************
1555    **             RFC2087 IMAP4 QUOTA extension BEGINS HERE
1556    *******************************************************************/
1557
1558    /**
1559     * Send the GETQUOTA command.
1560     *
1561     * @param string $mailbox_name The mailbox name to query for quota data
1562     *
1563     * @return mixed Returns a PEAR_Error with an error message on any
1564     *               kind of failure, or quota data on success
1565     * @access public
1566     * @since 1.0
1567     */
1568    function cmdGetQuota($mailbox_name)
1569    {
1570        //Check if the IMAP server has QUOTA support
1571        if (!$this->hasQuotaSupport()) {
1572            return new PEAR_Error('This IMAP server doen\'t support QUOTA\'s!');
1573        }
1574
1575        $mailbox_name = sprintf("%s", $this->utf7Encode($mailbox_name));
1576        $ret          = $this->_genericCommand('GETQUOTA', $mailbox_name);
1577
1578        if (isset($ret['PARSED'])) {
1579            // remove the array index because the quota response returns
1580            // only 1 line of output
1581            $ret['PARSED'] = $ret['PARSED'][0];
1582        }
1583        return $ret;
1584    }
1585
1586
1587
1588    /**
1589     * Send the GETQUOTAROOT command.
1590     *
1591     * @param string $mailbox_name The ailbox name to query for quota data
1592     *
1593     * @return mixed Returns a PEAR_Error with an error message on any
1594     *               kind of failure, or quota data on success
1595     * @access public
1596     * @since 1.0
1597     */
1598    function cmdGetQuotaRoot($mailbox_name)
1599    {
1600        //Check if the IMAP server has QUOTA support
1601        if (!$this->hasQuotaSupport()) {
1602            return new PEAR_Error('This IMAP server doesn\'t support QUOTA\'s!');
1603        }
1604
1605        $mailbox_name = sprintf("%s", $this->utf7Encode($mailbox_name));
1606        $ret          = $this->_genericCommand('GETQUOTAROOT', $mailbox_name);
1607
1608        if (isset($ret['PARSED'])) {
1609            // remove the array index because the quota response returns
1610            // only 1 line of output
1611            $ret['PARSED'] = $ret['PARSED'][1];
1612        }
1613        return $ret;
1614    }
1615
1616
1617
1618    /**
1619     * Send the SETQUOTA command.
1620     *
1621     * @param string $mailbox_name  The mailbox name to query for quota data
1622     * @param string $storageQuota  Sets the max number of bytes this mailbox
1623     *                              can handle
1624     * @param string $messagesQuota Sets the max number of messages this
1625     *                              mailbox can handle
1626     *
1627     * @return mixed Returns a PEAR_Error with an error message on any
1628     *               kind of failure, or quota data on success
1629     * @access public
1630     * @since 1.0
1631     */
1632    function cmdSetQuota($mailbox_name,
1633                         $storageQuota = null,
1634                         $messagesQuota = null)
1635    {
1636        // ToDo:  implement the quota by number of emails!!
1637
1638        //Check if the IMAP server has QUOTA support
1639        if (!$this->hasQuotaSupport()) {
1640            return new PEAR_Error('This IMAP server doesn\'t support QUOTA\'s!');
1641        }
1642
1643        if (($messagesQuota == null) && ($storageQuota == null)) {
1644            return new PEAR_Error('$storageQuota and $messagesQuota parameters '
1645                                  . 'can\'t be both null if you want to use '
1646                                  . 'quota');
1647        }
1648
1649        $mailbox_name = $this->_createQuotedString($mailbox_name);
1650        //Make the command request
1651        $param = sprintf("%s (", $mailbox_name);
1652
1653        if ($storageQuota != null) {
1654            if ($storageQuota == -1) {
1655                // set -1 to remove a quota
1656                $param = sprintf("%s", $param);
1657            } elseif ($storageQuota == strtolower('remove')) {
1658                // this is a cyrus rmquota specific feature
1659                // see http://email.uoa.gr/projects/cyrus/quota-patches/rmquota/
1660                $param = sprintf("%sREMOVE 1", $param);
1661            } else {
1662                $param = sprintf("%sSTORAGE %s", $param, $storageQuota);
1663            }
1664
1665            if ($messagesQuota != null) {
1666                // if we have both types of quota on the same call we must
1667                // append an space between those parameters
1668                $param = sprintf("%s ", $param);
1669            }
1670        }
1671        if ($messagesQuota != null) {
1672            $param = sprintf("%sMESSAGES %s", $param, $messagesQuota);
1673        }
1674        $param = sprintf("%s)", $param);
1675
1676        return $this->_genericCommand('SETQUOTA', $param);
1677    }
1678
1679
1680
1681    /**
1682     * Send the SETQUOTAROOT command.
1683     *
1684     * @param string $mailbox_name  The mailbox name to query for quota data
1685     * @param string $storageQuota  Sets the max number of bytes this mailbox
1686     *                              can handle
1687     * @param string $messagesQuota Sets the max number of messages this
1688     *                              mailbox can handle
1689     *
1690     * @return mixed Returns a PEAR_Error with an error message on any
1691     *               kind of failure, or quota data on success
1692     * @access public
1693     * @since 1.0
1694     */
1695    function cmdSetQuotaRoot($mailbox_name,
1696                             $storageQuota = null,
1697                             $messagesQuota = null)
1698    {
1699        //Check if the IMAP server has QUOTA support
1700        if (!$this->hasQuotaSupport()) {
1701            return new PEAR_Error('This IMAP server doesn\'t support QUOTA\'s!');
1702        }
1703
1704        if (($messagesQuota == null) && ($storageQuota == null)) {
1705            return new PEAR_Error('$storageQuota and $messagesQuota parameters '
1706                                  . 'can\'t be both null if you want to use '
1707                                  . 'quota');
1708        }
1709
1710        $mailbox_name = $this->_createQuotedString($mailbox_name);
1711        //Make the command request
1712        $param = sprintf("%s (", $mailbox_name);
1713
1714        if ($storageQuota != null) {
1715            $param = sprintf("%sSTORAGE %s", $param, $storageQuota);
1716            if ($messagesQuota != null) {
1717                // if we have both types of quota on the same call we must
1718                // append an space between those parameters
1719                $param = sprintf("%s ", $param);
1720            }
1721        }
1722
1723        if ($messagesQuota != null) {
1724            $param = sprintf("%sMESSAGES %s", $param, $messagesQuota);
1725        }
1726        $param = sprintf("%s)", $param);
1727
1728        return $this->_genericCommand('SETQUOTAROOT', $param);
1729    }
1730
1731
1732
1733    /********************************************************************
1734    ***             RFC2087 IMAP4 QUOTA extension ENDS HERE
1735    ********************************************************************/
1736
1737
1738
1739    /********************************************************************
1740    ***             RFC2086 IMAP4 ACL extension BEGINS HERE
1741    ********************************************************************/
1742
1743    /**
1744     * Send the SETACL command.
1745     *
1746     * @param string $mailbox_name Mailbox name
1747     * @param string $user         User
1748     * @param string $acl          ACL string
1749     *
1750     * @return mixed Returns a PEAR_Error with an error message on any
1751     *               kind of failure, or true on success
1752     * @access public
1753     * @since 1.0
1754     */
1755    function cmdSetACL($mailbox_name, $user, $acl)
1756    {
1757        //Check if the IMAP server has ACL support
1758        if (!$this->hasAclSupport()) {
1759            return new PEAR_Error('This IMAP server does not support ACL\'s!');
1760        }
1761
1762        $mailbox_name = $this->_createQuotedString($mailbox_name);
1763        $user_name    = $this->_createQuotedString($user);
1764
1765        if (is_array($acl)) {
1766            $acl = implode('', $acl);
1767        }
1768
1769        return $this->_genericCommand('SETACL',
1770                                      sprintf("%s %s \"%s\"",
1771                                              $mailbox_name,
1772                                              $user_name,
1773                                              $acl));
1774    }
1775
1776
1777
1778    /**
1779     * Send the DELETEACL command.
1780     *
1781     * @param string $mailbox_name Mailbox name
1782     * @param string $user         User
1783     *
1784     * @return mixed Returns a PEAR_Error with an error message on any
1785     *               kind of failure, or true on success
1786     * @access public
1787     * @since 1.0
1788     */
1789    function cmdDeleteACL($mailbox_name, $user)
1790    {
1791        //Check if the IMAP server has ACL support
1792        if (!$this->hasAclSupport()) {
1793            return new PEAR_Error('This IMAP server does not support ACL\'s!');
1794        }
1795
1796        $mailbox_name = $this->_createQuotedString($mailbox_name);
1797
1798        return $this->_genericCommand('DELETEACL',
1799                                      sprintf("%s \"%s\"",
1800                                              $mailbox_name,
1801                                              $user));
1802    }
1803
1804
1805
1806    /**
1807     * Send the GETACL command.
1808     *
1809     * @param string $mailbox_name Mailbox name
1810     *
1811     * @return mixed Returns a PEAR_Error with an error message on any
1812     *               kind of failure, or ACL list on success
1813     * @access public
1814     * @since 1.0
1815     */
1816    function cmdGetACL($mailbox_name)
1817    {
1818        //Check if the IMAP server has ACL support
1819        if (!$this->hasAclSupport()) {
1820            return new PEAR_Error('This IMAP server does not support ACL\'s!');
1821        }
1822
1823        $mailbox_name = $this->_createQuotedString($mailbox_name);
1824        $ret          = $this->_genericCommand('GETACL',
1825                                               sprintf("%s", $mailbox_name));
1826
1827        if (isset($ret['PARSED'])) {
1828            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1829        }
1830        return $ret;
1831    }
1832
1833
1834
1835    /**
1836     * Send the LISTRIGHTS command.
1837     *
1838     * @param string $mailbox_name Mailbox name
1839     * @param string $user         User
1840     *
1841     * @return mixed Returns a PEAR_Error with an error message on any
1842     *               kind of failure, or list of users rights
1843     * @access public
1844     * @since 1.0
1845     */
1846    function cmdListRights($mailbox_name, $user)
1847    {
1848        //Check if the IMAP server has ACL support
1849        if (!$this->hasAclSupport()) {
1850            return new PEAR_Error('This IMAP server does not support ACL\'s!');
1851        }
1852
1853        $mailbox_name = $this->_createQuotedString($mailbox_name);
1854        $ret          = $this->_genericCommand('LISTRIGHTS',
1855                                               sprintf("%s \"%s\"",
1856                                                       $mailbox_name,
1857                                                       $user));
1858        if (isset($ret['PARSED'])) {
1859            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1860        }
1861        return $ret;
1862    }
1863
1864
1865
1866    /**
1867     * Send the MYRIGHTS command.
1868     *
1869     * @param string $mailbox_name Mailbox name
1870     *
1871     * @return mixed Returns a PEAR_Error with an error message on any
1872     *               kind of failure, or MYRIGHTS response on success
1873     * @access public
1874     * @since 1.0
1875     */
1876    function cmdMyRights($mailbox_name)
1877    {
1878        // Check if the IMAP server has ACL support
1879        if (!$this->hasAclSupport()) {
1880            return new PEAR_Error('This IMAP server does not support ACL\'s!');
1881        }
1882
1883        $mailbox_name = $this->_createQuotedString($mailbox_name);
1884        $ret          = $this->_genericCommand('MYRIGHTS',
1885                                               sprintf("%s", $mailbox_name));
1886        if (isset($ret['PARSED'])) {
1887            $ret['PARSED'] = $ret['PARSED'][0]['EXT'];
1888        }
1889        return $ret;
1890    }
1891
1892
1893
1894    /********************************************************************
1895    ***             RFC2086 IMAP4 ACL extension ENDs HERE
1896    ********************************************************************/
1897
1898
1899    /********************************************************************
1900    ***  draft-daboo-imap-annotatemore-05 IMAP4 ANNOTATEMORE extension
1901    ***  BEGINS HERE
1902    ********************************************************************/
1903
1904    /**
1905     * Send the SETANNOTATION command.
1906     *
1907     * @param string $mailboxName Mailbox name
1908     * @param string $entry       Entry
1909     * @param string $values      Value
1910     *
1911     * @return mixed Returns a PEAR_Error with an error message on any
1912     *               kind of failure, or true on success
1913     * @access public
1914     * @since 1.0
1915     */
1916    function cmdSetAnnotation($mailboxName, $entry, $values)
1917    {
1918        // Check if the IMAP server has ANNOTATEMORE support
1919        if (!$this->hasAnnotateMoreSupport()) {
1920            return new PEAR_Error('This IMAP server does not support the '
1921                                  . 'ANNOTATEMORE extension!');
1922        }
1923
1924        if (!is_array($values)) {
1925            return new PEAR_Error('Invalid $values argument passed to '
1926                                  . 'cmdSetAnnotation');
1927        }
1928
1929        $mailboxName = $this->_createQuotedString($mailboxName);
1930
1931        $vallist = '';
1932        foreach ($values as $name => $value) {
1933            $vallist .= '"' . $name . '" "' . $value . '"';
1934        }
1935        $vallist = rtrim($vallist);
1936
1937        return $this->_genericCommand('SETANNOTATION',
1938                                      sprintf('%s "%s" (%s)',
1939                                              $mailboxName,
1940                                              $entry,
1941                                              $vallist));
1942    }
1943
1944
1945
1946    /**
1947     * Send the DELETEANNOTATION command.
1948     *
1949     * @param string $mailboxName Mailbox name
1950     * @param string $entry       Entry
1951     * @param string $values      Value
1952     *
1953     * @return mixed Returns a PEAR_Error with an error message on any
1954     *               kind of failure, or true on success
1955     * @access public
1956     * @since 1.0
1957     */
1958    function cmdDeleteAnnotation($mailboxName, $entry, $values)
1959    {
1960        // Check if the IMAP server has ANNOTATEMORE support
1961        if (!$this->hasAnnotateMoreSupport()) {
1962            return new PEAR_Error('This IMAP server does not support the '
1963                                  . 'ANNOTATEMORE extension!');
1964        }
1965
1966        if (!is_array($values)) {
1967            return new PEAR_Error('Invalid $values argument passed to '
1968                                  . 'cmdDeleteAnnotation');
1969        }
1970
1971        $mailboxName = $this->_createQuotedString($mailboxName);
1972
1973        $vallist = '';
1974        foreach ($values as $name) {
1975            $vallist .= '"' . $name . '" NIL';
1976        }
1977        $vallist = rtrim($vallist);
1978
1979        return $this->_genericCommand('SETANNOTATION',
1980                                      sprintf('%s "%s" (%s)',
1981                                              $mailboxName,
1982                                              $entry,
1983                                              $vallist));
1984    }
1985
1986
1987
1988    /**
1989     * Send the GETANNOTATION command.
1990     *
1991     * @param string $mailboxName Mailbox name
1992     * @param string $entries     Entries
1993     * @param string $values      Value
1994     *
1995     * @return mixed Returns a PEAR_Error with an error message on any
1996     *               kind of failure, or GETANNOTATION result on success
1997     * @access public
1998     * @since 1.0
1999     */
2000    function cmdGetAnnotation($mailboxName, $entries, $values)
2001    {
2002        // Check if the IMAP server has ANNOTATEMORE support
2003        if (!$this->hasAnnotateMoreSupport()) {
2004            return new PEAR_Error('This IMAP server does not support the '
2005                                  . 'ANNOTATEMORE extension!');
2006        }
2007
2008        $entlist = '';
2009
2010        if (!is_array($entries)) {
2011            $entries = array($entries);
2012        }
2013
2014        foreach ($entries as $name) {
2015            $entlist .= '"' . $name . '"';
2016        }
2017        $entlist = rtrim($entlist);
2018        if (count($entries) > 1) {
2019            $entlist = '(' . $entlist . ')';
2020        }
2021
2022        $vallist = '';
2023        if (!is_array($values)) {
2024            $values = array($values);
2025        }
2026
2027        foreach ($values as $name) {
2028            $vallist .= '"' . $name . '"';
2029        }
2030        $vallist = rtrim($vallist);
2031        if (count($values) > 1) {
2032            $vallist = '(' . $vallist . ')';
2033        }
2034
2035        $mailboxName = $this->_createQuotedString($mailboxName);
2036
2037        return $this->_genericCommand('GETANNOTATION',
2038                                      sprintf('%s %s %s',
2039                                              $mailboxName,
2040                                              $entlist,
2041                                              $vallist));
2042    }
2043
2044
2045    /***********************************************************************
2046    ***  draft-daboo-imap-annotatemore-05 IMAP4 ANNOTATEMORE extension
2047    ***  ENDs HERE
2048    ************************************************************************/
2049
2050
2051    /********************************************************************
2052    ***
2053    ***             HERE ENDS THE EXTENSIONS FUNCTIONS
2054    ***             AND BEGIN THE AUXILIARY FUNCTIONS
2055    ***
2056    ********************************************************************/
2057
2058    /**
2059     * tell if the server has capability $capability
2060     *
2061     * @return true or false
2062     * @access public
2063     * @since 1.0
2064     */
2065    function getServerAuthMethods()
2066    {
2067        if ($this->_serverAuthMethods == null) {
2068            $this->cmdCapability();
2069            return $this->_serverAuthMethods;
2070        }
2071        return false;
2072    }
2073
2074
2075
2076    /**
2077     * tell if the server has capability $capability
2078     *
2079     * @param string $capability Capability
2080     *
2081     * @return true or false
2082     * @access public
2083     * @since 1.0
2084     */
2085    function hasCapability($capability)
2086    {
2087        if ($this->_serverSupportedCapabilities == null) {
2088            $this->cmdCapability();
2089        }
2090        if ($this->_serverSupportedCapabilities != null) {
2091            if (in_array($capability, $this->_serverSupportedCapabilities)) {
2092                return true;
2093            }
2094        }
2095        return false;
2096    }
2097
2098
2099
2100    /**
2101     * tell if the server has Quota support
2102     *
2103     * @return true or false
2104     * @access public
2105     * @since 1.0
2106     */
2107    function hasQuotaSupport()
2108    {
2109        return $this->hasCapability('QUOTA');
2110    }
2111
2112
2113
2114    /**
2115     * tell if the server has Quota support
2116     *
2117     * @return true or false
2118     * @access public
2119     * @since 1.0
2120     */
2121    function hasAclSupport()
2122    {
2123        return $this->hasCapability('ACL');
2124    }
2125
2126
2127
2128    /**
2129     * tell if the server has support for the ANNOTATEMORE extension
2130     *
2131     * @return true or false
2132     * @access public
2133     * @since 1.0
2134     */
2135    function hasAnnotateMoreSupport()
2136    {
2137        return $this->hasCapability('ANNOTATEMORE');
2138    }
2139
2140
2141    /**
2142     * Create a quoted string
2143     *
2144     * @param string $str String
2145     *
2146     * @return string Quoted $str
2147     * @access public
2148     * @since 1.0
2149     */
2150    function _createQuotedString($str)
2151    {
2152        $search  = array('\\', '"');
2153        $replace = array('\\\\', '\\"');
2154
2155        $mailbox_name = str_replace($search, $replace, $str);
2156        $mailbox_name = sprintf("\"%s\"", $this->utf7Encode($mailbox_name));
2157
2158        return $mailbox_name;
2159    }
2160
2161
2162
2163    /**
2164     * Parses the responses like RFC822.SIZE and INTERNALDATE
2165     *
2166     * @param string &$str The IMAP's server response
2167     * @param int    $line Line number
2168     * @param string $file File
2169     *
2170     * @return string next token
2171     * @access private
2172     * @since 1.0
2173     */
2174    function _parseOneStringResponse(&$str, $line, $file)
2175    {
2176        $this->_parseSpace($str, $line, $file);
2177        $size = $this->_getNextToken($str, $uid);
2178        return $uid;
2179    }
2180
2181
2182
2183    /**
2184     * Parses the FLAG response
2185     *
2186     * @param string &$str The IMAP's server response
2187     *
2188     * @return Array containing  the parsed  response
2189     * @access private
2190     * @since 1.0
2191     */
2192    function _parseFLAGSresponse(&$str)
2193    {
2194        $this->_parseSpace($str, __LINE__, __FILE__);
2195        $params_arr[] = $this->_arrayfyContent($str);
2196        $flags_arr    = array();
2197        for ($i = 0; $i < count($params_arr[0]); $i++) {
2198            $flags_arr[] = $params_arr[0][$i];
2199        }
2200        return $flags_arr;
2201    }
2202
2203
2204
2205    /**
2206     * Parses the BODY response
2207     *
2208     * @param string &$str    The IMAP's server response
2209     * @param string $command Command
2210     *
2211     * @return array The parsed response
2212     * @access private
2213     * @since 1.0
2214     */
2215    function _parseBodyResponse(&$str, $command)
2216    {
2217        $this->_parseSpace($str, __LINE__, __FILE__);
2218        while ($str[0] != ')' && $str != '') {
2219            $params_arr[] = $this->_arrayfyContent($str);
2220        }
2221
2222        return $params_arr;
2223    }
2224
2225
2226
2227    /**
2228     * Makes the content an Array
2229     *
2230     * @param string &$str The IMAP's server response
2231     *
2232     * @return array The parsed response
2233     * @access private
2234     * @since 1.0
2235     */
2236    function _arrayfyContent(&$str)
2237    {
2238        $params_arr = array();
2239        $this->_getNextToken($str, $params);
2240        if ($params != '(') {
2241            return $params;
2242        }
2243        $this->_getNextToken($str, $params, false, false);
2244        while ($str != '' && $params != ')') {
2245            if ($params != '') {
2246                if ($params[0] == '(') {
2247                    $params = $this->_arrayfyContent($params);
2248                }
2249                if ($params != ' ') {
2250                    // I don't remove the colons (") to handle the case of
2251                    // retriving " "
2252                    // If I remove the colons the parser will interpret this
2253                    // field as an imap separator (space) instead of a valid
2254                    // field so I remove the colons here
2255                    if ($params == '""') {
2256                        $params = '';
2257                    } else {
2258                        if ($params[0] == '"') {
2259                            $params = $this->_getSubstr($params,
2260                                                        1,
2261                                                        $this->_getLineLength($params)-2);
2262                        }
2263                    }
2264                    $params_arr[] = $params;
2265                }
2266            } else {
2267                // if params if empty (for example i'm parsing 2 quotes ("")
2268                // I'll append an array entry to mantain compatibility
2269                $params_arr[] = $params;
2270            }
2271            $this->_getNextToken($str, $params, false, false);
2272        }
2273        return $params_arr;
2274    }
2275
2276
2277
2278    /**
2279     * Parses the BODY[],BODY[TEXT],.... responses
2280     *
2281     * @param string &$str    The IMAP's server response
2282     * @param string $command Command
2283     *
2284     * @return array The parsed response
2285     * @access private
2286     * @since 1.0
2287    */
2288    function _parseContentresponse(&$str, $command)
2289    {
2290        $content = '';
2291        $this->_parseSpace($str, __LINE__, __FILE__);
2292        $size = $this->_getNextToken($str, $content);
2293        return array('CONTENT' => $content, 'CONTENT_SIZE' => $size);
2294    }
2295
2296
2297
2298    /**
2299     * Parses the ENVELOPE response
2300     *
2301     * @param string &$str The IMAP's server response
2302     *
2303     * @return array The parsed response
2304     * @access private
2305     * @since 1.0
2306     */
2307    function _parseENVELOPEresponse(&$str)
2308    {
2309        $content = '';
2310        $this->_parseSpace($str, __LINE__, __FILE__);
2311
2312        $this->_getNextToken($str, $parenthesis);
2313        if ($parenthesis != '(') {
2314            $this->_protError('must be a "(" but is a "' . $parenthesis .'" '
2315                              . '!!!!',
2316                              __LINE__,
2317                              __FILE__);
2318        }
2319        // Get the email's Date
2320        $this->_getNextToken($str, $date);
2321
2322        $this->_parseSpace($str, __LINE__, __FILE__);
2323
2324        // Get the email's Subject:
2325        $this->_getNextToken($str, $subject);
2326        //$subject = $this->decode($subject);
2327
2328        $this->_parseSpace($str, __LINE__, __FILE__);
2329
2330        //FROM LIST;
2331        $from_arr = $this->_getAddressList($str);
2332
2333        $this->_parseSpace($str, __LINE__, __FILE__);
2334
2335        //"SENDER LIST\n";
2336        $sender_arr = $this->_getAddressList($str);
2337
2338        $this->_parseSpace($str, __LINE__, __FILE__);
2339
2340        //"REPLY-TO LIST\n";
2341        $reply_to_arr = $this->_getAddressList($str);
2342
2343        $this->_parseSpace($str, __LINE__, __FILE__);
2344
2345        //"TO LIST\n";
2346        $to_arr = $this->_getAddressList($str);
2347
2348        $this->_parseSpace($str, __LINE__, __FILE__);
2349
2350        //"CC LIST\n";
2351        $cc_arr = $this->_getAddressList($str);
2352
2353        $this->_parseSpace($str, __LINE__, __FILE__);
2354
2355        //"BCC LIST|$str|\n";
2356        $bcc_arr = $this->_getAddressList($str);
2357
2358        $this->_parseSpace($str, __LINE__, __FILE__);
2359
2360        $this->_getNextToken($str, $in_reply_to);
2361
2362        $this->_parseSpace($str, __LINE__, __FILE__);
2363
2364        $this->_getNextToken($str, $message_id);
2365
2366        $this->_getNextToken($str, $parenthesis);
2367
2368        if ($parenthesis != ')') {
2369            $this->_protError('must be a ")" but is a "' . $parenthesis .'" '
2370                              . '!!!!',
2371                              __LINE__,
2372                              __FILE__);
2373        }
2374
2375        return array('DATE'        => $date,
2376                     'SUBJECT'     => $subject,
2377                     'FROM'        => $from_arr,
2378                     'SENDER'      => $sender_arr,
2379                     'REPLY_TO'    => $reply_to_arr,
2380                     'TO'          => $to_arr,
2381                     'CC'          => $cc_arr,
2382                     'BCC'         => $bcc_arr,
2383                     'IN_REPLY_TO' => $in_reply_to,
2384                     'MESSAGE_ID'  => $message_id);
2385    }
2386
2387
2388
2389    /**
2390     * Parses the ARRDLIST as defined in RFC
2391     *
2392     * @param string &$str The IMAP's server response
2393     *
2394     * @return array The parsed response
2395     * @access private
2396     * @since 1.0
2397     */
2398    function _getAddressList(&$str)
2399    {
2400        $params_arr = $this->_arrayfyContent($str);
2401        if (!isset($params_arr)) {
2402            return $params_arr;
2403        }
2404
2405        if (is_array($params_arr)) {
2406            foreach ($params_arr as $index => $address_arr) {
2407                $personal_name  = $address_arr[0];
2408                $at_domain_list = $address_arr[1];
2409                $mailbox_name   = $address_arr[2];
2410                $host_name      = $address_arr[3];
2411                if ($mailbox_name != '' && $host_name != '') {
2412                    $email = $mailbox_name . "@" . $host_name;
2413                } else {
2414                    $email = false;
2415                }
2416                if ($email == false) {
2417                    $rfc822_email = false;
2418                } else {
2419                    if (!isset($personal_name)) {
2420                        $rfc822_email = '<' . $email . '>';
2421                    } else {
2422                        $rfc822_email = '"' . $personal_name . '" <'
2423                                        . $email . '>';
2424                    }
2425                }
2426                $email_arr[] = array('PERSONAL_NAME'  => $personal_name,
2427                                     'AT_DOMAIN_LIST' => $at_domain_list,
2428                                     'MAILBOX_NAME'   => $this->utf7Decode($mailbox_name),
2429                                     'HOST_NAME'      => $host_name,
2430                                     'EMAIL'          => $email ,
2431                                     'RFC822_EMAIL'   => $rfc822_email );
2432            }
2433            return $email_arr;
2434        }
2435        return array();
2436    }
2437
2438
2439
2440    /**
2441     * Utility funcion to find the closing parenthesis ")" Position it takes
2442     * care of quoted ones
2443     *
2444     * @param string $str_line   String
2445     * @param string $startDelim Start delimiter
2446     * @param string $stopDelim  Stop delimiter
2447     *
2448     * @return int the pos of the closing parenthesis ")"
2449     * @access private
2450     * @since 1.0
2451    */
2452    function _getClosingBracesPos($str_line,
2453                                  $startDelim = '(',
2454                                  $stopDelim = ')')
2455    {
2456        $len = $this->_getLineLength($str_line);
2457        $pos = 0;
2458        // ignore all extra characters
2459        // If inside of a string, skip string -- Boundary IDs and other
2460        // things can have ) in them.
2461        if ($str_line[$pos] != $startDelim) {
2462            $this->_protError('_getClosingParenthesisPos: must start with a '
2463                              . '"' . $startDelim . '" but is a '
2464                              . '"' . $str_line[$pos] . '"!!!!'
2465                              . 'STR_LINE: ' . $str_line
2466                              . ' |size: ' . $len
2467                              . ' |POS: ' . $pos,
2468                              __LINE__,
2469                              __FILE__);
2470            return( $len );
2471        }
2472        for ($pos = 1; $pos < $len; $pos++) {
2473            if ($str_line[$pos] == $stopDelim) {
2474                break;
2475            }
2476            if ($str_line[$pos] == '"') {
2477                $this->_advanceOverStr($str_line,
2478                                       $pos,
2479                                       $len,
2480                                       $startDelim,
2481                                       $stopDelim);
2482            }
2483            if ($str_line[$pos] == $startDelim) {
2484                $str_line_aux = $this->_getSubstr($str_line, $pos);
2485                $pos_aux      = $this->_getClosingBracesPos($str_line_aux);
2486                $pos         += $pos_aux;
2487                if ($pos == $len-1) {
2488                    break;
2489                }
2490            }
2491        }
2492        if ($str_line[$pos] != $stopDelim) {
2493            $this->_protError('_getClosingBracesPos: must be a '
2494                              . '"' . $stopDelim . '" but is a '
2495                              . '"' . $str_line[$pos] . '"'
2496                              . ' |POS: ' . $pos
2497                              . ' |STR_LINE: ' . $str_line . '!!!!',
2498                              __LINE__,
2499                              __FILE__);
2500        }
2501
2502        if ($pos >= $len) {
2503            return false;
2504        }
2505        return $pos;
2506    }
2507
2508
2509
2510    /**
2511     * Advances the position $pos in $str over an correct escaped string
2512     *
2513     * Examples: $str='"\\\"First Last\\\""', $pos=0
2514     *      --> returns true and $pos=strlen($str)-1
2515     *
2516     * @param string $str        String
2517     * @param int    &$pos       Current position in $str pointing to a
2518     *                            double quote ("), on return pointing
2519     *                            on the closing double quote
2520     * @param int    $len        Length of $str in bytes(!)
2521     * @param string $startDelim Start delimiter
2522     * @param string $stopDelim  Stop delimiter
2523     *
2524     * @return boolean true if we advanced over a correct string,
2525     *                 false otherwise
2526     * @access private
2527     * @author Nigel Vickers
2528     * @author Ralf Becker
2529     * @since 1.1
2530     */
2531    function _advanceOverStr($str,
2532                             &$pos,
2533                             $len,
2534                             $startDelim ='(',
2535                             $stopDelim = ')')
2536    {
2537        if ($str[$pos] !== '"') {
2538            // start condition failed
2539            return false;
2540        }
2541
2542        $pos++;
2543
2544        $delimCount = 0;
2545        while ($str[$pos] !== '"' && $pos < $len) {
2546            // this is a fix to stop before the delimiter, in broken
2547            // string messages containing an odd number of double quotes
2548            // the idea is to check for a stopDelimited followed by
2549            // eiter a new startDelimiter or an other stopDelimiter
2550            // that allows to have something
2551            // like '"Name (Nick)" <email>' containing one delimiter
2552            // if you have something like "Name ((something))" we must
2553            // count the delimiters (and hope that they are not unbalanced too)
2554            // and check if we have a negative amount of delimiters or no
2555            // delimiters to meet the stop condition, before we run into a
2556            // closing double quote
2557            if ($str[$pos] === $startDelim) {
2558                $delimCount++;
2559            }
2560            if ($str[$pos] === $stopDelim) {
2561                $delimCount--;
2562            }
2563            if ($str[$pos] === $stopDelim
2564                && ($str[$pos+1] === $startDelim
2565                || $str[$pos+1] === $stopDelim
2566                && $delimCount <= 0)) {
2567                // stopDelimited need to be parsed outside!
2568                $pos--;
2569                return false;
2570            }
2571
2572            // all escaped chars are overread (eg. \\,  \", \x)
2573            if ($str[$pos] === '\\') {
2574                $pos++;
2575            }
2576            $pos++;
2577        }
2578
2579        return $pos < $len && $str[$pos] === '"';
2580    }
2581
2582
2583
2584    /**
2585     * Utility funcion to get from here to the end of the line
2586     *
2587     * @param string  &$str      String
2588     * @param boolean $including true for Including EOL
2589     *                           false to not include EOL
2590     *
2591     * @return string The string to the first EOL
2592     * @access private
2593     * @since  1.0
2594     */
2595    function _getToEOL(&$str, $including = true)
2596    {
2597        $len = $this->_getLineLength($str);
2598        if ($including) {
2599            for ($i=0; $i<$len; $i++) {
2600                if ($str[$i] == "\n") {
2601                    break;
2602                }
2603            }
2604            $content = $this->_getSubstr($str, 0, $i + 1);
2605            $str     = $this->_getSubstr($str, $i + 1);
2606        } else {
2607            for ($i = 0 ; $i < $len ; $i++ ) {
2608                if ($str[$i] == "\n" || $str[$i] == "\r") {
2609                    break;
2610                }
2611            }
2612            $content = $this->_getSubstr($str, 0, $i);
2613            $str     = $this->_getSubstr($str, $i);
2614        }
2615        return $content;
2616    }
2617
2618
2619
2620    /**
2621     * Fetches the next IMAP token or parenthesis
2622     *
2623     * @param string  &$str               The IMAP's server response
2624     * @param string  &$content           The next token
2625     * @param boolean $parenthesisIsToken true: the parenthesis IS a token,
2626     *                                    false: I consider all the response
2627     *                                    in parenthesis as a token
2628     * @param boolean $colonIsToken       true: the colin IS a token
2629     *                                    false:
2630     *
2631     * @return int The content size
2632     * @access private
2633     * @since 1.0
2634     */
2635    function _getNextToken(&$str,
2636                           &$content,
2637                           $parenthesisIsToken = true,
2638                           $colonIsToken = true)
2639    {
2640        $len          = $this->_getLineLength($str);
2641        $pos          = 0;
2642        $content_size = false;
2643        $content      = false;
2644        if ($str == '' || $len < 2) {
2645            $content = $str;
2646            return $len;
2647        }
2648        switch ($str[0]) {
2649        case '{':
2650            if (($posClosingBraces = $this->_getClosingBracesPos($str,
2651                                                               '{',
2652                                                               '}')) == false) {
2653                $this->_protError('_getClosingBracesPos() error!!!',
2654                                  __LINE__,
2655                                  __FILE__);
2656            }
2657            if (!is_numeric(($strBytes = $this->_getSubstr($str,
2658                                                           1,
2659                                                           $posClosingBraces - 1)))) {
2660                $this->_protError('must be a number but is a '
2661                                  . '"' . $strBytes . '" !!!',
2662                                  __LINE__,
2663                                  __FILE__);
2664            }
2665            if ($str[$posClosingBraces] != '}') {
2666                $this->_protError('must be a "}" but is a '
2667                                  . '"' . $str[$posClosingBraces] . '"!!!',
2668                                  __LINE__,
2669                                  __FILE__);
2670            }
2671            if ($str[$posClosingBraces + 1] != "\r") {
2672                $this->_protError('must be a "\r" but is a '
2673                                  . '"' . $str[$posClosingBraces + 1] . '"!!!',
2674                                  __LINE__,
2675                                  __FILE__);
2676            }
2677            if ($str[$posClosingBraces + 2] != "\n") {
2678                $this->_protError('must be a "\n" but is a '
2679                                  . '"' . $str[$posClosingBraces + 2] . '"!!!',
2680                                  __LINE__,
2681                                  __FILE__);
2682            }
2683            $content = $this->_getSubstr($str,
2684                                         $posClosingBraces + 3,
2685                                         $strBytes);
2686            if ($this->_getLineLength($content) != $strBytes) {
2687                $this->_protError('content size is '
2688                                  . '"' . $this->_getLineLength($content) . '"'
2689                                  . ' but the string reports a size of '
2690                                  . $strBytes .'!!!!',
2691                                  __LINE__,
2692                                  __FILE__);
2693            }
2694            $content_size = $strBytes;
2695            //Advance the string
2696            $str = $this->_getSubstr($str, $posClosingBraces + $strBytes + 3);
2697            break;
2698
2699        case '"':
2700            if ($colonIsToken) {
2701                for ($pos=1; $pos<$len; $pos++) {
2702                    if ($str[$pos] == '"') {
2703                        break;
2704                    }
2705                    if ($str[$pos] == "\\" && $str[$pos + 1 ] == '"') {
2706                        $pos++;
2707                    }
2708                    if ($str[$pos] == "\\" && $str[$pos + 1 ] == "\\") {
2709                        $pos++;
2710                    }
2711                }
2712                if ($str[$pos] != '"') {
2713                    $this->_protError('must be a "\"" but is a '
2714                                      . '"' . $str[$pos] . '"!!!!',
2715                                      __LINE__,
2716                                      __FILE__);
2717                }
2718                $content_size = $pos;
2719                $content      = $this->_getSubstr($str, 1, $pos - 1);
2720                //Advance the string
2721                $str = $this->_getSubstr($str, $pos + 1);
2722            } else {
2723                for ($pos=1; $pos<$len; $pos++) {
2724                    if ($str[$pos] == '"') {
2725                        break;
2726                    }
2727                    if ($str[$pos] == "\\" && $str[$pos + 1 ] == '"' ) {
2728                        $pos++;
2729                    }
2730                    if ($str[$pos] == "\\" && $str[$pos + 1 ] == "\\" ) {
2731                        $pos++;
2732                    }
2733                }
2734                if ($str[$pos] != '"') {
2735                    $this->_protError('must be a "\"" but is a '
2736                                      . '"' . $str[$pos] . '"!!!!',
2737                                      __LINE__,
2738                                      __FILE__);
2739                }
2740                $content_size = $pos;
2741                $content      = $this->_getSubstr($str, 0, $pos + 1);
2742                //Advance the string
2743                $str = $this->_getSubstr($str, $pos + 1);
2744
2745            }
2746            // we need to strip slashes for a quoted string
2747            $content = stripslashes($content);
2748            break;
2749
2750        case "\r":
2751            $pos = 1;
2752            if ($str[1] == "\n") {
2753                $pos++;
2754            }
2755            $content_size = $pos;
2756            $content      = $this->_getSubstr($str, 0, $pos);
2757            $str          = $this->_getSubstr($str, $pos);
2758            break;
2759
2760        case "\n":
2761            $pos          = 1;
2762            $content_size = $pos;
2763            $content      = $this->_getSubstr($str, 0, $pos);
2764            $str          = $this->_getSubstr($str, $pos);
2765            break;
2766
2767        case '(':
2768            if ($parenthesisIsToken == false) {
2769                $pos          = $this->_getClosingBracesPos($str);
2770                $content_size = $pos + 1;
2771                $content      = $this->_getSubstr($str, 0, $pos + 1);
2772                $str          = $this->_getSubstr($str, $pos + 1);
2773            } else {
2774                $pos          = 1;
2775                $content_size = $pos;
2776                $content      = $this->_getSubstr($str, 0, $pos);
2777                $str          = $this->_getSubstr($str, $pos);
2778            }
2779            break;
2780
2781        case ')':
2782            $pos          = 1;
2783            $content_size = $pos;
2784            $content      = $this->_getSubstr($str, 0, $pos);
2785            $str          = $this->_getSubstr($str, $pos);
2786            break;
2787
2788        case ' ':
2789            $pos          = 1;
2790            $content_size = $pos;
2791            $content      = $this->_getSubstr($str, 0, $pos);
2792            $str          = $this->_getSubstr($str, $pos);
2793            break;
2794
2795        default:
2796            for ($pos = 0; $pos < $len; $pos++) {
2797                if ($this->_getSubstr($str, 0, 5) == 'BODY['
2798                    || $this->_getSubstr($str, 0, 5) == 'BODY.') {
2799                    if ($str[$pos] == ']') {
2800                        $pos++;
2801                        break;
2802                    }
2803                } elseif ($str[$pos] == ' '
2804                          || $str[$pos] == "\r"
2805                          || $str[$pos] == ')'
2806                          || $str[$pos] == '('
2807                          || $str[$pos] == "\n" ) {
2808                    break;
2809                }
2810                if ($str[$pos] == "\\" && $str[$pos + 1 ] == ' ') {
2811                    $pos++;
2812                }
2813                if ($str[$pos] == "\\" && $str[$pos + 1 ] == "\\") {
2814                    $pos++;
2815                }
2816            }
2817            //Advance the string
2818            if ($pos == 0) {
2819                $content_size = 1;
2820                $content      = $this->_getSubstr($str, 0, 1);
2821                $str          = $this->_getSubstr($str, 1);
2822            } else {
2823                $content_size = $pos;
2824                $content      = $this->_getSubstr($str, 0, $pos);
2825                if ($pos < $len) {
2826                    $str = $this->_getSubstr($str, $pos);
2827                } else {
2828                    //if this is the end of the string... exit the switch
2829                    break;
2830                }
2831            }
2832            break;
2833        }
2834        return $content_size;
2835    }
2836
2837
2838
2839    /**
2840     * Utility funcion to display to console the protocol errors
2841     * printErrors() additionally has to be set to true
2842     *
2843     * @param string  $str        The error message
2844     * @param int     $line       The line producing the error
2845     * @param string  $file       File where the error was produced
2846     * @param boolean $printError true: print the error
2847     *                            false: do not print the error
2848     *
2849     * @return nothing
2850     * @access private
2851     * @since 1.0
2852     */
2853    function _protError($str , $line , $file, $printError = true)
2854    {
2855        // ToDo: all real errors should be returned as PEAR error, others
2856        // hidden by default
2857        // NO extra output from this class!
2858        if ($this->_printErrors && $printError) {
2859            echo "$line,$file,PROTOCOL ERROR!:$str\n";
2860        }
2861    }
2862
2863
2864
2865    /**
2866     * get EXT array from string
2867     *
2868     * @param string &$str       String
2869     * @param string $startDelim Start delimiter
2870     * @param string $stopDelim  Stop delimiter
2871     *
2872     * @return array EXT array
2873     * @access private
2874     * @since 1.0
2875     */
2876    function _getEXTarray(&$str, $startDelim = '(', $stopDelim = ')')
2877    {
2878        /* I let choose the $startDelim  and $stopDelim to allow parsing
2879           the OK response  so I also can parse a response like this
2880           * OK [UIDNEXT 150] Predicted next UID
2881        */
2882        $this->_getNextToken($str, $parenthesis);
2883        if ($parenthesis != $startDelim) {
2884            $this->_protError('must be a "' . $startDelim . '" but is a '
2885                              . '"' . $parenthesis . '" !!!!',
2886                              __LINE__,
2887                              __FILE__);
2888        }
2889
2890        $parenthesis = '';
2891        $struct_arr  = array();
2892        while ($parenthesis != $stopDelim && $str != '') {
2893            // The command
2894            $this->_getNextToken($str, $token);
2895            $token = strtoupper($token);
2896
2897            if (($ret = $this->_retrParsedResponse($str, $token)) != false) {
2898                //$struct_arr[$token] = $ret;
2899                $struct_arr = array_merge($struct_arr, $ret);
2900            }
2901
2902            $parenthesis = $token;
2903
2904        } //While
2905
2906        if ($parenthesis != $stopDelim ) {
2907            $this->_protError('1_must be a "' . $stopDelim . '" but is a '
2908                              . '"' . $parenthesis . '"!!!!',
2909                              __LINE__,
2910                              __FILE__);
2911        }
2912        return $struct_arr;
2913    }
2914
2915
2916
2917    /**
2918     * retrieve parsed response
2919     *
2920     * @param string &$str          String
2921     * @param string $token         Token
2922     * @param string $previousToken Previous token
2923     *
2924     * @return array Parsed response
2925     * @access private
2926     * @since 1.0
2927     */
2928    function _retrParsedResponse(&$str, $token, $previousToken = null)
2929    {
2930        //echo "\n\nTOKEN:$token\r\n";
2931        $token = strtoupper($token);
2932
2933        switch ($token) {
2934        case 'RFC822.SIZE':
2935            return array($token => $this->_parseOneStringResponse($str,
2936                                                                  __LINE__,
2937                                                                  __FILE__));
2938            break;
2939
2940        // case 'RFC822.TEXT':
2941
2942        // case 'RFC822.HEADER':
2943
2944        case 'RFC822':
2945            return array($token => $this->_parseContentresponse($str,
2946                                                                $token));
2947            break;
2948
2949        case 'FLAGS':
2950        case 'PERMANENTFLAGS':
2951            return array($token => $this->_parseFLAGSresponse($str));
2952            break;
2953
2954        case 'ENVELOPE':
2955            return array($token => $this->_parseENVELOPEresponse($str));
2956            break;
2957
2958        case 'EXPUNGE':
2959            return false;
2960            break;
2961
2962        case 'NOMODSEQ':
2963            // ToDo: implement RFC 4551
2964            return array($token=>'');
2965            break;
2966
2967        case 'UID':
2968        case 'UIDNEXT':
2969        case 'UIDVALIDITY':
2970        case 'UNSEEN':
2971        case 'MESSAGES':
2972        case 'UIDNEXT':
2973        case 'UIDVALIDITY':
2974        case 'UNSEEN':
2975        case 'INTERNALDATE':
2976            return array($token => $this->_parseOneStringResponse($str,
2977                                                                  __LINE__,
2978                                                                  __FILE__));
2979            break;
2980
2981        case 'BODY':
2982        case 'BODYSTRUCTURE':
2983            return array($token => $this->_parseBodyResponse($str, $token));
2984            break;
2985
2986        case 'RECENT':
2987            if ($previousToken != null) {
2988                $aux['RECENT'] = $previousToken;
2989                return $aux;
2990            } else {
2991                return array($token => $this->_parseOneStringResponse($str,
2992                                                                      __LINE__,
2993                                                                      __FILE__));
2994            }
2995            break;
2996
2997        case 'EXISTS':
2998            return array($token => $previousToken);
2999            break;
3000
3001        case 'READ-WRITE':
3002        case 'READ-ONLY':
3003            return array($token => $token);
3004            break;
3005
3006        case 'QUOTA':
3007            /*
3008            A tipical GETQUOTA DIALOG IS AS FOLLOWS
3009
3010                C: A0004 GETQUOTA user.damian
3011                S: * QUOTA user.damian (STORAGE 1781460 4000000)
3012                S: A0004 OK Completed
3013
3014            another example of QUOTA response from GETQUOTAROOT:
3015                C: A0008 GETQUOTAROOT INBOX
3016                S: * QUOTAROOT INBOX ""
3017                S: * QUOTA "" (STORAGE 0 1024000 MESSAGE 0 40000)
3018                S: A0008 OK GETQUOTAROOT finished.
3019
3020            RFC 2087 section 5.1 says the list could be empty:
3021
3022                C: A0004 GETQUOTA user.damian
3023                S: * QUOTA user.damian ()
3024                S: A0004 OK Completed
3025
3026            quota_list      ::= "(" #quota_resource ")"
3027            quota_resource  ::= atom SP number SP number
3028            quota_response  ::= "QUOTA" SP astring SP quota_list
3029            */
3030
3031            $mailbox = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3032            $ret_aux = array('MAILBOX' => $this->utf7Decode($mailbox));
3033
3034            // courier fix
3035            if ($str[0] . $str[1] == "\r\n") {
3036                return array($token => $ret_aux);
3037            }
3038            // end courier fix
3039
3040            $this->_parseSpace($str, __LINE__, __FILE__);
3041            $this->_parseString($str, '(', __LINE__, __FILE__);
3042
3043            // fetching quota resources
3044            // (BNF ::= #quota_resource  but space separated instead of comma)
3045            $this->_getNextToken($str, $quota_resp);
3046            while ($quota_resp != ')') {
3047                if (($ext = $this->_retrParsedResponse($str,
3048                                                       $quota_resp)) == false) {
3049                    $this->_protError('bogus response!!!!',
3050                                      __LINE__,
3051                                      __FILE__);
3052                }
3053                $ret_aux = array_merge($ret_aux, $ext);
3054
3055                $this->_getNextToken($str, $quota_resp);
3056                if ($quota_resp == ' ') {
3057                    $this->_getNextToken($str, $quota_resp);
3058                }
3059            }
3060
3061            // if empty list, apparently no STORAGE or MESSAGE quota set
3062            return array($token => $ret_aux);
3063            break;
3064
3065        case 'QUOTAROOT':
3066            /*
3067            A tipical GETQUOTA DIALOG IS AS FOLLOWS
3068
3069                C: A0004 GETQUOTA user.damian
3070                S: * QUOTA user.damian (STORAGE 1781460 4000000)
3071                S: A0004 OK Completed
3072            */
3073            $mailbox = $this->utf7Decode($this->_parseOneStringResponse($str,
3074                                                            __LINE__,
3075                                                            __FILE__));
3076
3077            $str_line = rtrim(substr($this->_getToEOL($str, false), 0));
3078            if (empty($str_line)) {
3079                $ret = @array('MAILBOX' => $this->utf7Decode($mailbox));
3080            } else {
3081                $quotaroot = $this->_parseOneStringResponse($str_line,
3082                                                            __LINE__,
3083                                                            __FILE__);
3084                $ret       = @array('MAILBOX' => $this->utf7Decode($mailbox),
3085                                    $token    => $quotaroot);
3086            }
3087            return array($token => $ret);
3088            break;
3089
3090        case 'STORAGE':
3091            $used = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3092            $qmax = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3093            return array($token => array('USED' => $used, 'QMAX' => $qmax));
3094            break;
3095
3096        case 'MESSAGE':
3097            $mused = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3098            $mmax  = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3099            return array($token=>array("MUSED"=> $mused, "MMAX" => $mmax));
3100            break;
3101
3102        case 'FETCH':
3103            $this->_parseSpace($str, __LINE__, __FILE__);
3104            // Get the parsed pathenthesis
3105            $struct_arr = $this->_getEXTarray($str);
3106            return $struct_arr;
3107            break;
3108
3109        case 'NAMESPACE':
3110            $this->_parseSpace($str, __LINE__, __FILE__);
3111            $this->_getNextToken($str, $personal, false);
3112            $struct_arr['NAMESPACES']['personal'] = $this->_arrayfyContent($personal);
3113            $this->_parseSpace($str, __LINE__, __FILE__);
3114            $this->_getNextToken($str, $others, false);
3115            $struct_arr['NAMESPACES']['others'] = $this->_arrayfyContent($others);
3116
3117            $this->_parseSpace($str, __LINE__, __FILE__);
3118            $this->_getNextToken($str, $shared, false);
3119            $struct_arr['NAMESPACES']['shared'] = $this->_arrayfyContent($shared);
3120
3121            return array($token => $struct_arr);
3122            break;
3123
3124        case 'CAPABILITY':
3125            $this->_parseSpace($str, __LINE__, __FILE__);
3126            $str_line = rtrim(substr($this->_getToEOL($str, false), 0));
3127
3128            $struct_arr['CAPABILITIES'] = explode(' ', $str_line);
3129            return array($token => $struct_arr);
3130            break;
3131
3132        case 'STATUS':
3133            $mailbox = $this->_parseOneStringResponse($str, __LINE__, __FILE__);
3134            $this->_parseSpace($str, __LINE__, __FILE__);
3135            $ext                      = $this->_getEXTarray($str);
3136            $struct_arr['MAILBOX']    = $this->utf7Decode($mailbox);
3137            $struct_arr['ATTRIBUTES'] = $ext;
3138            return array($token => $struct_arr);
3139            break;
3140
3141        case 'LIST':
3142            $this->_parseSpace($str, __LINE__, __FILE__);
3143            $params_arr = $this->_arrayfyContent($str);
3144
3145            $this->_parseSpace($str, __LINE__, __FILE__);
3146            $this->_getNextToken($str, $hierarchydelim);
3147
3148            $this->_parseSpace($str, __LINE__, __FILE__);
3149            $this->_getNextToken($str, $mailbox_name);
3150
3151            $result_array = array('NAME_ATTRIBUTES'    => $params_arr,
3152                                  'HIERACHY_DELIMITER' => $hierarchydelim,
3153                                  'MAILBOX_NAME'       => $this->utf7Decode($mailbox_name));
3154            return array($token => $result_array);
3155            break;
3156
3157        case 'LSUB':
3158            $this->_parseSpace($str, __LINE__, __FILE__);
3159            $params_arr = $this->_arrayfyContent($str);
3160
3161            $this->_parseSpace($str, __LINE__, __FILE__);
3162            $this->_getNextToken($str, $hierarchydelim);
3163
3164            $this->_parseSpace($str, __LINE__, __FILE__);
3165            $this->_getNextToken($str, $mailbox_name);
3166
3167            $result_array = array('NAME_ATTRIBUTES'    => $params_arr,
3168                                  'HIERACHY_DELIMITER' => $hierarchydelim,
3169                                  'MAILBOX_NAME'       => $this->utf7Decode($mailbox_name));
3170            return array($token => $result_array);
3171            break;
3172
3173        case 'SEARCH':
3174        case 'SORT':
3175            $str_line = rtrim(substr($this->_getToEOL($str, false), 1));
3176
3177            $struct_arr[$token . '_LIST'] = explode(' ', $str_line);
3178            if (count($struct_arr[$token . '_LIST']) == 1
3179                && $struct_arr[$token . '_LIST'][0] == '') {
3180                $struct_arr[$token . '_LIST'] = null;
3181            }
3182            return array($token => $struct_arr);
3183            break;
3184
3185        case 'OK':
3186            /* TODO:
3187                parse the [ .... ] part of the response, use the method
3188                _getEXTarray(&$str,'[',$stopDelim=']')
3189            */
3190            $str_line = rtrim(substr($this->_getToEOL($str, false), 1));
3191            if ($str_line[0] == '[') {
3192                $braceLen = $this->_getClosingBracesPos($str_line, '[', ']');
3193                $str_aux  = '('. substr($str_line, 1, $braceLen -1). ')';
3194                $ext_arr  = $this->_getEXTarray($str_aux);
3195                //$ext_arr=array($token=>$this->_getEXTarray($str_aux));
3196            } else {
3197                $ext_arr = $str_line;
3198                //$ext_arr=array($token=>$str_line);
3199            }
3200            $result_array =  $ext_arr;
3201            return $result_array;
3202            break;
3203
3204        case 'NO':
3205            /* TODO:
3206                parse the [ .... ] part of the response, use the method
3207                _getEXTarray(&$str,'[',$stopDelim=']')
3208            */
3209            $str_line       = rtrim(substr($this->_getToEOL($str, false), 1));
3210            $result_array[] = @array('COMMAND' => $token, 'EXT' => $str_line);
3211            return $result_array;
3212            break;
3213
3214        case 'BAD':
3215            /* TODO:
3216                parse the [ .... ] part of the response, use the method
3217                _getEXTarray(&$str,'[',$stopDelim=']')
3218            */
3219            $str_line       = rtrim(substr($this->_getToEOL($str, false), 1));
3220            $result_array[] = array('COMMAND' => $token, 'EXT' => $str_line);
3221            return $result_array;
3222            break;
3223
3224        case 'BYE':
3225            /* TODO:
3226                parse the [ .... ] part of the response, use the method
3227                _getEXTarray(&$str,'[',$stopDelim=']')
3228            */
3229            $str_line       = rtrim(substr($this->_getToEOL($str, false), 1));
3230            $result_array[] = array('COMMAND' => $token, 'EXT' => $str_line);
3231            return $result_array;
3232            break;
3233
3234        case 'LISTRIGHTS':
3235            $this->_parseSpace($str, __LINE__, __FILE__);
3236            $this->_getNextToken($str, $mailbox);
3237            $this->_parseSpace($str, __LINE__, __FILE__);
3238            $this->_getNextToken($str, $user);
3239            $this->_parseSpace($str, __LINE__, __FILE__);
3240            $this->_getNextToken($str, $granted);
3241
3242            $ungranted = explode(' ', rtrim(substr($this->_getToEOL($str, false), 1)));
3243
3244            $result_array = @array('MAILBOX'   => $this->utf7Decode($mailbox),
3245                                   'USER'      => $user,
3246                                   'GRANTED'   => $granted,
3247                                   'UNGRANTED' => $ungranted);
3248            return $result_array;
3249            break;
3250
3251        case 'MYRIGHTS':
3252            $this->_parseSpace($str, __LINE__, __FILE__);
3253            $this->_getNextToken($str, $mailbox);
3254            // Patch to handle the alternate MYRIGHTS response from
3255            // Courier-IMAP
3256            if ($str==')') {
3257                $granted = $mailbox;
3258                $mailbox = $this->currentMailbox;
3259            } else {
3260                $this->_parseSpace($str, __LINE__, __FILE__);
3261                $this->_getNextToken($str, $granted);
3262            }
3263            // End Patch
3264
3265            $result_array = array('MAILBOX' => $this->utf7Decode($mailbox),
3266                                  'GRANTED' => $granted);
3267            return $result_array;
3268            break;
3269
3270        case 'ACL':
3271            /*
3272            RFC 4314:
3273            acl-data        = "ACL" SP mailbox *(SP identifier SP rights)
3274            identifier      = astring
3275            rights          = astring ;; only lowercase ASCII letters and
3276                                         digits are allowed.
3277            */
3278            //$str = " INBOX\r\nA0006 OK Completed\r\n";
3279            $this->_parseSpace($str, __LINE__, __FILE__);
3280            $this->_getNextToken($str, $mailbox);
3281
3282            $arr = array();
3283            while (substr($str, 0, 2) != "\r\n") {
3284                $this->_parseSpace($str, __LINE__, __FILE__);
3285                $this->_getNextToken($str, $acl_user);
3286                $this->_parseSpace($str, __LINE__, __FILE__);
3287                $this->_getNextToken($str, $acl_rights);
3288                $arr[] = array('USER' => $acl_user, 'RIGHTS' => $acl_rights);
3289            }
3290
3291            $result_array = array('MAILBOX' => $this->utf7Decode($mailbox),
3292                                  'USERS'   => $arr);
3293            return $result_array;
3294            break;
3295
3296        case 'ANNOTATION':
3297            $this->_parseSpace($str, __LINE__, __FILE__);
3298            $this->_getNextToken($str, $mailbox);
3299
3300            $this->_parseSpace($str, __LINE__, __FILE__);
3301            $this->_getNextToken($str, $entry);
3302
3303            $this->_parseSpace($str, __LINE__, __FILE__);
3304            $attrs = $this->_arrayfyContent($str);
3305
3306            $result_array = array('MAILBOX'    => $this->utf7Decode($mailbox),
3307                                  'ENTRY'      => $entry,
3308                                  'ATTRIBUTES' => $attrs);
3309            return $result_array;
3310            break;
3311
3312        case '':
3313            $this->_protError('PROTOCOL ERROR!:str empty!!',
3314                              __LINE__,
3315                              __FILE__);
3316            break;
3317
3318        case '(':
3319            $this->_protError('OPENING PARENTHESIS ERROR!!!',
3320                              __LINE__,
3321                              __FILE__);
3322            break;
3323
3324        case ')':
3325            //"CLOSING PARENTHESIS BREAK!!!!!!!"
3326            break;
3327
3328        case "\r\n":
3329            $this->_protError('BREAK!!!', __LINE__, __FILE__);
3330            break;
3331
3332        case ' ':
3333            // this can happen and we just ignore it
3334            // This happens when - for example - fetch returns more than 1
3335            // parammeter
3336            // for example you ask to get RFC822.SIZE and UID
3337            // $this->_protError('SPACE BREAK!!!', __LINE__, __FILE__);
3338            break;
3339
3340        default:
3341            $body_token   = strtoupper(substr($token, 0, 5));
3342            $rfc822_token = strtoupper(substr($token, 0, 7));
3343
3344            if ($body_token == 'BODY['
3345                || $body_token == 'BODY.'
3346                || $rfc822_token == 'RFC822.') {
3347                //echo "TOKEN:$token\n";
3348                //$this->_getNextToken( $str , $mailbox );
3349                return array($token => $this->_parseContentresponse($str,
3350                                                                    $token));
3351            } else {
3352                $this->_protError('UNIMPLEMMENTED! I don\'t know the '
3353                                  . 'parameter "' . $token . '"!!!',
3354                                  __LINE__,
3355                                  __FILE__);
3356            }
3357            break;
3358        }
3359
3360        return false;
3361    }
3362
3363
3364
3365    /**
3366     * Verifies that the next character IS a space
3367     *
3368     * @param string  &$str       String
3369     * @param int     $line       Line number
3370     * @param string  $file       File name
3371     * @param boolean $printError Print errors
3372     *
3373     * @return string First character of $str
3374     * @access private
3375     */
3376    function _parseSpace(&$str, $line, $file, $printError = true)
3377    {
3378        /*
3379        This code repeats a lot in this class
3380        so i make it a function to make all the code shorter
3381        */
3382        $this->_getNextToken($str, $space);
3383        if ($space != ' ') {
3384            $this->_protError('must be a " " but is a "' . $space . '"!!!!',
3385                              $line,
3386                              $file,
3387                              $printError);
3388        }
3389        return $space;
3390    }
3391
3392
3393
3394    /**
3395     * parse string for next character after token
3396     *
3397     * @param string &$str String
3398     * @param string $char Next character
3399     * @param int    $line Line number
3400     * @param string $file File name
3401     *
3402     * @return string Character after next token
3403     * @access private
3404     */
3405    function _parseString(&$str, $char, $line, $file)
3406    {
3407        /*
3408        This code repeats a lot in this class
3409        so i make it a function to make all the code shorter
3410        */
3411        $this->_getNextToken($str, $char_aux);
3412        if (strtoupper($char_aux) != strtoupper($char)) {
3413            $this->_protError('must be a "' . $char . '" but is a '
3414                              . '"' . $char_aux . '"!!!!',
3415                              $line,
3416                              $file);
3417        }
3418        return $char_aux;
3419    }
3420
3421
3422
3423    /**
3424     * parse IMAP response
3425     *
3426     * @param string &$str  Response string
3427     * @param int    $cmdid Command ID
3428     *
3429     * @return array Response array
3430     * @access private
3431     */
3432    function _genericImapResponseParser(&$str, $cmdid = null)
3433    {
3434        $result_array = array();
3435        if ($this->_unParsedReturn) {
3436            $unparsed_str = $str;
3437        }
3438
3439        $this->_getNextToken($str, $token);
3440
3441        while ($token != $cmdid && $str != '') {
3442            if ($token == '+' ) {
3443                //if the token  is + ignore the line
3444                // TODO: verify that this is correct!!!
3445                $this->_getToEOL($str);
3446                $this->_getNextToken($str, $token);
3447            }
3448
3449            $this->_parseString($str, ' ', __LINE__, __FILE__);
3450
3451            $this->_getNextToken($str, $token);
3452            if ($token == '+') {
3453                $this->_getToEOL($str);
3454                $this->_getNextToken($str, $token);
3455            } else {
3456                if (is_numeric($token)) {
3457                    // The token is a NUMBER so I store it
3458                    $msg_nro = $token;
3459                    $this->_parseSpace($str, __LINE__, __FILE__);
3460
3461                    // I get the command
3462                    $this->_getNextToken($str, $command);
3463
3464                    if (($ext_arr = $this->_retrParsedResponse($str, $command, $msg_nro)) == false) {
3465                        // if this bogus response is a FLAGS () or EXPUNGE
3466                        // response the ignore it
3467                        if ($command != 'FLAGS' && $command != 'EXPUNGE') {
3468                            $this->_protError('bogus response!!!!',
3469                                              __LINE__,
3470                                              __FILE__,
3471                                              false);
3472                        }
3473                    }
3474                    $result_array[] = array('COMMAND' => $command,
3475                                            'NRO'     => $msg_nro,
3476                                            'EXT'     => $ext_arr);
3477                } else {
3478                    // OK the token is not a NUMBER so it MUST be a COMMAND
3479                    $command = $token;
3480
3481                    /* Call the parser return the array
3482                        take care of bogus responses!
3483                    */
3484
3485                    if (($ext_arr = $this->_retrParsedResponse($str, $command)) == false) {
3486                        $this->_protError('bogus response!!!! (COMMAND:'
3487                                          . $command. ')',
3488                                          __LINE__,
3489                                          __FILE__);
3490                    }
3491                    $result_array[] = array('COMMAND' => $command,
3492                                            'EXT'     => $ext_arr);
3493                }
3494            }
3495
3496
3497            $this->_getNextToken($str, $token);
3498
3499            $token = strtoupper($token);
3500            if ($token != "\r\n" && $token != '') {
3501                $this->_protError('PARSE ERROR!!! must be a "\r\n" here but '
3502                                  . 'is a "' . $token . '"!!!! (getting the '
3503                                  . 'next line)|STR:|' . $str. '|',
3504                                  __LINE__,
3505                                  __FILE__);
3506            }
3507            $this->_getNextToken($str, $token);
3508
3509            if ($token == '+') {
3510                //if the token  is + ignore the line
3511                // TODO: verify that this is correct!!!
3512                $this->_getToEOL($str);
3513                $this->_getNextToken($str, $token);
3514            }
3515        } //While
3516        // OK we finish the UNTAGGED Response now we must parse
3517        // the FINAL TAGGED RESPONSE
3518        // TODO: make this a litle more elegant!
3519        $this->_parseSpace($str, __LINE__, __FILE__, false);
3520
3521        $this->_getNextToken($str, $cmd_status);
3522
3523        $str_line = rtrim(substr($this->_getToEOL($str), 1));
3524
3525
3526        $response['RESPONSE'] = array('CODE'     => $cmd_status,
3527                                      'STR_CODE' => $str_line,
3528                                      'CMDID'    => $cmdid);
3529
3530        $ret = $response;
3531        if (!empty($result_array)) {
3532            $ret = array_merge($ret, array('PARSED' => $result_array));
3533        }
3534
3535        if ($this->_unParsedReturn) {
3536            $unparsed['UNPARSED'] = $unparsed_str;
3537            $ret                  = array_merge($ret, $unparsed);
3538        }
3539
3540        if (isset($status_arr)) {
3541            $status['STATUS'] = $status_arr;
3542            $ret              = array_merge($ret, $status);
3543        }
3544
3545        return $ret;
3546    }
3547
3548
3549
3550    /**
3551     * Send generic command
3552     *
3553     * @param string $command Command
3554     * @param string $params  Parameters
3555     *
3556     * @return array Parsed response
3557     * @access private
3558     */
3559    function _genericCommand($command, $params = '')
3560    {
3561        if (!$this->_connected) {
3562            return new PEAR_Error("not connected! (CMD:$command)");
3563        }
3564        $cmdid = $this->_getCmdId();
3565        $this->_putCMD($cmdid, $command, $params);
3566        $args = $this->_getRawResponse($cmdid);
3567        return $this->_genericImapResponseParser($args, $cmdid);
3568    }
3569
3570
3571
3572    /**
3573     * Encode string to UTF7
3574     *
3575     * Use utf7Encode() instead. This method is only for BC.
3576     *
3577     * @param string $str String
3578     *
3579     * @return string UTF7 encoded string
3580     * @access public
3581     * @deprecated Use utf7Encode() instead
3582     */
3583    function utf_7_encode($str)
3584    {
3585        return utf7Encode($str);
3586    }
3587
3588
3589
3590    /**
3591     * Encode string to UTF7
3592     *
3593     * @param string $str String
3594     *
3595     * @return string UTF7 encoded string
3596     * @access public
3597     */
3598    function utf7Encode($str)
3599    {
3600        if ($this->_useUTF_7 == false) {
3601            return $str;
3602        }
3603
3604        if (function_exists('mb_convert_encoding')) {
3605            return mb_convert_encoding($str, 'UTF7-IMAP', $this->_encoding);
3606        }
3607
3608        $encoded_utf7 = '';
3609        $base64_part  = '';
3610        if (is_array($str)) {
3611            return new PEAR_Error('error');
3612        }
3613
3614        for ($i = 0; $i < $this->_getLineLength($str); $i++) {
3615            //those chars should be base64 encoded
3616            if (((ord($str[$i]) >= 39) && (ord($str[$i]) <= 126))
3617                || ((ord($str[$i]) >= 32) && (ord($str[$i]) <= 37))) {
3618                if ($base64_part) {
3619                    $encoded_utf7 = sprintf("%s&%s-",
3620                                            $encoded_utf7,
3621                                            str_replace('=', '', base64_encode($base64_part)));
3622                    $base64_part  = '';
3623                }
3624                $encoded_utf7 = sprintf("%s%s", $encoded_utf7, $str[$i]);
3625            } else {
3626                //handle &
3627                if (ord($str[$i]) == 38 ) {
3628                    if ($base64_part) {
3629                        $encoded_utf7 = sprintf("%s&%s-",
3630                                                $encoded_utf7,
3631                                                str_replace('=', '', base64_encode($base64_part)));
3632                        $base64_part  = '';
3633                    }
3634                    $encoded_utf7 = sprintf("%s&-", $encoded_utf7);
3635                } else {
3636                    $base64_part = sprintf("%s%s", $base64_part, $str[$i]);
3637                    //$base64_part = sprintf("%s%s%s",
3638                    //                       $base64_part,
3639                    //                       chr(0),
3640                    //                       $str[$i]);
3641                }
3642            }
3643        }
3644        if ($base64_part) {
3645            $encoded_utf7 = sprintf("%s&%s-",
3646                                    $encoded_utf7,
3647                                    str_replace('=', '', base64_encode($base64_part)));
3648            $base64_part  = '';
3649        }
3650
3651        return $encoded_utf7;
3652    }
3653
3654
3655
3656    /**
3657     * Decode string from UTF7
3658     *
3659     * Use utf7Decode() instead. This method is only for BC.
3660     *
3661     * @param string $str UTF7 encoded string
3662     *
3663     * @return string Decoded string
3664     * @access public
3665     * @deprecated Use utf7Decode() instead
3666     */
3667    function utf_7_decode($str)
3668    {
3669        utf7Decode($str);
3670    }
3671
3672
3673
3674    /**
3675     * Decode string from UTF7
3676     *
3677     * @param string $str UTF7 encoded string
3678     *
3679     * @return string Decoded string
3680     * @access public
3681     */
3682    function utf7Decode($str)
3683    {
3684        if ($this->_useUTF_7 == false) {
3685            return $str;
3686        }
3687
3688        //return imap_utf7_decode($str);
3689
3690        if (function_exists('mb_convert_encoding')) {
3691            return mb_convert_encoding($str, $this->_encoding, 'UTF7-IMAP');
3692        }
3693
3694        $base64_part  = '';
3695        $decoded_utf7 = '';
3696
3697        for ($i = 0; $i < $this->_getLineLength($str); $i++) {
3698            if ($this->_getLineLength($base64_part) > 0) {
3699                if ($str[$i] == '-') {
3700                    if ($base64_part == '&') {
3701                        $decoded_utf7 = sprintf("%s&", $decoded_utf7);
3702                    } else {
3703                        $next_part_decoded = base64_decode(substr($base64_part, 1));
3704                        $decoded_utf7      = sprintf("%s%s",
3705                                                $decoded_utf7,
3706                                                $next_part_decoded);
3707                    }
3708                    $base64_part = '';
3709
3710                } else {
3711                    $base64_part = sprintf("%s%s", $base64_part, $str[$i]);
3712                }
3713            } else {
3714                if ($str[$i] == '&') {
3715                    $base64_part = '&';
3716                } else {
3717                    $decoded_utf7 = sprintf("%s%s", $decoded_utf7, $str[$i]);
3718                }
3719            }
3720        }
3721        return $decoded_utf7;
3722    }
3723
3724
3725
3726    /**
3727     * Make  CREATE/RENAME compatible option params
3728     *
3729     * @param array $options options to format
3730     *
3731     * @return string Returns a string for formatted parameters
3732     * @access private
3733     * @since 1.1
3734     */
3735    function _getCreateParams($options)
3736    {
3737        $args = '';
3738        if (is_null($options) === false && is_array($options) === true) {
3739            foreach ($options as $opt => $data) {
3740                switch (strtoupper($opt)) {
3741                case 'PARTITION':
3742                    $args .= sprintf(" %s", $this->utf7Encode($data));
3743                    break;
3744
3745                default:
3746                    // ignore any unknown options
3747                    break;
3748
3749                }
3750            }
3751        }
3752        return $args;
3753    }
3754
3755
3756
3757    /**
3758     * Return true if the TLS negotiation was successful
3759     *
3760     * @access public
3761     * @return mixed true on success, PEAR_Error on failure
3762     */
3763    function cmdStartTLS()
3764    {
3765        $res = $this->_genericCommand('STARTTLS');
3766        if ($res instanceOf PEAR_Error) {
3767            return $res;
3768        }
3769
3770        if (stream_socket_enable_crypto($this->_socket->fp,
3771                                        true,
3772                                        STREAM_CRYPTO_METHOD_TLS_CLIENT) == false) {
3773            $msg = 'Failed to establish TLS connection';
3774            return new PEAR_Error($msg);
3775        }
3776
3777        if ($this->_debug === true) {
3778            echo "STARTTLS Negotiation Successful\n";
3779        }
3780
3781        // RFC says we need to query the server capabilities again
3782        $res = $this->cmdCapability();
3783        if ($res instanceOf PEAR_Error) {
3784            $msg = 'Failed to connect, server said: ' . $res->getMessage();
3785            return new PEAR_Error($msg);
3786        }
3787        return true;
3788    }
3789
3790
3791
3792    /**
3793     * get the length of string
3794     *
3795     * @param string $string String
3796     *
3797     * @return int Line length
3798     * @access private
3799     */
3800    function _getLineLength($string)
3801    {
3802        if (extension_loaded('mbstring')) {
3803            return mb_strlen($string, 'latin1');
3804        } else {
3805            return strlen($string);
3806        }
3807    }
3808
3809
3810
3811    /**
3812     * get substring from string
3813     *
3814     * @param string $string String
3815     * @param int    $start  Position to start from
3816     * @param int    $length Number of characters
3817     *
3818     * @return string Substring
3819     * @access private
3820     */
3821    function _getSubstr($string, $start, $length = false)
3822    {
3823        if (extension_loaded('mbstring')) {
3824            if ($length !== false) {
3825                return mb_substr($string, $start, $length, 'latin1');
3826            } else {
3827                $strlen = mb_strlen($string, 'latin1');
3828                return mb_substr($string, $start, $strlen, 'latin1');
3829            }
3830        } else {
3831            if ($length !== false) {
3832                return substr($string, $start, $length);
3833            } else {
3834                return substr($string, $start);
3835            }
3836        }
3837    }
3838
3839
3840    /**
3841     * Escapes a string when it contains special characters (RFC3501)
3842     *
3843     * @param string  $string       IMAP string
3844     * @param boolean $force_quotes Forces string quoting (for atoms)
3845     *
3846     * @return string String atom, quoted-string or string literal
3847     * @todo lists
3848     */
3849    static function escape($string, $force_quotes = false)
3850    {
3851        if ($string === null) {
3852            return 'NIL';
3853        }
3854
3855        if ($string === '') {
3856            return '""';
3857        }
3858
3859        // atom-string (only safe characters)
3860        if (!$force_quotes && !preg_match('/[\x00-\x20\x22\x25\x28-\x2A\x5B-\x5D\x7B\x7D\x80-\xFF]/', $string)) {
3861            return $string;
3862        }
3863
3864        // quoted-string
3865        if (!preg_match('/[\r\n\x00\x80-\xFF]/', $string)) {
3866            return '"' . addcslashes($string, '\\"') . '"';
3867        }
3868
3869        // literal-string
3870        return sprintf("{%d}\r\n%s", strlen($string), $string);
3871    }
3872
3873}//Class
3874?>
3875