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